aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-04-19 11:10:53 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-04-19 11:10:53 +0200
commit9034f7c51ef6437ce9d4547ba5bde217b4740fb2 (patch)
treebfaa297fda78b6cafe4487e2281420a4cf77c81a
parent457f65414031f45174f3c35230a0c0e1de88b51a (diff)
Use target recipe for auxiliary data storage during match-apply
In particular, we now have separate auxiliary data storage for inner and outer operations.
-rw-r--r--libbuild2/action.hxx13
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx22
-rw-r--r--libbuild2/adhoc-rule-regex-pattern.cxx8
-rw-r--r--libbuild2/algorithm.cxx9
-rw-r--r--libbuild2/algorithm.hxx5
-rw-r--r--libbuild2/algorithm.ixx3
-rw-r--r--libbuild2/bash/rule.cxx60
-rw-r--r--libbuild2/bash/rule.hxx3
-rw-r--r--libbuild2/cc/compile-rule.cxx41
-rw-r--r--libbuild2/cc/compile-rule.hxx5
-rw-r--r--libbuild2/cc/install-rule.cxx26
-rw-r--r--libbuild2/cc/link-rule.cxx29
-rw-r--r--libbuild2/cc/link-rule.hxx26
-rw-r--r--libbuild2/in/rule.hxx5
-rw-r--r--libbuild2/target.cxx1
-rw-r--r--libbuild2/target.hxx133
-rw-r--r--libbuild2/version/rule.cxx39
-rw-r--r--libbuild2/version/rule.hxx3
-rw-r--r--tests/recipe/cxx/testscript4
19 files changed, 288 insertions, 147 deletions
diff --git a/libbuild2/action.hxx b/libbuild2/action.hxx
index dce3a1a..85012ba 100644
--- a/libbuild2/action.hxx
+++ b/libbuild2/action.hxx
@@ -45,16 +45,17 @@ namespace build2
// inner rule. In particular, it should not replace or override the inner's
// logic.
//
- // While most of the relevant target state is duplicated, certain things are
- // shared among the inner/outer rules, such as the target data pad and the
- // group state. In particular, it is assumed the group state is always
- // determined by the inner rule (see resolve_members()).
+ // While most of the action-specific target state is duplicated (see
+ // target::opstate), certain things are shared among the inner/outer rules,
+ // such as the path, mtime, and group state. In particular, it is assumed
+ // the group state is always determined by the inner rule (see
+ // resolve_members()).
//
// Normally, an outer rule will be responsible for any additional, outer
// operation-specific work. Sometimes, however, the inner rule needs to
// customize its behavior. In this case the outer and inner rules must
- // communicate this explicitly (normally via the target's data pad) and
- // there is a number of restrictions to this approach. See
+ // communicate this explicitly (normally via the target's auxiliary data
+ // storage) and there is a number of restrictions to this approach. See
// cc::{link,install}_rule for details.
//
struct action
diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx
index aa30552..03e810d 100644
--- a/libbuild2/adhoc-rule-buildscript.cxx
+++ b/libbuild2/adhoc-rule-buildscript.cxx
@@ -470,10 +470,22 @@ namespace build2
//
if (a != perform_update_id || !xt.is_a<file> ())
{
- return [d, this] (action a, const target& t)
+ // Make sure we get small object optimization.
+ //
+ if (d)
{
- return default_action (a, t, d);
- };
+ return [dv = *d, this] (action a, const target& t)
+ {
+ return default_action (a, t, dv);
+ };
+ }
+ else
+ {
+ return [this] (action a, const target& t)
+ {
+ return default_action (a, t, nullopt);
+ };
+ }
}
// See if this is the simple case with only static dependencies.
@@ -589,6 +601,10 @@ namespace build2
}
}
+ // Note that while it's tempting to turn match_data* into recipes, some of
+ // their members are not movable. And in the end we will have the same
+ // result: one dynamic memory allocation.
+ //
unique_ptr<match_data> md;
unique_ptr<match_data_byproduct> mdb;
diff --git a/libbuild2/adhoc-rule-regex-pattern.cxx b/libbuild2/adhoc-rule-regex-pattern.cxx
index 3bd3bb1..98acfb6 100644
--- a/libbuild2/adhoc-rule-regex-pattern.cxx
+++ b/libbuild2/adhoc-rule-regex-pattern.cxx
@@ -272,7 +272,7 @@ namespace build2
return false;
}
- t.data (move (mr));
+ t.data (a, move (mr));
return true;
}
@@ -297,9 +297,9 @@ namespace build2
}
void adhoc_rule_regex_pattern::
- apply_adhoc_members (action, target& t, const scope&, match_extra&) const
+ apply_adhoc_members (action a, target& t, const scope&, match_extra&) const
{
- const auto& mr (t.data<regex_match_results> ());
+ const auto& mr (t.data<regex_match_results> (a));
for (auto i (targets_.begin () + 1); i != targets_.end (); ++i)
{
@@ -342,7 +342,7 @@ namespace build2
const scope& bs,
match_extra&) const
{
- const auto& mr (t.data<regex_match_results> ());
+ const auto& mr (t.data<regex_match_results> (a));
// Re-create the same clean semantics as in match_prerequisite_members().
//
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
index 7116b8b..355e633 100644
--- a/libbuild2/algorithm.cxx
+++ b/libbuild2/algorithm.cxx
@@ -821,9 +821,9 @@ namespace build2
//
// Clear the rule-specific variables, resolved targets list, and the
- // data pad before calling match(). The rule is free to modify these
- // in its match() (provided that it matches) in order to, for
- // example, convey some information to apply().
+ // auxiliary data storage before calling match(). The rule is free
+ // to modify these in its match() (provided that it matches) in
+ // order to, for example, convey some information to apply().
//
clear_target (a, t);
@@ -1920,7 +1920,8 @@ namespace build2
// group_state()) and should retain its value.
//
//
- s.recipe = nullptr;
+ if (!s.recipe_keep)
+ s.recipe = nullptr;
// Decrement the target count (see set_recipe() for details).
//
diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx
index 439af78..75976bf 100644
--- a/libbuild2/algorithm.hxx
+++ b/libbuild2/algorithm.hxx
@@ -396,9 +396,8 @@ namespace build2
// Match a "delegate rule" from withing another rules' apply() function
// avoiding recursive matches (thus the third argument). Unless try_match is
// true, fail if no rule is found. Otherwise return empty recipe. Note that
- // unlike match(), this function does not increment the dependents count and
- // the two rules must coordinate who is using the target's data pad and/or
- // prerequisite_targets. See also the companion execute_delegate().
+ // unlike match(), this function does not increment the dependents count.
+ // See also the companion execute_delegate().
//
recipe
match_delegate (action, target&, const rule&, bool try_match = false);
diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx
index 02c430b..2637349 100644
--- a/libbuild2/algorithm.ixx
+++ b/libbuild2/algorithm.ixx
@@ -495,8 +495,7 @@ namespace build2
{
t[a].vars.clear ();
t.prerequisite_targets[a].clear ();
- if (a.inner ())
- t.clear_data ();
+ t.clear_data (a);
}
inline void
diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx
index fec85be..55f3cf0 100644
--- a/libbuild2/bash/rule.cxx
+++ b/libbuild2/bash/rule.cxx
@@ -26,6 +26,9 @@ namespace build2
struct match_data
{
+ explicit
+ match_data (const in_rule& r): rule (r) {}
+
// The "for install" condition is signalled to us by install_rule when
// it is matched for the update operation. It also verifies that if we
// have already been executed, then it was for install.
@@ -33,6 +36,25 @@ namespace build2
// See cc::link_rule for a discussion of some subtleties in this logic.
//
optional<bool> for_install;
+
+ const in_rule& rule;
+
+ target_state
+ operator() (action a, const target& t)
+ {
+ // Unless the outer install rule signalled that this is update for
+ // install, signal back that we've performed plain update.
+ //
+ if (!for_install)
+ for_install = false;
+
+ //@@ TODO: need to verify all the modules we depend on are compatible
+ // with our for_install value, similar to cc::link_rule's
+ // append_libraries() (and which is the other half of the check
+ // in install_rule).
+
+ return rule.perform_update (a, t);
+ }
};
static_assert (sizeof (match_data) <= target::small_data_size,
@@ -66,37 +88,23 @@ namespace build2
if (!fm)
l4 ([&]{trace << "no bash module prerequisite for target " << t;});
- return (fi && fm);
+ return fi && fm;
}
recipe in_rule::
apply (action a, target& t) const
{
- // Note that for-install is signalled by install_rule and therefore
- // can only be relied upon during execute.
- //
- t.data (match_data ());
+ recipe r (rule::apply (a, t));
- return rule::apply (a, t);
- }
-
- target_state in_rule::
- perform_update (action a, const target& t) const
- {
- // Unless the outer install rule signalled that this is update for
- // install, signal back that we've performed plain update.
- //
- match_data& md (t.data<match_data> ());
-
- if (!md.for_install)
- md.for_install = false;
-
- //@@ TODO: need to verify all the modules we depend on are compatible
- // with our for_install value, similar to cc::link_rule's
- // append_libraries() (and which is the other half of the check
- // in install_rule).
+ if (a == perform_update_id)
+ {
+ // Note that for-install is signalled by install_rule and therefore
+ // can only be relied upon during execute.
+ //
+ return match_data (*this);
+ }
- return rule::perform_update (a, t);
+ return r;
}
prerequisite_target in_rule::
@@ -338,7 +346,7 @@ namespace build2
if (ap == nullptr)
fail (l) << "unable to resolve import path " << ip;
- match_data& md (t.data<match_data> ());
+ match_data& md (t.data<match_data> (a));
assert (md.for_install);
if (*md.for_install)
@@ -449,7 +457,7 @@ namespace build2
// Signal to the in rule that this is update for install. And if the
// update has already been executed, verify it was done for install.
//
- auto& md (t.data<match_data> ());
+ auto& md (t.data<match_data> (a.inner_action ()));
if (md.for_install)
{
diff --git a/libbuild2/bash/rule.hxx b/libbuild2/bash/rule.hxx
index 1a0cb36..3da0c73 100644
--- a/libbuild2/bash/rule.hxx
+++ b/libbuild2/bash/rule.hxx
@@ -37,9 +37,6 @@ namespace build2
virtual recipe
apply (action, target&) const override;
- virtual target_state
- perform_update (action, const target&) const override;
-
virtual prerequisite_target
search (action,
const target&,
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index f680001..ada0cc6 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -229,10 +229,16 @@ namespace build2
return nullopt;
}
+ // Note that we don't really need this for clean (where we only need
+ // unrefined unit type) so we could make this update-only. But let's keep
+ // it simple for now.
+ //
struct compile_rule::match_data
{
- match_data (unit_type t, const prerequisite_member& s)
- : type (t), src (s) {}
+ match_data (const compile_rule& r,
+ unit_type t,
+ const prerequisite_member& s)
+ : type (t), src (s), rule (r) {}
unit_type type;
preprocessed pp = preprocessed::none;
@@ -245,6 +251,14 @@ namespace build2
path dd; // Dependency database path.
size_t header_units = 0; // Number of imported header units.
module_positions modules = {0, 0, 0}; // Positions of imported modules.
+
+ const compile_rule& rule;
+
+ target_state
+ operator() (action a, const target& t)
+ {
+ return rule.perform_update (a, t, *this);
+ }
};
compile_rule::
@@ -464,7 +478,7 @@ namespace build2
{
// Save in the target's auxiliary storage.
//
- t.data (match_data (ut, p));
+ t.data (a, match_data (*this, ut, p));
return true;
}
}
@@ -809,7 +823,7 @@ namespace build2
file& t (xt.as<file> ()); // Either obj*{} or bmi*{}.
- match_data& md (t.data<match_data> ());
+ match_data& md (t.data<match_data> (a));
context& ctx (t.ctx);
@@ -1497,10 +1511,7 @@ namespace build2
switch (a)
{
- case perform_update_id: return [this] (action a, const target& t)
- {
- return perform_update (a, t);
- };
+ case perform_update_id: return move (md);
case perform_clean_id: return [this] (action a, const target& t)
{
return perform_clean (a, t);
@@ -5439,10 +5450,11 @@ namespace build2
// 1. There is no good place in prerequisite_targets to store the
// exported flag (no, using the marking facility across match/execute
// is a bad idea). So what we are going to do is put re-exported
- // bmi{}s at the back and store (in the target's data pad) the start
- // position. One bad aspect about this part is that we assume those
- // bmi{}s have been matched by the same rule. But let's not kid
- // ourselves, there will be no other rule that matches bmi{}s.
+ // bmi{}s at the back and store (in the target's auxiliary data
+ // storage) the start position. One bad aspect about this part is
+ // that we assume those bmi{}s have been matched by the same
+ // rule. But let's not kid ourselves, there will be no other rule
+ // that matches bmi{}s.
//
// @@ I think now we could use prerequisite_targets::data for this?
//
@@ -5833,7 +5845,7 @@ namespace build2
// Copy over bmi{}s from our prerequisites weeding out duplicates.
//
- if (size_t j = bt->data<match_data> ().modules.start)
+ if (size_t j = bt->data<match_data> (a).modules.start)
{
// Hard to say whether we should reserve or not. We will probably
// get quite a bit of duplications.
@@ -6578,12 +6590,11 @@ namespace build2
}
target_state compile_rule::
- perform_update (action a, const target& xt) const
+ perform_update (action a, const target& xt, match_data& md) const
{
const file& t (xt.as<file> ());
const path& tp (t.path ());
- match_data md (move (t.data<match_data> ()));
unit_type ut (md.type);
context& ctx (t.ctx);
diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx
index 16b33fa..49d33eb 100644
--- a/libbuild2/cc/compile-rule.hxx
+++ b/libbuild2/cc/compile-rule.hxx
@@ -44,6 +44,8 @@ namespace build2
dyndep_rule
{
public:
+ struct match_data;
+
compile_rule (data&&, const scope&);
virtual bool
@@ -53,7 +55,7 @@ namespace build2
apply (action, target&) const override;
target_state
- perform_update (action, const target&) const;
+ perform_update (action, const target&, match_data&) const;
target_state
perform_clean (action, const target&) const;
@@ -74,7 +76,6 @@ namespace build2
functions (function_family&, const char*); // functions.cxx
private:
- struct match_data;
using environment = small_vector<const char*, 2>;
template <typename T>
diff --git a/libbuild2/cc/install-rule.cxx b/libbuild2/cc/install-rule.cxx
index d83bf70..0b4d1e1 100644
--- a/libbuild2/cc/install-rule.cxx
+++ b/libbuild2/cc/install-rule.cxx
@@ -160,6 +160,20 @@ namespace build2
file_rule::match (a, t);
}
+ // Wrap the file_rule's recipe into a data-carrying recipe.
+ //
+ struct install_match_data
+ {
+ build2::recipe recipe;
+ link_rule::libs_paths libs_paths;
+
+ target_state
+ operator() (action a, const target& t)
+ {
+ return recipe (a, t);
+ }
+ };
+
recipe install_rule::
apply (action a, target& t) const
{
@@ -173,7 +187,7 @@ namespace build2
// Signal to the link rule that this is update for install. And if the
// update has already been executed, verify it was done for install.
//
- auto& md (t.data<link_rule::match_data> ());
+ auto& md (t.data<link_rule::match_data> (a.inner_action ()));
if (md.for_install)
{
@@ -198,10 +212,12 @@ namespace build2
{
const string* p (cast_null<string> (t["bin.lib.prefix"]));
const string* s (cast_null<string> (t["bin.lib.suffix"]));
- t.data (
+
+ return install_match_data {
+ move (r),
link_.derive_libs_paths (*f,
p != nullptr ? p->c_str (): nullptr,
- s != nullptr ? s->c_str (): nullptr));
+ s != nullptr ? s->c_str (): nullptr)};
}
}
}
@@ -219,7 +235,7 @@ namespace build2
// Here we may have a bunch of symlinks that we need to install.
//
const scope& rs (t.root_scope ());
- auto& lp (t.data<link_rule::libs_paths> ());
+ auto& lp (t.data<install_match_data> (perform_install_id).libs_paths);
auto ln = [&rs, &id] (const path& f, const path& l)
{
@@ -253,7 +269,7 @@ namespace build2
// Here we may have a bunch of symlinks that we need to uninstall.
//
const scope& rs (t.root_scope ());
- auto& lp (t.data<link_rule::libs_paths> ());
+ auto& lp (t.data<install_match_data> (perform_uninstall_id).libs_paths);
auto rm = [&rs, &id] (const path& l)
{
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index f2e3775..de47822 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -875,8 +875,11 @@ namespace build2
// Note that for_install is signalled by install_rule and therefore
// can only be relied upon during execute.
//
- t.data (match_data ());
- match_data& md (t.data<match_data> ());
+ // Note that we don't really need to set it as target data: while there
+ // are calls to get it, they should only happen after the target has
+ // been matched.
+ //
+ match_data md (*this);
const scope& bs (t.base_scope ());
const scope& rs (*bs.root_scope ());
@@ -2008,14 +2011,11 @@ namespace build2
switch (a)
{
- case perform_update_id: return [this] (action a, const target& t)
- {
- return perform_update (a, t);
- };
- case perform_clean_id: return [this] (action a, const target& t)
- {
- return perform_clean (a, t);
- };
+ // Keep the recipe (which is match_data) after execution to allow the
+ // install rule to examine it.
+ //
+ case perform_update_id: t.keep_data (a); // Fall through.
+ case perform_clean_id: return move (md);
default: return noop_recipe; // Configure update.
}
}
@@ -2158,7 +2158,7 @@ namespace build2
*type != "cc" &&
type->compare (0, 3, "cc,") != 0)
{
- auto& md (l->data<link_rule::match_data> ());
+ auto& md (l->data<link_rule::match_data> (d.a));
assert (md.for_install); // Must have been executed.
// The user will get the target name from the context info.
@@ -2556,7 +2556,7 @@ namespace build2
msvc_machine (const string& cpu); // msvc.cxx
target_state link_rule::
- perform_update (action a, const target& xt) const
+ perform_update (action a, const target& xt, match_data& md) const
{
tracer trace (x, "link_rule::perform_update");
@@ -2568,8 +2568,6 @@ namespace build2
const scope& bs (t.base_scope ());
const scope& rs (*bs.root_scope ());
- match_data& md (t.data<match_data> ());
-
// Unless the outer install rule signalled that this is update for
// install, signal back that we've performed plain update.
//
@@ -4078,12 +4076,11 @@ namespace build2
}
target_state link_rule::
- perform_clean (action a, const target& xt) const
+ perform_clean (action a, const target& xt, match_data& md) const
{
const file& t (xt.as<file> ());
ltype lt (link_type (t));
- const match_data& md (t.data<match_data> ());
clean_extras extras;
clean_adhoc_extras adhoc_extras;
diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx
index f0052e9..14a70d2 100644
--- a/libbuild2/cc/link-rule.hxx
+++ b/libbuild2/cc/link-rule.hxx
@@ -23,6 +23,8 @@ namespace build2
public:
link_rule (data&&);
+ struct match_data;
+
struct match_result
{
bool seen_x = false;
@@ -52,10 +54,10 @@ namespace build2
apply (action, target&, match_extra&) const override;
target_state
- perform_update (action, const target&) const;
+ perform_update (action, const target&, match_data&) const;
target_state
- perform_clean (action, const target&) const;
+ perform_clean (action, const target&, match_data&) const;
public:
// Library handling.
@@ -226,9 +228,9 @@ namespace build2
static void
functions (function_family&, const char*); // functions.cxx
- private:
- friend class install_rule;
- friend class libux_install_rule;
+ // Implementation details.
+ //
+ public:
// Shared library paths.
//
@@ -271,6 +273,9 @@ namespace build2
struct match_data
{
+ explicit
+ match_data (const link_rule& r): rule (r) {}
+
// The "for install" condition is signalled to us by install_rule when
// it is matched for the update operation. It also verifies that if we
// have already been executed, then it was for install.
@@ -305,10 +310,21 @@ namespace build2
size_t start; // Parallel prerequisites/prerequisite_targets start.
link_rule::libs_paths libs_paths;
+
+ const link_rule& rule;
+
+ target_state
+ operator() (action a, const target& t)
+ {
+ return a == perform_update_id
+ ? rule.perform_update (a, t, *this)
+ : rule.perform_clean (a, t, *this);
+ }
};
// Windows rpath emulation (windows-rpath.cxx).
//
+ private:
struct windows_dll
{
reference_wrapper<const string> dll;
diff --git a/libbuild2/in/rule.hxx b/libbuild2/in/rule.hxx
index 98ab3b4..bd0d15a 100644
--- a/libbuild2/in/rule.hxx
+++ b/libbuild2/in/rule.hxx
@@ -18,8 +18,9 @@ namespace build2
{
// Preprocess an .in file.
//
- // Note that a derived rule can use the target data pad to cache data
- // (e.g., in match() or apply()) to be used in substitute/lookup() calls.
+ // Note that a derived rule can use the target auxiliary data storage to
+ // cache data (e.g., in match() or apply()) to be used in substitute() and
+ // lookup() calls.
//
// Note also that currently this rule ignores the dry-run mode (see
// perform_update() for the rationale).
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index 4f11b54..78bc5ac 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -79,7 +79,6 @@ namespace build2
target::
~target ()
{
- clear_data ();
}
const string& target::
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx
index e3a64ca..563c264 100644
--- a/libbuild2/target.hxx
+++ b/libbuild2/target.hxx
@@ -625,8 +625,13 @@ namespace build2
// Applied recipe.
//
- build2::recipe recipe;
- bool recipe_group_action; // Recipe is group_action.
+ // Note: also used as the auxiliary data storage during match, which is
+ // why mutable (see the target::data() API below for details). The
+ // default recipe_keep value is set by clear_target().
+ //
+ mutable build2::recipe recipe;
+ mutable bool recipe_keep; // Keep after execution.
+ bool recipe_group_action; // Recipe is group_action.
// Target state for this operation. Note that it is undetermined until
// a rule is matched and recipe applied (see set_recipe()).
@@ -640,8 +645,8 @@ namespace build2
// no iffy modifications of the group's variables by member's rules).
//
// They are also automatically cleared before another rule is matched,
- // similar to the data pad. In other words, rule-specific variables are
- // only valid for this match-execute phase.
+ // similar to the auxiliary data storage. In other words, rule-specific
+ // variables are only valid for this match-execute phase.
//
variable_map vars;
@@ -785,30 +790,63 @@ namespace build2
//
mutable action_state<build2::prerequisite_targets> prerequisite_targets;
- // Auxilary data storage.
+ // Auxiliary data storage.
//
// A rule that matches (i.e., returns true from its match() function) may
- // use this pad to pass data between its match and apply functions as well
- // as the recipe. After the recipe is executed, the data is destroyed. The
- // rule may static assert that the small size of the pad (which doesn't
- // require dynamic memory allocation) is sufficient for its needs.
- //
- // Note also that normally at least 2 extra pointers may be stored without
- // a dynamic allocation in the returned recipe (small object optimization
- // in std::function). So if you need to pass data only between apply() and
- // the recipe, then this might be a more convenient way. @@ TMP
- //
- // Note also that a rule that delegates to another rule may not be able to
- // use this mechanism fully since the delegated-to rule may also need the
- // data pad.
- //
- // The data is not destroyed until the next match, which is relied upon to
- // communicate between rules for inner/outer operations. @@ TMP
- //
- // Note that the recipe may modify the data. Currently reserved for the
- // inner part of the action. @@ TMP
- //
- // See also match_extra::buffer.
+ // use this facility to pass data between its match and apply functions as
+ // well as the recipe. Specifically, between match() and apply() the data
+ // is stored in the recipe member (which is std::move_only_function-like).
+ // If the data needs to be passed on to the recipe, then it must become
+ // the recipe itself. Here is a typical arrangement:
+ //
+ // class compile_rule
+ // {
+ // struct match_data
+ // {
+ // ... // Data.
+ //
+ // const compile_rule& rule;
+ //
+ // target_state
+ // operator() (action a, const target& t)
+ // {
+ // return rule.perform_update (a, t, this);
+ // }
+ // };
+ //
+ // virtual bool
+ // match (action a, const target& t)
+ // {
+ // ... // Determine if matching.
+ //
+ // t.data (a, match_data {..., *this});
+ // return true;
+ // }
+ //
+ // virtual bool
+ // apply (action a, target& t)
+ // {
+ // match_data& md (t.data (a));
+ //
+ // ... // Match prerequisites, etc.
+ //
+ // return move (md); // Data becomes the recipe.
+ // }
+ //
+ // target_state
+ // perform_update (action a, const target& t, match_data& md) const
+ // {
+ // ... // Access data (also available as t.data<match_data> (a)).
+ // }
+ // };
+ //
+ // After the recipe is executed, the recipe/data is destroyed, unless
+ // explicitly requested not to (see below). The rule may static assert
+ // that the small size of the storage (which doesn't require dynamic
+ // memory allocation) is sufficient for its needs.
+ //
+ // Note also that a rule that delegates to another rule may need to store
+ // the base rule's data/recipe in its own data/recipe.
// Provide the small object optimization size for the common compilers
// (see recipe.hxx for details) in case a rule wants to make sure its data
@@ -825,14 +863,10 @@ namespace build2
#elif defined(_MSC_VER)
sizeof (void*) * 6
#else
- // Assume at least 2 pointers.
- //
- sizeof (void*) * 2
+ sizeof (void*) * 2 // Assume at least 2 pointers.
#endif
;
- mutable recipe data_pad;
-
template <typename T>
struct data_wrapper
{
@@ -857,43 +891,60 @@ namespace build2
template <typename T>
typename std::enable_if<!data_invocable<T>::value, void>::type
- data (T&& d) const
+ data (action a, T&& d) const
{
using V = typename std::remove_cv<
typename std::remove_reference<T>::type>::type;
- data_pad = data_wrapper<V> {forward<T> (d)};
+ const opstate& s (state[a]);
+ s.recipe = data_wrapper<V> {forward<T> (d)};
+ s.recipe_keep = false; // Can't keep non-recipe data.
}
template <typename T>
typename std::enable_if<!data_invocable<T>::value, T&>::type&
- data () const
+ data (action a) const
{
using V = typename std::remove_cv<T>::type;
- return data_pad.target<data_wrapper<V>> ()->d;
+ return state[a].recipe.target<data_wrapper<V>> ()->d;
}
// Note that in this case we don't strip const (the expectation is that we
// move the recipe in/out of data).
//
+ // If keep is true, then keep the recipe as data after execution. In
+ // particular, this can be used to communicate between inner/outer rules
+ // (see cc::install_rule for an example).
+ //
+ //
template <typename T>
typename std::enable_if<data_invocable<T>::value, void>::type
- data (T&& d) const
+ data (action a, T&& d, bool keep = false) const
+ {
+ const opstate& s (state[a]);
+ s.recipe = forward<T> (d);
+ s.recipe_keep = keep;
+ }
+
+ void
+ keep_data (action a, bool keep = true) const
{
- data_pad = forward<T> (d);
+ state[a].recipe_keep = keep;
}
template <typename T>
typename std::enable_if<data_invocable<T>::value, T&>::type&
- data () const
+ data (action a) const
{
- return *data_pad.target<T> ();
+ return *state[a].recipe.target<T> ();
}
void
- clear_data () const
+ clear_data (action a) const
{
- data_pad = nullptr;
+ const opstate& s (state[a]);
+ s.recipe = nullptr;
+ s.recipe_keep = false;
}
// Target type info and casting.
diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx
index ad26da4..1799666 100644
--- a/libbuild2/version/rule.cxx
+++ b/libbuild2/version/rule.cxx
@@ -46,6 +46,25 @@ namespace build2
// in_rule
//
+
+ // Wrap the in::rule's perform_update recipe into a data-carrying recipe.
+ //
+ // To optimize this a bit further (i.e., to avoid the dynamic memory
+ // allocation) we are going to call in::rule::perform_update() directly
+ // (after all it's virtual and thus part of the in_rule's interface).
+ //
+ struct match_data
+ {
+ const module& mod;
+ const in_rule& rule;
+
+ target_state
+ operator() (action a, const target& t)
+ {
+ return rule.perform_update (a, t);
+ }
+ };
+
bool in_rule::
match (action a, target& xt) const
{
@@ -74,14 +93,20 @@ namespace build2
if (!fi)
l5 ([&]{trace << "no in file prerequisite for target " << t;});
- bool r (fm && fi);
+ return fm && fi;
+ }
+
+ recipe in_rule::
+ apply (action a, target& t) const
+ {
+ recipe r (rule::apply (a, t));
- // If we match, lookup and cache the module for the update operation.
+ // Lookup and cache the module for the update operation.
//
- if (r && a == perform_update_id)
- t.data (rs.find_module<module> (module::name));
-
- return r;
+ return a == perform_update_id
+ ? match_data {*t.root_scope ().find_module<module> (module::name),
+ *this}
+ : move (r);
}
string in_rule::
@@ -97,7 +122,7 @@ namespace build2
// Note that this code will be executed during up-to-date check for each
// substitution so let's try not to do anything overly sub-optimal here.
//
- const module& m (*t.data<const module*> ());
+ const module& m (t.data<match_data> (a).mod);
// Split it into the package name and the variable/condition name.
//
diff --git a/libbuild2/version/rule.hxx b/libbuild2/version/rule.hxx
index 55b4aee..ba673e5 100644
--- a/libbuild2/version/rule.hxx
+++ b/libbuild2/version/rule.hxx
@@ -25,6 +25,9 @@ namespace build2
virtual bool
match (action, target&) const override;
+ virtual recipe
+ apply (action, target&) const override;
+
virtual string
lookup (const location&,
action,
diff --git a/tests/recipe/cxx/testscript b/tests/recipe/cxx/testscript
index c94148e..214468f 100644
--- a/tests/recipe/cxx/testscript
+++ b/tests/recipe/cxx/testscript
@@ -180,9 +180,9 @@ if (!$static && $test.target == $build.host)
--
recipe
- apply (action, target& t) const override
+ apply (action a, target& t) const override
{
- const auto& mrs (t.data<regex_match_results> ());
+ const auto& mrs (t.data<regex_match_results> (a));
return [this, mr = mrs.str (1)] (action a, const target& t)
{