From 23a8204d60a7f189fa4659f51b828599fc5838a3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 31 May 2021 16:32:40 +0200 Subject: Implement ad hoc regex pattern rule support An ad hoc pattern rule consists of a pattern that mimics a dependency declaration followed by one or more recipes. For example: exe{~'/(.*)/'}: cxx{~'/\1/'} {{ $cxx.path -o $path($>) $path($<[0]) }} If a pattern matches a dependency declaration of a target, then the recipe is used to perform the corresponding operation on this target. For example, the following dependency declaration matches the above pattern which means the rule's recipe will be used to update this target: exe{hello}: cxx{hello} While the following declarations do not match the above pattern: exe{hello}: c{hello} # Type mismatch. exe{hello}: cxx{howdy} # Name mismatch. On the left hand side of `:` in the pattern we can have a single target or an ad hoc target group. The single target or the first (primary) ad hoc group member must be a regex pattern (~). The rest of the ad hoc group members can be patterns or substitutions (^). For example: : cxx{~'/\1/'} {{ $cxx.path -o $path($>[0]) "-Wl,-Map=$path($>[1])" $path($<[0]) }} On the left hand side of `:` in the pattern we have prerequisites which can be patterns, substitutions, or non-patterns. For example: : cxx{~'/\1/'} hxx{^'/\1/'} hxx{common} {{ $cxx.path -o $path($>[0]) "-Wl,-Map=$path($>[1])" $path($<[0]) }} Substitutions on the left hand side of `:` and substitutions and non-patterns on the right hand side are added to the dependency declaration. For example, given the above rule and dependency declaration, the effective dependency is going to be: : cxx{hello} hxx{hello} hxx{common} --- libbuild2/adhoc-rule-buildscript.cxx | 22 +- libbuild2/adhoc-rule-buildscript.hxx | 8 +- libbuild2/adhoc-rule-cxx.cxx | 25 +- libbuild2/adhoc-rule-cxx.hxx | 23 +- libbuild2/adhoc-rule-regex-pattern.cxx | 442 ++++++++++++++++++ libbuild2/adhoc-rule-regex-pattern.hxx | 58 +++ libbuild2/algorithm.cxx | 162 ++++--- libbuild2/algorithm.hxx | 10 +- libbuild2/algorithm.ixx | 12 +- libbuild2/build/script/parser.cxx | 4 +- libbuild2/build/script/parser.hxx | 4 +- libbuild2/build/script/parser.test.cxx | 2 +- libbuild2/cc/guess.cxx | 2 +- libbuild2/dump.cxx | 91 +++- libbuild2/forward.hxx | 1 + libbuild2/parser.cxx | 789 +++++++++++++++++++++++++-------- libbuild2/parser.hxx | 3 +- libbuild2/recipe.hxx | 12 - libbuild2/rule-map.hxx | 49 +- libbuild2/rule.cxx | 27 +- libbuild2/rule.hxx | 94 +++- libbuild2/scope.cxx | 50 ++- libbuild2/scope.hxx | 20 +- libbuild2/target.hxx | 19 +- libbuild2/target.ixx | 16 + libbuild2/types.hxx | 1 + tests/dependency/recipe/testscript | 22 +- tests/recipe/buildscript/testscript | 20 + tests/recipe/cxx/testscript | 39 ++ 29 files changed, 1644 insertions(+), 383 deletions(-) create mode 100644 libbuild2/adhoc-rule-regex-pattern.cxx create mode 100644 libbuild2/adhoc-rule-regex-pattern.hxx 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 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 ()) - return perform_clean_id; - - return nullopt; + return a == perform_clean_id && tt.is_a () && + 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 + 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&) const; - adhoc_buildscript_rule (const location& l, size_t b) - : adhoc_rule ("", 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 s) - : adhoc_rule ("", l, b), + adhoc_cxx_rule (string n, const location& l, size_t b, + uint64_t v, + optional 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 (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 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 + +#include + +#include + +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& 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 + { + // 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 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 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 ()); + + 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 ()); + + // 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 ()); + + 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 +#include +#include + +#include + +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 targets_; // First is the primary target. + vector 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& 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 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& 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& 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 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& 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 (&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 (&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 (&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 (&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& as, istream& is, const path_name& pn, uint64_t line, optional 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&, istream&, const path_name&, uint64_t line, optional 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* 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 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 (f), + forward (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& r: rp.rules) + { + os << endl; + dump_recipe (os, ind, *r, s); + } + } + static void dump_target (optional 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& 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& 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; // // 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 #include +#include + #include // 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, 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 (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 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 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 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 ("adhoc_rules.size () + 1) + '>'); + + auto& ars (scope_->adhoc_rules); + + auto i (find_if (ars.begin (), ars.end (), + [&rn] (const unique_ptr& 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 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, 1> recipes; + parse_recipe (t, tt, token (t), recipes, rn); + + for (shared_ptr& 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, 1>& recipes) + small_vector, 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, 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 ar; if (!skip) { if (d.first) @@ -1270,11 +1640,16 @@ namespace build2 // location loc (get_location (st)); + shared_ptr ar; if (!lang) { // Buildscript // - ar.reset (new adhoc_buildscript_rule (loc, st.value.size ())); + ar.reset ( + new adhoc_buildscript_rule ( + d.name.empty () ? "" : 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 () ? "" : 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& 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& r (d.recipes[d.i]); + + for (const shared_ptr& 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, 1>&); + small_vector, 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; - struct adhoc_recipe - { - adhoc_actions actions; - shared_ptr 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 - 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 - void - insert (action_id a, const char* hint, const rule& r) + bool + insert (action_id a, string hint, const rule& r) { - insert (a >> 4, a & 0x0F, hint, r); + return insert (a, T::static_type, move (hint), r); } // 0 oid is a wildcard. // - template - 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 (oid, hint, r); + return map_.insert (oid, tt, move (hint), r); else { if (next_ == nullptr) next_.reset (new rule_map (mid)); - next_->insert (mid, oid, hint, r); + return next_->insert (mid, oid, tt, move (hint), r); } } + template + 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 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 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 (*this)) {} + rule_match (move (name), static_cast (*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 + 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 "" - // 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&) 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, 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, 1>& rules; + + explicit + fallback_rule (const small_vector, 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 +#include #include #include @@ -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 scope:: lookup_original (const variable& var, const target_key* tk, @@ -649,31 +662,34 @@ namespace build2 } pair> 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 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> - 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> adhoc_rules; template void - insert_rule (action_id a, const char* hint, const rule& r) + insert_rule (action_id a, string hint, const rule& r) { - rules.insert (a, hint, r); + rules.insert (a, move (hint), r); } template void insert_rule (meta_operation_id mid, operation_id oid, - const char* hint, + string hint, const rule& r) { - rules.insert (mid, oid, hint, r); + rules.insert (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_recipes; + vector> 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; // 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 -:2:3: error: duplicate recipe for perform(update) +:2:3: error: duplicate perform(update) recipe EOE : duplicate-action-multiple @@ -451,7 +451,25 @@ alias{x}: echo }} EOI -:5:3: error: duplicate recipe for perform(update) +:5:3: error: duplicate perform(update) recipe +EOE + +: duplicate-action-multipe-decls +: +$* <>EOE != 0 +alias{y}: +{{ + echo +}} + +alias{x y}: +% perform(update) +{{ + diag echo + echo +}} +EOI +: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 <=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 <=buildfile; + alias{far}: alias{bar} + alias{bar}: + + alias{~'/f(.+)/'}: alias{~'/b\1/'} + {{ c++ 1 -- + + #include + + -- + + 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 ()); + text << pattern->rule_name << ": " << mr.str (1); + return target_state::changed; + } + }} + EOI + + $* 2>>~%EOE% + %^(c\+\+|ld).*%+ + : ar + EOE + } + # Clean recipe builds if the testscript is enabled (see above for details). # -$* clean 2>- -- cgit v1.1