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

reread implemeted

parent 68af0091
Loading
Loading
Loading
Loading
+113 −27
Changes for main.cpp: 113 added lines, 27 removed lines.
Original line number Diff line number Diff line
@@ -246,12 +246,8 @@ int main(int argc, char* argv[]) {
        }
    }

    makeplus::Makefile mf;
    makeplus::Parser parser;

    // Setup default variables
    setup_defaults(mf, argv[0]);

    // GNU make propagates command-line variable overrides to every sub-make via
    // MAKEFLAGS: the portion after "--" holds NAME=value assignments (spaces in
    // a value are backslash-escaped). A child make re-reads them and gives them
@@ -353,25 +349,24 @@ int main(int argc, char* argv[]) {
        }
    }

    // Apply command-line variables (highest priority). Also export them into the
    // environment, like GNU make, so plain $$VAR references in recipes and any
    // child process observe the value.
    // Export command-line variables into the environment once (idempotent), like
    // GNU make, so plain $$VAR references in recipes and any child process
    // observe the value. The makefile-variable bindings are (re)applied per parse
    // inside load_makefile below so a re-read starts from a clean Makefile.
    for (const auto& [name, value] : cmdline_vars) {
        mf.variables[name] = {value, makeplus::VarFlavor::Simple, true};
        setenv(name.c_str(), value.c_str(), 1);
    }

    // MAKECMDGOALS and MAKEFLAGS must exist BEFORE parsing: build systems like
    // Kbuild inspect them at parse time (e.g. to generate per-goal rules and to
    // decide whether a sub-make recursion is still needed).
    {
    // MAKECMDGOALS string (Kbuild inspects it at parse time).
    std::string goals;
    for (size_t i = 0; i < targets.size(); ++i) {
        if (i) goals += ' ';
        goals += targets[i];
    }
        mf.variables["MAKECMDGOALS"] = {goals, makeplus::VarFlavor::Simple, false};
    }

    // Compute the MAKEFLAGS string (pure; depends only on options/overrides, not
    // on any parsed makefile state) so it can be (re)applied on every parse.
    std::string mkflags;
    {
        std::string flags;
        if (dry_run) flags += "n";
@@ -413,29 +408,120 @@ int main(int argc, char* argv[]) {
        // Only strip LEADING whitespace: a trailing escaped space ("\ ") in the
        // override section is significant, and trimming it would leave a dangling
        // backslash that corrupts the value when a child make decodes MAKEFLAGS.
        std::string mkflags = combined;
        mkflags = combined;
        mkflags.erase(0, mkflags.find_first_not_of(" \t"));
        mf.variables["MAKEFLAGS"] = {mkflags, makeplus::VarFlavor::Simple, false};
    }
    // MAKEFLAGS must be in the environment so a recursive $(MAKE) (a fresh
    // makeplus process) inherits the flags and the variable overrides above.
    setenv("MAKEFLAGS", mkflags.c_str(), 1);

    // Build a fresh Makefile from scratch: defaults, command-line variable
    // overrides, then the make-special variables that must exist before parsing.
    auto load_makefile = [&](makeplus::Makefile& m) -> bool {
        setup_defaults(m, argv[0]);
        // Command-line variables have the highest priority.
        for (const auto& [name, value] : cmdline_vars)
            m.variables[name] = {value, makeplus::VarFlavor::Simple, true};
        m.variables["MAKECMDGOALS"] = {goals, makeplus::VarFlavor::Simple, false};
        m.variables["MAKEFLAGS"] = {mkflags, makeplus::VarFlavor::Simple, false};
        return parser.parse_file(makefile_path, m);
    };

    auto configure_executor = [&](makeplus::Executor& e) {
        e.set_jobs(jobs);
        e.set_jobserver(js_read_fd, js_write_fd);
        e.set_dry_run(dry_run);
        e.set_always_make(always_make);
        e.set_keep_going(keep_going);
        e.set_silent(silent);
        e.set_question(question);
    };

    // Parse the makefile(s).
    makeplus::Makefile mf;
    if (!load_makefile(mf)) {
        std::cerr << "makeplus: " << parser.error() << std::endl;
        return 2;
    }

    // "How Makefiles Are Remade": after reading all makefiles, GNU make tries to
    // rebuild every included makefile using the rules just read; if any is
    // actually updated, it re-reads all makefiles from scratch (GNU make
    // re-execs itself). The Linux kernel relies on this to regenerate
    // include/config/auto.conf via the syncconfig rule and then re-read the fresh
    // CONFIG_* values *before* computing KBUILD_CFLAGS. Without it, switching the
    // toolchain (e.g. gcc -> clang/LLVM=1) leaves gcc-only flags such as
    // -fconserve-stack and -fmin-function-alignment in KBUILD_CFLAGS (they are
    // gated on the stale CONFIG_CC_IS_GCC / CONFIG_CC_HAS_MIN_FUNCTION_ALIGNMENT)
    // and the clang compile aborts with "unknown argument".
    {
        const int kMaxRestarts = 10;
        for (int restart = 0; restart < kMaxRestarts; ++restart) {
            // Candidate makefiles = the unique words of MAKEFILE_LIST (the main
            // makefile plus every file pulled in by include/-include).
            std::vector<std::string> makefiles;
            {
                std::set<std::string> seen;
                auto it = mf.variables.find("MAKEFILE_LIST");
                if (it != mf.variables.end()) {
                    for (const auto& w : makeplus::Parser::split_words(it->second.value))
                        if (seen.insert(w).second) makefiles.push_back(w);
                }
            }

    // Parse makefile
    if (!parser.parse_file(makefile_path, mf)) {
            bool changed = false;
            for (const auto& f : makefiles) {
                // Only attempt to remake a makefile that some rule can build.
                const makeplus::Rule* rule = mf.find_explicit_rule(f);
                if (!rule) {
                    std::string stem;
                    rule = mf.find_pattern_rule(f, stem);
                    // Skip match-anything ("%") implicit rules for makefiles, as
                    // GNU make does, so a catch-all rule never clobbers a real
                    // makefile that simply has no rule of its own.
                    if (rule) {
                        bool match_anything = false;
                        for (const auto& t : rule->targets)
                            if (t == "%") { match_anything = true; break; }
                        if (match_anything) rule = nullptr;
                    }
                }
                if (!rule) continue;

                std::error_code ec;
                bool had = fs::exists(f, ec);
                auto t0 = had ? fs::last_write_time(f, ec)
                              : fs::file_time_type::min();

                // Remake the makefile with its own executor so the real build's
                // dependency-visited state stays clean. Silence its output: this
                // is bookkeeping, not part of the user's requested goals.
                makeplus::Executor mexec(mf, parser);
                configure_executor(mexec);
                mexec.set_silent(true);
                if (mexec.build({f}) != 0)
                    continue;  // a makefile failed to remake; leave it as-is

                if (fs::exists(f, ec)) {
                    auto t1 = fs::last_write_time(f, ec);
                    if (!had || t1 != t0) changed = true;
                }
            }

            if (!changed) break;  // makefiles are up to date; converged

            // A makefile was regenerated: discard the parsed state and re-read
            // everything so the fresh values take effect.
            mf = makeplus::Makefile{};
            if (!load_makefile(mf)) {
                std::cerr << "makeplus: " << parser.error() << std::endl;
                return 2;
            }
        }
    }

    // Execute
    // Execute the user's goals against the final (re-read) makefile state.
    makeplus::Executor executor(mf, parser);
    executor.set_jobs(jobs);
    executor.set_jobserver(js_read_fd, js_write_fd);
    executor.set_dry_run(dry_run);
    executor.set_always_make(always_make);
    executor.set_keep_going(keep_going);
    executor.set_silent(silent);
    executor.set_question(question);

    configure_executor(executor);
    return executor.build(targets);
}