Loading executor.cpp +52 −1 Changes for executor.cpp: 52 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -13,6 +13,49 @@ Executor::Executor(Makefile& mf, Parser& parser) : mf_(mf), parser_(parser) {} // ======================== Rule Lookup ======================== // Determine whether `target` can ultimately be produced, allowing GNU make's // implicit-rule chaining (e.g. foo.o <- foo.c <- foo.c generated by another // pattern rule). To bound the search and avoid the self-similar explosion of // rules like "x/%: x/%_shipped", a given pattern rule may be used at most once // along any single chain (tracked in `used`) and depth is capped. bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& used, int depth) { std::string target = Parser::normalize_target(target_in); if (file_exists(target)) return true; if (mf_.phony_targets.count(target)) return true; if (mf_.find_explicit_rule(target)) return true; if (depth > 20) return false; for (const auto& rule : mf_.rules) { if (!rule.is_pattern || rule.recipes.empty()) continue; if (used.count(&rule)) continue; for (const auto& t : rule.targets) { size_t pct = t.find('%'); if (pct == std::string::npos) continue; std::string prefix = t.substr(0, pct); std::string suffix = t.substr(pct + 1); if (target.size() < prefix.size() + suffix.size()) continue; if (target.substr(0, prefix.size()) != prefix) continue; if (!suffix.empty() && target.substr(target.size() - suffix.size()) != suffix) continue; std::string s = target.substr(prefix.size(), target.size() - prefix.size() - suffix.size()); used.insert(&rule); bool ok = true; for (const auto& p : rule.prerequisites) { std::string prereq = p; size_t ppct = prereq.find('%'); if (ppct != std::string::npos) prereq = prereq.substr(0, ppct) + s + prereq.substr(ppct + 1); prereq = parser_.expand(prereq, mf_); if (prereq.empty()) continue; if (!can_be_made(prereq, used, depth + 1)) { ok = false; break; } } used.erase(&rule); if (ok) return true; } } return false; } const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) { std::string target = Parser::normalize_target(target_in); const Rule* r = mf_.find_explicit_rule(target); Loading Loading @@ -56,6 +99,12 @@ const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) if (file_exists(prereq)) continue; if (mf_.find_explicit_rule(prereq)) continue; if (mf_.phony_targets.count(prereq)) continue; // Allow implicit-rule chaining: the prerequisite may itself be // produced by another pattern rule (e.g. vdso-image-64.o <- // vdso-image-64.c, where the .c is generated by vdso-image-%.c). std::set<const Rule*> used; used.insert(rule); if (can_be_made(prereq, used, 0)) continue; return false; } return true; Loading Loading @@ -265,7 +314,9 @@ int Executor::execute_rule(const std::string& target, const Rule* rule, for (const auto& recipe : rule->recipes) { std::string expanded = parser_.expand_with_automatic( recipe, *mfp, target, first_prereq, prereqs, newer, stem); if (std::getenv("MP_DBG") && target == ".") std::cerr << "[REC pid=" << getpid() << "] target=. recipe=[" << recipe << "] expanded=[" << expanded << "]\n"; // A recipe line may expand (e.g. via a canned 'define' sequence) into // several physical lines. GNU make runs each as its own command with // independent @/-/+ prefix handling and its own shell. Loading executor.h +1 −0 Changes for executor.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ private: // Shared helpers const Rule* find_rule(const std::string& target, std::string& stem); bool can_be_made(const std::string& target, std::set<const Rule*>& used, int depth); std::vector<std::string> expand_prereqs(const std::string& target, const Rule* rule, const std::string& stem); bool needs_rebuild(const std::string& target, const std::vector<std::string>& prereqs); Loading parser.cpp +13 −1 Changes for parser.cpp: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -38,6 +38,10 @@ std::string Parser::normalize_target(const std::string& s) { while (i < r.size() && r[i] == '/') i++; // collapse "./////" r = r.substr(i); } // A path consisting solely of "./" (or ".///") denotes the current // directory; GNU make keeps it as a directory target. Map it to "." so // it does not collapse to an empty (and therefore unbuildable) target. if (r.empty() && !s.empty()) return "."; return r; } Loading Loading @@ -775,6 +779,7 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren auto targets = split_words(targets_str); if (targets.empty()) return false; std::string first_orig_target = targets[0]; for (auto& t : targets) t = normalize_target(t); // Static pattern rule: targets : target-pattern : prereq-patterns Loading Loading @@ -913,7 +918,14 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren current_rule = nullptr; } if (mf.default_target.empty() && !is_pattern && !targets.empty() && targets[0][0] != '.') { // GNU make: the first ordinary target becomes the default goal, unless its // name begins with '.' and contains no '/'. A target like "./" (the obj // directory in scripts/Makefile.build) DOES contain a slash and is thus a // valid default goal, even though normalization collapses it to ".". bool orig_is_special_dot = !first_orig_target.empty() && first_orig_target[0] == '.' && first_orig_target.find('/') == std::string::npos; if (mf.default_target.empty() && !is_pattern && !targets.empty() && !orig_is_special_dot) { mf.default_target = targets[0]; } Loading Loading
executor.cpp +52 −1 Changes for executor.cpp: 52 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -13,6 +13,49 @@ Executor::Executor(Makefile& mf, Parser& parser) : mf_(mf), parser_(parser) {} // ======================== Rule Lookup ======================== // Determine whether `target` can ultimately be produced, allowing GNU make's // implicit-rule chaining (e.g. foo.o <- foo.c <- foo.c generated by another // pattern rule). To bound the search and avoid the self-similar explosion of // rules like "x/%: x/%_shipped", a given pattern rule may be used at most once // along any single chain (tracked in `used`) and depth is capped. bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& used, int depth) { std::string target = Parser::normalize_target(target_in); if (file_exists(target)) return true; if (mf_.phony_targets.count(target)) return true; if (mf_.find_explicit_rule(target)) return true; if (depth > 20) return false; for (const auto& rule : mf_.rules) { if (!rule.is_pattern || rule.recipes.empty()) continue; if (used.count(&rule)) continue; for (const auto& t : rule.targets) { size_t pct = t.find('%'); if (pct == std::string::npos) continue; std::string prefix = t.substr(0, pct); std::string suffix = t.substr(pct + 1); if (target.size() < prefix.size() + suffix.size()) continue; if (target.substr(0, prefix.size()) != prefix) continue; if (!suffix.empty() && target.substr(target.size() - suffix.size()) != suffix) continue; std::string s = target.substr(prefix.size(), target.size() - prefix.size() - suffix.size()); used.insert(&rule); bool ok = true; for (const auto& p : rule.prerequisites) { std::string prereq = p; size_t ppct = prereq.find('%'); if (ppct != std::string::npos) prereq = prereq.substr(0, ppct) + s + prereq.substr(ppct + 1); prereq = parser_.expand(prereq, mf_); if (prereq.empty()) continue; if (!can_be_made(prereq, used, depth + 1)) { ok = false; break; } } used.erase(&rule); if (ok) return true; } } return false; } const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) { std::string target = Parser::normalize_target(target_in); const Rule* r = mf_.find_explicit_rule(target); Loading Loading @@ -56,6 +99,12 @@ const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) if (file_exists(prereq)) continue; if (mf_.find_explicit_rule(prereq)) continue; if (mf_.phony_targets.count(prereq)) continue; // Allow implicit-rule chaining: the prerequisite may itself be // produced by another pattern rule (e.g. vdso-image-64.o <- // vdso-image-64.c, where the .c is generated by vdso-image-%.c). std::set<const Rule*> used; used.insert(rule); if (can_be_made(prereq, used, 0)) continue; return false; } return true; Loading Loading @@ -265,7 +314,9 @@ int Executor::execute_rule(const std::string& target, const Rule* rule, for (const auto& recipe : rule->recipes) { std::string expanded = parser_.expand_with_automatic( recipe, *mfp, target, first_prereq, prereqs, newer, stem); if (std::getenv("MP_DBG") && target == ".") std::cerr << "[REC pid=" << getpid() << "] target=. recipe=[" << recipe << "] expanded=[" << expanded << "]\n"; // A recipe line may expand (e.g. via a canned 'define' sequence) into // several physical lines. GNU make runs each as its own command with // independent @/-/+ prefix handling and its own shell. Loading
executor.h +1 −0 Changes for executor.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ private: // Shared helpers const Rule* find_rule(const std::string& target, std::string& stem); bool can_be_made(const std::string& target, std::set<const Rule*>& used, int depth); std::vector<std::string> expand_prereqs(const std::string& target, const Rule* rule, const std::string& stem); bool needs_rebuild(const std::string& target, const std::vector<std::string>& prereqs); Loading
parser.cpp +13 −1 Changes for parser.cpp: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -38,6 +38,10 @@ std::string Parser::normalize_target(const std::string& s) { while (i < r.size() && r[i] == '/') i++; // collapse "./////" r = r.substr(i); } // A path consisting solely of "./" (or ".///") denotes the current // directory; GNU make keeps it as a directory target. Map it to "." so // it does not collapse to an empty (and therefore unbuildable) target. if (r.empty() && !s.empty()) return "."; return r; } Loading Loading @@ -775,6 +779,7 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren auto targets = split_words(targets_str); if (targets.empty()) return false; std::string first_orig_target = targets[0]; for (auto& t : targets) t = normalize_target(t); // Static pattern rule: targets : target-pattern : prereq-patterns Loading Loading @@ -913,7 +918,14 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren current_rule = nullptr; } if (mf.default_target.empty() && !is_pattern && !targets.empty() && targets[0][0] != '.') { // GNU make: the first ordinary target becomes the default goal, unless its // name begins with '.' and contains no '/'. A target like "./" (the obj // directory in scripts/Makefile.build) DOES contain a slash and is thus a // valid default goal, even though normalization collapses it to ".". bool orig_is_special_dot = !first_orig_target.empty() && first_orig_target[0] == '.' && first_orig_target.find('/') == std::string::npos; if (mf.default_target.empty() && !is_pattern && !targets.empty() && !orig_is_special_dot) { mf.default_target = targets[0]; } Loading