From 634048a861658af2bc5c37507bf96116cf1968aa Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 24 Feb 2022 10:03:43 +0200 Subject: Add update operation-specific variable with unmatch|match additional values Note that the unmatch (match but do not update) and match (update during match) values are only supported by certain rules (and potentially only for certain prerequisite types). Additionally: - All operation-specific variables are now checked for false as an override for the prerequisite-specific include value. In particular, this can now be used to disable a prerequisite for update, for example: ./: exe{test}: update = false - The cc::link_rule now supports the update=match value for headers and ad hoc prerequisites. In particular, this can be used to make sure all the library headers are updated before matching any of its (or dependent's) object files. --- libbuild2/action.hxx | 4 ++ libbuild2/adhoc-rule-buildscript.cxx | 16 +++--- libbuild2/algorithm.cxx | 68 ++++++++++++++++++++++- libbuild2/algorithm.hxx | 14 +++++ libbuild2/bash/rule.cxx | 2 +- libbuild2/build/script/parser.cxx | 12 ++-- libbuild2/build/script/script.cxx | 2 +- libbuild2/cc/common.cxx | 2 +- libbuild2/cc/link-rule.cxx | 104 +++++++++++++++++++++++++++++++---- libbuild2/cc/windows-rpath.cxx | 4 +- libbuild2/context.cxx | 24 ++++++-- libbuild2/context.hxx | 36 +++++++++--- libbuild2/dist/operation.cxx | 7 ++- libbuild2/dyndep.cxx | 56 +------------------ libbuild2/dyndep.hxx | 9 ++- libbuild2/install/operation.cxx | 3 + libbuild2/install/rule.cxx | 36 ++++++++---- libbuild2/operation.cxx | 3 + libbuild2/operation.hxx | 15 +++-- libbuild2/target.cxx | 73 +++++++++++++++++++----- libbuild2/target.hxx | 31 +++++++---- libbuild2/target.ixx | 18 +++++- libbuild2/test/operation.cxx | 2 + libbuild2/variable.cxx | 2 +- 24 files changed, 395 insertions(+), 148 deletions(-) diff --git a/libbuild2/action.hxx b/libbuild2/action.hxx index e149574..dce3a1a 100644 --- a/libbuild2/action.hxx +++ b/libbuild2/action.hxx @@ -150,6 +150,7 @@ namespace build2 // Id constants for build-in and pre-defined meta/operations. // // Note: currently max 15 (see above). + // Note: update small_vector in meta_operations if adding more. // const meta_operation_id noop_id = 1; // nomop? const meta_operation_id perform_id = 2; @@ -164,6 +165,7 @@ namespace build2 // something here remember to update the man page. // // Note: currently max 15 (see above). + // Note: update small_vector in operations if adding more. // const operation_id default_id = 1; // Shall be first. const operation_id update_id = 2; // Shall be second. @@ -176,6 +178,8 @@ namespace build2 const operation_id uninstall_id = 7; const operation_id update_for_install_id = 8; // update(for install) alias. + // Commonly-used action ids. + // const action_id perform_update_id = (perform_id << 4) | update_id; const action_id perform_clean_id = (perform_id << 4) | clean_id; const action_id perform_test_id = (perform_id << 4) | test_id; diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx index 7174296..e9bd2c6 100644 --- a/libbuild2/adhoc-rule-buildscript.cxx +++ b/libbuild2/adhoc-rule-buildscript.cxx @@ -399,7 +399,7 @@ namespace build2 { // Note that fsdir{} injected above is adhoc. // - if (p.target != nullptr && p.adhoc) + if (p.target != nullptr && p.adhoc ()) { p.data = reinterpret_cast (p.target); p.target = nullptr; @@ -426,7 +426,7 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) : + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage); @@ -812,10 +812,10 @@ namespace build2 if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) : + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { - if (ft == pt && (p.adhoc || p.data == 1)) + if (ft == pt && (p.adhoc () || p.data == 1)) return; } } @@ -1036,7 +1036,7 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage); @@ -1251,7 +1251,7 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) : nullptr)) + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { target_state s (execute_async (a, *pt, busy, t[a].task_count)); assert (s != target_state::postponed); @@ -1265,7 +1265,7 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) : nullptr)) + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { ctx.sched.wait (exec, (*pt)[a].task_count, scheduler::work_none); @@ -1297,7 +1297,7 @@ namespace build2 // Blank out adhoc. // - if (p.adhoc) + if (p.adhoc ()) { p.data = reinterpret_cast (p.target); p.target = nullptr; diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 9b6fd4e..cbf0495 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -2057,6 +2057,68 @@ namespace build2 return t.executed_state (a); } + bool + update_during_match (tracer& trace, action a, const target& t, timestamp ts) + { + assert (a == perform_update_id); + + // Note: this function is used to make sure header dependencies are up to + // date (and which is where it originated). + // + // There would normally be a lot of headers for every source file (think + // all the system headers) and just calling execute_direct() on all of + // them can get expensive. At the same time, most of these headers are + // existing files that we will never be updating (again, system headers, + // for example) and the rule that will match them is the fallback + // file_rule. That rule has an optimization: it returns noop_recipe (which + // causes the target state to be automatically set to unchanged) if the + // file is known to be up to date. So we do the update "smartly". + // + const path_target* pt (t.is_a ()); + + if (pt == nullptr) + ts = timestamp_unknown; + + target_state os (t.matched_state (a)); + + if (os == target_state::unchanged) + { + if (ts == timestamp_unknown) + return false; + else + { + // We expect the timestamp to be known (i.e., existing file). + // + timestamp mt (pt->mtime ()); + assert (mt != timestamp_unknown); + return mt > ts; + } + } + else + { + // We only want to return true if our call to execute() actually caused + // an update. In particular, the target could already have been in + // target_state::changed because of the dynamic dependency extraction + // run for some other target. + // + // @@ MT perf: so we are going to switch the phase and execute for + // any generated header. + // + phase_switch ps (t.ctx, run_phase::execute); + target_state ns (execute_direct (a, t)); + + if (ns != os && ns != target_state::unchanged) + { + l6 ([&]{trace << "updated " << t + << "; old state " << os + << "; new state " << ns;}); + return true; + } + else + return ts != timestamp_unknown ? pt->newer (ts, ns) : false; + } + } + static inline void blank_adhoc_member (const target*&) { @@ -2065,7 +2127,7 @@ namespace build2 static inline void blank_adhoc_member (prerequisite_target& pt) { - if (pt.adhoc) + if (pt.adhoc ()) pt.target = nullptr; } @@ -2254,7 +2316,7 @@ namespace build2 // Should we compare the timestamp to this target's? // - if (!e && (p.adhoc || !ef || ef (pt, i))) + if (!e && (p.adhoc () || !ef || ef (pt, i))) { // If this is an mtime-based target, then compare timestamps. // @@ -2272,7 +2334,7 @@ namespace build2 } } - if (p.adhoc) + if (p.adhoc ()) p.target = nullptr; // Blank out. else { diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index 8a6eb65..c30680a 100644 --- a/libbuild2/algorithm.hxx +++ b/libbuild2/algorithm.hxx @@ -595,6 +595,20 @@ namespace build2 LIBBUILD2_SYMEXPORT target_state execute_direct (action, const target&); + // Update the target during the match phase (by switching the phase and + // calling execute_direct()). Return true if the target has changed or, if + // the passed timestamp is not timestamp_unknown, it is older than the + // target. + // + // Note that such a target must still be updated normally during the execute + // phase in order to keep the dependency counts straight (at which point the + // target state/timestamp will be re-incorporated into the result). + // + LIBBUILD2_SYMEXPORT bool + update_during_match (tracer&, + action, const target&, + timestamp = timestamp_unknown); + // The default prerequisite execute implementation. Call execute_async() on // each non-ignored (non-NULL) prerequisite target in a loop and then wait // for their completion. Return target_state::changed if any of them were diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx index 5ba298c..ec24226 100644 --- a/libbuild2/bash/rule.cxx +++ b/libbuild2/bash/rule.cxx @@ -267,7 +267,7 @@ namespace build2 const path* ap (nullptr); for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - if (pt.adhoc || pt.target == nullptr) + if (pt.target == nullptr || pt.adhoc ()) continue; if (const bash* b = pt.target->is_a ()) diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx index cba4b88..e9c8d9c 100644 --- a/libbuild2/build/script/parser.cxx +++ b/libbuild2/build/script/parser.cxx @@ -1622,12 +1622,12 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { // Apply the --update-* filter. // - if (!p.adhoc && !filters.empty ()) + if (!p.adhoc () && !filters.empty ()) { // Compute and cache "effective" name that we will be pattern- // matching (similar code to variable_type_map::find()). @@ -1695,7 +1695,7 @@ namespace build2 // Mark as updated (see execute_update_prerequisites() for // details. // - if (!p.adhoc) + if (!p.adhoc ()) p.data = 1; } } @@ -1923,10 +1923,10 @@ namespace build2 if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast (p.data) : + p.adhoc () ? reinterpret_cast (p.data) : nullptr)) { - if (ft == pt && (p.adhoc || p.data == 1)) + if (ft == pt && (p.adhoc () || p.data == 1)) return false; } } @@ -1968,7 +1968,7 @@ namespace build2 { prerequisite_target& pt (pts.back ()); - if (pt.adhoc) + if (pt.adhoc ()) { pt.data = reinterpret_cast (pt.target); pt.target = nullptr; diff --git a/libbuild2/build/script/script.cxx b/libbuild2/build/script/script.cxx index 480903e..b230ca5 100644 --- a/libbuild2/build/script/script.cxx +++ b/libbuild2/build/script/script.cxx @@ -78,7 +78,7 @@ namespace build2 { // See adhoc_buildscript_rule::execute_update_prerequisites(). // - if (pt.target != nullptr && !pt.adhoc) + if (pt.target != nullptr && !pt.adhoc ()) pt.target->as_name (ns); } diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx index ccb678c..97ac6b8 100644 --- a/libbuild2/cc/common.cxx +++ b/libbuild2/cc/common.cxx @@ -317,7 +317,7 @@ namespace build2 // Note: adhoc prerequisites are not part of the library metadata // protocol (and we should check for adhoc first to avoid races). // - if (pt.adhoc || pt == nullptr) + if (pt == nullptr || pt.adhoc ()) continue; if (marked (pt)) diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 28bd54f..c993df6 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -282,7 +282,11 @@ namespace build2 { // If excluded or ad hoc, then don't factor it into our tests. // - if (include (a, t, p) != include_type::normal) + // Note that here we don't validate the update operation override + // value (since we may not match). Instead we do this in apply(). + // + lookup l; + if (include (a, t, p, &l) != include_type::normal) continue; if (p.is_a (x_src) || @@ -914,26 +918,68 @@ namespace build2 return a.operation () == clean_id && !pt.dir.sub (rs.out_path ()); }; + bool update_match (false); // Have update during match. + auto& pts (t.prerequisite_targets[a]); size_t start (pts.size ()); for (prerequisite_member p: group_prerequisite_members (a, t)) { - include_type pi (include (a, t, p)); + // Note that we have to recognize update=match for *(update), not just + // perform(update). But only actually update for perform(update). + // + lookup l; // The `update` variable value, if any. + include_type pi ( + include (a, t, p, a.operation () == update_id ? &l : nullptr)); // We pre-allocate a NULL slot for each (potential; see clean) // prerequisite target. // pts.push_back (prerequisite_target (nullptr, pi)); - const target*& pt (pts.back ()); + auto& pto (pts.back ()); + + // Use bit 2 of prerequisite_target::include to signal update during + // match. + // + // Not that for now we only allow updating during match ad hoc and + // mark 3 (headers, etc; see below) prerequisites. + // + bool um (false); + + if (l) + { + const string& v (cast (l)); + + if (v == "match") + { + if (a == perform_update_id) + { + pto.include |= 2; + update_match = um = true; + } + } + else if (v != "false" && v != "true") + { + fail << "unrecognized update variable value '" << v + << "' specified for prerequisite " << p.prerequisite; + } + } - // Skip excluded and ad hoc on this pass. + // Skip excluded and ad hoc (unless updated during match) on this + // pass. // if (pi != include_type::normal) + { + if (pi == include_type::adhoc && um) + pto.target = &p.search (t); // mark 0 + continue; + } + + const target*& pt (pto); - // Mark: - // 0 - lib + // Mark (2 bits): + // 0 - lib or update during match // 1 - src // 2 - mod // 3 - obj/bmi and also lib not to be cleaned (and other stuff) @@ -1121,13 +1167,45 @@ namespace build2 if (user_binless && !binless) fail << t << " cannot be binless due to " << p << " prerequisite"; + // Upgrade update during match prerequisites to mark 0 (see above for + // details). + // + if (um) + { + if (m != 3) + fail << "unable to update during match prerequisite " << p << + info << "updating this type of prerequisites during match is " + << "not supported by this rule"; + + m = 0; + } + mark (pt, m); } - // Match lib{} (the only unmarked) in parallel and wait for completion. + // Match lib{} and update during match (the only unmarked) in parallel + // and wait for completion. // match_members (a, t, pts, start); + // If we have any update during match prerequisites, now is the time to + // update them. Note that we have to do it before any further matches + // since they may rely on these prerequisites already being updated (for + // example, object file matches may need the headers to be already + // updated). + // + // Note also that we ignore the result and whether it renders us out of + // date, leaving it to the common execute logic in perform_update(). + // + if (update_match) + { + for (prerequisite_target& pto: pts) + { + if ((pto.include & 2) != 0) + update_during_match (trace, a, *pto.target); + } + } + // Check if we have any binful utility libraries. // if (binless) @@ -1432,6 +1510,7 @@ namespace build2 continue; // New mark: + // 0 - already matched // 1 - completion // 2 - verification // @@ -1619,7 +1698,7 @@ namespace build2 m = 2; // Needs verification. } } - else // lib*{} + else // lib*{} or update during match { // If this is a static library, see if we need to link it whole. // Note that we have to do it after match since we rely on the @@ -1669,7 +1748,7 @@ namespace build2 i = start; for (prerequisite_member p: group_prerequisite_members (a, t)) { - bool adhoc (pts[i].adhoc); + bool adhoc (pts[i].adhoc ()); const target*& pt (pts[i++]); uint8_t m; @@ -1684,8 +1763,13 @@ namespace build2 pt = &p.search (t); m = 1; // Mark for completion. } - else if ((m = unmark (pt)) != 0) + else { + m = unmark (pt); + + if (m == 0) + continue; // Already matched. + // If this is a library not to be cleaned, we can finally blank it // out. // diff --git a/libbuild2/cc/windows-rpath.cxx b/libbuild2/cc/windows-rpath.cxx index 2d90ace..e205924 100644 --- a/libbuild2/cc/windows-rpath.cxx +++ b/libbuild2/cc/windows-rpath.cxx @@ -128,7 +128,7 @@ namespace build2 library_cache lib_cache; for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - if (pt.adhoc || pt == nullptr) + if (pt == nullptr || pt.adhoc ()) continue; bool la; @@ -253,7 +253,7 @@ namespace build2 library_cache lib_cache; for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - if (pt.adhoc || pt == nullptr) + if (pt == nullptr || pt.adhoc ()) continue; bool la; diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index d78efba..d0b24e0 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -586,9 +586,10 @@ namespace build2 var_export_metadata = &vp.insert ("export.metadata", v_t); // Untyped. var_extension = &vp.insert ("extension", v_t); - var_clean = &vp.insert ("clean", v_t); - var_backlink = &vp.insert ("backlink", v_t); - var_include = &vp.insert ("include", v_q); + var_update = &vp.insert ("update", v_q); + var_clean = &vp.insert ("clean", v_t); + var_backlink = &vp.insert ("backlink", v_t); + var_include = &vp.insert ("include", v_q); // Backlink executables and (generated) documentation by default. // @@ -696,13 +697,28 @@ namespace build2 const operation_info* outer_oif, bool diag_noise) { - current_oname = (outer_oif == nullptr ? inner_oif : *outer_oif).name; + const auto& oif (outer_oif == nullptr ? inner_oif : *outer_oif); + + current_oname = oif.name; current_inner_oif = &inner_oif; current_outer_oif = outer_oif; current_on++; current_mode = inner_oif.mode; current_diag_noise = diag_noise; + if (oif.var_name != nullptr) + { + current_ovar = var_pool.find (oif.var_name); + + // The operation variable should have prerequisite or target visibility. + // + assert (current_ovar != nullptr && + (current_ovar->visibility == variable_visibility::prereq || + current_ovar->visibility == variable_visibility::target)); + } + else + current_ovar = nullptr; + // Reset counters (serial execution). // dependency_count.store (0, memory_order_relaxed); diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index 3563c16..20098dc 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -283,6 +283,8 @@ namespace build2 const operation_info* current_inner_oif; const operation_info* current_outer_oif; + const variable* current_ovar; // Current (outer) operation variable. + action current_action () const { @@ -428,6 +430,19 @@ namespace build2 // const variable* var_extension; + // This variable can only be specified as prerequisite-specific (see the + // `include` variable for details). + // + // [string] prerequisite visibility + // + // Valid values are `true` and `false`. Additionally, some rules (and + // potentially only for certain types of prerequisites) may support the + // `unmatch` (match but do not update, if possible) and `match` (update + // during match) values. Note that if unmatch is impossible, then the + // prerequisite is treated as ad hoc. + // + const variable* var_update; + // Note that this variable can also be specified as prerequisite-specific // (see the `include` variable for details). // @@ -473,14 +488,19 @@ namespace build2 // Sometimes it may be desirable to apply exclusions only to specific // operations. The initial idea was to extend this value to allow // specifying the operation (e.g., clean@false). However, later we - // realized that we could reuse the "operation variables" (clean, install, - // test) with a more natural-looking result. Note that currently we only - // recognize the built-in clean variable (for other variables we will need - // some kind of registration in an operation-to-variable map, probably in - // root scope). See also install::file_rule::filter(). - // - // To query this value in rule implementations use the include() helpers - // from . + // realized that we could reuse the "operation-specific variables" + // (update, clean, install, test; see context::current_ovar) with a more + // natural-looking and composable result. Plus, this allows for + // operation-specific "modifiers", for example, "unmatch" and "update + // during match" logic for update (see var_update for details) or + // requiring explicit install=true to install exe{} prerequisites (see + // install::file_rule::filter()). + // + // To query this value and its operation-specific override if any, the + // rule implementations use the include() helper. + // + // Note that there are also related (but quite different) for_ + // variables for operations that act as outer (e.g., test, install). // // [string] prereq visibility // diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index f3db8ad..150a5a1 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -1001,7 +1001,8 @@ namespace build2 dist_include (action, const target&, const prerequisite_member& p, - include_type i) + include_type i, + lookup& l) { tracer trace ("dist::dist_include"); @@ -1016,6 +1017,10 @@ namespace build2 i = include_type::adhoc; } + // Also clear any operation-specific overrides. + // + l = lookup (); + return i; } diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx index f1fc5ec..47c6396 100644 --- a/libbuild2/dyndep.cxx +++ b/libbuild2/dyndep.cxx @@ -18,61 +18,7 @@ namespace build2 bool dyndep_rule:: update (tracer& trace, action a, const target& t, timestamp ts) { - // In particular, this function is used to make sure header dependencies - // are up to date. - // - // There would normally be a lot of headers for every source file (think - // all the system headers) and just calling execute_direct() on all of - // them can get expensive. At the same time, most of these headers are - // existing files that we will never be updating (again, system headers, - // for example) and the rule that will match them is the fallback - // file_rule. That rule has an optimization: it returns noop_recipe (which - // causes the target state to be automatically set to unchanged) if the - // file is known to be up to date. So we do the update "smartly". - // - const path_target* pt (t.is_a ()); - - if (pt == nullptr) - ts = timestamp_unknown; - - target_state os (t.matched_state (a)); - - if (os == target_state::unchanged) - { - if (ts == timestamp_unknown) - return false; - else - { - // We expect the timestamp to be known (i.e., existing file). - // - timestamp mt (pt->mtime ()); - assert (mt != timestamp_unknown); - return mt > ts; - } - } - else - { - // We only want to return true if our call to execute() actually caused - // an update. In particular, the target could already have been in - // target_state::changed because of the dynamic dependency extraction - // run for some other target. - // - // @@ MT perf: so we are going to switch the phase and execute for - // any generated header. - // - phase_switch ps (t.ctx, run_phase::execute); - target_state ns (execute_direct (a, t)); - - if (ns != os && ns != target_state::unchanged) - { - l6 ([&]{trace << "updated " << t - << "; old state " << os - << "; new state " << ns;}); - return true; - } - else - return ts != timestamp_unknown ? pt->newer (ts, ns) : false; - } + return update_during_match (trace, a, t, ts); } optional dyndep_rule:: diff --git a/libbuild2/dyndep.hxx b/libbuild2/dyndep.hxx index 257ef7b..6632eb6 100644 --- a/libbuild2/dyndep.hxx +++ b/libbuild2/dyndep.hxx @@ -22,9 +22,12 @@ namespace build2 class LIBBUILD2_SYMEXPORT dyndep_rule { public: - // Update the target during the match phase. Return true if it has changed - // or if the passed timestamp is not timestamp_unknown and is older than - // the target. + // Update the target during the match phase. Return true if the target has + // changed or, if the passed timestamp is not timestamp_unknown, it is + // older than the target. + // + // Note that such a target must still be updated during the execute phase + // in order to keep the dependency counts straight. // static bool update (tracer&, action, const target&, timestamp); diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx index 52e8c94..6ae2819 100644 --- a/libbuild2/install/operation.cxx +++ b/libbuild2/install/operation.cxx @@ -37,6 +37,7 @@ namespace build2 0, "install", "install", + "install", "installing", "installed", "has nothing to install", // We cannot "be installed". @@ -61,6 +62,7 @@ namespace build2 uninstall_id, 0, "uninstall", + "install", "uninstall", "uninstalling", "uninstalled", @@ -79,6 +81,7 @@ namespace build2 update_id, // Note: not update_for_install_id. install_id, op_update.name, + nullptr, // Outer operation variable is always used. op_update.name_do, op_update.name_doing, op_update.name_did, diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index b8d716d..db9c64a 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -74,6 +74,8 @@ namespace build2 { tracer trace ("install::alias_rule::apply"); + context& ctx (t.ctx); + // Pass-through to our installable prerequisites. // // @@ Shouldn't we do match in parallel (here and below)? @@ -125,7 +127,7 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*pt)["install"]); + auto l ((*pt)[ctx.current_ovar]); // "install" if (l && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); @@ -207,6 +209,8 @@ namespace build2 { tracer trace ("install::group_rule::apply"); + context& ctx (t.ctx); + // Resolve group members. // // Remember that we are called twice: first during update for install @@ -245,7 +249,7 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*mt)["install"]); + auto l ((*mt)[ctx.current_ovar]); // "install" if (l && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); @@ -290,11 +294,13 @@ namespace build2 { if (p.is_a ()) { - // Feels like one day this should be unified with include (see - // context::var_include). + // Note that while include() checks for install=false, here we need to + // check for explicit install=true. We could have re-used the lookup + // performed by include(), but then we would have had to drag it + // through and also diagnose any invalid values. // if (p.vars.empty () || - cast_empty (p.vars["install"]).string () != "true") + cast_empty (p.vars[t.ctx.current_ovar]).string () != "true") return nullptr; } @@ -314,6 +320,8 @@ namespace build2 { tracer trace ("install::file_rule::apply"); + context& ctx (t.ctx); + // 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. @@ -381,7 +389,7 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*pt)["install"]); + auto l ((*pt)[ctx.current_ovar]); // "install" if (l && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); @@ -936,6 +944,8 @@ namespace build2 target_state file_rule:: perform_install (action a, const target& xt) const { + context& ctx (xt.ctx); + const file& t (xt.as ()); const path& tp (t.path ()); @@ -1033,7 +1043,7 @@ namespace build2 // if (!tp.empty ()) { - install_target (t, cast (t["install"]), 1); + install_target (t, cast (t[ctx.current_ovar]), 1); // "install" r |= target_state::changed; } @@ -1160,6 +1170,8 @@ namespace build2 const path& name, uint16_t verbosity) { + context& ctx (rs.ctx); + assert (t != nullptr || !name.empty ()); path f (chroot_path (rs, base.dir) / (name.empty () ? t->path ().leaf () : name)); @@ -1196,7 +1208,7 @@ namespace build2 if (verb >= verbosity && verb >= 2) text << "rm " << relf; - if (!rs.ctx.dry_run) + if (!ctx.dry_run) { try { @@ -1225,7 +1237,7 @@ namespace build2 if (verb >= verbosity && verb >= 2) print_process (args); - if (!rs.ctx.dry_run) + if (!ctx.dry_run) run (pp, args); } @@ -1235,6 +1247,8 @@ namespace build2 target_state file_rule:: perform_uninstall (action a, const target& xt) const { + context& ctx (xt.ctx); + const file& t (xt.as ()); const path& tp (t.path ()); @@ -1298,7 +1312,9 @@ namespace build2 target_state r (target_state::unchanged); if (!tp.empty ()) - r |= uninstall_target (t, cast (t["install"]), 1); + r |= uninstall_target (t, + cast (t[ctx.current_ovar]), // "install" + 1); // Then installable ad hoc group members, if any. To be anally precise, // we would have to do it in reverse, but that's not easy (it's a diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx index 0f30c4a..68666cb 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -737,6 +737,7 @@ namespace build2 default_id, 0, "", + nullptr, "", "", "", @@ -764,6 +765,7 @@ namespace build2 0, "update", "update", + "update", "updating", "updated", "is up to date", @@ -780,6 +782,7 @@ namespace build2 0, "clean", "clean", + "clean", "cleaning", "cleaned", "is clean", diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx index 2f88e88..ce3cd79 100644 --- a/libbuild2/operation.hxx +++ b/libbuild2/operation.hxx @@ -125,12 +125,14 @@ namespace build2 void (*meta_operation_post) (context&, const values&); // Optional prerequisite exclusion override callback. See include() for - // details. Note that it's not called for include_type::normal; + // details. Note that it's not called for include_type::normal without + // operation-specific override. // include_type (*include) (action, const target&, const prerequisite_member&, - include_type); + include_type, + lookup&); }; // Built-in meta-operations. @@ -194,6 +196,7 @@ namespace build2 const operation_id id; const operation_id outer_id; const char* name; + const char* var_name; // Operation variable or NULL if not used. // Name derivatives for diagnostics. Note that unlike meta-operations, // these can only be empty for the default operation (id 1), And @@ -309,10 +312,10 @@ namespace build2 // are represented as NULL pointers. Also, lookup out of bounds // is treated as a hole. // - template + template struct sparse_vector { - using base_type = vector; + using base_type = small_vector; using size_type = typename base_type::size_type; void @@ -348,8 +351,8 @@ namespace build2 base_type v_; }; - using meta_operations = sparse_vector; - using operations = sparse_vector; + using meta_operations = sparse_vector; + using operations = sparse_vector; } namespace butl diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index bc5dbba..f829cca 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -541,40 +541,87 @@ namespace build2 // include() // + // See var_include documentation for details on what's going on here. + // include_type include_impl (action a, const target& t, const prerequisite& p, - const target* m) + const target* m, + lookup* rl) { context& ctx (t.ctx); include_type r (include_type::normal); - // If var_clean is defined, then it takes precedence over include for - // the clean operation. - // - lookup l; - if (a.operation () == clean_id && (l = p.vars[ctx.var_clean])) - { - r = cast (l) ? include_type::normal : include_type::excluded; - } - else if (const string* v = cast_null (p.vars[ctx.var_include])) + if (const string* v = cast_null (p.vars[ctx.var_include])) { if (*v == "false") r = include_type::excluded; else if (*v == "adhoc") r = include_type::adhoc; else if (*v == "true") r = include_type::normal; else - fail << "invalid " << ctx.var_include->name << " variable value " + fail << "invalid " << *ctx.var_include << " variable value " << "'" << *v << "' specified for prerequisite " << p; } + // Handle operation-specific override. + // + lookup l; + optional r1; // Absent means something other than true|false. + + names storage; + names_view ns; + + if (r != include_type::excluded && ctx.current_ovar != nullptr) + { + if ((l = p.vars[*ctx.current_ovar])) + { + // Maybe we should optimize this for the common cases (bool, path, + // name)? But then again we don't expect many such overrides. Plus + // will complicate the diagnostics below. + // + ns = reverse (*l, storage); + + if (ns.size () == 1) + { + const name& n (ns[0]); + + if (n.simple ()) + { + const string& v (n.value); + + if (v == "false") + r1 = false; + else if (v == "true") + r1 = true; + } + } + + if (r1 && !*r1) + r = include_type::excluded; + } + } + // Call the meta-operation override, if any (currently used by dist). // - if (r != include_type::normal) + if (r != include_type::normal || l) { if (auto f = ctx.current_mif->include) - r = f (a, t, prerequisite_member {p, m}, r); + r = f (a, t, prerequisite_member {p, m}, r, l); + } + + if (l) + { + if (rl != nullptr) + *rl = l; + else if (!r1) + { + // Note: we have to delay this until the meta-operation callback above + // had a chance to override it. + // + fail << "unrecognized " << *ctx.current_ovar << " variable value " + << "'" << ns << "' specified for prerequisite " << p; + } } return r; diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 5eed0a5..efc3291 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -70,15 +70,19 @@ namespace build2 }; // List of prerequisites resolved to targets. Unless additional storage is - // needed, it can be used as just vector (which is what we + // needed, it can be treated as just vector (which is what we // used to have initially). // + // The include member normally just indicates (in the first bit) whether + // this prerequisite is ad hoc. But it can also carry additional information + // (for example, from operation-specific override) in other bits. + // struct prerequisite_target { using target_type = build2::target; prerequisite_target (const target_type* t, bool a = false, uintptr_t d = 0) - : target (t), adhoc (a), data (d) {} + : target (t), include (a ? 1 : 0), data (d) {} prerequisite_target (const target_type* t, include_type a, uintptr_t d = 0) : prerequisite_target (t, a == include_type::adhoc, d) {} @@ -87,8 +91,10 @@ namespace build2 operator const target_type* () const {return target;} const target_type* operator-> () const {return target;} + bool adhoc () const {return (include & 1) != 0;} + const target_type* target; - bool adhoc; // True if include=adhoc. + uintptr_t include; // First bit is 1 if include=adhoc. uintptr_t data; }; using prerequisite_targets = vector; @@ -892,13 +898,15 @@ namespace build2 // Helper for dealing with the prerequisite inclusion/exclusion (see // var_include in context.hxx). // + // If the lookup argument is not NULL, then it will be set to the operation- + // specific override, if present. Note that in this case the caller is + // expected to validate that the override value is valid (note: use the same + // diagnostics as in include() for consistency). + // // Note that the include(prerequisite_member) overload is also provided. // include_type - include (action, - const target&, - const prerequisite&, - const target* = nullptr); + include (action, const target&, const prerequisite&, lookup* = nullptr); // A "range" that presents the prerequisites of a group and one of // its members as one continuous sequence, or, in other words, as @@ -1111,11 +1119,10 @@ namespace build2 return os << pm.key (); } - inline include_type - include (action a, const target& t, const prerequisite_member& pm) - { - return include (a, t, pm.prerequisite, pm.member); - } + include_type + include (action, const target&, + const prerequisite_member&, + lookup* = nullptr); // A "range" that presents a sequence of prerequisites (e.g., from // group_prerequisites()) as a sequence of prerequisite_member's. For each diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 05f9698..a5ad32b 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -338,15 +338,27 @@ namespace build2 // include() // LIBBUILD2_SYMEXPORT include_type - include_impl (action, const target&, const prerequisite&, const target*); + include_impl (action, const target&, + const prerequisite&, const target*, + lookup*); inline include_type - include (action a, const target& t, const prerequisite& p, const target* m) + include (action a, const target& t, const prerequisite& p, lookup* l) { // Most of the time no prerequisite-specific variables will be specified, // so let's optimize for that. // - return p.vars.empty () ? include_type (true) : include_impl (a, t, p, m); + return p.vars.empty () + ? include_type (true) + : include_impl (a, t, p, nullptr, l); + } + + inline include_type + include (action a, const target& t, const prerequisite_member& pm, lookup* l) + { + return pm.prerequisite.vars.empty () + ? include_type (true) + : include_impl (a, t, pm.prerequisite, pm.member, l); } // group_prerequisites diff --git a/libbuild2/test/operation.cxx b/libbuild2/test/operation.cxx index 841abb5..ff841a6 100644 --- a/libbuild2/test/operation.cxx +++ b/libbuild2/test/operation.cxx @@ -65,6 +65,7 @@ namespace build2 0, "test", "test", + "test", "testing", "tested", "has nothing to test", // We cannot "be tested". @@ -82,6 +83,7 @@ namespace build2 update_id, // Note: not update_for_test_id. test_id, op_update.name, + nullptr, // Outer operation variable is always used. op_update.name_do, op_update.name_doing, op_update.name_did, diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index 8ed9605..8a063f7 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -470,7 +470,7 @@ namespace build2 bool value_traits:: convert (const name& n, const name* r) { - if (r == nullptr && !n.pattern && n.simple () ) + if (r == nullptr && !n.pattern && n.simple ()) { const string& s (n.value); -- cgit v1.1