Commit 0689a722 authored by jan.koester's avatar jan.koester
Browse files

test

parent 194c15e9
Loading
Loading
Loading
Loading
+35 −1
Changes for executor.cpp: 35 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -136,10 +136,44 @@ int Executor::run_command(const std::string& cmd) {
    return 1;
}

void Executor::apply_exports(const Makefile& mf) {
    auto export_one = [&](const std::string& name, const Variable& var) {
        std::string val = (var.flavor == VarFlavor::Recursive)
            ? parser_.expand(var.value, mf) : var.value;
        setenv(name.c_str(), val.c_str(), 1);
    };
    if (mf.export_all) {
        for (const auto& kv : mf.variables) {
            if (kv.second.from_cmdline) continue;
            export_one(kv.first, kv.second);
        }
    } else {
        for (const auto& name : mf.exported) {
            auto it = mf.variables.find(name);
            if (it != mf.variables.end()) export_one(name, it->second);
        }
    }
}

int Executor::execute_rule(const std::string& target, const Rule* rule,
                            const std::string& stem, const std::vector<std::string>& prereqs) {
    std::string first_prereq = prereqs.empty() ? "" : prereqs[0];

    // Apply target-specific variables (recursive flavor) for this target by
    // expanding the recipes against a local override of the makefile.
    const Makefile* mfp = &mf_;
    Makefile local_copy;
    auto tv = mf_.target_vars.find(target);
    if (tv != mf_.target_vars.end() && !tv->second.empty()) {
        local_copy = mf_;
        for (const auto& kv : tv->second)
            local_copy.variables[kv.first] = {kv.second, VarFlavor::Recursive, false};
        mfp = &local_copy;
    }
    // Export marked variables into the environment so recipes and any
    // recursive sub-makes ($(MAKE)) inherit them.
    apply_exports(*mfp);

    std::vector<std::string> newer;
    if (file_exists(target)) {
        long long target_mtime = file_mtime(target);
@@ -152,7 +186,7 @@ int Executor::execute_rule(const std::string& target, const Rule* rule,

    for (const auto& recipe : rule->recipes) {
        std::string cmd = parser_.expand_with_automatic(
            recipe, mf_, target, first_prereq, prereqs, newer, stem);
            recipe, *mfp, target, first_prereq, prereqs, newer, stem);

        bool is_silent = silent_;
        bool ignore_error = false;
+1 −0
Changes for executor.h: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ private:
    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);
    void apply_exports(const Makefile& mf);
    int run_command(const std::string& cmd);
    bool file_exists(const std::string& path);
    long long file_mtime(const std::string& path);
+159 −3
Changes for parser.cpp: 159 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>

namespace fs = std::filesystem;
using namespace makeplus;
@@ -296,6 +297,39 @@ bool Parser::handle_conditional(const std::string& line, Makefile& mf) {
}

void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_rule) {
    // Inside a 'define NAME ... endef' block: capture the body verbatim (no
    // conditional / rule interpretation) until the closing 'endef'.
    if (in_define_) {
        std::string dt = trim(line);
        if (dt == "endef" || (dt.size() > 6 && dt.substr(0, 6) == "endef ")) {
            std::string body;
            for (size_t i = 0; i < define_body_.size(); ++i) {
                if (i) body += "\n";
                body += define_body_[i];
            }
            if (define_op_ == ":=") {
                mf.variables[define_name_] = {expand(body, mf), VarFlavor::Simple, false};
            } else if (define_op_ == "+=") {
                auto it = mf.variables.find(define_name_);
                if (it != mf.variables.end() && !it->second.value.empty())
                    it->second.value += "\n" + body;
                else
                    mf.variables[define_name_] = {body, VarFlavor::Recursive, false};
            } else if (define_op_ == "?=") {
                if (mf.variables.find(define_name_) == mf.variables.end())
                    mf.variables[define_name_] = {body, VarFlavor::Recursive, false};
            } else {
                mf.variables[define_name_] = {body, VarFlavor::Recursive, false};
            }
            in_define_ = false;
            define_body_.clear();
            current_rule = nullptr;
            return;
        }
        define_body_.push_back(line);
        return;
    }

    if (line.empty()) {
        current_rule = nullptr;
        return;
@@ -363,10 +397,54 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_
        return;
    }

    // export
    // define NAME [op] ... endef  (multi-line variable definition)
    if (trimmed == "define" || (trimmed.size() > 7 && trimmed.substr(0, 7) == "define ")) {
        std::string rest = trim(trimmed.size() > 7 ? trimmed.substr(7) : "");
        std::string name = rest, op = "=";
        for (const std::string& cand : {std::string("::="), std::string(":="),
                                        std::string("+="), std::string("?="),
                                        std::string("=")}) {
            if (rest.size() >= cand.size() &&
                rest.substr(rest.size() - cand.size()) == cand) {
                name = trim(rest.substr(0, rest.size() - cand.size()));
                op = (cand == "::=") ? ":=" : cand;
                break;
            }
        }
        define_name_ = trim(name);
        define_op_ = op;
        define_body_.clear();
        in_define_ = true;
        current_rule = nullptr;
        return;
    }

    // export / unexport
    if (trimmed == "export") {
        mf.export_all = true;
        current_rule = nullptr;
        return;
    }
    if (trimmed.size() > 7 && trimmed.substr(0, 7) == "export ") {
        std::string rest = trim(trimmed.substr(7));
        if (rest.find('=') != std::string::npos) {
            try_parse_assignment(rest, mf);
            size_t ne = rest.find_first_of(":+?=");
            std::string nm = trim(rest.substr(0, ne));
            if (!nm.empty()) mf.exported.insert(nm);
        } else {
            for (const auto& nm : split_words(rest)) mf.exported.insert(nm);
        }
        current_rule = nullptr;
        return;
    }
    if (trimmed == "unexport") {
        mf.export_all = false;
        current_rule = nullptr;
        return;
    }
    if (trimmed.size() > 9 && trimmed.substr(0, 9) == "unexport ") {
        for (const auto& nm : split_words(trim(trimmed.substr(9)))) mf.exported.erase(nm);
        current_rule = nullptr;
        return;
    }
@@ -388,6 +466,25 @@ void Parser::process_line(const std::string& line, Makefile& mf, Rule*& current_
        return;
    }

    // Standalone function/variable line (e.g. $(eval ...), $(call tmpl,args),
    // $(info ...), $(warning ...), $(error ...)). GNU make expands every
    // non-recipe line; the expansion runs side effects and may itself produce
    // makefile text (assignments / rules) that must be re-parsed.
    if (trimmed.find("$(") != std::string::npos || trimmed.find("${") != std::string::npos) {
        std::string expanded = expand(trimmed, mf);
        current_rule = nullptr;
        std::string et = trim(expanded);
        if (!et.empty() && et != trimmed) {
            std::istringstream iss(expanded);
            std::string sub;
            Rule* sub_rule = nullptr;
            while (std::getline(iss, sub)) {
                process_line(sub, mf, sub_rule);
            }
        }
        return;
    }

    current_rule = nullptr;
}

@@ -486,6 +583,40 @@ bool Parser::try_parse_assignment(const std::string& line, Makefile& mf) {
    return true;
}

bool Parser::parse_target_var(const std::string& rest, std::string& name,
                              std::string& op, std::string& value) const {
    if (rest.empty()) return false;
    int depth = 0;
    for (size_t i = 0; i < rest.size(); ++i) {
        char c = rest[i];
        if (c == '$' && i + 1 < rest.size() && (rest[i+1] == '(' || rest[i+1] == '{')) {
            depth++; i++; continue;
        }
        if ((c == ')' || c == '}') && depth > 0) { depth--; continue; }
        if (depth != 0) continue;
        if (c == '=') {
            size_t opstart = i;
            op = "=";
            if (i >= 1 && (rest[i-1] == '+' || rest[i-1] == '?')) {
                op = std::string(1, rest[i-1]) + "=";
                opstart = i - 1;
            } else if (i >= 1 && rest[i-1] == ':') {
                op = ":=";
                opstart = (i >= 2 && rest[i-2] == ':') ? i - 2 : i - 1;
            }
            name = trim(rest.substr(0, opstart));
            value = trim(rest.substr(i + 1));
            if (name.empty()) return false;
            for (char nc : name) {
                if (!(std::isalnum(static_cast<unsigned char>(nc)) || nc == '_' || nc == '.'))
                    return false;
            }
            return true;
        }
    }
    return false;
}

bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& current_rule) {
    int depth = 0;
    size_t colon_pos = std::string::npos;
@@ -514,14 +645,39 @@ bool Parser::try_parse_rule(const std::string& line, Makefile& mf, Rule*& curren

    std::string targets_str = trim(line.substr(0, colon_pos));
    size_t prereq_start = colon_pos + 1 + (double_colon ? 1 : 0);
    std::string prereqs_str = prereq_start < line.size() ? trim(line.substr(prereq_start)) : "";
    std::string rhs_raw = prereq_start < line.size() ? trim(line.substr(prereq_start)) : "";

    targets_str = expand(targets_str, mf);
    prereqs_str = expand(prereqs_str, mf);

    auto targets = split_words(targets_str);
    if (targets.empty()) return false;

    // Target-specific variable assignment:  targets: NAME op VALUE
    {
        std::string tv_name, tv_op, tv_value;
        if (parse_target_var(rhs_raw, tv_name, tv_op, tv_value)) {
            std::string val = (tv_op == ":=") ? expand(tv_value, mf) : tv_value;
            for (const auto& t : targets) {
                auto& vec = mf.target_vars[t];
                bool found = false;
                for (auto& pr : vec) {
                    if (pr.first == tv_name) {
                        pr.second = (tv_op == "+=")
                            ? (pr.second.empty() ? val : pr.second + " " + val)
                            : val;
                        found = true;
                        break;
                    }
                }
                if (!found) vec.push_back({tv_name, val});
            }
            current_rule = nullptr;
            return true;
        }
    }

    std::string prereqs_str = expand(rhs_raw, mf);

    // Split prerequisites on |
    std::vector<std::string> prereqs;
    std::vector<std::string> order_only;
+15 −0
Changes for parser.h: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -33,6 +33,12 @@ struct Makefile {
    std::set<std::string> phony_targets;
    std::string default_target;
    std::string base_dir;
    // Target-specific variable assignments: target name -> list of (name, value).
    // Applied (recursive flavor) while the target's recipes are expanded.
    std::map<std::string, std::vector<std::pair<std::string, std::string>>> target_vars;
    // Variables marked for export into the recipe / sub-make environment.
    std::set<std::string> exported;
    bool export_all = false;

    const Rule* find_explicit_rule(const std::string& target) const;
    const Rule* find_pattern_rule(const std::string& target, std::string& stem) const;
@@ -66,7 +72,16 @@ private:
    };
    std::vector<CondState> cond_stack_;

    // Multi-line 'define NAME ... endef' variable accumulation state.
    bool in_define_ = false;
    std::string define_name_;
    std::string define_op_;   // "=", ":=", "+=", "?="
    std::vector<std::string> define_body_;

    void process_line(const std::string& line, Makefile& mf, Rule*& current_rule);
    // Detect 'NAME op VALUE' target-specific variable bodies after a ':'.
    bool parse_target_var(const std::string& rest, std::string& name,
                          std::string& op, std::string& value) const;
    bool try_parse_assignment(const std::string& line, Makefile& mf);
    bool try_parse_rule(const std::string& line, Makefile& mf, Rule*& current_rule);
    void handle_include(const std::string& args, Makefile& mf, bool optional);