From 934f2a9a90c5cad3cdc8a66b50c17827a3ddbcee Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 20 Jan 2018 13:46:11 +0200 Subject: Get rid of action rule override semantics Instead we now have two more or less separate match states for outer and inner parts of an action. --- build2/install/init.cxx | 2 +- build2/install/rule.cxx | 257 ++++++++++++++++++++++++++++-------------------- build2/install/rule.hxx | 73 ++++++++------ 3 files changed, 198 insertions(+), 134 deletions(-) (limited to 'build2/install') diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 0eb9521..a11f3e5 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -217,7 +217,7 @@ namespace build2 bs.rules.insert (perform_uninstall_id, "uninstall.alias", ar); bs.rules.insert (perform_install_id, "install.file", fr); - bs.rules.insert (perform_uninstall_id, "uinstall.file", fr); + bs.rules.insert (perform_uninstall_id, "uninstall.file", fr); } // Configuration. diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 79287f8..4d4cb51 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -40,9 +40,15 @@ namespace build2 // const alias_rule alias_rule::instance; - match_result alias_rule:: + bool alias_rule:: match (action, target&, const string&) const { + // We always match. + // + // Note that we are called both as the outer part during the "update for + // un/install" pre-operation and as the inner part during the + // un/install operation itself. + // return true; } @@ -57,78 +63,134 @@ namespace build2 { tracer trace ("install::alias_rule::apply"); + // Pass-through to our installable prerequisites. + // + // @@ Shouldn't we do match in parallel (here and below)? + // + auto& pts (t.prerequisite_targets[a]); for (prerequisite_member p: group_prerequisite_members (a, t)) { + // Ignore unresolved targets that are imported from other projects. + // We are definitely not installing those. + // + if (p.proj ()) + continue; + // Let a customized rule have its say. // const target* pt (filter (a, t, p)); if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); continue; + } - // Check if this prerequisite is explicitly "not installable", - // that is, there is the 'install' variable and its value is - // false. + // Check if this prerequisite is explicitly "not installable", that + // is, there is the 'install' variable and its value is false. // - // At first, this might seem redundand since we could have let - // the file_rule below take care of it. The nuance is this: this - // prerequsite can be in a different subproject that hasn't loaded - // the install module (and therefore has no file_rule registered). - // The typical example would be the 'tests' subproject. + // At first, this might seem redundand since we could have let the + // file_rule below take care of it. The nuance is this: this + // prerequsite can be in a different subproject that hasn't loaded the + // install module (and therefore has no file_rule registered). The + // typical example would be the 'tests' subproject. // // Note: not the same as lookup() above. // auto l ((*pt)["install"]); if (l && cast (l).string () == "false") { - l5 ([&]{trace << "ignoring " << *pt;}); + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); continue; } build2::match (a, *pt); - t.prerequisite_targets.push_back (pt); + pts.push_back (pt); } return default_recipe; } - // file_rule + // group_rule // - const file_rule file_rule::instance; + const group_rule group_rule::instance; - struct match_data + const target* group_rule:: + filter (action, const target&, const target& m) const { - bool install; - }; - - static_assert (sizeof (match_data) <= target::data_size, - "insufficient space"); + return &m; + } - match_result file_rule:: - match (action a, target& t, const string&) const + recipe group_rule:: + apply (action a, target& t) const { - // First determine if this target should be installed (called - // "installable" for short). + tracer trace ("install::group_rule::apply"); + + // Resolve group members. + // + // Remember that we are called twice: first during update for install + // (pre-operation) and then during install. During the former, we rely + // on the normall update rule to resolve the group members. During the + // latter, there will be no rule to do this but the group will already + // have been resolved by the pre-operation. // - match_data md {lookup_install (t, "install") != nullptr}; - match_result mr (true); + // If the rule could not resolve the group, then we ignore it. + // + group_view gv (a.outer () + ? resolve_group_members (a, t) + : t.group_members (a)); - if (a.operation () == update_id) + if (gv.members != nullptr) { - // If this is the update pre-operation and the target is installable, - // change the recipe action to (update, 0) (i.e., "unconditional - // update") so that we don't get matched for its prerequisites. - // - if (md.install) - mr.recipe_action = action (a.meta_operation (), update_id); - else - // Otherwise, signal that we don't match so that some other rule can - // take care of it. + auto& pts (t.prerequisite_targets[a]); + for (size_t i (0); i != gv.count; ++i) + { + const target* m (gv.members[i]); + + if (m == nullptr) + continue; + + // Let a customized rule have its say. // - return false; + const target* mt (filter (a, t, *m)); + if (mt == nullptr) + { + l5 ([&]{trace << "ignoring " << *m << " (filtered out)";}); + continue; + } + + // See if we were explicitly instructed not to touch this target. + // + // Note: not the same as lookup() above. + // + auto l ((*mt)["install"]); + if (l && cast (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); + continue; + } + + build2::match (a, *mt); + pts.push_back (mt); + } } - t.data (md); // Save the data in the target's auxilary storage. - return mr; + // Delegate to the base rule. + // + return alias_rule::apply (a, t); + } + + + // file_rule + // + const file_rule file_rule::instance; + + bool file_rule:: + match (action, target&, const string&) const + { + // We always match, even if this target is not installable (so that we + // can ignore it; see apply()). + // + return true; } const target* file_rule:: @@ -141,28 +203,27 @@ namespace build2 recipe file_rule:: apply (action a, target& t) const { - match_data md (move (t.data ())); - t.clear_data (); // In case delegated-to rule (or the rule that overrides - // us; see cc/install) also uses aux storage. + tracer trace ("install::file_rule::apply"); - if (!md.install) // Not installable. - return noop_recipe; - - // Ok, if we are here, then this means: + // Note that we are called both as the outer part during the "update for + // un/install" pre-operation and as the inner part during the + // un/install operation itself. // - // 1. This target is installable. - // 2. The action is either - // a. (perform, [un]install, 0) or - // b. (*, update, [un]install) + // In both cases we first determine if the target is installable and + // return noop if it's not. Otherwise, in the first case (update for + // un/install) we delegate to the normal update and in the second + // (un/install) -- perform the test. // + if (lookup_install (t, "install") == nullptr) + return noop_recipe; + // In both cases, the next step is to search, match, and collect all the // installable prerequisites. // - // @@ Perhaps if [noinstall] will be handled by the - // group_prerequisite_members machinery, then we can just - // run standard search_and_match()? Will need an indicator - // that it was forced (e.g., [install]) for filter() below. + // @@ Unconditional group? How does it work for cli? Change to maybe + // same like test? If so, also in alias_rule. // + auto& pts (t.prerequisite_targets[a]); for (prerequisite_member p: group_prerequisite_members (a, t)) { // Ignore unresolved targets that are imported from other projects. @@ -175,84 +236,70 @@ namespace build2 // const target* pt (filter (a, t, p)); if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); continue; + } // See if we were explicitly instructed not to touch this target. // + // Note: not the same as lookup() above. + // auto l ((*pt)["install"]); if (l && cast (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); continue; + } - // If the matched rule returned noop_recipe, then the target - // state will be set to unchanged as an optimization. Use this - // knowledge to optimize things on our side as well since this - // will help a lot in case of any static installable content - // (headers, documentation, etc). + // If the matched rule returned noop_recipe, then the target state is + // set to unchanged as an optimization. Use this knowledge to optimize + // things on our side as well since this will help a lot when updating + // static installable content (headers, documentation, etc). // if (!build2::match (a, *pt, unmatch::unchanged)) - t.prerequisite_targets.push_back (pt); + pts.push_back (pt); } - // This is where we diverge depending on the operation. In the - // update pre-operation, we need to make sure that this target - // as well as all its installable prerequisites are up to date. - // if (a.operation () == update_id) { - // Save the prerequisite targets that we found since the - // call to match_delegate() below will wipe them out. - // - prerequisite_targets p; - - if (!t.prerequisite_targets.empty ()) - p.swap (t.prerequisite_targets); - - // Find the "real" update rule, that is, the rule that would - // have been found if we signalled that we do not match from - // match() above. - // - recipe d (match_delegate (a, t, *this)); - - // If we have no installable prerequisites, then simply redirect - // to it. + // For the update pre-operation match the inner rule (actual update). // - if (p.empty ()) - return d; - - // Ok, the worst case scenario: we need to cause update of - // prerequisite targets and also delegate to the real update. - // - return [pt = move (p), dr = move (d)] ( - action a, const target& t) mutable -> target_state + if (match_inner (a, t, unmatch::unchanged)) { - // Do the target update first. - // - target_state r (execute_delegate (dr, a, t)); - - // Swap our prerequisite targets back in and execute. - // - t.prerequisite_targets.swap (pt); - r |= straight_execute_prerequisites (a, t); - pt.swap (t.prerequisite_targets); // In case we get re-executed. + return pts.empty () ? noop_recipe : default_recipe; + } - return r; - }; + return &perform_update; } - else if (a.operation () == install_id) - return [this] (action a, const target& t) - { - return perform_install (a, t); - }; else + { return [this] (action a, const target& t) { - return perform_uninstall (a, t); + return a.operation () == install_id + ? perform_install (a, t) + : perform_uninstall (a, t); }; + } } - void file_rule:: + target_state file_rule:: + perform_update (action a, const target& t) + { + // First execute the inner recipe then prerequisites. + // + target_state ts (execute_inner (a, t)); + + if (t.prerequisite_targets[a].size () != 0) + ts |= straight_execute_prerequisites (a, t); + + return ts; + } + + bool file_rule:: install_extra (const file&, const install_dir&) const { + return false; } bool file_rule:: diff --git a/build2/install/rule.hxx b/build2/install/rule.hxx index 642ab96..ffab206 100644 --- a/build2/install/rule.hxx +++ b/build2/install/rule.hxx @@ -19,21 +19,39 @@ namespace build2 class alias_rule: public rule { public: - static const alias_rule instance; - - alias_rule () {} - - virtual match_result + virtual bool match (action, target&, const string&) const override; - virtual recipe - apply (action, target&) const override; - // Return NULL if this prerequisite should be ignored and pointer to its // target otherwise. The default implementation accepts all prerequsites. // virtual const target* filter (action, const target&, prerequisite_member) const; + + virtual recipe + apply (action, target&) const override; + + alias_rule () {} + static const alias_rule instance; + }; + + // In addition to the alias rule's semantics, this rule sees through to + // the group's members. + // + class group_rule: public alias_rule + { + public: + // Return NULL if this group member should be ignored and pointer to its + // target otherwise. The default implementation accepts all members. + // + virtual const target* + filter (action, const target&, const target& group_member) const; + + virtual recipe + apply (action, target&) const override; + + group_rule () {} + static const group_rule instance; }; struct install_dir; @@ -41,41 +59,38 @@ namespace build2 class file_rule: public rule { public: - static const file_rule instance; - - file_rule () {} - - virtual match_result + virtual bool match (action, target&, const string&) const override; - virtual recipe - apply (action, target&) const override; - // Return NULL if this prerequisite should be ignored and pointer to its - // target otherwise. The default implementation ignores prerequsites that - // are outside of this target's project. + // target otherwise. The default implementation ignores prerequsites + // that are outside of this target's project. // virtual const target* filter (action, const target&, prerequisite_member) const; - // Extra installation hooks. + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + // Extra un/installation hooks. Return true if anything was + // un/installed. // - using install_dir = install::install_dir; + using install_dir = install::install_dir; // For derived rules. - virtual void + virtual bool install_extra (const file&, const install_dir&) const; - // Return true if anything was uninstalled. - // virtual bool uninstall_extra (const file&, const install_dir&) const; - // Installation "commands". + // Installation/uninstallation "commands". // // If verbose is false, then only print the command at verbosity level 2 // or higher. - // - public: + // Install a symlink: base/link -> target. // static void @@ -96,16 +111,18 @@ namespace build2 static bool uninstall_f (const scope& rs, const install_dir& base, - const file* t, + const file* target, const path& name, bool verbose); - protected: target_state perform_install (action, const target&) const; target_state perform_uninstall (action, const target&) const; + + static const file_rule instance; + file_rule () {} }; } } -- cgit v1.1