Loading main.cpp +113 −27 Changes for main.cpp: 113 added lines, 27 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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"; Loading Loading @@ -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); } Loading
main.cpp +113 −27 Changes for main.cpp: 113 added lines, 27 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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"; Loading Loading @@ -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); }