aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-06-05 06:36:30 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-06-05 06:36:30 +0200
commit9ec2bdd87659438b4aa021a10c4a4977ef77118e (patch)
tree12580b4d0b82bce80047b067c3bb221b49be7449 /libbuild2
parentd280946474568925016359be742b59fd6c000c52 (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.cxx36
-rw-r--r--libbuild2/build/script/parser.cxx2
-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.cxx2
-rw-r--r--libbuild2/dump.cxx13
-rw-r--r--libbuild2/forward.hxx2
-rw-r--r--libbuild2/lexer.cxx8
-rw-r--r--libbuild2/parser.cxx130
-rw-r--r--libbuild2/parser.hxx4
-rw-r--r--libbuild2/recipe.hxx6
-rw-r--r--libbuild2/rule.cxx34
-rw-r--r--libbuild2/rule.hxx22
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.