diff options
29 files changed, 1644 insertions, 383 deletions
diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx index c94b50f..c4b9169 100644 --- a/libbuild2/adhoc-rule-buildscript.cxx +++ b/libbuild2/adhoc-rule-buildscript.cxx @@ -26,7 +26,6 @@ namespace build2 recipe_text (context& ctx, const scope& s, const target* tg, - const adhoc_actions& acts, string&& t, attributes& as) { @@ -61,7 +60,7 @@ namespace build2 istringstream is (move (t)); build::script::parser p (ctx); - script = p.pre_parse (s, tg, acts, + script = p.pre_parse (s, tg, actions, is, loc.file, loc.line + 1, move (diag), as.loc); @@ -104,15 +103,14 @@ namespace build2 os << ind << string (braces, '}'); } - optional<action> adhoc_buildscript_rule:: + bool adhoc_buildscript_rule:: reverse_fallback (action a, const target_type& tt) const { // We can provide clean for a file target if we are providing update. // - if (a == perform_update_id && tt.is_a<file> ()) - return perform_clean_id; - - return nullopt; + return a == perform_clean_id && tt.is_a<file> () && + find (actions.begin (), actions.end (), + perform_update_id) != actions.end (); } recipe adhoc_buildscript_rule:: @@ -143,6 +141,11 @@ namespace build2 return execute_inner; } + // Inject pattern's ad hoc group members, if any. + // + if (pattern != nullptr) + pattern->apply_adhoc_members (a, t, me); + // Derive file names for the target and its ad hoc group members, if any. // if (a == perform_update_id || a == perform_clean_id) @@ -165,6 +168,11 @@ namespace build2 // match_prerequisite_members (a, t); + // Inject pattern's prerequisites, if any. + // + if (pattern != nullptr) + pattern->apply_prerequisites (a, t, me); + // See if we are providing the standard clean as a fallback. // if (me.fallback) diff --git a/libbuild2/adhoc-rule-buildscript.hxx b/libbuild2/adhoc-rule-buildscript.hxx index bf14472..a04840d 100644 --- a/libbuild2/adhoc-rule-buildscript.hxx +++ b/libbuild2/adhoc-rule-buildscript.hxx @@ -22,7 +22,7 @@ namespace build2 public adhoc_rule_with_deadline { public: - virtual optional<action> + virtual bool reverse_fallback (action, const target_type&) const override; virtual recipe @@ -38,11 +38,11 @@ namespace build2 target_state default_action (action, const target&, const optional<timestamp>&) const; - adhoc_buildscript_rule (const location& l, size_t b) - : adhoc_rule ("<ad hoc buildscript recipe>", l, b) {} + adhoc_buildscript_rule (string n, const location& l, size_t b) + : adhoc_rule (move (n), l, b) {} virtual bool - recipe_text (context&, const scope&, const target*, const adhoc_actions&, + recipe_text (context&, const scope&, const target*, string&&, attributes&) override; virtual void diff --git a/libbuild2/adhoc-rule-cxx.cxx b/libbuild2/adhoc-rule-cxx.cxx index e061a06..5576eda 100644 --- a/libbuild2/adhoc-rule-cxx.cxx +++ b/libbuild2/adhoc-rule-cxx.cxx @@ -27,8 +27,10 @@ namespace build2 // adhoc_cxx_rule // adhoc_cxx_rule:: - adhoc_cxx_rule (const location& l, size_t b, uint64_t v, optional<string> s) - : adhoc_rule ("<ad hoc c++ recipe>", l, b), + adhoc_cxx_rule (string n, const location& l, size_t b, + uint64_t v, + optional<string> s) + : adhoc_rule (move (n), l, b), version (v), separator (move (s)), impl (nullptr) @@ -38,8 +40,7 @@ namespace build2 } bool adhoc_cxx_rule:: - recipe_text (context&, const scope&, const target*, const adhoc_actions&, - string&& t, attributes&) + recipe_text (context&, const scope&, const target*, string&& t, attributes&) { code = move (t); return true; @@ -95,6 +96,9 @@ namespace build2 bool adhoc_cxx_rule:: match (action a, target& t, const string& hint, match_extra& me) const { + if (pattern != nullptr && !pattern->match (a, t, hint, me)) + return false; + tracer trace ("adhoc_cxx_rule::match"); context& ctx (t.ctx); @@ -123,7 +127,8 @@ namespace build2 if ((impl = this->impl.load (memory_order_relaxed)) != nullptr) break; - using create_function = cxx_rule_v1* (const location&, target_state); + using create_function = cxx_rule_v1* ( + const location&, target_state, const adhoc_rule_pattern*); using load_function = create_function* (); // The only way to guarantee that the name of our module matches its @@ -434,9 +439,10 @@ namespace build2 // user-defined. // ofs << " static cxx_rule_v1*" << '\n' - << " create_" << id << " (const location& l, target_state s)" << '\n' + << " create_" << id << " (const location& l, target_state s, " << + "const adhoc_rule_pattern* p)" << '\n' << " {" << '\n' - << " return new rule (l, s);" << '\n' + << " return new rule (l, s, p);" << '\n' << " }" << '\n' << '\n'; @@ -463,7 +469,8 @@ namespace build2 << "#ifdef _WIN32" << '\n' << "__declspec(dllexport)" << '\n' << "#endif" << '\n' - << "cxx_rule_v1* (*" << sym << " ()) (const location&, target_state)" << '\n' + << "cxx_rule_v1* (*" << sym << " ()) (const location&, " << + "target_state, const adhoc_rule_pattern*)" << '\n' << "{" << '\n' << " return &rule_" << id << "::create_" << id << ";" << '\n' << "}" << '\n' @@ -652,7 +659,7 @@ namespace build2 load_function* lf (function_cast<load_function*> (hs.second)); create_function* cf (lf ()); - impl = cf (loc, l->executed_state (perform_update_id)); + impl = cf (loc, l->executed_state (perform_update_id), pattern); this->impl.store (impl, memory_order_relaxed); // Still in load phase. } } diff --git a/libbuild2/adhoc-rule-cxx.hxx b/libbuild2/adhoc-rule-cxx.hxx index 7b83607..8254906 100644 --- a/libbuild2/adhoc-rule-cxx.hxx +++ b/libbuild2/adhoc-rule-cxx.hxx @@ -25,6 +25,9 @@ namespace build2 // in its base. }; + // Note that when used as part of a pattern, the implementation cannot use + // the match_extra::buffer nor the target auxilary data storage. + // class LIBBUILD2_SYMEXPORT cxx_rule_v1: public cxx_rule { public: @@ -33,11 +36,19 @@ namespace build2 // cannot be injected as a real prerequisite since it's from a different // build context). // - const location recipe_loc; // Buildfile location of the recipe. - const target_state recipe_state; // State of recipe library target. + // If pattern is not NULL then this recipe belongs to an ad hoc pattern + // rule and apply() may need to call the pattern's apply_*() functions if + // the pattern has any ad hoc group member substitutions or prerequisite + // substitutions/non-patterns, respectively. + // + const location recipe_loc; // Buildfile location of the recipe. + const target_state recipe_state; // State of recipe library target. + const adhoc_rule_pattern* pattern; // Ad hoc pattern rule of recipe. - cxx_rule_v1 (const location& l, target_state s) - : recipe_loc (l), recipe_state (s) {} + cxx_rule_v1 (const location& l, + target_state s, + const adhoc_rule_pattern* p) + : recipe_loc (l), recipe_state (s), pattern (p) {} // Return true by default. // @@ -56,12 +67,12 @@ namespace build2 virtual recipe apply (action, target&, match_extra&) const override; - adhoc_cxx_rule (const location&, size_t, + adhoc_cxx_rule (string, const location&, size_t, uint64_t ver, optional<string> sep); virtual bool - recipe_text (context&, const scope&, const target*, const adhoc_actions&, + recipe_text (context&, const scope&, const target*, string&&, attributes&) override; virtual diff --git a/libbuild2/adhoc-rule-regex-pattern.cxx b/libbuild2/adhoc-rule-regex-pattern.cxx new file mode 100644 index 0000000..4c8c1e5 --- /dev/null +++ b/libbuild2/adhoc-rule-regex-pattern.cxx @@ -0,0 +1,442 @@ +// file : libbuild2/adhoc-rule-regex-pattern.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/adhoc-rule-regex-pattern.hxx> + +#include <libbutl/regex.mxx> + +#include <libbuild2/algorithm.hxx> + +namespace build2 +{ + using pattern_type = name::pattern_type; + + adhoc_rule_regex_pattern:: + adhoc_rule_regex_pattern ( + const scope& s, string rn, const target_type& tt, + name&& n, const location& nloc, + names&& ans, const location& aloc, + names&& pns, const location& ploc) + : adhoc_rule_pattern (s, move (rn), tt) + { + // Semantically, our rule pattern is one logical regular expression that + // spans multiple targets and prerequisites with a single back reference + // (\N) space. + // + // To implement this we are going to concatenate all the target and + // prerequisite sub-patterns separated with a character which cannot + // appear in the name (nor is a special regex character) but which is + // printable (for diagnostics). The directory separator (`/`) feels like a + // natural choice. We will call such a concatenated string of names a + // "name signature" (we also have a "type signature"; see below) and its + // pattern a "name signature pattern". + // + regex::flag_type flags (regex::ECMAScript); + + // Append the sub-pattern to text_ returning the status of the `e` flag. + // + auto append_pattern = [this, &flags, first = true] ( + const string& t, + const location& loc) mutable -> bool + { + size_t n (t.size ()), p (t.rfind (t[0])); + + // Process flags. + // + bool fi (false), fe (false); + for (size_t i (p + 1); i != n; ++i) + { + switch (t[i]) + { + case 'i': fi = true; break; + case 'e': fe = true; break; + } + } + + // For icase we require all or none of the patterns to have it. + // + if (first) + { + if (fi) + flags |= regex::icase; + } + else if (((flags & regex::icase) != 0) != fi) + fail (loc) << "inconsistent regex 'i' flag in '" << t << "'"; + + if (!first) + text_ += '/'; + else + first = false; + + text_.append (t.c_str () + 1, p - 1); + + return fe; + }; + + // Append an element either to targets_ or prereqs_. + // + auto append_element = [&s, &append_pattern] ( + vector<element>& v, + name&& n, + const location& loc, + const target_type* tt = nullptr) + { + if (tt == nullptr) + { + tt = n.untyped () || n.type == "*" + ? &target::static_type + : s.find_target_type (n.type); + + if (tt == nullptr) + fail (loc) << "unknown target type " << n.type; + } + + bool e (n.pattern && + *n.pattern == pattern_type::regex_pattern && + append_pattern (n.value, loc)); + + v.push_back (element {move (n), *tt, e}); + }; + + // This one is always a pattern. + // + append_element (targets_, move (n), nloc, &tt); + + // These are all patterns or substitutions. + // + for (name& an: ans) + append_element (targets_, move (an), aloc); + + // These can be patterns, substitutions, or non-patterns. + // + for (name& pn: pns) + append_element (prereqs_, move (pn), ploc); + + try + { + regex_ = regex (text_, flags); + } + catch (const regex_error& e) + { + // Print regex_error description if meaningful (no space). + // + // This may not necessarily be pointing at the actual location of the + // error but it should be close enough. + // + fail (nloc) << "invalid regex pattern '" << text_ << "'" << e; + } + } + + bool adhoc_rule_regex_pattern:: + match (action a, target& t, const string&, match_extra& me) const + { + tracer trace ("adhoc_rule_regex_pattern::match"); + + // The plan is as follows: First check the "type signature" of the target + // and its prerequisites (the primary target type has already been matched + // by the rule matching machinery). If there is a match, then concatenate + // their names into a "name signature" in the same way as for sub-patterns + // above and match that against the name signature regex pattern. If there + // is a match then this rule matches and the apply_*() functions should be + // called to process any member/prerequisite substitutions and inject them + // along with non-pattern prerequisites. + // + // It would be natural to perform the type match and concatenation of the + // names simultaneously. However, while the former should be quite cheap, + // the latter will most likely require dynamic allocation. To mitigate + // this we are going to pre-type-match the first prerequisite before + // concatenating any names. This should weed out most of the non-matches + // for sane patterns. + // + // Note also that we don't backtrack and try different combinations of the + // type-matching targets/prerequisites. We also ignore prerequisites + // marked ad hoc for type-matching. + // + auto pattern = [] (const element& e) -> bool + { + return e.name.pattern && *e.name.pattern == pattern_type::regex_pattern; + }; + + auto find_prereq = [a, &t] (const target_type& tt) -> optional<target_key> + { + // We use the standard logic that one would use in the rule::match() + // implementation. + // + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (include (a, t, p) == include_type::normal && p.is_a (tt)) + return p.key ().tk; + } + return nullopt; + }; + + // Pre-type-match the first prerequisite, if any. + // + auto pe (prereqs_.end ()), pi (find_if (prereqs_.begin (), pe, pattern)); + + optional<target_key> pk1; + if (pi != pe) + { + if (!(pk1 = find_prereq (pi->type))) + { + l4 ([&]{trace << rule_name << ": no " << pi->type.name + << "{} prerequisite for target " << t;}); + return false; + } + } + + // Ok, this is a potential match, start concatenating the names. + // + // Note that the regex_match_results object (which we will be passing + // through to apply() in the target's auxiliary data storage) contains + // iterators pointing to the string being matched. Which means this string + // must be kept around until we are done with replacing the subsitutions. + // In fact, we cannot even move it because this may invalidate the + // iterators (e.g., in case of a small string optimization). So the plan + // is to store the string in match_extra::buffer and regex_match_results + // (which we can move) in the auxiliary data storage. + // + string& ns (me.buffer); + + auto append_name = [&ns, first = true] (const target_key& tk, + const element& e) mutable + { + if (!first) + ns += '/'; + else + first = false; + + ns += *tk.name; + + // The same semantics as in variable_type_map::find(). + // + if (tk.ext && !tk.ext->empty () && + (e.match_ext || + tk.type->fixed_extension == &target_extension_none || + tk.type->fixed_extension == &target_extension_must)) + { + ns += '.'; + ns += *tk.ext; + } + }; + + // Primary target (always a pattern). + // + auto te (targets_.end ()), ti (targets_.begin ()); + append_name (t.key (), *ti); + + // Match ad hoc group members. + // + while ((ti = find_if (ti + 1, te, pattern)) != te) + { + const target* at (find_adhoc_member (t, ti->type)); + + if (at == nullptr) + { + l4 ([&]{trace << rule_name << ": no " << ti->type.name + << "{} ad hoc target group member for target " << t;}); + return false; + } + + append_name (at->key (), *ti); + } + + // Finish prerequisites. + // + if (pi != pe) + { + append_name (*pk1, *pi); + + while ((pi = find_if (pi + 1, pe, pattern)) != pe) + { + optional<target_key> pk (find_prereq (pi->type)); + + if (!pk) + { + l4 ([&]{trace << rule_name << ": no " << pi->type.name + << "{} prerequisite for target " << t;}); + return false; + } + + append_name (*pk, *pi); + } + } + + // While it can be tempting to optimize this for patterns that don't have + // any substitutions (which would be most of them), keep in mind that we + // will also need match_results for $N variables in the recipe (or a C++ + // rule implementation may want to access the match_results object). + // + regex_match_results mr; + if (!regex_match (ns, mr, regex_)) + { + l4 ([&]{trace << rule_name << ": name signature '" << ns + << "' does not match regex '" << text_ + << "' for target " << t;}); + return false; + } + + static_assert (sizeof (regex_match_results) <= target::data_size, + "insufficient space"); + t.data (move (mr)); + + return true; + } + + static inline string + substitute (const target& t, + const regex_match_results& mr, + const string& s, + const char* what) + { + string r (butl::regex_replace_match_results ( + mr, s.c_str () + 1, s.rfind (s[0]) - 1)); + + // @@ Note that while it would have been nice to print the location here, + // (and also pass to search()->find_target_type()), we would need to + // save location_value in each element to cover multiple declarations. + // + if (r.empty ()) + fail << what << " substitution '" << s << "' for target " << t + << " results in empty name"; + + return r; + } + + void adhoc_rule_regex_pattern:: + apply_adhoc_members (action, target& t, match_extra&) const + { + const auto& mr (t.data<regex_match_results> ()); + + for (auto i (targets_.begin () + 1); i != targets_.end (); ++i) + { + // These are all patterns or substitutions. + // + const element& e (*i); + + if (*e.name.pattern == pattern_type::regex_pattern) + continue; + + // Similar to prerequisites below, we treat member substitutions + // relative to the target. + // + dir_path d; + if (e.name.dir.empty ()) + d = t.dir; // Absolute and normalized. + else + { + if (e.name.dir.absolute ()) + d = e.name.dir; + else + d = t.dir / e.name.dir; + + d.normalize (); + } + + // @@ TODO: currently this uses type as the ad hoc member identity. + // + add_adhoc_member ( + t, + e.type, + move (d), + dir_path () /* out */, + substitute (t, mr, e.name.value, "ad hoc target group member")); + } + } + + void adhoc_rule_regex_pattern:: + apply_prerequisites (action a, target& t, match_extra&) const + { + const auto& mr (t.data<regex_match_results> ()); + + // Resolve and cache target scope lazily. + // + auto base_scope = [&t, bs = (const scope*) nullptr] () mutable + -> const scope& + { + if (bs == nullptr) + bs = &t.base_scope (); + + return *bs; + }; + + // Re-create the same clean semantics as in match_prerequisite_members(). + // + bool clean (a.operation () == clean_id && !t.is_a<alias> ()); + + auto& pts (t.prerequisite_targets[a]); + size_t start (pts.size ()); + + for (const element& e: prereqs_) + { + // While it would be nice to avoid copying here, the semantics of + // search() (and find_target_type() that it calls) is just too hairy to + // duplicate and try to optimize. It feels like most of the cases will + // either fall under the small string optimization or be absolute target + // names (e.g., imported tools). + // + // @@ Perhaps we should try to optimize the absolute target name case? + // + // Which scope should we use to resolve this prerequisite? After some + // meditation it feels natural to use the target's scope for patterns + // and the rule's scope for non-patterns. + // + name n; + const scope* s; + if (e.name.pattern) + { + if (*e.name.pattern == pattern_type::regex_pattern) + continue; + + // Note: cannot be project-qualified. + // + n = name (e.name.dir, + e.name.type, + substitute (t, mr, e.name.value, "prerequisite")); + s = &base_scope (); + } + else + { + n = e.name; + s = &rule_scope; + } + + const target& pt (search (t, move (n), *s, &e.type)); + + if (clean && !pt.in (*base_scope ().root_scope ())) + continue; + + // @@ TODO: it could be handy to mark a prerequisite (e.g., a tool) + // ad hoc so that it doesn't interfere with the $< list. + // + pts.push_back (prerequisite_target (&pt, false /* adhoc */)); + } + + if (start != pts.size ()) + match_members (a, t, pts, start); + } + + void adhoc_rule_regex_pattern:: + dump (ostream& os) const + { + // Targets. + // + size_t tn (targets_.size ()); + + if (tn != 1) + os << '<'; + + for (size_t i (0); i != tn; ++i) + os << (i != 0 ? " " : "") << targets_[i].name; + + if (tn != 1) + os << '>'; + + // Prerequisites. + // + os << ':'; + + for (size_t i (0); i != prereqs_.size (); ++i) + os << ' ' << prereqs_[i].name; + } +} diff --git a/libbuild2/adhoc-rule-regex-pattern.hxx b/libbuild2/adhoc-rule-regex-pattern.hxx new file mode 100644 index 0000000..ed8bf59 --- /dev/null +++ b/libbuild2/adhoc-rule-regex-pattern.hxx @@ -0,0 +1,58 @@ +// file : libbuild2/adhoc-rule-regex-pattern.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_ADHOC_RULE_REGEX_PATTERN_HXX +#define LIBBUILD2_ADHOC_RULE_REGEX_PATTERN_HXX + +#include <libbuild2/types.hxx> +#include <libbuild2/forward.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/rule.hxx> + +namespace build2 +{ + // Ad hoc rule regex pattern. + // + // Note: exported since may be accessed by ad hoc recipe implementation. + // + class LIBBUILD2_SYMEXPORT adhoc_rule_regex_pattern: public adhoc_rule_pattern + { + public: + using name = build2::name; + using scope = build2::scope; + + adhoc_rule_regex_pattern (const scope&, string, const target_type&, + name&&, const location&, + names&&, const location&, + names&&, const location&); + + virtual bool + match (action, target&, const string&, match_extra&) const override; + + virtual void + apply_adhoc_members (action, target&, match_extra&) const override; + + virtual void + apply_prerequisites (action, target&, match_extra&) const override; + + virtual void + dump (ostream&) const override; + + private: + string text_; // Pattern text. + regex regex_; // Pattern regex. + + struct element + { + build2::name name; + const target_type& type; + bool match_ext; // Match extension flag. + }; + + vector<element> targets_; // First is the primary target. + vector<element> prereqs_; + }; +} + +#endif // LIBBUILD2_ADHOC_RULE_REGEX_PATTERN_HXX diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index acff325..54ddf78 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -88,12 +88,12 @@ namespace build2 } const target& - search (const target& t, name n, const scope& s) + search (const target& t, name n, const scope& s, const target_type* tt) { assert (t.ctx.phase == run_phase::match); - auto rp (s.find_target_type (n, location ())); - const target_type* tt (rp.first); + auto rp (s.find_target_type (n, location (), tt)); + tt = rp.first; optional<string>& ext (rp.second); if (tt == nullptr) @@ -291,8 +291,8 @@ namespace build2 target& add_adhoc_member (target& t, const target_type& tt, - const dir_path& dir, - const dir_path& out, + dir_path dir, + dir_path out, string n) { tracer trace ("add_adhoc_member"); @@ -305,8 +305,8 @@ namespace build2 pair<target&, ulock> r ( t.ctx.targets.insert_locked (tt, - dir, - out, + move (dir), + move (out), move (n), nullopt /* ext */, target_decl::implied, @@ -334,8 +334,12 @@ namespace build2 if (const scope* rs = bs.root_scope ()) penv = auto_project_env (*rs); + match_extra& me (t[a].match_extra); + // First check for an ad hoc recipe. // + // Note that a fallback recipe is preferred over a non-fallback rule. + // if (!t.adhoc_recipes.empty ()) { auto df = make_diag_frame ( @@ -345,70 +349,60 @@ namespace build2 dr << info << "while matching ad hoc recipe to " << diag_do (a, t); }); - auto match = [a, &t] (const adhoc_rule& r, bool fallback) -> bool + auto match = [a, &t, &me] (const adhoc_rule& r, bool fallback) -> bool { - match_extra me {fallback}; + me.init (fallback); - bool m; if (auto* f = (a.outer () ? t.ctx.current_outer_oif : t.ctx.current_inner_oif)->adhoc_match) - m = f (r, a, t, string () /* hint */, me); + return f (r, a, t, string () /* hint */, me); else - m = r.match (a, t, string () /* hint */, me); - - if (m) - t[a].match_extra = move (me); - - return m; + return r.match (a, t, string () /* hint */, me); }; // The action could be Y-for-X while the ad hoc recipes are always for // X. So strip the Y-for part for comparison (but not for the match() // calls; see below for the hairy inner/outer semantics details). // - action ca (a.outer () - ? action (a.meta_operation (), a.outer_operation ()) - : a); + action ca (a.inner () + ? a + : action (a.meta_operation (), a.outer_operation ())); auto b (t.adhoc_recipes.begin ()), e (t.adhoc_recipes.end ()); auto i (find_if ( b, e, - [&match, ca] (const adhoc_recipe& r) + [&match, ca] (const shared_ptr<adhoc_rule>& r) { - auto& as (r.actions); + auto& as (r->actions); return (find (as.begin (), as.end (), ca) != as.end () && - match (*r.rule, false)); + match (*r, false)); })); if (i == e) { // See if we have a fallback implementation. // + // See the adhoc_rule::reverse_fallback() documentation for details on + // what's going on here. + // i = find_if ( b, e, - [&match, ca, &t] (const adhoc_recipe& r) + [&match, ca, &t] (const shared_ptr<adhoc_rule>& r) { - // See the adhoc_rule::match() documentation for details on what's - // going on here. - // - auto& as (r.actions); - if (find (as.begin (), as.end (), ca) == as.end ()) - { - for (auto sa: as) - { - optional<action> ra (r.rule->reverse_fallback (sa, t.type ())); + auto& as (r->actions); - if (ra && *ra == ca && match (*r.rule, true)) - return true; - } - } - return false; + // Note that the rule could be there but not match (see above), + // thus this extra check. + // + return (find (as.begin (), as.end (), ca) == as.end () && + r->reverse_fallback (ca, t.type ()) && + match (*r, true)); }); } if (i != e) - return &i->rule->rule_match; + return &(*i)->rule_match; } // If this is an outer operation (Y-for-X), then we look for rules @@ -457,7 +451,7 @@ namespace build2 const auto& rules (i->second); // Hint map. - // @@ TODO + // @@ TODO hint // // Different rules can be used for different operations (update vs // test is a good example). So, at some point, we will probably have @@ -477,14 +471,49 @@ namespace build2 for (auto i (rs.first); i != rs.second; ++i) { - const auto& r (*i); - const string& n (r.first); - const rule& ru (r.second); + const rule_match* r (&*i); + + // In a somewhat hackish way we reuse operation wildcards to plumb + // the ad hoc rule's reverse operation fallback logic. + // + // The difficulty is two-fold: + // + // 1. It's difficult to add the fallback flag to the rule map + // because of rule_match which is used throughout. + // + // 2. Even if we could do that, we pass the reverse action to + // reverse_fallback() rather than it returning (a list) of + // reverse actions, which would be necessary to register them. + // + using fallback_rule = adhoc_rule_pattern::fallback_rule; + + auto find_fallback = [mo, o, tt] (const fallback_rule& fr) + -> const rule_match* + { + for (const shared_ptr<adhoc_rule>& ar: fr.rules) + if (ar->reverse_fallback (action (mo, o), *tt)) + return &ar->rule_match; + + return nullptr; + }; + + if (oi == 0) + { + if (auto* fr = + dynamic_cast<const fallback_rule*> (&r->second.get ())) + { + if ((r = find_fallback (*fr)) == nullptr) + continue; + } + } + + const string& n (r->first); + const rule& ru (r->second); if (&ru == skip) continue; - match_extra me {false}; + me.init (oi == 0 /* fallback */); { auto df = make_diag_frame ( [a, &t, &n](const diag_record& dr) @@ -505,8 +534,20 @@ namespace build2 diag_record dr; for (++i; i != rs.second; ++i) { - const string& n1 (i->first); - const rule& ru1 (i->second); + const rule_match* r1 (&*i); + + if (oi == 0) + { + if (auto* fr = + dynamic_cast<const fallback_rule*> (&r1->second.get ())) + { + if ((r1 = find_fallback (*fr)) == nullptr) + continue; + } + } + + const string& n1 (r1->first); + const rule& ru1 (r1->second); { auto df = make_diag_frame ( @@ -523,7 +564,8 @@ namespace build2 // // @@ Can't we temporarily swap things out in target? // - match_extra me1 {false}; + match_extra me1; + me1.init (oi == 0); if (!ru1.match (a, t, hint, me1)) continue; } @@ -539,10 +581,7 @@ namespace build2 } if (!ambig) - { - t[a].match_extra = move (me); - return &r; - } + return r; else dr << info << "use rule hint to disambiguate this match"; } @@ -550,6 +589,8 @@ namespace build2 } } + me.free (); + if (!try_match) { diag_record dr; @@ -624,7 +665,7 @@ namespace build2 if (const scope* rs = bs.root_scope ()) penv = auto_project_env (*rs); - const rule& r (m.second); + const rule& ru (m.second); match_extra& me (t[a].match_extra); auto df = make_diag_frame ( @@ -635,15 +676,16 @@ namespace build2 << diag_do (a, t); }); - if (auto* f = (a.outer () - ? t.ctx.current_outer_oif - : t.ctx.current_inner_oif)->adhoc_apply) - { - if (auto* ar = dynamic_cast<const adhoc_rule*> (&r)) - return f (*ar, a, t, me); - } + auto* f ((a.outer () + ? t.ctx.current_outer_oif + : t.ctx.current_inner_oif)->adhoc_apply); + + auto* ar (f == nullptr ? nullptr : dynamic_cast<const adhoc_rule*> (&ru)); + + recipe re (ar != nullptr ? f (*ar, a, t, me) : ru.apply (a, t, me)); - return r.apply (a, t, me); + me.free (); + return re; } // If step is true then perform only one step of the match/apply sequence. diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index 90159d3..984b786 100644 --- a/libbuild2/algorithm.hxx +++ b/libbuild2/algorithm.hxx @@ -111,10 +111,12 @@ namespace build2 // Search for a target identified by the name. The semantics is "as if" we // first created a prerequisite based on this name in exactly the same way - // as the parser would and then searched based on this prerequisite. + // as the parser would and then searched based on this prerequisite. If the + // target type is already resolved, then it can be passed as the last + // argument. // LIBBUILD2_SYMEXPORT const target& - search (const target&, name, const scope&); + search (const target&, name, const scope&, const target_type* = nullptr); // Return NULL for unknown target types. Note that unlike the above version, // these ones can be called during the load and execute phases. @@ -231,8 +233,8 @@ namespace build2 LIBBUILD2_SYMEXPORT target& add_adhoc_member (target&, const target_type&, - const dir_path& dir, - const dir_path& out, + dir_path dir, + dir_path out, string name); // If the extension is specified then it is added to the member's target diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx index 396d518..24d9e5b 100644 --- a/libbuild2/algorithm.ixx +++ b/libbuild2/algorithm.ixx @@ -10,19 +10,19 @@ namespace build2 { inline const target& - search_custom (const prerequisite& p, const target& t) + search_custom (const prerequisite& p, const target& pt) { - assert (t.ctx.phase == run_phase::match || - t.ctx.phase == run_phase::execute); + assert (pt.ctx.phase == run_phase::match || + pt.ctx.phase == run_phase::execute); const target* e (nullptr); if (!p.target.compare_exchange_strong ( - e, &t, + e, &pt, memory_order_release, memory_order_consume)) - assert (e == &t); + assert (e == &pt); - return t; + return pt; } inline const target& diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 063ec68..9194e30 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -27,7 +27,9 @@ namespace build2 // script parser:: - pre_parse (const scope& bs, const target* tg, const adhoc_actions& as, + pre_parse (const scope& bs, + const target* tg, + const small_vector<action, 1>& as, istream& is, const path_name& pn, uint64_t line, optional<string> diag, const location& diag_loc) { diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx index af43e35..8c787f9 100644 --- a/libbuild2/build/script/parser.hxx +++ b/libbuild2/build/script/parser.hxx @@ -36,7 +36,7 @@ namespace build2 // targets. // script - pre_parse (const scope&, const target*, const adhoc_actions& acts, + pre_parse (const scope&, const target*, const small_vector<action, 1>&, istream&, const path_name&, uint64_t line, optional<string> diag_name, const location& diag_loc); @@ -144,7 +144,7 @@ namespace build2 protected: script* script_; - const adhoc_actions* actions_; // Non-NULL during pre-parsing. + const small_vector<action, 1>* actions_; // Non-NULL during pre-parse. // True if performing update is one of the actions. Only set for the // pre-parse mode. diff --git a/libbuild2/build/script/parser.test.cxx b/libbuild2/build/script/parser.test.cxx index c1ba1d1..0bc1c5d 100644 --- a/libbuild2/build/script/parser.test.cxx +++ b/libbuild2/build/script/parser.test.cxx @@ -201,7 +201,7 @@ namespace build2 tt.path (path ("driver")); - adhoc_actions acts {perform_update_id}; + small_vector<action, 1> acts {perform_update_id}; // Parse and run. // diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index 64139bf..49a6a13 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -976,7 +976,7 @@ namespace build2 3 /* verbosity */, env, args.data (), - forward<decltype(f)> (f), + forward<decltype (f)> (f), false /* error */, false /* ignore_exit */, checksum ? &cs : nullptr); diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index 23d430e..b1a16ba 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -195,6 +195,47 @@ namespace build2 } } + // Dump ad hoc recipe. + // + static void + dump_recipe (ostream& os, string& ind, const adhoc_rule& r, const scope& s) + { + auto& re (*s.root_scope ()->root_extra); + + os << ind << '%'; + + r.dump_attributes (os); + + for (action a: r.actions) + os << ' ' << re.meta_operations[a.meta_operation ()]->name << + '(' << re.operations[a.operation ()]->name << ')'; + + os << endl; + r.dump_text (os, ind); + } + + // Dump pattern rule. + // + static void + dump_rule (ostream& os, + string& ind, + const adhoc_rule_pattern& rp, + const scope& s) + { + // Pattern. + // + os << ind; + rp.dump (os); + + // Recipes. + // + for (const shared_ptr<adhoc_rule>& r: rp.rules) + { + os << endl; + dump_recipe (os, ind, *r, s); + } + } + static void dump_target (optional<action> a, ostream& os, @@ -356,21 +397,10 @@ namespace build2 // if (!t.adhoc_recipes.empty ()) { - auto& re (*s.root_scope ()->root_extra); - - for (const adhoc_recipe& r: t.adhoc_recipes) + for (const shared_ptr<adhoc_rule>& r: t.adhoc_recipes) { - os << endl - << ind << '%'; - - r.rule->dump_attributes (os); - - for (action a: r.actions) - os << ' ' << re.meta_operations[a.meta_operation ()]->name << - '(' << re.operations[a.operation ()]->name << ')'; - os << endl; - r.rule->dump_text (os, ind); + dump_recipe (os, ind, *r, s); } used = true; @@ -469,9 +499,11 @@ namespace build2 ind += " "; - bool vb (false), sb (false), tb (false); // Variable/scope/target block. + // Variable/rule/scope/target block. + // + bool vb (false), rb (false), sb (false), tb (false); - // Target type/pattern-sepcific variables. + // Target type/pattern-specific variables. // if (!p.target_vars.empty ()) { @@ -490,6 +522,21 @@ namespace build2 vb = true; } + // Pattern rules. + // + for (const unique_ptr<adhoc_rule_pattern>& rp: p.adhoc_rules) + { + if (vb || rb) + { + os << endl; + vb = false; + } + + os << endl; // Extra newline between rules. + dump_rule (os, ind, *rp, p); + rb = true; + } + // Nested scopes of which we are an immediate parent. Only consider the // out hierarchy. // @@ -503,16 +550,14 @@ namespace build2 i->second.front () != nullptr && i->second.front ()->parent_scope () == &p); ) { - if (vb) + if (vb || rb || sb) { os << endl; - vb = false; + vb = rb = false; } - if (sb) - os << endl; // Extra newline between scope blocks. + os << endl; // Extra newline between scope blocks. - os << endl; dump_scope (a, os, ind, i, true /* relative */); sb = true; } @@ -529,13 +574,13 @@ namespace build2 if (&p != &t.base_scope ()) continue; - if (vb || sb || tb) + if (vb || rb || sb || tb) { os << endl; - vb = sb = false; + vb = rb = sb = false; } - os << endl; + os << endl; // Extra newline between targets. dump_target (a, os, ind, t, p, true /* relative */); tb = true; } diff --git a/libbuild2/forward.hxx b/libbuild2/forward.hxx index 5fffaaf..4c9a50f 100644 --- a/libbuild2/forward.hxx +++ b/libbuild2/forward.hxx @@ -69,6 +69,7 @@ namespace build2 struct match_extra; class rule; class adhoc_rule; + class adhoc_rule_pattern; // <libbuild2/context.hxx> // diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index d1db568..a7e84f7 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -22,6 +22,8 @@ #include <libbuild2/adhoc-rule-cxx.hxx> #include <libbuild2/adhoc-rule-buildscript.hxx> +#include <libbuild2/adhoc-rule-regex-pattern.hxx> + #include <libbuild2/config/utility.hxx> // lookup_config using namespace std; @@ -534,7 +536,8 @@ namespace build2 // Handle ad hoc target group specification (<...>). // - // We keep an "optional" (empty) vector of names parallel to ns. + // We keep an "optional" (empty) vector of names parallel to ns that + // contains the ad hoc group members. // adhoc_names ans; if (tt == type::labrace) @@ -632,11 +635,11 @@ namespace build2 else attributes_pop (); - // Call the specified parsing function (either variable or block) for - // each target. We handle multiple targets by replaying the tokens - // since the value/block may contain variable expansions that would be - // sensitive to the target context in which they are evaluated. The - // function signature is: + // Call the specified parsing function (variable value/block) for + // one/each pattern/target. We handle multiple targets by replaying + // the tokens since the value/block may contain variable expansions + // that would be sensitive to the target context in which they are + // evaluated. The function signature is: // // void (token& t, type& tt, // optional<pattern_type>, const target_type* pat_tt, string pat, @@ -646,7 +649,111 @@ namespace build2 // but this flag can be cleared and default_target logic applied if // appropriate. // - auto for_each = [this, &trace, &t, &tt, &ns, &nloc, &ans] (auto&& f) + auto for_one_pat = [this, &t, &tt] (auto&& f, + name&& n, + const location& nloc) + { + // Reduce the various directory/value combinations to the scope + // directory (if any) and the pattern. Here are more interesting + // examples of patterns: + // + // */ -- */{} + // dir{*} -- dir{*} + // dir{*/} -- */dir{} + // + // foo/*/ -- foo/*/{} + // foo/dir{*/} -- foo/*/dir{} + // + // Note that these are not patterns: + // + // foo*/file{bar} + // foo*/dir{bar/} + // + // While these are: + // + // file{foo*/bar} + // dir{foo*/bar/} + // + // And this is a half-pattern (foo* should no be treated as a + // pattern but that's unfortunately indistinguishable): + // + // foo*/dir{*/} -- foo*/*/dir{} + // + // Note also that none of this applies to regex patterns (see + // the parsing code for details). + // + if (*n.pattern == pattern_type::path) + { + if (n.value.empty () && !n.dir.empty ()) + { + // Note that we use string and not the representation: in a + // sense the trailing slash in the pattern is subsumed by + // the target type. + // + if (n.dir.simple ()) + n.value = move (n.dir).string (); + else + { + n.value = n.dir.leaf ().string (); + n.dir.make_directory (); + } + + // Treat directory as type dir{} similar to other places. + // + if (n.untyped ()) + n.type = "dir"; + } + else + { + // Move the directory part, if any, from value to dir. + // + try + { + n.canonicalize (); + } + catch (const invalid_path& e) + { + fail (nloc) << "invalid path '" << e.path << "'"; + } + catch (const invalid_argument&) + { + fail (nloc) << "invalid pattern '" << n.value << "'"; + } + } + } + + // If we have the directory, then it is the scope. + // + enter_scope sg; + if (!n.dir.empty ()) + { + if (path_pattern (n.dir)) + fail (nloc) << "pattern in directory " << n.dir.representation (); + + sg = enter_scope (*this, move (n.dir)); + } + + // Resolve target type. If none is specified or if it is '*', + // use the root of the target type hierarchy. So these are all + // equivalent: + // + // *: foo = bar + // {*}: foo = bar + // *{*}: foo = bar + // + const target_type* ttype ( + n.untyped () || n.type == "*" + ? &target::static_type + : scope_->find_target_type (n.type)); + + if (ttype == nullptr) + fail (nloc) << "unknown target type " << n.type; + + f (t, tt, n.pattern, ttype, move (n.value), nloc); + }; + + auto for_each = [this, &trace, &for_one_pat, + &t, &tt, &ns, &nloc, &ans] (auto&& f) { // Note: watch out for an out-qualified single target (two names). // @@ -671,107 +778,11 @@ namespace build2 if (!ans.empty () && !ans[i].ns.empty ()) fail (ans[i].loc) << "ad hoc member in target type/pattern"; - // Reduce the various directory/value combinations to the scope - // directory (if any) and the pattern. Here are more interesting - // examples of patterns: - // - // */ -- */{} - // dir{*} -- dir{*} - // dir{*/} -- */dir{} - // - // foo/*/ -- foo/*/{} - // foo/dir{*/} -- foo/*/dir{} - // - // Note that these are not patterns: - // - // foo*/file{bar} - // foo*/dir{bar/} - // - // While these are: - // - // file{foo*/bar} - // dir{foo*/bar/} - // - // And this is a half-pattern (foo* should no be treated as a - // pattern but that's unfortunately indistinguishable): - // - // foo*/dir{*/} -- foo*/*/dir{} - // - // Note also that none of this applies to regex patterns (see - // the parsing code for details). - // - if (*n.pattern == pattern_type::path) - { - if (n.value.empty () && !n.dir.empty ()) - { - // Note that we use string and not the representation: in a - // sense the trailing slash in the pattern is subsumed by - // the target type. - // - if (n.dir.simple ()) - n.value = move (n.dir).string (); - else - { - n.value = n.dir.leaf ().string (); - n.dir.make_directory (); - } - - // Treat directory as type dir{} similar to other places. - // - if (n.untyped ()) - n.type = "dir"; - } - else - { - // Move the directory part, if any, from value to dir. - // - try - { - n.canonicalize (); - } - catch (const invalid_path& e) - { - fail (nloc) << "invalid path '" << e.path << "'"; - } - catch (const invalid_argument&) - { - fail (nloc) << "invalid pattern '" << n.value << "'"; - } - } - } - else if (*n.pattern == pattern_type::regex_substitution) + if (*n.pattern == pattern_type::regex_substitution) fail (nloc) << "regex substitution " << n << " without " << "regex pattern"; - // If we have the directory, then it is the scope. - // - enter_scope sg; - if (!n.dir.empty ()) - { - if (path_pattern (n.dir)) - fail (nloc) << "pattern in directory " - << n.dir.representation (); - - sg = enter_scope (*this, move (n.dir)); - } - - // Resolve target type. If none is specified or if it is '*', - // use the root of the target type hierarchy. So these are all - // equivalent: - // - // *: foo = bar - // {*}: foo = bar - // *{*}: foo = bar - // - const target_type* ti ( - n.untyped () || n.type == "*" - ? &target::static_type - : scope_->find_target_type (n.type)); - - if (ti == nullptr) - fail (nloc) << "unknown target type " << n.type; - - f (t, tt, n.pattern, ti, move (n.value), nloc); + for_one_pat (forward<decltype (f)> (f), move (n), nloc); } else { @@ -802,6 +813,357 @@ namespace build2 next_with_attributes (t, tt); // Recognize attributes after `:`. + // See if this could be an ad hoc pattern rule. It's a pattern rule if + // the primary target is a pattern and it has (1) prerequisites and/or + // (2) recipes. Only one primary target per pattern rule declaration + // is allowed. + // + // Note, however, that what looks like a pattern may turn out to be + // just a pattern-specific variable assignment or variable block, + // which both can appear with multiple targets/patterns on the left + // hand side, or even a mixture of them. Still, instead of trying to + // weave the pattern rule logic into the already hairy code below, we + // are going to handle it separately and deal with the "degenerate" + // cases (variable assignment/block) both here and below. + // + if (ns[0].pattern && ns.size () == (ns[0].pair ? 2 : 1)) + { + name& n (ns[0]); + + if (n.qualified ()) + fail (nloc) << "project name in target pattern " << n; + + if (n.pair) + fail (nloc) << "out-qualified target pattern"; + + if (*n.pattern == pattern_type::regex_substitution) + fail (nloc) << "regex substitution " << n << " without " + << "regex pattern"; + + // Parse prerequisites, if any. + // + location ploc; + names pns; + if (tt != type::newline) + { + auto at (attributes_push (t, tt)); + + if (!start_names (tt)) + fail (t) << "unexpected " << t; + + // Note that unlike below, here we preserve the pattern in the + // prerequisites. + // + ploc = get_location (t); + pns = parse_names (t, tt, pattern_mode::preserve); + + // Target-specific variable assignment. + // + if (tt == type::assign || tt == type::prepend || tt == type::append) + { + if (!ans.empty ()) + fail (ans[0].loc) << "ad hoc member in target type/pattern"; + + // Note: see the same code below if changing anything here. + // + type akind (tt); + const location aloc (get_location (t)); + + const variable& var (parse_variable_name (move (pns), ploc)); + apply_variable_attributes (var); + + if (var.visibility > variable_visibility::target) + { + fail (nloc) << "variable " << var << " has " << var.visibility + << " visibility but is assigned on a target"; + } + + for_one_pat ( + [this, &var, akind, &aloc] ( + token& t, type& tt, + optional<pattern_type> pt, const target_type* ptt, + string pat, const location& ploc) + { + + parse_type_pattern_variable (t, tt, + *pt, *ptt, move (pat), ploc, + var, akind, aloc); + }, + move (n), + nloc); + + next_after_newline (t, tt); + continue; // Just a target type/pattern-specific var assignment. + } + + if (at.first) + fail (at.second) << "attributes before prerequisite pattern"; + else + attributes_pop (); + + // @@ TODO + // + if (tt == type::colon) + fail (t) << "prerequisite type/pattern-specific variables " + << "not yet supported"; + } + + // Next we may have a target type/pattern specific variable block + // potentially followed by recipes. + // + next_after_newline (t, tt); + if (tt == type::lcbrace && peek () == type::newline) + { + // Note: see the same code below if changing anything here. + // + next (t, tt); // Newline. + next (t, tt); // First token inside the variable block. + + for_one_pat ( + [this] ( + token& t, type& tt, + optional<pattern_type> pt, const target_type* ptt, + string pat, const location& ploc) + { + parse_variable_block (t, tt, pt, ptt, move (pat), ploc); + }, + name (n), // Note: can't move (could still be a rule). + nloc); + + if (tt != type::rcbrace) + fail (t) << "expected '}' instead of " << t; + + next (t, tt); // Newline. + next_after_newline (t, tt, '}'); // Should be on its own line. + + // See if this is just a target type/pattern-specific var block. + // + if (pns.empty () && + tt != type::percent && tt != type::multi_lcbrace) + { + if (!ans.empty ()) + fail (ans[0].loc) << "ad hoc member in target type/pattern"; + + continue; + } + } + + // Ok, this is an ad hoc pattern rule. + // + // What should we do if we have neither prerequisites nor recipes? + // While such a declaration doesn't make much sense, it can happen, + // for example, with an empty variable expansion: + // + // file{*.txt}: $extra + // + // So let's silently ignore it. + // + if (pns.empty () && tt != type::percent && tt != type::multi_lcbrace) + continue; + + // Process and verify the pattern. + // + pattern_type pt (*n.pattern); + optional<pattern_type> st; + const char* pn; + + switch (pt) + { + case pattern_type::path: + pn = "path"; + break; + case pattern_type::regex_pattern: + pn = "regex"; + st = pattern_type::regex_substitution; + break; + case pattern_type::regex_substitution: + // Unreachable. + break; + } + + // Make sure patterns have no directory components. While we may + // decide to support this in the future, currently the appropriate + // semantics is not immediately obvious. Whatever we decide, it + // should be consistent with the target type/pattern-specific + // variables where it is interpreted as a scope (and which doesn't + // feel like the best option for pattern rules). + // + auto check_pattern = [this] (name& n, const location& loc) + { + try + { + // Move the directory component for path patterns. + // + if (*n.pattern == pattern_type::path) + n.canonicalize (); + + if (n.dir.empty ()) + return; + } + catch (const invalid_path&) + { + // Fall through. + } + + fail (loc) << "directory in pattern " << n; + }; + + check_pattern (n, nloc); + + // Verify all the ad hoc members are patterns or substitutions and + // of the correct type. + // + names ns (ans.empty () ? names () : move (ans[0].ns)); + const location& aloc (ans.empty () ? location () : ans[0].loc); + + for (name& n: ns) + { + if (!n.pattern || !(*n.pattern == pt || (st && *n.pattern == *st))) + { + fail (aloc) << "expected " << pn << " pattern or substitution " + << "instead of " << n; + } + + if (*n.pattern != pattern_type::regex_substitution) + check_pattern (n, aloc); + } + + // The same for prerequisites except here we can have non-patterns. + // + for (name& n: pns) + { + if (n.pattern) + { + if (!(*n.pattern == pt || (st && *n.pattern == *st))) + { + fail (ploc) << "expected " << pn << " pattern or substitution " + << "instead of " << n; + } + + if (*n.pattern != pattern_type::regex_substitution) + check_pattern (n, ploc); + } + } + + // Derive the rule name. It must be unique in this scope. + // + // It would have been nice to include the location but unless we + // include the absolute path to the buildfile (which would be + // unwieldy), it could be ambigous. + // + string rn ("<ad hoc pattern rule #" + + to_string (scope_->adhoc_rules.size () + 1) + '>'); + + auto& ars (scope_->adhoc_rules); + + auto i (find_if (ars.begin (), ars.end (), + [&rn] (const unique_ptr<adhoc_rule_pattern>& rp) + { + return rp->rule_name == rn; + })); + + const target_type* ttype (nullptr); + if (i != ars.end ()) + { + // @@ TODO: append ad hoc members, prereqs. + // + ttype = &(*i)->type; + assert (false); + } + else + { + // Resolve target type (same as in for_one_pat()). + // + // @@ TODO: maybe untyped should mean file{} as everywhere else? + // Also, why do we bother with *{}, is it that hard to write + // target{*}? Note: here, in vars, and in regex_pattern. + // + ttype = n.untyped () || n.type == "*" + ? &target::static_type + : scope_->find_target_type (n.type); + + if (ttype == nullptr) + fail (nloc) << "unknown target type " << n.type; + + unique_ptr<adhoc_rule_pattern> rp; + switch (pt) + { + case pattern_type::path: + // @@ TODO + fail (nloc) << "path pattern rules not yet supported"; + break; + case pattern_type::regex_pattern: + rp.reset (new adhoc_rule_regex_pattern ( + *scope_, rn, *ttype, + move (n), nloc, + move (ns), aloc, + move (pns), ploc)); + break; + case pattern_type::regex_substitution: + // Unreachable. + break; + } + + ars.push_back (move (rp)); + i = --ars.end (); + } + + adhoc_rule_pattern& rp (**i); + + // Parse the recipe chain if any. + // + if (tt == type::percent || tt == type::multi_lcbrace) + { + small_vector<shared_ptr<adhoc_rule>, 1> recipes; + parse_recipe (t, tt, token (t), recipes, rn); + + for (shared_ptr<adhoc_rule>& pr: recipes) + { + adhoc_rule& r (*pr); + r.pattern = &rp; // Connect recipe to pattern. + rp.rules.push_back (move (pr)); + + // Register this adhoc rule for all its actions. + // + for (action a: r.actions) + { + // This covers both duplicate recipe actions withing the rule + // pattern (similar to parse_recipe()) as well as conflicts + // with other rules (ad hoc or not). + // + if (!scope_->rules.insert (a, *ttype, rp.rule_name, r)) + { + const meta_operation_info* mf ( + root_->root_extra->meta_operations[a.meta_operation ()]); + + const operation_info* of ( + root_->root_extra->operations[a.operation ()]); + + fail (r.loc) + << "duplicate " << mf->name << '(' << of->name << ") rule " + << rp.rule_name << " for target type " << ttype->name + << "{}"; + } + + // We also register for a wildcard operation in order to get + // called to provide the reverse operation fallback (see + // match_impl() for the gory details). + // + // Note that we may end up trying to insert a duplicate of the + // same rule (e.g., for the same meta-operation). Feels like + // we should never try to insert for a different rule since + // for ad hoc rules names are unique. + // + scope_->rules.insert ( + a.meta_operation (), 0, + *ttype, rp.rule_name, rp.fallback_rule_); + } + } + } + + continue; + } + if (tt == type::newline) { // See if this is a target-specific variable and/or recipe block(s). @@ -819,11 +1181,6 @@ namespace build2 // x = y x = y // } } // - // @@ This might change a bit once we support ad hoc rules (where we - // may have prerequisites for a pattern; but perhaps this should be - // handled separately since the parse_dependency() is already too - // complex and there will be no chains in this case). - // next (t, tt); if (tt == type::percent || tt == type::multi_lcbrace || @@ -852,6 +1209,8 @@ namespace build2 // if (st.type == type::lcbrace) { + // Note: see the same code above if changing anything here. + // next (t, tt); // Newline. next (t, tt); // First token inside the variable block. parse_variable_block (t, tt, pt, ptt, move (pat), ploc); @@ -871,7 +1230,9 @@ namespace build2 rt = st; if (pt) - fail (rt) << "recipe in target type/pattern"; + fail (rt) << "unexpected recipe after target type/pattern" << + info << "ad hoc pattern rule may not be combined with other " + << "targets or patterns"; parse_recipe (t, tt, rt, recipes); }; @@ -903,7 +1264,7 @@ namespace build2 if (!start_names (tt)) fail (t) << "unexpected " << t; - // @@ PAT: currently we pattern-expand target-specific vars. + // @@ PAT: currently we pattern-expand target-specific var names. // const location ploc (get_location (t)); names pns (parse_names (t, tt, pattern_mode::expand)); @@ -916,6 +1277,8 @@ namespace build2 // if (tt == type::assign || tt == type::prepend || tt == type::append) { + // Note: see the same code above if changing anything here. + // type akind (tt); const location aloc (get_location (t)); @@ -1180,7 +1543,8 @@ namespace build2 void parser:: parse_recipe (token& t, type& tt, const token& start, - small_vector<shared_ptr<adhoc_rule>, 1>& recipes) + small_vector<shared_ptr<adhoc_rule>, 1>& recipes, + const string& name) { // Parse a recipe chain. // @@ -1193,19 +1557,25 @@ namespace build2 // // enter: start is percent or openining multi-curly-brace // leave: token past newline after last closing multi-curly-brace + // + // If target_ is not NULL, then add the recipe to its adhoc_recipes. + // Otherwise, return it in recipes (used for pattern rules). if (stage_ == stage::boot) fail (t) << "ad hoc recipe specified during bootstrap"; // If we have a recipe, the target is not implied. // - if (target_->decl != target_decl::real) + if (target_ != nullptr) { - for (target* m (target_); m != nullptr; m = m->adhoc_member) - m->decl = target_decl::real; + if (target_->decl != target_decl::real) + { + for (target* m (target_); m != nullptr; m = m->adhoc_member) + m->decl = target_decl::real; - if (default_target_ == nullptr) - default_target_ = target_; + if (default_target_ == nullptr) + default_target_ = target_; + } } bool multi (replay_ != replay::stop); // Multiple targets. @@ -1223,6 +1593,7 @@ namespace build2 struct data { + const string& name; small_vector<shared_ptr<adhoc_rule>, 1>& recipes; bool multi; bool first; @@ -1231,7 +1602,7 @@ namespace build2 attributes& as; buildspec& bs; const location& bsloc; - } d {recipes, multi, first, clean, i, as, bs, bsloc}; + } d {name, recipes, multi, first, clean, i, as, bs, bsloc}; // Note that this function must be called at most once per iteration. // @@ -1258,7 +1629,6 @@ namespace build2 else fail (t) << "expected recipe language instead of " << t; - shared_ptr<adhoc_rule> ar; if (!skip) { if (d.first) @@ -1270,11 +1640,16 @@ namespace build2 // location loc (get_location (st)); + shared_ptr<adhoc_rule> ar; if (!lang) { // Buildscript // - ar.reset (new adhoc_buildscript_rule (loc, st.value.size ())); + ar.reset ( + new adhoc_buildscript_rule ( + d.name.empty () ? "<ad hoc buildscript recipe>" : d.name, + loc, + st.value.size ())); } else if (icasecmp (*lang, "c++") == 0) { @@ -1324,20 +1699,23 @@ namespace build2 } ar.reset ( - new adhoc_cxx_rule (loc, st.value.size (), ver, move (sep))); + new adhoc_cxx_rule ( + d.name.empty () ? "<ad hoc c++ recipe>" : d.name, + loc, + st.value.size (), + ver, + move (sep))); } else fail (lloc) << "unknown recipe language '" << *lang << "'"; assert (d.recipes[d.i] == nullptr); - d.recipes[d.i] = ar; + d.recipes[d.i] = move (ar); } else { skip_line (t, tt); - assert (d.recipes[d.i] != nullptr); - ar = d.recipes[d.i]; } } else @@ -1360,85 +1738,83 @@ namespace build2 if (!skip) { - auto& ars (target_->adhoc_recipes); - ars.push_back (adhoc_recipe {{}, move (ar)}); + if (d.first) + { + adhoc_rule& ar (*d.recipes.back ()); - // Translate each buildspec entry into action and add it into the - // target's ad hoc recipes entry. - // - const location& l (d.bsloc); + // Translate each buildspec entry into action and add it to the + // recipe entry. + // + const location& l (d.bsloc); - for (metaopspec& m: d.bs) - { - meta_operation_id mi (ctx.meta_operation_table.find (m.name)); + for (metaopspec& m: d.bs) + { + meta_operation_id mi (ctx.meta_operation_table.find (m.name)); - if (mi == 0) - fail (l) << "unknown meta-operation " << m.name; + if (mi == 0) + fail (l) << "unknown meta-operation " << m.name; - const meta_operation_info* mf ( - root_->root_extra->meta_operations[mi]); + const meta_operation_info* mf ( + root_->root_extra->meta_operations[mi]); - if (mf == nullptr) - fail (l) << "target " << *target_ << " does not support meta-" - << "operation " << ctx.meta_operation_table[mi].name; + if (mf == nullptr) + fail (l) << "project " << *root_ << " does not support meta-" + << "operation " << ctx.meta_operation_table[mi].name; - for (opspec& o: m) - { - operation_id oi; - if (o.name.empty ()) + for (opspec& o: m) { - if (mf->operation_pre == nullptr) - oi = update_id; + operation_id oi; + if (o.name.empty ()) + { + if (mf->operation_pre == nullptr) + oi = update_id; + else + // Calling operation_pre() to translate doesn't feel + // appropriate here. + // + fail (l) << "default operation in recipe action" << endf; + } else - // Calling operation_pre() to translate doesn't feel - // appropriate here. - // - fail (l) << "default operation in recipe action" << endf; - } - else - oi = ctx.operation_table.find (o.name); + oi = ctx.operation_table.find (o.name); - if (oi == 0) - fail (l) << "unknown operation " << o.name; + if (oi == 0) + fail (l) << "unknown operation " << o.name; - const operation_info* of (root_->root_extra->operations[oi]); + const operation_info* of (root_->root_extra->operations[oi]); - if (of == nullptr) - fail (l) << "target " << *target_ << " does not support " - << "operation " << ctx.operation_table[oi]; + if (of == nullptr) + fail (l) << "project " << *root_ << " does not support " + << "operation " << ctx.operation_table[oi]; - // Note: for now always inner (see match_rule() for details). - // - action a (mi, oi); + // Note: for now always inner (see match_rule() for details). + // + action a (mi, oi); - // Check for duplicates. - // - if (find_if ( - ars.begin (), ars.end (), - [a] (const adhoc_recipe& r) - { - auto& as (r.actions); - return find (as.begin (), as.end (), a) != as.end (); - }) != ars.end ()) - { - fail (l) << "duplicate recipe for " << mf->name << '(' - << of->name << ')'; - } + // Check for duplicates (local). + // + if (find_if ( + d.recipes.begin (), d.recipes.end (), + [a] (const shared_ptr<adhoc_rule>& r) + { + auto& as (r->actions); + return find (as.begin (), as.end (), a) != as.end (); + }) != d.recipes.end ()) + { + fail (l) << "duplicate " << mf->name << '(' << of->name + << ") recipe"; + } - ars.back ().actions.push_back (a); + ar.actions.push_back (a); + } } - } - if (d.first) - { - adhoc_recipe& ar (ars.back ()); - - if (ar.rule->recipe_text (ctx, - *scope_, - d.multi ? nullptr : target_, - ar.actions, - move (t.value), - d.as)) + // Set the recipe text. + // + if (ar.recipe_text (ctx, + *scope_, + d.multi ? nullptr : target_, + move (t.value), + d.as)) d.clean = true; // Verify we have no unhandled attributes. @@ -1446,6 +1822,37 @@ namespace build2 for (attribute& a: d.as) fail (d.as.loc) << "unknown recipe attribute " << a << endf; } + + // Copy the recipe over to the target verifying there are no + // duplicates (global). + // + if (target_ != nullptr) + { + const shared_ptr<adhoc_rule>& r (d.recipes[d.i]); + + for (const shared_ptr<adhoc_rule>& er: target_->adhoc_recipes) + { + auto& as (er->actions); + + for (action a: r->actions) + { + if (find (as.begin (), as.end (), a) != as.end ()) + { + const meta_operation_info* mf ( + root_->root_extra->meta_operations[a.meta_operation ()]); + + const operation_info* of ( + root_->root_extra->operations[a.operation ()]); + + fail (d.bsloc) + << "duplicate " << mf->name << '(' << of->name + << ") recipe for target " << *target_; + } + } + } + + target_->adhoc_recipes.push_back (r); + } } next (t, tt); @@ -1455,6 +1862,8 @@ namespace build2 next_after_newline (t, tt, token (t)); // Should be on its own line. }; + bsloc = get_location (t); // Fallback location. + if (tt == type::percent) { // Similar code to parse_buildspec() except here we recognize @@ -1681,7 +2090,9 @@ namespace build2 // Make sure none of our targets are patterns. // if (n.pattern) - fail (tloc) << "pattern in target " << n; + fail (tloc) << "unexpected pattern in target " << n << + info << "ad hoc pattern rule may not be combined with other " + << "targets or patterns"; enter_target tg (*this, move (n), move (o), diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 007e508..1095eeb 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -138,7 +138,8 @@ namespace build2 void parse_recipe (token&, token_type&, const token&, - small_vector<shared_ptr<adhoc_rule>, 1>&); + small_vector<shared_ptr<adhoc_rule>, 1>&, + const string& = {}); // Ad hoc target names inside < ... >. // diff --git a/libbuild2/recipe.hxx b/libbuild2/recipe.hxx index 7eca34a..508c059 100644 --- a/libbuild2/recipe.hxx +++ b/libbuild2/recipe.hxx @@ -48,18 +48,6 @@ namespace build2 LIBBUILD2_SYMEXPORT extern const recipe noop_recipe; LIBBUILD2_SYMEXPORT extern const recipe default_recipe; LIBBUILD2_SYMEXPORT extern const recipe group_recipe; - - // Ad hoc recipe. - // - // A recipe is a fragment of a rule so we handle ad hoc recipies by - // "completing" them to rules. - // - using adhoc_actions = small_vector<action, 1>; - struct adhoc_recipe - { - adhoc_actions actions; - shared_ptr<adhoc_rule> rule; - }; } #endif // LIBBUILD2_RECIPE_HXX diff --git a/libbuild2/rule-map.hxx b/libbuild2/rule-map.hxx index 8014d02..c4cdb9f 100644 --- a/libbuild2/rule-map.hxx +++ b/libbuild2/rule-map.hxx @@ -28,16 +28,20 @@ namespace build2 class operation_rule_map { public: - template <typename T> - void - insert (operation_id oid, const char* hint, const rule& r) + // Return false in case of a duplicate. + // + bool + insert (operation_id oid, + const target_type& tt, + string hint, + const rule& r) { // 3 is the number of builtin operations. // if (oid >= map_.size ()) map_.resize ((oid < 3 ? 3 : oid) + 1); - map_[oid][&T::static_type].emplace (hint, r); + return map_[oid][&tt].emplace (move (hint), r).second; } // Return NULL if not found. @@ -69,33 +73,54 @@ namespace build2 class rule_map { public: + // Return false in case of a duplicate. + // + bool + insert (action_id a, + const target_type& tt, + string hint, + const rule& r) + { + return insert (a >> 4, a & 0x0F, tt, move (hint), r); + } + template <typename T> - void - insert (action_id a, const char* hint, const rule& r) + bool + insert (action_id a, string hint, const rule& r) { - insert<T> (a >> 4, a & 0x0F, hint, r); + return insert (a, T::static_type, move (hint), r); } // 0 oid is a wildcard. // - template <typename T> - void + bool insert (meta_operation_id mid, operation_id oid, - const char* hint, + const target_type& tt, + string hint, const rule& r) { if (mid_ == mid) - map_.insert<T> (oid, hint, r); + return map_.insert (oid, tt, move (hint), r); else { if (next_ == nullptr) next_.reset (new rule_map (mid)); - next_->insert<T> (mid, oid, hint, r); + return next_->insert (mid, oid, tt, move (hint), r); } } + template <typename T> + bool + insert (meta_operation_id mid, + operation_id oid, + string hint, + const rule& r) + { + return insert (mid, oid, T::static_type, move (hint), r); + } + // Return NULL if not found. // const operation_rule_map* diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index 49da7cb..6dad685 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -332,16 +332,16 @@ namespace build2 // const dir_path adhoc_rule::recipes_build_dir ("recipes"); - optional<action> adhoc_rule:: + bool adhoc_rule:: reverse_fallback (action, const target_type&) const { - return nullopt; + return false; } bool adhoc_rule:: - match (action, target&, const string&, match_extra&) const + match (action a, target& t, const string& h, match_extra& me) const { - return true; + return pattern == nullptr || pattern->match (a, t, h, me); } void adhoc_rule:: @@ -392,4 +392,23 @@ namespace build2 return target_state::unchanged; } + + // adhoc_rule_pattern (vtable) + // + adhoc_rule_pattern:: + ~adhoc_rule_pattern () + { + } + + bool adhoc_rule_pattern::fallback_rule:: + match (action, target&, const string&, match_extra&) const + { + return false; + } + + recipe adhoc_rule_pattern::fallback_rule:: + apply (action, target&, match_extra&) const + { + return empty_recipe; + } } diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx index af89124..67c0f6d 100644 --- a/libbuild2/rule.hxx +++ b/libbuild2/rule.hxx @@ -143,18 +143,32 @@ namespace build2 // Ad hoc rule. // + // Used for both ad hoc pattern rules and ad hoc recipes. For recipes, it's + // essentially a rule of one case. Note that when used as part of a pattern, + // the implementation cannot use the match_extra::buffer nor the target + // auxilary data storage. + // // Note: not exported. // + class adhoc_rule_pattern; + class adhoc_rule: public rule { public: - location_value loc; // Buildfile location of the recipe. - size_t braces; // Number of braces in multi-brace tokens. + location_value loc; // Buildfile location of the recipe. + size_t braces; // Number of braces in multi-brace tokens. + small_vector<action, 1> actions; // Actions this rule is for. - adhoc_rule (const char* name, const location& l, size_t b) + // If not NULL then this rule (recipe, really) belongs to an ad hoc + // pattern rule and match() should call the pattern's match() and + // apply() should call the pattern's apply_*() functions (see below). + // + const adhoc_rule_pattern* pattern = nullptr; + + adhoc_rule (string name, const location& l, size_t b) : loc (l), braces (b), - rule_match (name, static_cast<const rule&> (*this)) {} + rule_match (move (name), static_cast<const rule&> (*this)) {} // Set the rule text, handle any recipe-specific attributes, and return // true if the recipe builds anything in the build/recipes/ directory and @@ -164,8 +178,11 @@ namespace build2 // the scope of the recipe (not necessarily the same as the target's base // scope). // + // Note that this function is called after the actions member has been + // populated. + // virtual bool - recipe_text (context&, const scope&, const target*, const adhoc_actions&, + recipe_text (context&, const scope&, const target*, string&&, attributes&) = 0; public: @@ -174,7 +191,7 @@ namespace build2 // to provide a fallback implementation of a reverse operation if it is // providing the other half. // - virtual optional<action> + virtual bool reverse_fallback (action, const target_type&) const; // The default implementation forwards to the pattern's match() if there @@ -195,9 +212,8 @@ namespace build2 // public: // The name in rule_match is used as a hint and as a name in diagnostics. - // The former does not apply to us (but will apply to ad hoc rules) while - // latter does. As a result, we use special-looking "<ad hoc X recipe>" - // names. + // The former does not apply to ad hoc recipes (but does apply to ad hoc + // rules). // const build2::rule_match rule_match; @@ -224,6 +240,66 @@ namespace build2 virtual recipe apply (action, target&, match_extra&, const optional<timestamp>&) const = 0; }; + + // Ad hoc rule pattern. + // + // Note: exported since may be accessed by ad hoc recipe implementation. + // + class LIBBUILD2_SYMEXPORT adhoc_rule_pattern + { + public: + const scope& rule_scope; + const string rule_name; + const target_type& type; // Primary target type. + small_vector<shared_ptr<adhoc_rule>, 1> rules; // Really a unique_ptr. + + adhoc_rule_pattern (const scope& s, string n, const target_type& t) + : rule_scope (s), + rule_name (move (n)), + type (t), + fallback_rule_ (rules) {} + + virtual + ~adhoc_rule_pattern (); + + public: + virtual bool + match (action, target&, const string&, match_extra&) const = 0; + + virtual void + apply_adhoc_members (action, target&, match_extra&) const = 0; + + virtual void + apply_prerequisites (action, target&, match_extra&) const = 0; + + // Dump support. + // + virtual void + dump (ostream&) const = 0; + + // Gory implementation details (see match_impl()). + // + public: + class fallback_rule: public rule + { + public: + const small_vector<shared_ptr<adhoc_rule>, 1>& rules; + + explicit + fallback_rule (const small_vector<shared_ptr<adhoc_rule>, 1>& rs) + : rules (rs) {} + + // Dummy (never called). + // + virtual bool + match (action, target&, const string&, match_extra&) const override; + + virtual recipe + apply (action, target&, match_extra&) const override; + }; + + fallback_rule fallback_rule_; + }; } #endif // LIBBUILD2_RULE_HXX diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index f2700c4..ad01aa7 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -3,6 +3,7 @@ #include <libbuild2/scope.hxx> +#include <libbuild2/rule.hxx> #include <libbuild2/target.hxx> #include <libbuild2/context.hxx> @@ -30,6 +31,18 @@ namespace build2 // scope // + scope:: + scope (context& c, bool global) + : ctx (c), vars (c, global), target_vars (c, global) + { + } + + scope:: + ~scope () + { + // Definition of adhoc_rule_pattern. + } + pair<lookup, size_t> scope:: lookup_original (const variable& var, const target_key* tk, @@ -649,31 +662,34 @@ namespace build2 } pair<const target_type*, optional<string>> scope:: - find_target_type (name& n, const location& loc) const + find_target_type (name& n, const location& loc, const target_type* tt) const { - const target_type* tt (nullptr); optional<string> ext; string& v (n.value); - // If the target type is specified, resolve it and bail out if not found. - // Otherwise, we know in the end it will resolve to something (if nothing - // else, either dir{} or file{}), so we can go ahead and process the name. + // If the name is typed, resolve the target type it and bail out if not + // found. Otherwise, we know in the end it will resolve to something (if + // nothing else, either dir{} or file{}), so we can go ahead and process + // the name. // - if (n.typed ()) + if (tt == nullptr) { - tt = find_target_type (n.type); + if (n.typed ()) + { + tt = find_target_type (n.type); - if (tt == nullptr) - return make_pair (tt, move (ext)); - } - else - { - // Empty name as well as '.' and '..' signify a directory. Note that - // this logic must be consistent with other places (grep for ".."). - // - if (v.empty () || v == "." || v == "..") - tt = &dir::static_type; + if (tt == nullptr) + return make_pair (tt, move (ext)); + } + else + { + // Empty name as well as '.' and '..' signify a directory. Note that + // this logic must be consistent with other places (grep for ".."). + // + if (v.empty () || v == "." || v == "..") + tt = &dir::static_type; + } } // Directories require special name processing. If we find that more diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 3529495..b83f699 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -338,10 +338,13 @@ namespace build2 // extensions, special names (e.g., '.' and '..'), or anything else that // might be relevant. Process the name (in place) by extracting (and // returning) extension, adjusting dir/leaf, etc., (note that the dir is - // not necessarily normalized). Return NULL if not found. + // not necessarily normalized). If the target type is already resolved, + // then it can be passed as the last argument. Return NULL if not found. // pair<const target_type*, optional<string>> - find_target_type (name&, const location&) const; + find_target_type (name&, + const location&, + const target_type* = nullptr) const; // As above but process the potentially out-qualified target name further // by completing (relative to this scope) and normalizing the directories @@ -401,21 +404,22 @@ namespace build2 // public: rule_map rules; + vector<unique_ptr<adhoc_rule_pattern>> adhoc_rules; template <typename T> void - insert_rule (action_id a, const char* hint, const rule& r) + insert_rule (action_id a, string hint, const rule& r) { - rules.insert<T> (a, hint, r); + rules.insert<T> (a, move (hint), r); } template <typename T> void insert_rule (meta_operation_id mid, operation_id oid, - const char* hint, + string hint, const rule& r) { - rules.insert<T> (mid, oid, hint, r); + rules.insert<T> (mid, oid, move (hint), r); } // Operation callbacks. @@ -579,8 +583,8 @@ namespace build2 friend LIBBUILD2_SYMEXPORT scope& create_bootstrap_inner (scope&, const dir_path&); - scope (context& c, bool global) - : ctx (c), vars (c, global), target_vars (c, global) {} + scope (context&, bool global); + ~scope (); // Return true if this root scope can be amalgamated. // diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 45f285c..e8895ea 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -99,12 +99,21 @@ namespace build2 // Additional information about a rule match (see rule.hxx for details). // - // @@ TODO: will probably want to clear it after apply() if add anything - // dynamically-allocated here (see apply_impl()). - // struct match_extra { - bool fallback; // True if matching a fallback rule. + bool fallback; // True if matching a fallback rule. + string buffer; // Auxiliary buffer that's reused during match/apply. + + // Implementation details. + // + public: + void + init (bool fallback); + + // Force freeing of the dynamically-allocated memory. + // + void + free (); }; // Target. @@ -458,7 +467,7 @@ namespace build2 // Ad hoc recipes. // public: - vector<adhoc_recipe> adhoc_recipes; + vector<shared_ptr<adhoc_rule>> adhoc_recipes; // Target operation state. // diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 2d1906e..1c8dd8d 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -9,6 +9,22 @@ namespace build2 { + // match_extra + // + inline void match_extra:: + init (bool f) + { + fallback = f; + buffer.clear (); + } + + inline void match_extra:: + free () + { + string s; + buffer.swap (s); + } + // target // inline const string* target:: diff --git a/libbuild2/types.hxx b/libbuild2/types.hxx index 8dfda6e..c8fe221 100644 --- a/libbuild2/types.hxx +++ b/libbuild2/types.hxx @@ -114,6 +114,7 @@ namespace build2 // using std::regex; using std::regex_error; + using regex_match_results = std::match_results<string::const_iterator>; // Concurrency. // diff --git a/tests/dependency/recipe/testscript b/tests/dependency/recipe/testscript index 4c4abe5..3126691 100644 --- a/tests/dependency/recipe/testscript +++ b/tests/dependency/recipe/testscript @@ -436,7 +436,7 @@ alias{x}: echo }} EOI -<stdin>:2:3: error: duplicate recipe for perform(update) +<stdin>:2:3: error: duplicate perform(update) recipe EOE : duplicate-action-multiple @@ -451,7 +451,25 @@ alias{x}: echo }} EOI -<stdin>:5:3: error: duplicate recipe for perform(update) +<stdin>:5:3: error: duplicate perform(update) recipe +EOE + +: duplicate-action-multipe-decls +: +$* <<EOI 2>>EOE != 0 +alias{y}: +{{ + echo +}} + +alias{x y}: +% perform(update) +{{ + diag echo + echo +}} +EOI +<stdin>:7:3: error: duplicate perform(update) recipe for target alias{y} EOE : if-else diff --git a/tests/recipe/buildscript/testscript b/tests/recipe/buildscript/testscript index 3ccfdd5..12c5717 100644 --- a/tests/recipe/buildscript/testscript +++ b/tests/recipe/buildscript/testscript @@ -724,3 +724,23 @@ if $posix } } } + +# @@ TODO: test $1 when implemented. +# +: rule +: +{ + cat <<EOI >=buildfile; + alias{far}: alias{bar} + alias{bar}: + + alias{~'/f(.+)/'}: alias{~'/b\1/'} + {{ + diag $< $> + }} + EOI + + $* 2>>EOE + alias{bar} alias{far} + EOE +} diff --git a/tests/recipe/cxx/testscript b/tests/recipe/cxx/testscript index 323e049..9a87c24 100644 --- a/tests/recipe/cxx/testscript +++ b/tests/recipe/cxx/testscript @@ -165,6 +165,45 @@ if (!$static && $test.target == $build.host) $* clean 2>- } + : rule + : + { + cat <<EOI >=buildfile; + alias{far}: alias{bar} + alias{bar}: + + alias{~'/f(.+)/'}: alias{~'/b\1/'} + {{ c++ 1 -- + + #include <iostream> + + -- + + recipe + apply (action, target&) const override + { + return [this] (action a, const target& t) + { + return perform_update (a, t); + }; + } + + target_state + perform_update (action, const target& t) const + { + const auto& mr (t.data<regex_match_results> ()); + text << pattern->rule_name << ": " << mr.str (1); + return target_state::changed; + } + }} + EOI + + $* 2>>~%EOE% + %^(c\+\+|ld).*%+ + <ad hoc pattern rule #1>: ar + EOE + } + # Clean recipe builds if the testscript is enabled (see above for details). # -$* clean 2>- |