diff options
Diffstat (limited to 'libbuild2')
-rw-r--r-- | libbuild2/action.hxx | 4 | ||||
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.cxx | 16 | ||||
-rw-r--r-- | libbuild2/algorithm.cxx | 68 | ||||
-rw-r--r-- | libbuild2/algorithm.hxx | 14 | ||||
-rw-r--r-- | libbuild2/bash/rule.cxx | 2 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 12 | ||||
-rw-r--r-- | libbuild2/build/script/script.cxx | 2 | ||||
-rw-r--r-- | libbuild2/cc/common.cxx | 2 | ||||
-rw-r--r-- | libbuild2/cc/link-rule.cxx | 104 | ||||
-rw-r--r-- | libbuild2/cc/windows-rpath.cxx | 4 | ||||
-rw-r--r-- | libbuild2/context.cxx | 24 | ||||
-rw-r--r-- | libbuild2/context.hxx | 36 | ||||
-rw-r--r-- | libbuild2/dist/operation.cxx | 7 | ||||
-rw-r--r-- | libbuild2/dyndep.cxx | 56 | ||||
-rw-r--r-- | libbuild2/dyndep.hxx | 9 | ||||
-rw-r--r-- | libbuild2/install/operation.cxx | 3 | ||||
-rw-r--r-- | libbuild2/install/rule.cxx | 36 | ||||
-rw-r--r-- | libbuild2/operation.cxx | 3 | ||||
-rw-r--r-- | libbuild2/operation.hxx | 15 | ||||
-rw-r--r-- | libbuild2/target.cxx | 73 | ||||
-rw-r--r-- | libbuild2/target.hxx | 31 | ||||
-rw-r--r-- | libbuild2/target.ixx | 18 | ||||
-rw-r--r-- | libbuild2/test/operation.cxx | 2 | ||||
-rw-r--r-- | 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<uintptr_t> (p.target); p.target = nullptr; @@ -426,7 +426,7 @@ namespace build2 { if (const target* pt = (p.target != nullptr ? p.target : - p.adhoc ? reinterpret_cast<target*> (p.data) : + p.adhoc () ? reinterpret_cast<target*> (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<target*> (p.data) : + p.adhoc () ? reinterpret_cast<target*> (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<target*> (p.data) + p.adhoc () ? reinterpret_cast<target*> (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<target*> (p.data) : nullptr)) + p.adhoc () ? reinterpret_cast<target*> (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<target*> (p.data) : nullptr)) + p.adhoc () ? reinterpret_cast<target*> (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<uintptr_t> (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<path_target> ()); + + 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<bash> ()) 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<target*> (p.data) + p.adhoc () ? reinterpret_cast<target*> (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<target*> (p.data) : + p.adhoc () ? reinterpret_cast<target*> (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<uintptr_t> (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<string> (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<string> ("extension", v_t); - var_clean = &vp.insert<bool> ("clean", v_t); - var_backlink = &vp.insert<string> ("backlink", v_t); - var_include = &vp.insert<string> ("include", v_q); + var_update = &vp.insert<string> ("update", v_q); + var_clean = &vp.insert<bool> ("clean", v_t); + var_backlink = &vp.insert<string> ("backlink", v_t); + var_include = &vp.insert<string> ("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 <libbuild2/prerequisites.hxx>. + // 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_<operation> + // 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<path_target> ()); - - 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<bool> 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<path> (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<path> (l).string () == "false") { l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); @@ -290,11 +294,13 @@ namespace build2 { if (p.is_a<exe> ()) { - // 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<path> (p.vars["install"]).string () != "true") + cast_empty<path> (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<path> (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<file> ()); const path& tp (t.path ()); @@ -1033,7 +1043,7 @@ namespace build2 // if (!tp.empty ()) { - install_target (t, cast<path> (t["install"]), 1); + install_target (t, cast<path> (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<file> ()); 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<path> (t["install"]), 1); + r |= uninstall_target (t, + cast<path> (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, "<default>", + 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 <typename T> + template <typename T, size_t N> struct sparse_vector { - using base_type = vector<T*>; + using base_type = small_vector<T*, N>; using size_type = typename base_type::size_type; void @@ -348,8 +351,8 @@ namespace build2 base_type v_; }; - using meta_operations = sparse_vector<const meta_operation_info>; - using operations = sparse_vector<const operation_info>; + using meta_operations = sparse_vector<const meta_operation_info, 8>; + using operations = sparse_vector<const operation_info, 10>; } 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<bool> (l) ? include_type::normal : include_type::excluded; - } - else if (const string* v = cast_null<string> (p.vars[ctx.var_include])) + if (const string* v = cast_null<string> (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<bool> 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<const target*> (which is what we + // needed, it can be treated as just vector<const target*> (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<prerequisite_target>; @@ -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<bool>:: 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); |