Loading executor.cpp +147 −2 Changes for executor.cpp: 147 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -29,6 +29,12 @@ bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& if (file_exists(target)) return true; if (mf_.phony_targets.count(target)) return true; if (mf_.find_explicit_rule(target)) return true; // A leaf source file make cannot build itself may still exist elsewhere, // reachable only via a 'vpath'/VPATH directory search (e.g. zstd's // "$(BUILD_DIR)/%.o : %.c" pattern rule, whose '%.c' prerequisites live in // common/, compress/, decompress/, ... and are found via // "vpath %.c $(ZSTD_SUBDIR)", not in the current directory). if (resolve_vpath(target) != target) return true; if (depth > 20) return false; for (const auto& rule : mf_.rules) { if (!rule.is_pattern || rule.recipes.empty()) continue; Loading Loading @@ -62,6 +68,64 @@ bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& return false; } std::string Executor::resolve_vpath(const std::string& file) { if (file.empty() || file_exists(file)) return file; // A prerequisite that make can build itself (has an explicit rule) stays // local; vpath/VPATH only locates pre-existing files make cannot produce. if (mf_.find_explicit_rule(file)) return file; auto search_dirs = [&](const std::vector<std::string>& dirs) -> std::string { for (const auto& d : dirs) { if (d.empty()) continue; std::string cand = (d.back() == '/') ? d + file : d + "/" + file; if (file_exists(cand)) return cand; } return ""; }; for (const auto& vp : mf_.vpaths) { const std::string& pat = vp.first; size_t pct = pat.find('%'); bool matches; if (pct == std::string::npos) { matches = (file == pat); } else { std::string prefix = pat.substr(0, pct); std::string suffix = pat.substr(pct + 1); matches = file.size() >= prefix.size() + suffix.size() && file.compare(0, prefix.size(), prefix) == 0 && (suffix.empty() || file.compare(file.size() - suffix.size(), suffix.size(), suffix) == 0); } if (!matches) continue; std::string found = search_dirs(vp.second); if (!found.empty()) return found; } // Fall back to the global VPATH variable (colon- and/or space-separated // directory list), which applies to every prerequisite regardless of // pattern. auto it = mf_.variables.find("VPATH"); if (it != mf_.variables.end()) { std::string val = (it->second.flavor == VarFlavor::Recursive) ? parser_.expand(it->second.value, mf_) : it->second.value; std::vector<std::string> dirs; std::string cur; for (char c : val) { if (c == ':' || c == ' ' || c == '\t') { if (!cur.empty()) { dirs.push_back(cur); cur.clear(); } } else { cur += c; } } if (!cur.empty()) dirs.push_back(cur); std::string found = search_dirs(dirs); if (!found.empty()) return found; } return file; } 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 @@ -169,7 +233,7 @@ const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) if (ppct != std::string::npos) { prereq = prereq.substr(0, ppct) + stem + prereq.substr(ppct + 1); } if (file_exists(prereq)) return &rule; if (file_exists(prereq) || resolve_vpath(prereq) != prereq) return &rule; } } } Loading Loading @@ -197,7 +261,17 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, } std::string expanded = parser_.expand(ep, mf_); auto words = Parser::split_words(expanded); for (auto& w : words) w = Parser::normalize_target(w); for (auto& w : words) { w = Parser::normalize_target(w); // GNU make directory search: a prerequisite that does not // exist locally and has no local rule may still be found via // 'vpath'/VPATH (e.g. zstd's pattern-rule '%.c' prerequisites, // which live in common/, compress/, decompress/, ... and are // located via "vpath %.c $(ZSTD_SUBDIR)"). Substitute the // resolved path so $<, $^, and mtime comparisons see the file // make actually found rather than the bare, nonexistent name. w = resolve_vpath(w); } prereqs.insert(prereqs.end(), words.begin(), words.end()); } }; Loading @@ -222,6 +296,50 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, return prereqs; } std::vector<std::string> Executor::expand_order_only(const std::string& target, const Rule* rule, const std::string& stem) { std::vector<std::string> order_only; auto add_order_only = [&](const Rule* r, bool pattern) { for (const auto& p : r->order_only_prereqs) { std::string ep = p; if (pattern) { size_t pct = ep.find('%'); while (pct != std::string::npos) { ep = ep.substr(0, pct) + stem + ep.substr(pct + 1); pct = ep.find('%', pct + stem.size()); } } std::string expanded = parser_.expand(ep, mf_); auto words = Parser::split_words(expanded); for (auto& w : words) { w = Parser::normalize_target(w); w = resolve_vpath(w); } order_only.insert(order_only.end(), words.begin(), words.end()); } }; add_order_only(rule, rule->is_pattern || !rule->target_pattern.empty()); // Mirror expand_prereqs: merge order-only prerequisites from every // explicit rule for this target (a target's normal and order-only // prerequisites are commonly split across separate rule lines, e.g. // zstd's "$(ZSTD_STATICLIB): ARFLAGS = ...", "$(ZSTD_STATICLIB): | $(DIR)", // "$(ZSTD_STATICLIB): $(OBJS)"). for (const auto& r : mf_.rules) { if (r.is_pattern) continue; if (&r == rule) continue; for (const auto& t : r.targets) { if (t == target) { add_order_only(&r, false); break; } } } return order_only; } // ======================== File Operations ======================== bool Executor::file_exists(const std::string& path) { Loading Loading @@ -522,6 +640,7 @@ int Executor::build_sequential(const std::string& target_in, } auto prereqs = expand_prereqs(target, rule, stem); auto order_only = expand_order_only(target, rule, stem); // Compute the effective target-specific variable scope for this target: // inherited bindings from ancestors merged with this target's own bindings. Loading @@ -539,6 +658,18 @@ int Executor::build_sequential(const std::string& target_in, } } } // Order-only prerequisites must exist/be built before the recipe runs // (e.g. a directory-creation rule for '| $(BUILD_DIR)'), but never force a // rebuild themselves, so they are excluded from needs_rebuild()/execute_rule(). for (const auto& prereq : order_only) { int rc = build_sequential(prereq, scope); if (rc != 0) { if (!keep_going_) { visited_[target] = rc; return rc; } } } if (!needs_rebuild(target, prereqs)) { visited_[target] = 0; Loading Loading @@ -573,12 +704,21 @@ void Executor::add_to_dag(const std::string& target, std::set<std::string>& visi if (node.rule) { node.prereqs = expand_prereqs(target, node.rule, node.stem); node.order_only = expand_order_only(target, node.rule, node.stem); node.unresolved_deps = 0; for (const auto& p : node.prereqs) { add_to_dag(p, visited, scope); node.unresolved_deps++; } // Order-only prerequisites still gate scheduling (must be built first) // but are kept out of node.prereqs, so needs_rebuild()/execute_rule() // (which only see node.prereqs) never treat them as forcing a rebuild // or feed them into $^/$+/$?. for (const auto& p : node.order_only) { add_to_dag(p, visited, scope); node.unresolved_deps++; } } dag_[target] = std::move(node); Loading @@ -589,6 +729,11 @@ void Executor::add_to_dag(const std::string& target, std::set<std::string>& visi dag_[p].dependents.push_back(target); } } for (const auto& p : dag_[target].order_only) { if (dag_.count(p)) { dag_[p].dependents.push_back(target); } } } void Executor::worker_thread() { Loading executor.h +17 −0 Changes for executor.h: 17 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ private: const Rule* rule = nullptr; std::string stem; std::vector<std::string> prereqs; std::vector<std::string> order_only; std::vector<std::string> dependents; int unresolved_deps = 0; // Target-specific variable scope (own bindings merged with those Loading @@ -98,6 +99,16 @@ 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); // GNU make directory search (vpath / VPATH): if `file` does not exist in // the current directory and has no explicit rule to build it there (i.e. // it is expected to be a pre-existing source file, not something make will // produce locally), search the vpath directories registered for a pattern // matching `file` (in the order the 'vpath' directives appeared, trying // each pattern's directories in listed order), then fall back to the // directories named in the global VPATH variable. Returns the found path, // or `file` unchanged if it already exists, has a local rule, or nothing // matched. std::string resolve_vpath(const std::string& file); // Merge a target's own target-specific variable bindings onto the bindings // inherited from ancestors (GNU make propagates target-specific variables // to prerequisites). Returns the effective override map for `target`. Loading @@ -105,6 +116,12 @@ private: const std::string& target, const std::map<std::string, std::string>& inherited); std::vector<std::string> expand_prereqs(const std::string& target, const Rule* rule, const std::string& stem); // Order-only prerequisites ('target: normal-prereqs | order-only-prereqs'). // Must exist/be built before the recipe runs, but (unlike expand_prereqs) // never force a rebuild by themselves and are excluded from the automatic // variables ($^, $+, $?) passed to execute_rule. std::vector<std::string> expand_order_only(const std::string& target, const Rule* rule, const std::string& stem); bool needs_rebuild(const std::string& target, const std::vector<std::string>& prereqs); int execute_rule(const std::string& target, const Rule* rule, const std::string& stem, const std::vector<std::string>& prereqs, Loading parser.cpp +36 −2 Changes for parser.cpp: 36 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -568,8 +568,42 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_ return; } // vpath - ignore for now if (trimmed.size() > 5 && trimmed.substr(0, 5) == "vpath") { // vpath PATTERN dirs... : search dirs (in order) for a prerequisite // matching PATTERN when it does not exist in the current directory and has // no rule to build it locally (see Executor::resolve_vpath). Bare 'vpath' // clears every directive; 'vpath PATTERN' (no dirs) clears just that // pattern's directive, matching GNU make. if (trimmed == "vpath" || (trimmed.size() > 5 && (trimmed[5] == ' ' || trimmed[5] == '\t') && trimmed.substr(0, 5) == "vpath")) { std::string rest = trimmed.size() > 5 ? trim(trimmed.substr(5)) : ""; if (rest.empty()) { mf.vpaths.clear(); } else { size_t sp = rest.find_first_of(" \t"); std::string pattern = expand((sp == std::string::npos) ? rest : rest.substr(0, sp), mf); std::string dirs_str = (sp == std::string::npos) ? "" : trim(rest.substr(sp + 1)); if (dirs_str.empty()) { mf.vpaths.erase( std::remove_if(mf.vpaths.begin(), mf.vpaths.end(), [&](const auto& kv) { return kv.first == pattern; }), mf.vpaths.end()); } else { dirs_str = expand(dirs_str, mf); // Directories may be separated by whitespace and/or colons. std::vector<std::string> dirs; std::string cur; for (char c : dirs_str) { if (c == ':' || c == ' ' || c == '\t') { if (!cur.empty()) { dirs.push_back(cur); cur.clear(); } } else { cur += c; } } if (!cur.empty()) dirs.push_back(cur); mf.vpaths.push_back({pattern, dirs}); } } current_rule = nullptr; return; } Loading parser.h +7 −0 Changes for parser.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -51,6 +51,13 @@ struct Makefile { std::set<std::string> exported; bool export_all = false; // 'vpath PATTERN dirs...' directives, in the order they were encountered. // A prerequisite that does not exist in the current directory and has no // rule to build it locally is searched for in these directories (first // matching pattern, directories tried in listed order), falling back to // the global VPATH variable. See Executor::resolve_vpath. std::vector<std::pair<std::string, std::vector<std::string>>> vpaths; const Rule* find_explicit_rule(const std::string& target) const; const Rule* find_pattern_rule(const std::string& target, std::string& stem) const; }; Loading Loading
executor.cpp +147 −2 Changes for executor.cpp: 147 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -29,6 +29,12 @@ bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& if (file_exists(target)) return true; if (mf_.phony_targets.count(target)) return true; if (mf_.find_explicit_rule(target)) return true; // A leaf source file make cannot build itself may still exist elsewhere, // reachable only via a 'vpath'/VPATH directory search (e.g. zstd's // "$(BUILD_DIR)/%.o : %.c" pattern rule, whose '%.c' prerequisites live in // common/, compress/, decompress/, ... and are found via // "vpath %.c $(ZSTD_SUBDIR)", not in the current directory). if (resolve_vpath(target) != target) return true; if (depth > 20) return false; for (const auto& rule : mf_.rules) { if (!rule.is_pattern || rule.recipes.empty()) continue; Loading Loading @@ -62,6 +68,64 @@ bool Executor::can_be_made(const std::string& target_in, std::set<const Rule*>& return false; } std::string Executor::resolve_vpath(const std::string& file) { if (file.empty() || file_exists(file)) return file; // A prerequisite that make can build itself (has an explicit rule) stays // local; vpath/VPATH only locates pre-existing files make cannot produce. if (mf_.find_explicit_rule(file)) return file; auto search_dirs = [&](const std::vector<std::string>& dirs) -> std::string { for (const auto& d : dirs) { if (d.empty()) continue; std::string cand = (d.back() == '/') ? d + file : d + "/" + file; if (file_exists(cand)) return cand; } return ""; }; for (const auto& vp : mf_.vpaths) { const std::string& pat = vp.first; size_t pct = pat.find('%'); bool matches; if (pct == std::string::npos) { matches = (file == pat); } else { std::string prefix = pat.substr(0, pct); std::string suffix = pat.substr(pct + 1); matches = file.size() >= prefix.size() + suffix.size() && file.compare(0, prefix.size(), prefix) == 0 && (suffix.empty() || file.compare(file.size() - suffix.size(), suffix.size(), suffix) == 0); } if (!matches) continue; std::string found = search_dirs(vp.second); if (!found.empty()) return found; } // Fall back to the global VPATH variable (colon- and/or space-separated // directory list), which applies to every prerequisite regardless of // pattern. auto it = mf_.variables.find("VPATH"); if (it != mf_.variables.end()) { std::string val = (it->second.flavor == VarFlavor::Recursive) ? parser_.expand(it->second.value, mf_) : it->second.value; std::vector<std::string> dirs; std::string cur; for (char c : val) { if (c == ':' || c == ' ' || c == '\t') { if (!cur.empty()) { dirs.push_back(cur); cur.clear(); } } else { cur += c; } } if (!cur.empty()) dirs.push_back(cur); std::string found = search_dirs(dirs); if (!found.empty()) return found; } return file; } 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 @@ -169,7 +233,7 @@ const Rule* Executor::find_rule(const std::string& target_in, std::string& stem) if (ppct != std::string::npos) { prereq = prereq.substr(0, ppct) + stem + prereq.substr(ppct + 1); } if (file_exists(prereq)) return &rule; if (file_exists(prereq) || resolve_vpath(prereq) != prereq) return &rule; } } } Loading Loading @@ -197,7 +261,17 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, } std::string expanded = parser_.expand(ep, mf_); auto words = Parser::split_words(expanded); for (auto& w : words) w = Parser::normalize_target(w); for (auto& w : words) { w = Parser::normalize_target(w); // GNU make directory search: a prerequisite that does not // exist locally and has no local rule may still be found via // 'vpath'/VPATH (e.g. zstd's pattern-rule '%.c' prerequisites, // which live in common/, compress/, decompress/, ... and are // located via "vpath %.c $(ZSTD_SUBDIR)"). Substitute the // resolved path so $<, $^, and mtime comparisons see the file // make actually found rather than the bare, nonexistent name. w = resolve_vpath(w); } prereqs.insert(prereqs.end(), words.begin(), words.end()); } }; Loading @@ -222,6 +296,50 @@ std::vector<std::string> Executor::expand_prereqs(const std::string& target, return prereqs; } std::vector<std::string> Executor::expand_order_only(const std::string& target, const Rule* rule, const std::string& stem) { std::vector<std::string> order_only; auto add_order_only = [&](const Rule* r, bool pattern) { for (const auto& p : r->order_only_prereqs) { std::string ep = p; if (pattern) { size_t pct = ep.find('%'); while (pct != std::string::npos) { ep = ep.substr(0, pct) + stem + ep.substr(pct + 1); pct = ep.find('%', pct + stem.size()); } } std::string expanded = parser_.expand(ep, mf_); auto words = Parser::split_words(expanded); for (auto& w : words) { w = Parser::normalize_target(w); w = resolve_vpath(w); } order_only.insert(order_only.end(), words.begin(), words.end()); } }; add_order_only(rule, rule->is_pattern || !rule->target_pattern.empty()); // Mirror expand_prereqs: merge order-only prerequisites from every // explicit rule for this target (a target's normal and order-only // prerequisites are commonly split across separate rule lines, e.g. // zstd's "$(ZSTD_STATICLIB): ARFLAGS = ...", "$(ZSTD_STATICLIB): | $(DIR)", // "$(ZSTD_STATICLIB): $(OBJS)"). for (const auto& r : mf_.rules) { if (r.is_pattern) continue; if (&r == rule) continue; for (const auto& t : r.targets) { if (t == target) { add_order_only(&r, false); break; } } } return order_only; } // ======================== File Operations ======================== bool Executor::file_exists(const std::string& path) { Loading Loading @@ -522,6 +640,7 @@ int Executor::build_sequential(const std::string& target_in, } auto prereqs = expand_prereqs(target, rule, stem); auto order_only = expand_order_only(target, rule, stem); // Compute the effective target-specific variable scope for this target: // inherited bindings from ancestors merged with this target's own bindings. Loading @@ -539,6 +658,18 @@ int Executor::build_sequential(const std::string& target_in, } } } // Order-only prerequisites must exist/be built before the recipe runs // (e.g. a directory-creation rule for '| $(BUILD_DIR)'), but never force a // rebuild themselves, so they are excluded from needs_rebuild()/execute_rule(). for (const auto& prereq : order_only) { int rc = build_sequential(prereq, scope); if (rc != 0) { if (!keep_going_) { visited_[target] = rc; return rc; } } } if (!needs_rebuild(target, prereqs)) { visited_[target] = 0; Loading Loading @@ -573,12 +704,21 @@ void Executor::add_to_dag(const std::string& target, std::set<std::string>& visi if (node.rule) { node.prereqs = expand_prereqs(target, node.rule, node.stem); node.order_only = expand_order_only(target, node.rule, node.stem); node.unresolved_deps = 0; for (const auto& p : node.prereqs) { add_to_dag(p, visited, scope); node.unresolved_deps++; } // Order-only prerequisites still gate scheduling (must be built first) // but are kept out of node.prereqs, so needs_rebuild()/execute_rule() // (which only see node.prereqs) never treat them as forcing a rebuild // or feed them into $^/$+/$?. for (const auto& p : node.order_only) { add_to_dag(p, visited, scope); node.unresolved_deps++; } } dag_[target] = std::move(node); Loading @@ -589,6 +729,11 @@ void Executor::add_to_dag(const std::string& target, std::set<std::string>& visi dag_[p].dependents.push_back(target); } } for (const auto& p : dag_[target].order_only) { if (dag_.count(p)) { dag_[p].dependents.push_back(target); } } } void Executor::worker_thread() { Loading
executor.h +17 −0 Changes for executor.h: 17 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ private: const Rule* rule = nullptr; std::string stem; std::vector<std::string> prereqs; std::vector<std::string> order_only; std::vector<std::string> dependents; int unresolved_deps = 0; // Target-specific variable scope (own bindings merged with those Loading @@ -98,6 +99,16 @@ 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); // GNU make directory search (vpath / VPATH): if `file` does not exist in // the current directory and has no explicit rule to build it there (i.e. // it is expected to be a pre-existing source file, not something make will // produce locally), search the vpath directories registered for a pattern // matching `file` (in the order the 'vpath' directives appeared, trying // each pattern's directories in listed order), then fall back to the // directories named in the global VPATH variable. Returns the found path, // or `file` unchanged if it already exists, has a local rule, or nothing // matched. std::string resolve_vpath(const std::string& file); // Merge a target's own target-specific variable bindings onto the bindings // inherited from ancestors (GNU make propagates target-specific variables // to prerequisites). Returns the effective override map for `target`. Loading @@ -105,6 +116,12 @@ private: const std::string& target, const std::map<std::string, std::string>& inherited); std::vector<std::string> expand_prereqs(const std::string& target, const Rule* rule, const std::string& stem); // Order-only prerequisites ('target: normal-prereqs | order-only-prereqs'). // Must exist/be built before the recipe runs, but (unlike expand_prereqs) // never force a rebuild by themselves and are excluded from the automatic // variables ($^, $+, $?) passed to execute_rule. std::vector<std::string> expand_order_only(const std::string& target, const Rule* rule, const std::string& stem); bool needs_rebuild(const std::string& target, const std::vector<std::string>& prereqs); int execute_rule(const std::string& target, const Rule* rule, const std::string& stem, const std::vector<std::string>& prereqs, Loading
parser.cpp +36 −2 Changes for parser.cpp: 36 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -568,8 +568,42 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_ return; } // vpath - ignore for now if (trimmed.size() > 5 && trimmed.substr(0, 5) == "vpath") { // vpath PATTERN dirs... : search dirs (in order) for a prerequisite // matching PATTERN when it does not exist in the current directory and has // no rule to build it locally (see Executor::resolve_vpath). Bare 'vpath' // clears every directive; 'vpath PATTERN' (no dirs) clears just that // pattern's directive, matching GNU make. if (trimmed == "vpath" || (trimmed.size() > 5 && (trimmed[5] == ' ' || trimmed[5] == '\t') && trimmed.substr(0, 5) == "vpath")) { std::string rest = trimmed.size() > 5 ? trim(trimmed.substr(5)) : ""; if (rest.empty()) { mf.vpaths.clear(); } else { size_t sp = rest.find_first_of(" \t"); std::string pattern = expand((sp == std::string::npos) ? rest : rest.substr(0, sp), mf); std::string dirs_str = (sp == std::string::npos) ? "" : trim(rest.substr(sp + 1)); if (dirs_str.empty()) { mf.vpaths.erase( std::remove_if(mf.vpaths.begin(), mf.vpaths.end(), [&](const auto& kv) { return kv.first == pattern; }), mf.vpaths.end()); } else { dirs_str = expand(dirs_str, mf); // Directories may be separated by whitespace and/or colons. std::vector<std::string> dirs; std::string cur; for (char c : dirs_str) { if (c == ':' || c == ' ' || c == '\t') { if (!cur.empty()) { dirs.push_back(cur); cur.clear(); } } else { cur += c; } } if (!cur.empty()) dirs.push_back(cur); mf.vpaths.push_back({pattern, dirs}); } } current_rule = nullptr; return; } Loading
parser.h +7 −0 Changes for parser.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -51,6 +51,13 @@ struct Makefile { std::set<std::string> exported; bool export_all = false; // 'vpath PATTERN dirs...' directives, in the order they were encountered. // A prerequisite that does not exist in the current directory and has no // rule to build it locally is searched for in these directories (first // matching pattern, directories tried in listed order), falling back to // the global VPATH variable. See Executor::resolve_vpath. std::vector<std::pair<std::string, std::vector<std::string>>> vpaths; const Rule* find_explicit_rule(const std::string& target) const; const Rule* find_pattern_rule(const std::string& target, std::string& stem) const; }; Loading