Commit fb6a3e41 authored by jan.koester's avatar jan.koester
Browse files

bugfix

parent 7566e63a
Loading
Loading
Loading
Loading
+24 −7
Changes for executor.cpp: 24 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -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();
@@ -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--;
@@ -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_--;
        }
+18 −6
Changes for parser.cpp: 18 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -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);
@@ -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);

@@ -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);
@@ -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);