aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-02-24 10:03:43 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-03-02 13:26:51 +0200
commit634048a861658af2bc5c37507bf96116cf1968aa (patch)
treed6122830ed2744d86e1ddb389f0e76131e54d6be /libbuild2
parentbe66fff5ff42eaab81d2a526d8b6296c28848775 (diff)
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.
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/action.hxx4
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx16
-rw-r--r--libbuild2/algorithm.cxx68
-rw-r--r--libbuild2/algorithm.hxx14
-rw-r--r--libbuild2/bash/rule.cxx2
-rw-r--r--libbuild2/build/script/parser.cxx12
-rw-r--r--libbuild2/build/script/script.cxx2
-rw-r--r--libbuild2/cc/common.cxx2
-rw-r--r--libbuild2/cc/link-rule.cxx104
-rw-r--r--libbuild2/cc/windows-rpath.cxx4
-rw-r--r--libbuild2/context.cxx24
-rw-r--r--libbuild2/context.hxx36
-rw-r--r--libbuild2/dist/operation.cxx7
-rw-r--r--libbuild2/dyndep.cxx56
-rw-r--r--libbuild2/dyndep.hxx9
-rw-r--r--libbuild2/install/operation.cxx3
-rw-r--r--libbuild2/install/rule.cxx36
-rw-r--r--libbuild2/operation.cxx3
-rw-r--r--libbuild2/operation.hxx15
-rw-r--r--libbuild2/target.cxx73
-rw-r--r--libbuild2/target.hxx31
-rw-r--r--libbuild2/target.ixx18
-rw-r--r--libbuild2/test/operation.cxx2
-rw-r--r--libbuild2/variable.cxx2
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);