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

expand order only implemented

parent fb6a3e41
Loading
Loading
Loading
Loading
+147 −2
Changes for executor.cpp: 147 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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;
                }
            }
        }
@@ -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());
        }
    };
@@ -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) {
@@ -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.
@@ -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;
@@ -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);
@@ -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() {
+17 −0
Changes for executor.h: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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
@@ -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`.
@@ -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,
+36 −2
Changes for parser.cpp: 36 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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;
    }
+7 −0
Changes for parser.h: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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;
};