Loading executor.cpp +38 −8 Changes for executor.cpp: 38 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -14,7 +14,25 @@ Executor::Executor(Makefile& mf, Parser& parser) : mf_(mf), parser_(parser) {} const Rule* Executor::find_rule(const std::string& target, std::string& stem) { const Rule* r = mf_.find_explicit_rule(target); if (r && !r->recipes.empty()) { stem.clear(); return r; } if (r && !r->recipes.empty()) { // Static pattern rule: derive the stem by matching the target pattern. if (!r->target_pattern.empty()) { size_t pct = r->target_pattern.find('%'); std::string prefix = r->target_pattern.substr(0, pct); std::string suffix = r->target_pattern.substr(pct + 1); if (target.size() >= prefix.size() + suffix.size() && target.substr(0, prefix.size()) == prefix && (suffix.empty() || target.substr(target.size() - suffix.size()) == suffix)) { stem = target.substr(prefix.size(), target.size() - prefix.size() - suffix.size()); } else { stem.clear(); } return r; } stem.clear(); return r; } // Explicit rule has no recipe - try pattern/implicit rules for recipe const Rule* pattern = mf_.find_pattern_rule(target, stem); Loading Loading @@ -82,7 +100,7 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, }; // Collect prerequisites from the chosen rule. add_prereqs(rule, rule->is_pattern); add_prereqs(rule, rule->is_pattern || !rule->target_pattern.empty()); // GNU Make merges prerequisites from all explicit rules for the same target. // CMake relies on this: a target's prerequisites are spread across several Loading Loading @@ -160,16 +178,28 @@ int Executor::execute_rule(const std::string& target, const Rule* rule, std::string first_prereq = prereqs.empty() ? "" : prereqs[0]; // Apply target-specific variables (recursive flavor) for this target by // expanding the recipes against a local override of the makefile. // expanding the recipes against a local override of the makefile. Both // exact-name and pattern ('%') keys are honoured. const Makefile* mfp = &mf_; Makefile local_copy; auto tv = mf_.target_vars.find(target); if (tv != mf_.target_vars.end() && !tv->second.empty()) { local_copy = mf_; for (const auto& kv : tv->second) bool have_overrides = false; auto pattern_key_matches = [](const std::string& key, const std::string& tgt) { size_t pct = key.find('%'); if (pct == std::string::npos) return false; std::string prefix = key.substr(0, pct); std::string suffix = key.substr(pct + 1); return tgt.size() >= prefix.size() + suffix.size() && tgt.substr(0, prefix.size()) == prefix && (suffix.empty() || tgt.substr(tgt.size() - suffix.size()) == suffix); }; for (const auto& entry : mf_.target_vars) { if (entry.first != target && !pattern_key_matches(entry.first, target)) continue; if (entry.second.empty()) continue; if (!have_overrides) { local_copy = mf_; have_overrides = true; } for (const auto& kv : entry.second) local_copy.variables[kv.first] = {kv.second, VarFlavor::Recursive, false}; mfp = &local_copy; } if (have_overrides) mfp = &local_copy; // Export marked variables into the environment so recipes and any // recursive sub-makes ($(MAKE)) inherit them. apply_exports(*mfp); Loading parser.cpp +36 −0 Changes for parser.cpp: 36 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -652,6 +652,42 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren auto targets = split_words(targets_str); if (targets.empty()) return false; // Static pattern rule: targets : target-pattern : prereq-patterns // Detect a second depth-0 colon in the right-hand side whose left part is // a '%' pattern. (Distinguished from target-specific vars, which have '='.) { int d = 0; size_t c2 = std::string::npos; for (size_t i = 0; i < rhs_raw.size(); ++i) { char c = rhs_raw[i]; if (c == '$' && i + 1 < rhs_raw.size() && (rhs_raw[i+1] == '(' || rhs_raw[i+1] == '{')) { d++; i++; } else if ((c == ')' || c == '}') && d > 0) { d--; } else if (c == ':' && d == 0) { if (i + 1 < rhs_raw.size() && rhs_raw[i+1] == '=') break; c2 = i; break; } else if (c == '=' && d == 0) { break; // target-specific variable, not a static pattern rule } } if (c2 != std::string::npos) { std::string tpat = expand(trim(rhs_raw.substr(0, c2)), mf); std::string ppat = expand(trim(rhs_raw.substr(c2 + 1)), mf); if (tpat.find('%') != std::string::npos) { auto pprereqs = split_words(ppat); Rule rule; rule.targets = targets; rule.prerequisites = pprereqs; rule.is_pattern = false; rule.target_pattern = tpat; mf.rules.push_back(std::move(rule)); current_rule = &mf.rules.back(); return true; } } } // Target-specific variable assignment: targets: NAME op VALUE { std::string tv_name, tv_op, tv_value; Loading parser.h +4 −0 Changes for parser.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ struct Rule { std::vector<std::string> order_only_prereqs; std::vector<std::string> recipes; bool is_pattern = false; // For static pattern rules ("targets: target-pattern: prereq-patterns"). // When set, targets are concrete but the stem is derived per target by // matching this pattern, and prerequisites contain '%' to substitute. std::string target_pattern; }; struct Makefile { Loading Loading
executor.cpp +38 −8 Changes for executor.cpp: 38 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -14,7 +14,25 @@ Executor::Executor(Makefile& mf, Parser& parser) : mf_(mf), parser_(parser) {} const Rule* Executor::find_rule(const std::string& target, std::string& stem) { const Rule* r = mf_.find_explicit_rule(target); if (r && !r->recipes.empty()) { stem.clear(); return r; } if (r && !r->recipes.empty()) { // Static pattern rule: derive the stem by matching the target pattern. if (!r->target_pattern.empty()) { size_t pct = r->target_pattern.find('%'); std::string prefix = r->target_pattern.substr(0, pct); std::string suffix = r->target_pattern.substr(pct + 1); if (target.size() >= prefix.size() + suffix.size() && target.substr(0, prefix.size()) == prefix && (suffix.empty() || target.substr(target.size() - suffix.size()) == suffix)) { stem = target.substr(prefix.size(), target.size() - prefix.size() - suffix.size()); } else { stem.clear(); } return r; } stem.clear(); return r; } // Explicit rule has no recipe - try pattern/implicit rules for recipe const Rule* pattern = mf_.find_pattern_rule(target, stem); Loading Loading @@ -82,7 +100,7 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, }; // Collect prerequisites from the chosen rule. add_prereqs(rule, rule->is_pattern); add_prereqs(rule, rule->is_pattern || !rule->target_pattern.empty()); // GNU Make merges prerequisites from all explicit rules for the same target. // CMake relies on this: a target's prerequisites are spread across several Loading Loading @@ -160,16 +178,28 @@ int Executor::execute_rule(const std::string& target, const Rule* rule, std::string first_prereq = prereqs.empty() ? "" : prereqs[0]; // Apply target-specific variables (recursive flavor) for this target by // expanding the recipes against a local override of the makefile. // expanding the recipes against a local override of the makefile. Both // exact-name and pattern ('%') keys are honoured. const Makefile* mfp = &mf_; Makefile local_copy; auto tv = mf_.target_vars.find(target); if (tv != mf_.target_vars.end() && !tv->second.empty()) { local_copy = mf_; for (const auto& kv : tv->second) bool have_overrides = false; auto pattern_key_matches = [](const std::string& key, const std::string& tgt) { size_t pct = key.find('%'); if (pct == std::string::npos) return false; std::string prefix = key.substr(0, pct); std::string suffix = key.substr(pct + 1); return tgt.size() >= prefix.size() + suffix.size() && tgt.substr(0, prefix.size()) == prefix && (suffix.empty() || tgt.substr(tgt.size() - suffix.size()) == suffix); }; for (const auto& entry : mf_.target_vars) { if (entry.first != target && !pattern_key_matches(entry.first, target)) continue; if (entry.second.empty()) continue; if (!have_overrides) { local_copy = mf_; have_overrides = true; } for (const auto& kv : entry.second) local_copy.variables[kv.first] = {kv.second, VarFlavor::Recursive, false}; mfp = &local_copy; } if (have_overrides) mfp = &local_copy; // Export marked variables into the environment so recipes and any // recursive sub-makes ($(MAKE)) inherit them. apply_exports(*mfp); Loading
parser.cpp +36 −0 Changes for parser.cpp: 36 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -652,6 +652,42 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren auto targets = split_words(targets_str); if (targets.empty()) return false; // Static pattern rule: targets : target-pattern : prereq-patterns // Detect a second depth-0 colon in the right-hand side whose left part is // a '%' pattern. (Distinguished from target-specific vars, which have '='.) { int d = 0; size_t c2 = std::string::npos; for (size_t i = 0; i < rhs_raw.size(); ++i) { char c = rhs_raw[i]; if (c == '$' && i + 1 < rhs_raw.size() && (rhs_raw[i+1] == '(' || rhs_raw[i+1] == '{')) { d++; i++; } else if ((c == ')' || c == '}') && d > 0) { d--; } else if (c == ':' && d == 0) { if (i + 1 < rhs_raw.size() && rhs_raw[i+1] == '=') break; c2 = i; break; } else if (c == '=' && d == 0) { break; // target-specific variable, not a static pattern rule } } if (c2 != std::string::npos) { std::string tpat = expand(trim(rhs_raw.substr(0, c2)), mf); std::string ppat = expand(trim(rhs_raw.substr(c2 + 1)), mf); if (tpat.find('%') != std::string::npos) { auto pprereqs = split_words(ppat); Rule rule; rule.targets = targets; rule.prerequisites = pprereqs; rule.is_pattern = false; rule.target_pattern = tpat; mf.rules.push_back(std::move(rule)); current_rule = &mf.rules.back(); return true; } } } // Target-specific variable assignment: targets: NAME op VALUE { std::string tv_name, tv_op, tv_value; Loading
parser.h +4 −0 Changes for parser.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,10 @@ struct Rule { std::vector<std::string> order_only_prereqs; std::vector<std::string> recipes; bool is_pattern = false; // For static pattern rules ("targets: target-pattern: prereq-patterns"). // When set, targets are concrete but the stem is derived per target by // matching this pattern, and prerequisites contain '%' to substitute. std::string target_pattern; }; struct Makefile { Loading