From ae9baf01f2a9627b7f1f2dc9db349d89c992f740 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 1 Nov 2023 09:42:57 +0200 Subject: Add support for adjusting match options of post hoc prerequisites --- libbuild2/algorithm.cxx | 79 ++++++++++++++++++++++++++++++++++++++------ libbuild2/context.hxx | 8 ++++- libbuild2/operation.cxx | 88 ++++++++++++++++++++++++++++++------------------- libbuild2/rule.cxx | 5 +++ libbuild2/rule.hxx | 6 ++++ libbuild2/target.hxx | 12 ++++++- libbuild2/target.ixx | 1 + 7 files changed, 153 insertions(+), 46 deletions(-) diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index bc4b835..4ec4db5 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -966,6 +966,37 @@ namespace build2 } static void + apply_posthoc_impl ( + action a, target& t, + const pair>& m, + context::posthoc_target& pt) + { + const scope& bs (t.base_scope ()); + + // Apply rules in project environment. + // + auto_project_env penv; + if (const scope* rs = bs.root_scope ()) + penv = auto_project_env (*rs); + + const rule& ru (m.second); + match_extra& me (t[a].match_extra); + me.posthoc_prerequisite_targets = &pt.prerequisite_targets; + + auto df = make_diag_frame ( + [a, &t, &m](const diag_record& dr) + { + if (verb != 0) + dr << info << "while applying rule " << m.first << " to " + << diag_do (a, t) << " for post hoc prerequisites"; + }); + + // Note: for now no adhoc_apply_posthoc(). + // + ru.apply_posthoc (a, t, me); + } + + static void reapply_impl (action a, target& t, const pair>& m) @@ -980,6 +1011,7 @@ namespace build2 const rule& ru (m.second); match_extra& me (t[a].match_extra); + // Note: me.posthoc_prerequisite_targets carried over. auto df = make_diag_frame ( [a, &t, &m](const diag_record& dr) @@ -994,11 +1026,15 @@ namespace build2 ru.reapply (a, t, me); } - // If anything goes wrong, set target state to failed and return false. + // If anything goes wrong, set target state to failed and return nullopt. + // Otherwise return the pointer to the new posthoc_target entry if any post + // hoc prerequisites were present or NULL otherwise. Note that the returned + // entry is stable (because we use a list) and should only be accessed + // during the match phase if the holding the target lock. // // Note: must be called while holding target_lock. // - static bool + static optional match_posthoc (action a, target& t) { // The plan is to, while holding the lock, search and collect all the post @@ -1024,11 +1060,18 @@ namespace build2 // In the end, matching (and execution) "inline" (i.e., as we match/ // execute the corresponding target) appears to be unworkable in the // face of cycles. - + // + // Note also that this delayed match also helps with allowing the rule to + // adjust match options of post hoc prerequisites without needing the + // rematch support (see match_extra::posthoc_prerequisites). + // // @@ Anything we need to do for group members (see through)? Feels quite // far-fetched. // - vector pts; + using posthoc_target = context::posthoc_target; + using posthoc_prerequisite_target = posthoc_target::prerequisite_target; + + vector pts; try { for (const prerequisite& p: group_prerequisites (t)) @@ -1053,14 +1096,17 @@ namespace build2 } } - pts.push_back (&search (t, p)); // May fail. + pts.push_back ( + posthoc_prerequisite_target { + &search (t, p), // May fail. + match_extra::all_options}); } } } catch (const failed&) { t.state[a].state = target_state::failed; - return false; + return nullopt; } if (!pts.empty ()) @@ -1068,11 +1114,11 @@ namespace build2 context& ctx (t.ctx); mlock l (ctx.current_posthoc_targets_mutex); - ctx.current_posthoc_targets.push_back ( - context::posthoc_target {a, t, move (pts)}); + ctx.current_posthoc_targets.push_back (posthoc_target {a, t, move (pts)}); + return &ctx.current_posthoc_targets.back (); // Stable. } - return true; + return nullptr; } // If step is true then perform only one step of the match/apply sequence. @@ -1230,7 +1276,20 @@ namespace build2 if (t.has_group_prerequisites ()) // Ok since already matched. { - if (!match_posthoc (a, t)) + if (optional p = match_posthoc (a, t)) + { + if (*p != nullptr) + { + // It would have been more elegant to do this before calling + // apply_impl() and then expose the post hoc prerequisites to + // apply(). The problem is the group may not be resolved until + // the call to apply(). And so we resort to the separate + // apply_posthoc() function. + // + apply_posthoc_impl (a, t, *s.rule, **p); + } + } + else s.state = target_state::failed; } diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index 8898c92..2dec54a 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -432,9 +432,15 @@ namespace build2 // struct posthoc_target { + struct prerequisite_target + { + const build2::target* target; + uint64_t match_options; + }; + build2::action action; reference_wrapper target; - vector prerequisite_targets; + vector prerequisite_targets; }; list current_posthoc_targets; diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx index 7b6dc3c..6f88e38 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -360,11 +360,14 @@ namespace build2 bool posthoc_fail (false); if (!ctx.current_posthoc_targets.empty () && (!fail || ctx.keep_going)) { + using posthoc_target = context::posthoc_target; + using posthoc_prerequisite_target = posthoc_target::prerequisite_target; + // Note that on each iteration we may end up with new entries at the // back. Since we start and end each iteration in serial execution, we // don't need to mess with the mutex. // - for (const context::posthoc_target& p: ctx.current_posthoc_targets) + for (const posthoc_target& p: ctx.current_posthoc_targets) { action a (p.action); // May not be the same as argument action. const target& t (p.target); @@ -383,18 +386,21 @@ namespace build2 // // @@ PERF: match in parallel (need match_direct_async(), etc). // - for (const target* pt: p.prerequisite_targets) + for (const posthoc_prerequisite_target& pt: p.prerequisite_targets) { - target_state s (match_direct_sync (a, *pt, - match_extra::all_options, - false /* fail */)); - - if (s == target_state::failed) + if (pt.target != nullptr) { - posthoc_fail = true; + target_state s (match_direct_sync (a, *pt.target, + pt.match_options, + false /* fail */)); - if (!ctx.keep_going) - break; + if (s == target_state::failed) + { + posthoc_fail = true; + + if (!ctx.keep_going) + break; + } } } @@ -495,7 +501,10 @@ namespace build2 bool posthoc_fail (false); auto execute_posthoc = [&ctx, &posthoc_fail] () { - for (const context::posthoc_target& p: ctx.current_posthoc_targets) + using posthoc_target = context::posthoc_target; + using posthoc_prerequisite_target = posthoc_target::prerequisite_target; + + for (const posthoc_target& p: ctx.current_posthoc_targets) { action a (p.action); // May not be the same as argument action. const target& t (p.target); @@ -509,16 +518,20 @@ namespace build2 }); #if 0 - for (const target* pt: p.prerequisite_targets) + for (const posthoc_prerequisite_target& pt: p.prerequisite_targets) { - target_state s (execute_direct_sync (a, *pt, false /* fail */)); - - if (s == target_state::failed) + if (pt.target != nullptr) { - posthoc_fail = true; + target_state s ( + execute_direct_sync (a, *pt.target, false /* fail */)); - if (!ctx.keep_going) - break; + if (s == target_state::failed) + { + posthoc_fail = true; + + if (!ctx.keep_going) + break; + } } } #else @@ -528,16 +541,20 @@ namespace build2 atomic_count tc (0); wait_guard wg (ctx, tc); - for (const target* pt: p.prerequisite_targets) + for (const posthoc_prerequisite_target& pt: p.prerequisite_targets) { - target_state s (execute_direct_async (a, *pt, 0, tc, false /*fail*/)); - - if (s == target_state::failed) + if (pt.target != nullptr) { - posthoc_fail = true; + target_state s ( + execute_direct_async (a, *pt.target, 0, tc, false /*fail*/)); - if (!ctx.keep_going) - break; + if (s == target_state::failed) + { + posthoc_fail = true; + + if (!ctx.keep_going) + break; + } } } @@ -545,18 +562,21 @@ namespace build2 // Process the result. // - for (const target* pt: p.prerequisite_targets) + for (const posthoc_prerequisite_target& pt: p.prerequisite_targets) { - // Similar to below, no need to wait. - // - target_state s (pt->executed_state (a, false /* fail */)); - - if (s == target_state::failed) + if (pt.target != nullptr) { - // Note: no need to keep going. + // Similar to below, no need to wait. // - posthoc_fail = true; - break; + target_state s (pt.target->executed_state (a, false /* fail */)); + + if (s == target_state::failed) + { + // Note: no need to keep going. + // + posthoc_fail = true; + break; + } } } #endif diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index 04d6b38..a3e3268 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -23,6 +23,11 @@ namespace build2 } void rule:: + apply_posthoc (action, target&, match_extra&) const + { + } + + void rule:: reapply (action, target&, match_extra&) const { // Unless the rule overrode cur_options, this function should never get diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx index acd22fe..7e5ddb1 100644 --- a/libbuild2/rule.hxx +++ b/libbuild2/rule.hxx @@ -34,6 +34,9 @@ namespace build2 // implementations. It is also a way for us to later pass more information // without breaking source compatibility. // + // A rule may adjust post hoc prerequisites by overriding apply_posthoc(). + // See match_extra::posthoc_prerequisite_targets for background and details. + // // A rule may support match options and if such a rule is rematched with // different options, then reapply() is called. See // match_extra::{cur,new}_options for background and details. @@ -50,6 +53,9 @@ namespace build2 apply (action, target&, match_extra&) const = 0; virtual void + apply_posthoc (action, target&, match_extra&) const; + + virtual void reapply (action, target&, match_extra&) const; rule () = default; diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index f537d59..83e6994 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -269,6 +269,15 @@ namespace build2 static constexpr uint64_t all_options = ~uint64_t (0); + // The list of post hoc prerequisite targets for this target. Only not + // NULL in rule::apply_posthoc() and rule::reapply() functions and only if + // there are post hoc prerequisites. Primarily useful for adjusting match + // options for post hoc prerequisites (but can also be used to blank some + // of them out). + // + vector* + posthoc_prerequisite_targets; + // Auxiliary data storage. // // A rule (whether matches or not) may use this pad to pass data between @@ -340,7 +349,8 @@ namespace build2 explicit match_extra (bool l = true, bool f = false) : locked (l), fallback (f), - cur_options (all_options), new_options (0) {} + cur_options (all_options), new_options (0), + posthoc_prerequisite_targets (nullptr) {} void reinit (bool fallback); diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 03cf444..ad09cd4 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -142,6 +142,7 @@ namespace build2 fallback = f; cur_options = all_options; new_options = 0; + posthoc_prerequisite_targets = nullptr; } inline void match_extra:: -- cgit v1.1