aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/parser.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-05-31 16:32:40 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-06-08 15:43:08 +0200
commit0baeb5209d3a111a53070c032d7cdb1e609e3516 (patch)
tree61e34e8998b5724274aa2c477608d9fc67b39c1a /libbuild2/parser.cxx
parent1346f4cd0d20a5dc7e0471edbbb6ce00f2da5c18 (diff)
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: <exe{~'/(.*)/'} file{^'/\1.map/'}>: 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: <exe{~'/(.*)/'} file{^'/\1.map/'}>: 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: <exe{hello} file{hello.map>: cxx{hello} hxx{hello} hxx{common}
Diffstat (limited to 'libbuild2/parser.cxx')
-rw-r--r--libbuild2/parser.cxx789
1 files changed, 600 insertions, 189 deletions
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),