diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2020-06-05 06:36:30 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2020-06-05 06:36:30 +0200 |
commit | 9ec2bdd87659438b4aa021a10c4a4977ef77118e (patch) | |
tree | 12580b4d0b82bce80047b067c3bb221b49be7449 /libbuild2 | |
parent | d280946474568925016359be742b59fd6c000c52 (diff) |
Add ability to specify ad hoc recipe actions
We are reusing the buildspec syntax for that.
Diffstat (limited to 'libbuild2')
-rw-r--r-- | libbuild2/algorithm.cxx | 36 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 2 | ||||
-rw-r--r-- | libbuild2/buildspec.cxx (renamed from libbuild2/spec.cxx) | 4 | ||||
-rw-r--r-- | libbuild2/buildspec.hxx (renamed from libbuild2/spec.hxx) | 14 | ||||
-rw-r--r-- | libbuild2/config/operation.cxx | 2 | ||||
-rw-r--r-- | libbuild2/dump.cxx | 13 | ||||
-rw-r--r-- | libbuild2/forward.hxx | 2 | ||||
-rw-r--r-- | libbuild2/lexer.cxx | 8 | ||||
-rw-r--r-- | libbuild2/parser.cxx | 130 | ||||
-rw-r--r-- | libbuild2/parser.hxx | 4 | ||||
-rw-r--r-- | libbuild2/recipe.hxx | 6 | ||||
-rw-r--r-- | libbuild2/rule.cxx | 34 | ||||
-rw-r--r-- | libbuild2/rule.hxx | 22 |
13 files changed, 210 insertions, 67 deletions
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 0924540..6ea1e1f 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -335,20 +335,32 @@ namespace build2 // the correct semantics. auto b (t.adhoc_recipes.begin ()), e (t.adhoc_recipes.end ()); - auto i (find_if (b, e, - [a, &t] (const adhoc_recipe& r) - { - return r.action == a && - r.rule->match (a, t, string () /* hint */, nullopt); - })); + auto i (find_if ( + b, e, + [a, &t] (const adhoc_recipe& r) + { + auto& as (r.actions); + return (find (as.begin (), as.end (), a) != as.end () && + r.rule->match (a, t, string () /* hint */, nullopt)); + })); if (i == e) - i = find_if (b, e, - [a, &t] (const adhoc_recipe& r) - { - return r.action != a && - r.rule->match (a, t, string () /* hint */, r.action); - }); + i = find_if ( + b, e, + [a, &t] (const adhoc_recipe& r) + { + // See the adhoc_rule::match() documentation for details. + // + auto& as (r.actions); + if (find (as.begin (), as.end (), a) == as.end ()) + { + for (auto ra: as) + if (r.rule->match (a, t, string () /* hint */, ra)) + return true; + } + return false; + }); + if (i != e) return &i->rule->rule_match; } diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index 72c99ad..274faf0 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -385,7 +385,7 @@ namespace build2 << "the 'diag' recipe attribute"; dr << info << "or provide custom low-verbosity diagnostics " - << " with the 'diag' builtin"; + << "with the 'diag' builtin"; }; parse_names_result pr; diff --git a/libbuild2/spec.cxx b/libbuild2/buildspec.cxx index d1a39ab..bd580ca 100644 --- a/libbuild2/spec.cxx +++ b/libbuild2/buildspec.cxx @@ -1,7 +1,7 @@ -// file : libbuild2/spec.cxx -*- C++ -*- +// file : libbuild2/buildspec.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#include <libbuild2/spec.hxx> +#include <libbuild2/buildspec.hxx> #include <libbuild2/diagnostics.hxx> diff --git a/libbuild2/spec.hxx b/libbuild2/buildspec.hxx index d2ed609..b181d15 100644 --- a/libbuild2/spec.hxx +++ b/libbuild2/buildspec.hxx @@ -1,8 +1,8 @@ -// file : libbuild2/spec.hxx -*- C++ -*- +// file : libbuild2/buildspec.hxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#ifndef LIBBUILD2_SPEC_HXX -#define LIBBUILD2_SPEC_HXX +#ifndef LIBBUILD2_BUILDSPEC_HXX +#define LIBBUILD2_BUILDSPEC_HXX #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> @@ -34,7 +34,7 @@ namespace build2 bool forwarded = false; }; - struct opspec: vector<targetspec> + struct opspec: small_vector<targetspec, 1> { opspec () = default; opspec (string n): name (move (n)) {} @@ -43,7 +43,7 @@ namespace build2 values params; }; - struct metaopspec: vector<opspec> + struct metaopspec: small_vector<opspec, 1> { metaopspec () = default; metaopspec (string n): name (move (n)) {} @@ -52,7 +52,7 @@ namespace build2 values params; }; - using buildspec = vector<metaopspec>; + using buildspec = small_vector<metaopspec, 1>; LIBBUILD2_SYMEXPORT ostream& operator<< (ostream&, const targetspec&); @@ -67,4 +67,4 @@ namespace build2 operator<< (ostream&, const buildspec&); } -#endif // LIBBUILD2_SPEC_HXX +#endif // LIBBUILD2_BUILDSPEC_HXX diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 41d982b..6e28b0a 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -4,11 +4,11 @@ #include <libbuild2/config/operation.hxx> #include <libbuild2/file.hxx> -#include <libbuild2/spec.hxx> #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> #include <libbuild2/context.hxx> #include <libbuild2/algorithm.hxx> +#include <libbuild2/buildspec.hxx> // opspec #include <libbuild2/filesystem.hxx> #include <libbuild2/diagnostics.hxx> diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index 9f60900..11eb4b3 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -350,10 +350,21 @@ namespace build2 // if (!t.adhoc_recipes.empty ()) { + auto& re (*s.root_scope ()->root_extra); + for (const adhoc_recipe 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 (os, ind); // @@ TODO: pass action(s). + r.rule->dump_text (os, ind); } used = true; diff --git a/libbuild2/forward.hxx b/libbuild2/forward.hxx index 4443610..94eb006 100644 --- a/libbuild2/forward.hxx +++ b/libbuild2/forward.hxx @@ -14,7 +14,7 @@ namespace build2 // struct action; - // <libbuild2/spec.hxx> + // <libbuild2/buildspec.hxx> // struct opspec; diff --git a/libbuild2/lexer.cxx b/libbuild2/lexer.cxx index 7149d45..e50ec16 100644 --- a/libbuild2/lexer.cxx +++ b/libbuild2/lexer.cxx @@ -125,13 +125,13 @@ namespace build2 // // 2. Recognizes comma. // - // 3. Treat newline as an ordinary space. - // - // Also note that we don't have buildspec attributes. + // Note that because we use this mode for both the command line + // buildspec and ad hoc recipe actions, we control the recognition of + // newlines as tokens via the auxiliary data. // s1 = " $(){},\t\n"; s2 = " "; - n = false; + n = (data != 0); break; } case lexer_mode::foreign: diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index d8baf06..6fb20fe 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -1046,11 +1046,12 @@ namespace build2 { // Parse a recipe chain. // - // % [<attrs>] + // % [<attrs>] [<buildspec>] // [if|switch ...] // {{ [<lang> ...] // ... // }} + // ... // // enter: start is percent or openining multi-curly-brace // leave: token past newline after last closing multi-curly-brace @@ -1078,6 +1079,9 @@ namespace build2 recipes.push_back (nullptr); // For missing else/default (see below). attributes as; + buildspec bs; + location bsloc; + struct data { small_vector<shared_ptr<adhoc_rule>, 1>& recipes; @@ -1085,7 +1089,9 @@ namespace build2 bool& clean; size_t i; attributes& as; - } d {recipes, first, clean, i, as}; + buildspec& bs; + const location& bsloc; + } d {recipes, first, clean, i, as, bs, bsloc}; // Note that this function must be called at most once per iteration. // @@ -1197,8 +1203,74 @@ namespace build2 fail (d.as.loc) << "unknown recipe attribute " << a << endf; } - target_->adhoc_recipes.push_back ( - adhoc_recipe {perform_update_id, move (ar)}); + auto& ars (target_->adhoc_recipes); + ars.push_back (adhoc_recipe {{}, move (ar)}); + + // Translate each buildspec entry into action and add it into the + // target's ad hoc recipes entry. + // + const location& l (d.bsloc); + + 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; + + 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; + + for (opspec& o: m) + { + 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"; + } + else + oi = ctx.operation_table.find (o.name); + + if (oi == 0) + fail (l) << "unknown operation " << o.name; + + const operation_info* of (root_->root_extra->operations[oi]); + + if (of == nullptr) + fail (l) << "target " << *target_ << " does not support " + << "operation " << ctx.operation_table[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 << ')'; + } + + ars.back ().actions.push_back (a); + } + } } next (t, tt); @@ -1210,6 +1282,11 @@ namespace build2 if (tt == type::percent) { + // Similar code to parse_buildspec() except here we recognize + // attributes and newlines. + // + mode (lexer_mode::buildspec, '@', 1 /* recognize newline */); + next_with_attributes (t, tt); attributes_push (t, tt, true /* standalone */); @@ -1221,7 +1298,36 @@ namespace build2 as = move (attributes_top ()); attributes_pop (); - next_after_newline (t, tt, '%'); + // Handle the buildspec. + // + // @@ TODO: diagnostics is a bit off ("operation or target"). + // + if (tt != type::newline && tt != type::eos) + { + const location& l (bsloc = get_location (t)); + bs = parse_buildspec_clause (t, tt); + + // Verify we have no targets and assign default meta-operations. + // + // Note that here we don't bother with lifting operations to meta- + // operations like we do in the driver (this seems unlikely to be a + // pain point). + // + for (metaopspec& m: bs) + { + for (opspec& o: m) + { + if (!o.empty ()) + fail (l) << "target in recipe action"; + } + + if (m.name.empty ()) + m.name = "perform"; + } + } + + expire_mode (); + next_after_newline (t, tt, "recipe action"); // See if this is if-else or switch. // @@ -1259,6 +1365,14 @@ namespace build2 // Fall through. } + // Default is perform(update). + // + if (bs.empty ()) + { + bs.push_back (metaopspec ("perform")); + bs.back ().push_back (opspec ("update")); + } + parse_block (t, tt, false /* skip */, "" /* kind */); } @@ -6308,7 +6422,7 @@ namespace build2 next (t, tt); buildspec r (tt != type::eos - ? parse_buildspec_clause (t, tt, 0) + ? parse_buildspec_clause (t, tt) : buildspec ()); if (tt != type::eos) @@ -6437,8 +6551,8 @@ namespace build2 } } - // No nested meta-operations means we should have a single - // metaopspec object with empty meta-operation name. + // No nested meta-operations means we should have a single metaopspec + // object with empty meta-operation name. // assert (nbs.size () == 1); const metaopspec& nmo (nbs.back ()); diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 88b4ad9..78c7d17 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -8,11 +8,11 @@ #include <libbuild2/forward.hxx> #include <libbuild2/utility.hxx> -#include <libbuild2/spec.hxx> #include <libbuild2/file.hxx> #include <libbuild2/lexer.hxx> #include <libbuild2/token.hxx> #include <libbuild2/variable.hxx> +#include <libbuild2/buildspec.hxx> #include <libbuild2/diagnostics.hxx> #include <libbuild2/export.hxx> @@ -496,7 +496,7 @@ namespace build2 // Buildspec. // buildspec - parse_buildspec_clause (token&, token_type&, size_t); + parse_buildspec_clause (token&, token_type&, size_t = 0); // Customization hooks. // diff --git a/libbuild2/recipe.hxx b/libbuild2/recipe.hxx index efd184a..823f804 100644 --- a/libbuild2/recipe.hxx +++ b/libbuild2/recipe.hxx @@ -58,10 +58,8 @@ namespace build2 struct adhoc_recipe { - // @@ TODO: maybe we should have a small vector of actions (for dump). - // - build2::action action; - shared_ptr<adhoc_rule> rule; + small_vector<action, 1> actions; + shared_ptr<adhoc_rule> rule; }; } diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index 7ea6e68..0b9f066 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -333,6 +333,11 @@ namespace build2 return true; } + void adhoc_rule:: + dump_attributes (ostream&) const + { + } + // Scope operation callback that cleans up recipe builds. // target_state adhoc_rule:: @@ -406,28 +411,23 @@ namespace build2 } void adhoc_script_rule:: - dump (ostream& os, string& ind) const + dump_attributes (ostream& os) const { - // Do we need the header? - // - // @@ TODO: for now we dump it as an attribute whether it was specified or - // derived from the script. Maybe that's ok? + // For now we dump it as an attribute whether it was specified or derived + // from the script. Maybe that's ok (we use this in tests)? // if (script.diag_name) { - os << ind << '%'; - - if (script.diag_name) - { - os << " ["; - os << "diag="; - to_stream (os, name (*script.diag_name), true /* quote */, '@'); - os << ']'; - } - - os << endl; + os << " ["; + os << "diag="; + to_stream (os, name (*script.diag_name), true /* quote */, '@'); + os << ']'; } + } + void adhoc_script_rule:: + dump_text (ostream& os, string& ind) const + { os << ind << string (braces, '{') << endl; ind += " "; script::dump (os, ind, script.lines); @@ -807,7 +807,7 @@ namespace build2 } void adhoc_cxx_rule:: - dump (ostream& os, string& ind) const + dump_text (ostream& os, string& ind) const { // @@ TODO: indentation is multi-line recipes is off (would need to insert // indentation after every newline). diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx index a79eeed..c98c29e 100644 --- a/libbuild2/rule.hxx +++ b/libbuild2/rule.hxx @@ -153,8 +153,13 @@ namespace build2 virtual bool match (action, target&, const string&) const override; + // Dump support. + // + virtual void + dump_attributes (ostream&) const; + virtual void - dump (ostream&, string& indentation) const = 0; + dump_text (ostream&, string& indentation) const = 0; // Implementation details. // @@ -188,14 +193,17 @@ namespace build2 target_state default_action (action, const target&) const; - virtual void - dump (ostream&, string&) const override; - adhoc_script_rule (const location& l, size_t b): adhoc_rule (l, b) {} virtual bool recipe_text (context&, const target&, string&&, attributes&) override; + virtual void + dump_attributes (ostream&) const override; + + virtual void + dump_text (ostream&, string&) const override; + public: using script_type = build::script::script; @@ -245,9 +253,6 @@ namespace build2 virtual recipe apply (action, target&) const override; - virtual void - dump (ostream&, string&) const override; - adhoc_cxx_rule (const location&, size_t, uint64_t version); virtual bool @@ -256,6 +261,9 @@ namespace build2 virtual ~adhoc_cxx_rule () override; + virtual void + dump_text (ostream&, string&) const override; + public: // Note that this recipe (rule instance) can be shared between multiple // targets which could all be matched in parallel. |