From 9ec2bdd87659438b4aa021a10c4a4977ef77118e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 5 Jun 2020 06:36:30 +0200 Subject: Add ability to specify ad hoc recipe actions We are reusing the buildspec syntax for that. --- libbuild2/algorithm.cxx | 36 +++++++---- libbuild2/build/script/parser.cxx | 2 +- libbuild2/buildspec.cxx | 109 ++++++++++++++++++++++++++++++++ libbuild2/buildspec.hxx | 70 ++++++++++++++++++++ libbuild2/config/operation.cxx | 2 +- libbuild2/dump.cxx | 13 +++- libbuild2/forward.hxx | 2 +- libbuild2/lexer.cxx | 8 +-- libbuild2/parser.cxx | 130 +++++++++++++++++++++++++++++++++++--- libbuild2/parser.hxx | 4 +- libbuild2/recipe.hxx | 6 +- libbuild2/rule.cxx | 34 +++++----- libbuild2/rule.hxx | 22 +++++-- libbuild2/spec.cxx | 109 -------------------------------- libbuild2/spec.hxx | 70 -------------------- 15 files changed, 380 insertions(+), 237 deletions(-) create mode 100644 libbuild2/buildspec.cxx create mode 100644 libbuild2/buildspec.hxx delete mode 100644 libbuild2/spec.cxx delete mode 100644 libbuild2/spec.hxx (limited to 'libbuild2') 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/buildspec.cxx b/libbuild2/buildspec.cxx new file mode 100644 index 0000000..bd580ca --- /dev/null +++ b/libbuild2/buildspec.cxx @@ -0,0 +1,109 @@ +// file : libbuild2/buildspec.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + ostream& + operator<< (ostream& os, const targetspec& s) + { + if (!s.src_base.empty ()) + { + // Avoid printing './' in './@...', similar to what we do for the + // {target,prerequisite}_key. + // + if (stream_verb (os).path < 1) + { + const string& r (diag_relative (s.src_base, false)); + + if (!r.empty ()) + os << r << '@'; + } + else + os << s.src_base << '@'; + } + + os << s.name; + return os; + } + + ostream& + operator<< (ostream& os, const opspec& s) + { + bool hn (!s.name.empty ()); + bool ht (!s.empty ()); + + os << (hn ? "\"" : "") << s.name << (hn ? "\"" : ""); + + if (hn && ht) + os << '('; + + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + for (const value& v: s.params) + { + os << ", "; + + if (v) + { + names storage; + os << reverse (v, storage); + } + else + os << "[null]"; + } + + if (hn && ht) + os << ')'; + + return os; + } + + ostream& + operator<< (ostream& os, const metaopspec& s) + { + bool hn (!s.name.empty ()); + bool ho (!s.empty ()); + + os << (hn ? "\'" : "") << s.name << (hn ? "\'" : ""); + + if (hn && ho) + os << '('; + + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + for (const value& v: s.params) + { + os << ", "; + + if (v) + { + names storage; + os << reverse (v, storage); + } + else + os << "[null]"; + } + + if (hn && ho) + os << ')'; + + return os; + } + + ostream& + operator<< (ostream& os, const buildspec& s) + { + for (auto b (s.begin ()), i (b); i != s.end (); ++i) + os << (i != b ? " " : "") << *i; + + return os; + } +} diff --git a/libbuild2/buildspec.hxx b/libbuild2/buildspec.hxx new file mode 100644 index 0000000..b181d15 --- /dev/null +++ b/libbuild2/buildspec.hxx @@ -0,0 +1,70 @@ +// file : libbuild2/buildspec.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_BUILDSPEC_HXX +#define LIBBUILD2_BUILDSPEC_HXX + +#include +#include +#include + +#include + +#include + +namespace build2 +{ + struct targetspec + { + using name_type = build2::name; + + explicit + targetspec (name_type n): name (move (n)) {} + targetspec (dir_path sb, name_type n) + : src_base (move (sb)), name (move (n)) {} + + dir_path src_base; + name_type name; + + // The rest is calculated and cached. + // + scope* root_scope = nullptr; + dir_path out_base; + path buildfile; // Empty if implied. + bool forwarded = false; + }; + + struct opspec: small_vector + { + opspec () = default; + opspec (string n): name (move (n)) {} + + string name; + values params; + }; + + struct metaopspec: small_vector + { + metaopspec () = default; + metaopspec (string n): name (move (n)) {} + + string name; + values params; + }; + + using buildspec = small_vector; + + LIBBUILD2_SYMEXPORT ostream& + operator<< (ostream&, const targetspec&); + + LIBBUILD2_SYMEXPORT ostream& + operator<< (ostream&, const opspec&); + + LIBBUILD2_SYMEXPORT ostream& + operator<< (ostream&, const metaopspec&); + + LIBBUILD2_SYMEXPORT ostream& + operator<< (ostream&, const buildspec&); +} + +#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 #include -#include #include #include #include #include +#include // opspec #include #include 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; - // + // // 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. // - // % [] + // % [] [] // [if|switch ...] // {{ [ ...] // ... // }} + // ... // // 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, 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 #include -#include #include #include #include #include +#include #include #include @@ -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 rule; + small_vector actions; + shared_ptr 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. diff --git a/libbuild2/spec.cxx b/libbuild2/spec.cxx deleted file mode 100644 index d1a39ab..0000000 --- a/libbuild2/spec.cxx +++ /dev/null @@ -1,109 +0,0 @@ -// file : libbuild2/spec.cxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#include - -#include - -using namespace std; - -namespace build2 -{ - ostream& - operator<< (ostream& os, const targetspec& s) - { - if (!s.src_base.empty ()) - { - // Avoid printing './' in './@...', similar to what we do for the - // {target,prerequisite}_key. - // - if (stream_verb (os).path < 1) - { - const string& r (diag_relative (s.src_base, false)); - - if (!r.empty ()) - os << r << '@'; - } - else - os << s.src_base << '@'; - } - - os << s.name; - return os; - } - - ostream& - operator<< (ostream& os, const opspec& s) - { - bool hn (!s.name.empty ()); - bool ht (!s.empty ()); - - os << (hn ? "\"" : "") << s.name << (hn ? "\"" : ""); - - if (hn && ht) - os << '('; - - for (auto b (s.begin ()), i (b); i != s.end (); ++i) - os << (i != b ? " " : "") << *i; - - for (const value& v: s.params) - { - os << ", "; - - if (v) - { - names storage; - os << reverse (v, storage); - } - else - os << "[null]"; - } - - if (hn && ht) - os << ')'; - - return os; - } - - ostream& - operator<< (ostream& os, const metaopspec& s) - { - bool hn (!s.name.empty ()); - bool ho (!s.empty ()); - - os << (hn ? "\'" : "") << s.name << (hn ? "\'" : ""); - - if (hn && ho) - os << '('; - - for (auto b (s.begin ()), i (b); i != s.end (); ++i) - os << (i != b ? " " : "") << *i; - - for (const value& v: s.params) - { - os << ", "; - - if (v) - { - names storage; - os << reverse (v, storage); - } - else - os << "[null]"; - } - - if (hn && ho) - os << ')'; - - return os; - } - - ostream& - operator<< (ostream& os, const buildspec& s) - { - for (auto b (s.begin ()), i (b); i != s.end (); ++i) - os << (i != b ? " " : "") << *i; - - return os; - } -} diff --git a/libbuild2/spec.hxx b/libbuild2/spec.hxx deleted file mode 100644 index d2ed609..0000000 --- a/libbuild2/spec.hxx +++ /dev/null @@ -1,70 +0,0 @@ -// file : libbuild2/spec.hxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef LIBBUILD2_SPEC_HXX -#define LIBBUILD2_SPEC_HXX - -#include -#include -#include - -#include - -#include - -namespace build2 -{ - struct targetspec - { - using name_type = build2::name; - - explicit - targetspec (name_type n): name (move (n)) {} - targetspec (dir_path sb, name_type n) - : src_base (move (sb)), name (move (n)) {} - - dir_path src_base; - name_type name; - - // The rest is calculated and cached. - // - scope* root_scope = nullptr; - dir_path out_base; - path buildfile; // Empty if implied. - bool forwarded = false; - }; - - struct opspec: vector - { - opspec () = default; - opspec (string n): name (move (n)) {} - - string name; - values params; - }; - - struct metaopspec: vector - { - metaopspec () = default; - metaopspec (string n): name (move (n)) {} - - string name; - values params; - }; - - using buildspec = vector; - - LIBBUILD2_SYMEXPORT ostream& - operator<< (ostream&, const targetspec&); - - LIBBUILD2_SYMEXPORT ostream& - operator<< (ostream&, const opspec&); - - LIBBUILD2_SYMEXPORT ostream& - operator<< (ostream&, const metaopspec&); - - LIBBUILD2_SYMEXPORT ostream& - operator<< (ostream&, const buildspec&); -} - -#endif // LIBBUILD2_SPEC_HXX -- cgit v1.1