aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx22
-rw-r--r--libbuild2/adhoc-rule-buildscript.hxx8
-rw-r--r--libbuild2/adhoc-rule-cxx.cxx25
-rw-r--r--libbuild2/adhoc-rule-cxx.hxx23
-rw-r--r--libbuild2/adhoc-rule-regex-pattern.cxx442
-rw-r--r--libbuild2/adhoc-rule-regex-pattern.hxx58
-rw-r--r--libbuild2/algorithm.cxx162
-rw-r--r--libbuild2/algorithm.hxx10
-rw-r--r--libbuild2/algorithm.ixx12
-rw-r--r--libbuild2/build/script/parser.cxx4
-rw-r--r--libbuild2/build/script/parser.hxx4
-rw-r--r--libbuild2/build/script/parser.test.cxx2
-rw-r--r--libbuild2/cc/guess.cxx2
-rw-r--r--libbuild2/dump.cxx91
-rw-r--r--libbuild2/forward.hxx1
-rw-r--r--libbuild2/parser.cxx789
-rw-r--r--libbuild2/parser.hxx3
-rw-r--r--libbuild2/recipe.hxx12
-rw-r--r--libbuild2/rule-map.hxx49
-rw-r--r--libbuild2/rule.cxx27
-rw-r--r--libbuild2/rule.hxx94
-rw-r--r--libbuild2/scope.cxx50
-rw-r--r--libbuild2/scope.hxx20
-rw-r--r--libbuild2/target.hxx19
-rw-r--r--libbuild2/target.ixx16
-rw-r--r--libbuild2/types.hxx1
-rw-r--r--tests/dependency/recipe/testscript22
-rw-r--r--tests/recipe/buildscript/testscript20
-rw-r--r--tests/recipe/cxx/testscript39
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>-