diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2022-04-19 11:10:53 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2022-04-19 11:10:53 +0200 |
commit | 9034f7c51ef6437ce9d4547ba5bde217b4740fb2 (patch) | |
tree | bfaa297fda78b6cafe4487e2281420a4cf77c81a | |
parent | 457f65414031f45174f3c35230a0c0e1de88b51a (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.hxx | 13 | ||||
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.cxx | 22 | ||||
-rw-r--r-- | libbuild2/adhoc-rule-regex-pattern.cxx | 8 | ||||
-rw-r--r-- | libbuild2/algorithm.cxx | 9 | ||||
-rw-r--r-- | libbuild2/algorithm.hxx | 5 | ||||
-rw-r--r-- | libbuild2/algorithm.ixx | 3 | ||||
-rw-r--r-- | libbuild2/bash/rule.cxx | 60 | ||||
-rw-r--r-- | libbuild2/bash/rule.hxx | 3 | ||||
-rw-r--r-- | libbuild2/cc/compile-rule.cxx | 41 | ||||
-rw-r--r-- | libbuild2/cc/compile-rule.hxx | 5 | ||||
-rw-r--r-- | libbuild2/cc/install-rule.cxx | 26 | ||||
-rw-r--r-- | libbuild2/cc/link-rule.cxx | 29 | ||||
-rw-r--r-- | libbuild2/cc/link-rule.hxx | 26 | ||||
-rw-r--r-- | libbuild2/in/rule.hxx | 5 | ||||
-rw-r--r-- | libbuild2/target.cxx | 1 | ||||
-rw-r--r-- | libbuild2/target.hxx | 133 | ||||
-rw-r--r-- | libbuild2/version/rule.cxx | 39 | ||||
-rw-r--r-- | libbuild2/version/rule.hxx | 3 | ||||
-rw-r--r-- | tests/recipe/cxx/testscript | 4 |
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) { |