Loading executor.cpp +35 −1 Changes for executor.cpp: 35 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; Loading executor.h +1 −0 Changes for executor.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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); Loading parser.cpp +159 −3 Changes for parser.cpp: 159 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ #include <iostream> #include <cstdio> #include <cstring> #include <cctype> namespace fs = std::filesystem; using namespace makeplus; Loading Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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; Loading parser.h +15 −0 Changes for parser.h: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading
executor.cpp +35 −1 Changes for executor.cpp: 35 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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; Loading
executor.h +1 −0 Changes for executor.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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); Loading
parser.cpp +159 −3 Changes for parser.cpp: 159 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ #include <iostream> #include <cstdio> #include <cstring> #include <cctype> namespace fs = std::filesystem; using namespace makeplus; Loading Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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; Loading
parser.h +15 −0 Changes for parser.h: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading