Loading executor.cpp +24 −7 Changes for executor.cpp: 24 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -600,7 +600,11 @@ void Executor::worker_thread() { return !ready_queue_.empty() || remaining_ == 0 || failed_; }); if ((remaining_ == 0 || failed_) && ready_queue_.empty()) return; if (remaining_ == 0 && ready_queue_.empty()) return; // When a recipe has failed and we are not in -k mode, stop // picking up new work. Currently running recipes finish // naturally; once they do, remaining_ hits 0 and we exit. if (failed_ && !keep_going_) return; if (ready_queue_.empty()) continue; target = ready_queue_.front(); Loading Loading @@ -637,12 +641,8 @@ void Executor::worker_thread() { std::lock_guard<std::mutex> lock(mutex_); if (success) { node.state = DagNode::State::Done; } else { node.state = DagNode::State::Failed; if (!keep_going_) failed_ = true; } // Update dependents // Only propagate to dependents on success: decrement their // unresolved count and queue them when all prereqs are done. for (const auto& dep : node.dependents) { auto& dep_node = dag_[dep]; dep_node.unresolved_deps--; Loading @@ -651,6 +651,23 @@ void Executor::worker_thread() { ready_queue_.push(dep); } } } else { node.state = DagNode::State::Failed; failed_ = true; // Cascade failure: mark all transitive dependents as Failed // so they never become Ready and remaining_ is decremented // correctly (preventing worker threads from hanging). std::queue<std::string> fail_q; for (const auto& d : node.dependents) fail_q.push(d); while (!fail_q.empty()) { auto& fn = dag_[fail_q.front()]; fail_q.pop(); if (fn.state != DagNode::State::Pending) continue; fn.state = DagNode::State::Failed; remaining_--; for (const auto& d2 : fn.dependents) fail_q.push(d2); } } remaining_--; } Loading parser.cpp +18 −6 Changes for parser.cpp: 18 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -592,6 +592,7 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_ if (trimmed.find("$(") != std::string::npos || trimmed.find("${") != std::string::npos) { std::string expanded = expand(trimmed, mf); current_rule = nullptr; current_rule = nullptr; std::string et = trim(expanded); if (!et.empty() && et != trimmed) { std::istringstream iss(expanded); Loading Loading @@ -811,7 +812,18 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren targets_str = expand(targets_str, mf); auto targets = split_words(targets_str); if (targets.empty()) return false; if (targets.empty()) { // A rule whose targets expand to nothing (e.g. $(host-cmulti): FORCE // when host-cmulti is empty). GNU make silently discards the rule AND // any following recipe lines. Push a dummy rule so that current_rule is // non-null and the parser absorbs subsequent tab-indented recipe lines // instead of treating them as standalone statements (which would expand // $(call if_changed,...) outside recipe context and trigger spurious // $(warning FORCE prerequisite is missing) diagnostics from Kbuild). mf.rules.push_back(Rule{}); current_rule = &mf.rules.back(); return true; } std::string first_orig_target = targets[0]; for (auto& t : targets) t = normalize_target(t); Loading Loading @@ -1032,11 +1044,6 @@ std::string Parser::expand(const std::string& input, const Makefile& mf) const { std::string Parser::expand_recursive_var( const std::string& name, const std::string& value, const Makefile& mf, const std::map<std::string, std::string>* auto_vars) const { // Break self-referential expansion cycles. GNU make aborts with // "Recursive variable references itself"; we instead treat the recursive // reference as empty so a single legitimate self-append still terminates. // thread_local so concurrent recipe/export expansions under -j each get // their own recursion guard rather than corrupting a shared set. static thread_local std::set<std::string> expanding_; if (!expanding_.insert(name).second) return ""; std::string out = do_expand(value, mf, auto_vars); Loading Loading @@ -1325,6 +1332,11 @@ std::string Parser::func_wildcard(const std::string& pattern) const { if (fs::exists(dir, ec) && fs::is_directory(dir, ec)) { for (const auto& entry : fs::directory_iterator(dir, ec)) { std::string fname = entry.path().filename().string(); // POSIX glob: a leading dot in a filename is only matched by a // pattern that also starts with a dot. $(wildcard *.h) must NOT // match `.tmp_foo.h`. GNU make behaves the same way. if (!fname.empty() && fname[0] == '.' && !name_pat.empty() && name_pat[0] != '.') continue; if (glob_match(name_pat, fname)) { if (dir == ".") { results.push_back(fname); Loading Loading
executor.cpp +24 −7 Changes for executor.cpp: 24 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -600,7 +600,11 @@ void Executor::worker_thread() { return !ready_queue_.empty() || remaining_ == 0 || failed_; }); if ((remaining_ == 0 || failed_) && ready_queue_.empty()) return; if (remaining_ == 0 && ready_queue_.empty()) return; // When a recipe has failed and we are not in -k mode, stop // picking up new work. Currently running recipes finish // naturally; once they do, remaining_ hits 0 and we exit. if (failed_ && !keep_going_) return; if (ready_queue_.empty()) continue; target = ready_queue_.front(); Loading Loading @@ -637,12 +641,8 @@ void Executor::worker_thread() { std::lock_guard<std::mutex> lock(mutex_); if (success) { node.state = DagNode::State::Done; } else { node.state = DagNode::State::Failed; if (!keep_going_) failed_ = true; } // Update dependents // Only propagate to dependents on success: decrement their // unresolved count and queue them when all prereqs are done. for (const auto& dep : node.dependents) { auto& dep_node = dag_[dep]; dep_node.unresolved_deps--; Loading @@ -651,6 +651,23 @@ void Executor::worker_thread() { ready_queue_.push(dep); } } } else { node.state = DagNode::State::Failed; failed_ = true; // Cascade failure: mark all transitive dependents as Failed // so they never become Ready and remaining_ is decremented // correctly (preventing worker threads from hanging). std::queue<std::string> fail_q; for (const auto& d : node.dependents) fail_q.push(d); while (!fail_q.empty()) { auto& fn = dag_[fail_q.front()]; fail_q.pop(); if (fn.state != DagNode::State::Pending) continue; fn.state = DagNode::State::Failed; remaining_--; for (const auto& d2 : fn.dependents) fail_q.push(d2); } } remaining_--; } Loading
parser.cpp +18 −6 Changes for parser.cpp: 18 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -592,6 +592,7 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_ if (trimmed.find("$(") != std::string::npos || trimmed.find("${") != std::string::npos) { std::string expanded = expand(trimmed, mf); current_rule = nullptr; current_rule = nullptr; std::string et = trim(expanded); if (!et.empty() && et != trimmed) { std::istringstream iss(expanded); Loading Loading @@ -811,7 +812,18 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren targets_str = expand(targets_str, mf); auto targets = split_words(targets_str); if (targets.empty()) return false; if (targets.empty()) { // A rule whose targets expand to nothing (e.g. $(host-cmulti): FORCE // when host-cmulti is empty). GNU make silently discards the rule AND // any following recipe lines. Push a dummy rule so that current_rule is // non-null and the parser absorbs subsequent tab-indented recipe lines // instead of treating them as standalone statements (which would expand // $(call if_changed,...) outside recipe context and trigger spurious // $(warning FORCE prerequisite is missing) diagnostics from Kbuild). mf.rules.push_back(Rule{}); current_rule = &mf.rules.back(); return true; } std::string first_orig_target = targets[0]; for (auto& t : targets) t = normalize_target(t); Loading Loading @@ -1032,11 +1044,6 @@ std::string Parser::expand(const std::string& input, const Makefile& mf) const { std::string Parser::expand_recursive_var( const std::string& name, const std::string& value, const Makefile& mf, const std::map<std::string, std::string>* auto_vars) const { // Break self-referential expansion cycles. GNU make aborts with // "Recursive variable references itself"; we instead treat the recursive // reference as empty so a single legitimate self-append still terminates. // thread_local so concurrent recipe/export expansions under -j each get // their own recursion guard rather than corrupting a shared set. static thread_local std::set<std::string> expanding_; if (!expanding_.insert(name).second) return ""; std::string out = do_expand(value, mf, auto_vars); Loading Loading @@ -1325,6 +1332,11 @@ std::string Parser::func_wildcard(const std::string& pattern) const { if (fs::exists(dir, ec) && fs::is_directory(dir, ec)) { for (const auto& entry : fs::directory_iterator(dir, ec)) { std::string fname = entry.path().filename().string(); // POSIX glob: a leading dot in a filename is only matched by a // pattern that also starts with a dot. $(wildcard *.h) must NOT // match `.tmp_foo.h`. GNU make behaves the same way. if (!fname.empty() && fname[0] == '.' && !name_pat.empty() && name_pat[0] != '.') continue; if (glob_match(name_pat, fname)) { if (dir == ".") { results.push_back(fname); Loading