aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-01-04 14:56:56 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-01-06 07:24:42 +0200
commitb236b111e52d08245d9bc1caadd6b78f7723f42c (patch)
tree35464e35cd43c4613918e8929b2085892a7baa93
parent6a242a8050edd0a33ea0e770a6eca4823a98b8d1 (diff)
Add depdb-dyndep --update-{include,exclude} options
These options specify prerequisite targets/patterns to include/exclude (from the static prerequisite set) for update during match as part of dynamic dependency extraction (those excluded will be updated during execute). For example: depdb dyndep ... --update-exclude libue{hello-meta} ... depdb dyndep ... --update-exclude libue{*} ... depdb dyndep ... --update-include $moc --update-include hxx{*} ... The order in which these options are specified is significant with the first target/pattern that matches determining the result. If only the --update-include options are specified, then only the explicitly included prerequisites will be updated. Otherwise, all prerequisites that are not explicitly excluded will be updated. If none of these options is specified, then all the static prerequisites are updated during match. Note also that these options do not apply to ad hoc prerequisites which are always updated during match.
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx290
-rw-r--r--libbuild2/adhoc-rule-buildscript.hxx3
-rw-r--r--libbuild2/algorithm.hxx4
-rw-r--r--libbuild2/build/script/builtin.cli20
-rw-r--r--libbuild2/build/script/parser.cxx326
-rw-r--r--libbuild2/build/script/parser.hxx17
-rw-r--r--libbuild2/dyndep.cxx11
-rw-r--r--libbuild2/dyndep.hxx8
-rw-r--r--libbuild2/name.hxx2
-rw-r--r--libbuild2/parser.cxx3
10 files changed, 504 insertions, 180 deletions
diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx
index e0e69dc..f4f3af9 100644
--- a/libbuild2/adhoc-rule-buildscript.cxx
+++ b/libbuild2/adhoc-rule-buildscript.cxx
@@ -391,8 +391,8 @@ namespace build2
// Because the depdb preamble can access $<, we have to blank out all the
// ad hoc prerequisites. Since we will still need them later, we "move"
- // them to the auxiliary data member in prerequisite_target (which also
- // means we cannot use the standard execute_prerequisites()).
+ // them to the auxiliary data member in prerequisite_target (see
+ // execute_update_prerequisites() for details).
//
auto& pts (t.prerequisite_targets[a]);
for (prerequisite_target& p: pts)
@@ -426,7 +426,7 @@ namespace build2
{
if (const target* pt =
(p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) :
+ p.adhoc ? reinterpret_cast<target*> (p.data) :
nullptr))
{
hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage);
@@ -504,46 +504,32 @@ namespace build2
if (update)
mt = timestamp_nonexistent;
- // Update our prerequisite targets. While strictly speaking we only need
- // to update those that are referenced by depdb-dyndep, communicating
- // this is both tedious and error-prone. So we update them all.
- //
- for (const prerequisite_target& p: pts)
- {
- if (const target* pt =
- (p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) : nullptr))
- {
- update = dyndep_rule::update (
- trace, a, *pt, update ? timestamp_unknown : mt) || update;
- }
- }
-
if (script.depdb_dyndep_byproduct)
{
// If we have the dynamic dependency information as byproduct of the
// recipe body, then do the first part: verify the entries in depdb
// unless we are already updating. Essentially, this is the `if(cache)`
// equivalent of the restart loop in exec_depdb_dyndep().
- //
- // Do we really need to update our prerequisite targets in this case (as
- // we do above)? While it may seem like we should be able to avoid it by
- // triggering update on encountering any non-existent files in depbd, we
- // may actually incorrectly "validate" some number of depdb entires
- // while having an out-of-date main source file. We could probably avoid
- // the update if we are already updating.
using dyndep = dyndep_rule;
- // Extract the depdb-dyndep command's information (we may also execute
- // some variable assignments).
+ // Update our prerequisite targets and extract the depdb-dyndep
+ // command's information (we may also execute some variable
+ // assignments).
+ //
+ // Do we really need to update our prerequisite targets in this case?
+ // While it may seem like we should be able to avoid it by triggering
+ // update on encountering any non-existent files in depbd, we may
+ // actually incorrectly "validate" some number of depdb entires while
+ // having an out-of-date main source file. We could probably avoid the
+ // update if we are already updating.
//
{
build::script::parser p (ctx);
mdb->byp = p.execute_depdb_preamble_dyndep_byproduct (
a, bs, t,
env, script, run,
- dd);
+ dd, update, mt);
}
mdb->pts_n = pts.size ();
@@ -582,10 +568,16 @@ namespace build2
fp, true /* cache */, true /* normalized */,
map_ext, *byp.default_type).first)
{
+ // Note: mark the injected prerequisite target as updated (see
+ // execute_update_prerequisites() for details).
+ //
if (optional<bool> u = dyndep::inject_existing_file (
trace, what,
a, t,
- *ft, mt, false /* fail */))
+ *ft, mt,
+ false /* fail */,
+ false /* adhoc */,
+ 1 /* data */))
{
skip_count++;
return *u;
@@ -671,8 +663,8 @@ namespace build2
}
else
{
- // Run the second half of the preamble (depdb-dyndep commands) to
- // extract dynamic dependencies.
+ // Run the second half of the preamble (depdb-dyndep commands) to update
+ // our prerequisite targets and extract dynamic dependencies.
//
// Note that this should be the last update to depdb (the invalidation
// order semantics).
@@ -684,8 +676,8 @@ namespace build2
env, script, run,
dd,
update,
- deferred_failure,
- mt);
+ mt,
+ deferred_failure);
}
if (update && dd.reading () && !ctx.dry_run)
@@ -733,21 +725,13 @@ namespace build2
const file& t (xt.as<file> ());
- // While we've updated all our prerequisites in apply(), we still need to
- // execute them here to keep the dependency counts straight.
+ // Note that even if we've updated all our prerequisites in apply(), we
+ // still need to execute them here to keep the dependency counts straight.
//
- const auto& pts (t.prerequisite_targets[a]);
+ optional<target_state> ps (execute_update_prerequisites (a, t, md.mt));
- for (const prerequisite_target& p: pts)
- {
- if (const target* pt =
- (p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) : nullptr))
- {
- target_state ts (execute_wait (a, *pt));
- assert (ts == target_state::unchanged || ts == target_state::changed);
- }
- }
+ if (!ps)
+ md.mt = timestamp_nonexistent; // Update.
build::script::environment& env (md.env);
build::script::default_runner& run (md.run);
@@ -755,7 +739,7 @@ namespace build2
if (md.mt != timestamp_nonexistent)
{
run.leave (env, script.end_loc);
- return target_state::unchanged;
+ return *ps;
}
const scope& bs (*md.bs);
@@ -805,6 +789,7 @@ namespace build2
// Note that fp is expected to be absolute.
//
size_t skip (md.skip_count);
+ const auto& pts (t.prerequisite_targets[a]);
auto add = [&trace, what,
a, &bs, &t, &pts, pts_n = md.pts_n,
@@ -818,7 +803,8 @@ namespace build2
fp, false /* cache */, true /* normalized */,
map_ext, *byp.default_type).first)
{
- // Skip if this is one of the static prerequisites.
+ // Skip if this is one of the static prerequisites provided it was
+ // updated.
//
for (size_t i (0); i != pts_n; ++i)
{
@@ -826,10 +812,10 @@ namespace build2
if (const target* pt =
(p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) :
+ p.adhoc ? reinterpret_cast<target*> (p.data) :
nullptr))
{
- if (ft == pt)
+ if (ft == pt && (p.adhoc || p.data == 1))
return;
}
}
@@ -855,6 +841,9 @@ namespace build2
// Verify it has noop recipe.
//
+ // @@ Currently we will issue an imprecise diagnostics if this is
+ // a static prerequisite that was not updated (see above).
+ //
dyndep::verify_existing_file (trace, what, a, t, *ft);
}
@@ -974,19 +963,13 @@ namespace build2
const file& t (xt.as<file> ());
- // While we've updated all our prerequisites in apply(), we still need to
- // execute them here to keep the dependency counts straight.
+ // Note that even if we've updated all our prerequisites in apply(), we
+ // still need to execute them here to keep the dependency counts straight.
//
- for (const prerequisite_target& p: t.prerequisite_targets[a])
- {
- if (const target* pt =
- (p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) : nullptr))
- {
- target_state ts (execute_wait (a, *pt));
- assert (ts == target_state::unchanged || ts == target_state::changed);
- }
- }
+ optional<target_state> ps (execute_update_prerequisites (a, t, md.mt));
+
+ if (!ps)
+ md.mt = timestamp_nonexistent; // Update.
build::script::environment& env (md.env);
build::script::default_runner& run (md.run);
@@ -996,7 +979,7 @@ namespace build2
if (md.mt != timestamp_nonexistent && !md.deferred_failure)
{
run.leave (env, script.end_loc);
- return target_state::unchanged;
+ return *ps;
}
// Sequence start time for mtime checks below.
@@ -1035,86 +1018,30 @@ namespace build2
// out-of-date.
//
timestamp mt (t.load_mtime ());
- optional<target_state> ps;
- names storage;
+ // This is essentially ps=execute_prerequisites(a, t, mt) which we
+ // cannot use because we need to see ad hoc prerequisites.
+ //
+ optional<target_state> ps (execute_update_prerequisites (a, t, mt));
+ // Calculate prerequisite checksums (that need to include ad hoc
+ // prerequisites) unless the script tracks changes itself.
+ //
+ names storage;
sha256 prq_cs, exe_cs, env_cs;
- {
- // This is essentially ps=execute_prerequisites(a, t, mt) which we
- // cannot use because we need to see ad hoc prerequisites.
- //
- size_t busy (ctx.count_busy ());
- size_t exec (ctx.count_executed ());
-
- target_state rs (target_state::unchanged);
-
- wait_guard wg (ctx, busy, t[a].task_count);
-
- auto& pts (t.prerequisite_targets[a]);
-
- for (const target*& pt: pts)
- {
- if (pt == nullptr) // Skipped.
- continue;
-
- target_state s (execute_async (a, *pt, busy, t[a].task_count));
-
- if (s == target_state::postponed)
- {
- rs |= s;
- pt = nullptr;
- }
- }
- wg.wait ();
-
- bool e (mt == timestamp_nonexistent);
- for (prerequisite_target& p: pts)
+ if (!script.depdb_clear)
+ {
+ for (const prerequisite_target& p: t.prerequisite_targets[a])
{
- if (p == nullptr)
- continue;
-
- const target& pt (*p.target);
-
- ctx.sched.wait (exec, pt[a].task_count, scheduler::work_none);
-
- target_state s (pt.executed_state (a));
- rs |= s;
-
- // Compare our timestamp to this prerequisite's.
- //
- if (!e)
+ if (const target* pt =
+ (p.target != nullptr ? p.target :
+ p.adhoc ? reinterpret_cast<target*> (p.data)
+ : nullptr))
{
- // If this is an mtime-based target, then compare timestamps.
- //
- if (const mtime_target* mpt = pt.is_a<mtime_target> ())
- {
- if (mpt->newer (mt, s))
- e = true;
- }
- else
- {
- // Otherwise we assume the prerequisite is newer if it was
- // changed.
- //
- if (s == target_state::changed)
- e = true;
- }
+ hash_prerequisite_target (prq_cs, exe_cs, env_cs, *pt, storage);
}
-
- if (p.adhoc)
- p.target = nullptr; // Blank out.
-
- // As part of this loop calculate checksums that need to include ad
- // hoc prerequisites (unless the script tracks changes itself).
- //
- if (!script.depdb_clear)
- hash_prerequisite_target (prq_cs, exe_cs, env_cs, pt, storage);
}
-
- if (!e)
- ps = rs;
}
bool update (!ps);
@@ -1287,6 +1214,99 @@ namespace build2
return target_state::changed;
}
+ // Update prerequisite targets.
+ //
+ // Each prerequisite target should be in one of the following states:
+ //
+ // target adhoc data
+ // --------------------
+ // !NULL false 0 - normal prerequisite to be updated
+ // !NULL false 1 - normal prerequisite already updated
+ // !NULL true 0 - ad hoc prerequisite to be updated and blanked
+ // NULL true !NULL - ad hoc prerequisite already updated and blanked
+ //
+ // Note that we still execute already updated prerequisites to keep the
+ // dependency counts straight. But we don't consider them for the "renders
+ // us out-of-date" check assuming this has already been done.
+ //
+ optional<target_state> adhoc_buildscript_rule::
+ execute_update_prerequisites (action a, const target& t, timestamp mt) const
+ {
+ context& ctx (t.ctx);
+
+ // This is essentially a customized execute_prerequisites(a, t, mt).
+ //
+ size_t busy (ctx.count_busy ());
+ size_t exec (ctx.count_executed ());
+
+ target_state rs (target_state::unchanged);
+
+ wait_guard wg (ctx, busy, t[a].task_count);
+
+ auto& pts (t.prerequisite_targets[a]);
+
+ for (const prerequisite_target& p: pts)
+ {
+ if (const target* pt =
+ (p.target != nullptr ? p.target :
+ 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);
+ }
+ }
+
+ wg.wait ();
+
+ bool e (mt == timestamp_nonexistent);
+ for (prerequisite_target& p: pts)
+ {
+ if (const target* pt =
+ (p.target != nullptr ? p.target :
+ p.adhoc ? reinterpret_cast<target*> (p.data) : nullptr))
+ {
+ ctx.sched.wait (exec, (*pt)[a].task_count, scheduler::work_none);
+
+ if (p.data == 0)
+ {
+ target_state s (pt->executed_state (a));
+ rs |= s;
+
+ // Compare our timestamp to this prerequisite's.
+ //
+ if (!e)
+ {
+ // If this is an mtime-based target, then compare timestamps.
+ //
+ if (const mtime_target* mpt = pt->is_a<mtime_target> ())
+ {
+ if (mpt->newer (mt, s))
+ e = true;
+ }
+ else
+ {
+ // Otherwise we assume the prerequisite is newer if it was
+ // changed.
+ //
+ if (s == target_state::changed)
+ e = true;
+ }
+ }
+
+ // Blank out adhoc.
+ //
+ if (p.adhoc)
+ {
+ p.data = reinterpret_cast<uintptr_t> (p.target);
+ p.target = nullptr;
+ }
+ }
+ }
+ }
+
+ return e ? nullopt : optional<target_state> (rs);
+ }
+
// Return true if execute_body() was called and thus the caller should call
// run.leave().
//
diff --git a/libbuild2/adhoc-rule-buildscript.hxx b/libbuild2/adhoc-rule-buildscript.hxx
index 4c36bf8..e7b18e2 100644
--- a/libbuild2/adhoc-rule-buildscript.hxx
+++ b/libbuild2/adhoc-rule-buildscript.hxx
@@ -48,6 +48,9 @@ namespace build2
perform_update_file_dyndep_byproduct (
action, const target&, match_data_byproduct&) const;
+ optional<target_state>
+ execute_update_prerequisites (action, const target&, timestamp) const;
+
bool
execute_update_file (const scope&,
action a, const file&,
diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx
index 73705d8..01b69f2 100644
--- a/libbuild2/algorithm.hxx
+++ b/libbuild2/algorithm.hxx
@@ -118,8 +118,8 @@ namespace build2
LIBBUILD2_SYMEXPORT const target&
search (const target&, name, const scope&, const target_type* = nullptr);
- // Return NULL for unknown target types. Note that unlike the above version,
- // these ones can be called during the load and execute phases.
+ // Note: returns NULL for unknown target types. Note that unlike the above
+ // version, these ones can be called during the load and execute phases.
//
LIBBUILD2_SYMEXPORT const target*
search_existing (const name&,
diff --git a/libbuild2/build/script/builtin.cli b/libbuild2/build/script/builtin.cli
index 938c554..9f3f2ba 100644
--- a/libbuild2/build/script/builtin.cli
+++ b/libbuild2/build/script/builtin.cli
@@ -20,6 +20,20 @@ namespace build2
// Note that --byproduct, if any, must be the first option and is
// handled ad hoc, kind of as a sub-command.
//
+ // Similarly, --update-{include,exclude} are handled ad hoc and must
+ // be literals, similar to the -- separator. They specify prerequisite
+ // targets/patterns to include/exclude (from the static prerequisite
+ // set) for update during match (those excluded will be updated during
+ // execute). The order in which these options are specified is
+ // significant with the first target/pattern that matches determining
+ // the result. If only the --update-include options are specified,
+ // then only the explicitly included prerequisites will be updated.
+ // Otherwise, all prerequisites that are not explicitly excluded will
+ // be updated. If none of these options is specified, then all the
+ // static prerequisites are updated during match. Note also that these
+ // options do not apply to ad hoc prerequisites which are always
+ // updated during match.
+ //
// Note that in the future we may extend --cwd support to the non-
// byproduct mode where it will also have the `env --cwd` semantics
// (thus the matching name). Note that it will also be incompatible
@@ -34,14 +48,20 @@ namespace build2
// --default-type --default-target-type
//
path --file; // Read from file rather than stdin.
+
string --format; // Dependency format: make (default).
+
string --what; // Dependency kind, e.g., "header".
+
dir_paths --include-path|-I; // Search paths for generated files.
+
string --default-type; // Default prerequisite type to use
// if none could be derived from ext.
+
dir_path --cwd; // Builtin's working directory used
// to complete relative paths (only
// in --byproduct mode).
+
bool --drop-cycles; // Drop prerequisites that are also
// targets. Only use if you are sure
// such cycles are harmless, that is,
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index 41a040b..6f3c300 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -7,6 +7,7 @@
#include <sstream>
#include <libbutl/builtin.hxx>
+#include <libbutl/path-pattern.hxx>
#include <libbuild2/depdb.hxx>
#include <libbuild2/dyndep.hxx>
@@ -933,8 +934,8 @@ namespace build2
lines_iterator begin, lines_iterator end,
depdb& dd,
bool* update,
- bool* deferred_failure,
optional<timestamp> mt,
+ bool* deferred_failure,
dyndep_byproduct* byp)
{
tracer trace ("exec_depdb_preamble");
@@ -997,8 +998,8 @@ namespace build2
data.a, data.bs, const_cast<file&> (data.t),
data.dd,
*data.update,
- *data.deferred_failure,
*data.mt,
+ *data.deferred_failure,
data.byp);
}
else
@@ -1211,19 +1212,53 @@ namespace build2
action a, const scope& bs, file& t,
depdb& dd,
bool& update,
- bool& deferred_failure,
timestamp mt,
+ bool& deferred_failure,
dyndep_byproduct* byprod_result)
{
tracer trace ("exec_depdb_dyndep");
context& ctx (t.ctx);
- // Similar approach to parse_env_builtin().
- //
depdb_dyndep_options ops;
bool prog (false);
bool byprod (false);
+
+ // Prerequisite update filter (--update-*).
+ //
+ struct filter
+ {
+ location loc;
+ build2::name name;
+ bool include;
+ bool used = false;
+
+ union
+ {
+ const target_type* type; // For patterns.
+ const build2::target* target; // For non-patterns.
+ };
+
+ filter (const location& l,
+ build2::name n, bool i, const target_type& tt)
+ : loc (l), name (move (n)), include (i), type (&tt) {}
+
+ filter (const location& l,
+ build2::name n, bool i, const build2::target& t)
+ : loc (l), name (move (n)), include (i), target (&t) {}
+
+ const char*
+ option () const
+ {
+ return include ? "--update-include" : "--update-exclude";
+ }
+ };
+
+ vector<filter> filters;
+ bool filter_default (false); // Note: incorrect if filter is empty.
+
+ // Similar approach to parse_env_builtin().
+ //
{
auto& t (lt);
auto& tt (ltt);
@@ -1247,16 +1282,141 @@ namespace build2
//
strings args;
- names ns; // Reuse to reduce allocations.
- while (tt != type::newline && tt != type::eos)
+ for (names ns; tt != type::newline && tt != type::eos; ns.clear ())
{
- if (tt == type::word && t.value == "--")
+ location l (get_location (t));
+
+ if (tt == type::word)
{
- prog = true;
- break;
- }
+ if (t.value == "--")
+ {
+ prog = true;
+ break;
+ }
- location l (get_location (t));
+ // See also the non-literal check in the options parsing below.
+ //
+ if ((t.value.compare (0, 16, "--update-include") == 0 ||
+ t.value.compare (0, 16, "--update-exclude") == 0) &&
+ (t.value[16] == '\0' || t.value[16] == '='))
+ {
+ string o;
+
+ if (t.value[16] == '\0')
+ {
+ o = t.value;
+ next (t, tt);
+ }
+ else
+ {
+ o.assign (t.value, 0, 16);
+ t.value.erase (0, 17);
+
+ if (t.value.empty ()) // Think `--update-include=$yacc`.
+ {
+ next (t, tt);
+
+ if (t.separated) // Think `--update-include= $yacc`.
+ fail (l) << "depdb dyndep: expected name after " << o;
+ }
+ }
+
+ if (!start_names (tt))
+ fail (l) << "depdb dyndep: expected name instead of " << t
+ << " after " << o;
+
+ // The chunk may actually contain multiple (or zero) names
+ // (e.g., as a result of a variable expansion or {}-list). Oh,
+ // well, I guess it can be viewed as a feature (to compensate
+ // for the literal option names).
+ //
+ parse_names (t, tt,
+ ns,
+ pattern_mode::preserve,
+ true /* chunk */,
+ ("depdb dyndep " + o + " option value").c_str (),
+ nullptr);
+
+ if (ns.empty ())
+ continue;
+
+ bool i (o[9] == 'i');
+
+ for (name& n: ns)
+ {
+ // @@ Maybe we will want to support out-qualified targets
+ // one day (but they should not be patterns).
+ //
+ if (n.pair)
+ fail (l) << "depdb dyndep: name pair in " << o << " value";
+
+ if (n.pattern)
+ {
+ if (*n.pattern != name::pattern_type::path)
+ fail (l) << "depdb dyndep: non-path pattern in " << o
+ << " value";
+
+ n.canonicalize ();
+
+ // @@ TODO (here and below).
+ //
+ // The reasonable directory semantics for a pattern seems
+ // to be:
+ //
+ // - empty - any directory (the common case)
+ // - relative - complete with base scope and fall through
+ // - absolute - only match targets in subdirectories
+ //
+ // Plus things are complicated by the src/out split (feels
+ // like we should do this in terms of scopes).
+ //
+ // See also target type/pattern-specific vars (where the
+ // directory is used to open a scope) and ad hoc pattern
+ // rules (where we currently don't allow directories).
+ //
+ if (!n.dir.empty ())
+ {
+ if (path_pattern (n.dir))
+ fail (l) << "depdb dyndep: pattern in directory in "
+ << o << " value";
+
+ fail (l) << "depdb dyndep: directory in pattern " << o
+ << " value";
+ }
+
+ // Resolve target type. If none is specified, then it's
+ // file{}.
+ //
+ const target_type* tt (n.untyped ()
+ ? &file::static_type
+ : bs.find_target_type (n.type));
+
+ if (tt == nullptr)
+ fail (l) << "depdb dyndep: unknown target type "
+ << n.type << " in " << o << " value";
+
+ filters.push_back (filter (l, move (n), i, *tt));
+ }
+ else
+ {
+ const target* t (search_existing (n, bs));
+
+ if (t == nullptr)
+ fail (l) << "depdb dyndep: unknown target " << n
+ << " in " << o << " value";
+
+ filters.push_back (filter (l, move (n), i, *t));
+ }
+ }
+
+ // If we have --update-exclude, then the default is include.
+ //
+ if (!i)
+ filter_default = true;
+
+ continue;
+ }
+ }
if (!start_names (tt))
fail (l) << "depdb dyndep: expected option or '--' separator "
@@ -1278,12 +1438,10 @@ namespace build2
catch (const invalid_argument&)
{
diag_record dr (fail (l));
- dr << "invalid string value ";
+ dr << "depdb dyndep: invalid string value ";
to_stream (dr.os, n, true /* quote */);
}
}
-
- ns.clear ();
}
if (prog)
@@ -1335,6 +1493,13 @@ namespace build2
if (strcmp (a, "--byproduct") == 0)
fail (ll) << "depdb dyndep: --byproduct must be first option";
+ // Handle non-literal --update-*.
+ //
+ if ((strncmp (a, "--update-include", 16) == 0 ||
+ strncmp (a, "--update-exclude", 16) == 0) &&
+ (a[16] == '\0' || a[16] == '='))
+ fail (ll) << "depdb dyndep: " << a << " must be literal";
+
// Handle unknown option.
//
if (a[0] == '-')
@@ -1385,6 +1550,14 @@ namespace build2
fail (ll) << "depdb dyndep: relative path specified with --cwd";
}
+ // --include
+ //
+ if (!ops.include_path ().empty ())
+ {
+ if (byprod)
+ fail (ll) << "depdb dyndep: -I specified with --byproduct";
+ }
+
// --file
//
// Note that if --file is specified without a program, then we assume
@@ -1425,17 +1598,112 @@ namespace build2
def_pt = bs.find_target_type (t);
if (def_pt == nullptr)
- fail (ll) << "unknown target type '" << t << "' specific with "
- << "--default-type";
+ fail (ll) << "depdb dyndep: unknown target type '" << t
+ << "' specific with --default-type";
}
else
def_pt = &file::static_type;
- if (byprod)
+ // Update prerequisite targets.
+ //
+ using dyndep = dyndep_rule;
+
+ auto& pts (t.prerequisite_targets[a]);
+
+ for (prerequisite_target& p: pts)
{
- if (!ops.include_path ().empty ())
- fail (ll) << "depdb dyndep: -I specified with --byproduct";
+ if (const target* pt =
+ (p.target != nullptr ? p.target :
+ p.adhoc ? reinterpret_cast<target*> (p.data)
+ : nullptr))
+ {
+ // Apply the --update-* filter.
+ //
+ if (!p.adhoc && !filters.empty ())
+ {
+ // Compute and cache "effective" name that we will be pattern-
+ // matching (similar code to variable_type_map::find()).
+ //
+ auto ename = [pt, en = optional<string> ()] () mutable
+ -> const string&
+ {
+ if (!en)
+ {
+ en = string ();
+ pt->key ().effective_name (*en);
+ }
+
+ return en->empty () ? pt->name : *en;
+ };
+
+ bool i (filter_default);
+
+ for (filter& f: filters)
+ {
+ if (f.name.pattern)
+ {
+ const name& n (f.name);
+
+#if 0
+ // Match directory if any.
+ //
+ if (!n.dir.empty ())
+ {
+ // @@ TODO (here and above).
+ }
+#endif
+
+ // Match type.
+ //
+ if (!pt->is_a (*f.type))
+ continue;
+
+ // Match name.
+ //
+ if (n.value == "*" || butl::path_match (ename (), n.value))
+ {
+ i = f.include;
+ break;
+ }
+ }
+ else
+ {
+ if (pt == f.target)
+ {
+ i = f.include;
+ f.used = true;
+ break;
+ }
+ }
+ }
+ if (!i)
+ continue;
+ }
+
+ update = dyndep::update (
+ trace, a, *pt, update ? timestamp_unknown : mt) || update;
+
+ // Mark as updated (see execute_update_prerequisites() for
+ // details.
+ //
+ if (!p.adhoc)
+ p.data = 1;
+ }
+ }
+
+ // Detect target filters that do not match anything.
+ //
+ for (const filter& f: filters)
+ {
+ if (!f.name.pattern && !f.used)
+ fail (f.loc) << "depdb dyndep: target " << f.name << " in "
+ << f.option () << " value does not match any "
+ << "prerequisites";
+ }
+
+ if (byprod)
+ {
*byprod_result = dyndep_byproduct {
ll,
format,
@@ -1452,8 +1720,6 @@ namespace build2
// extract_headers()) where you can often find more detailed rationale
// for some of the steps performed.
- using dyndep = dyndep_rule;
-
// Build the maps lazily, only if/when needed.
//
using prefix_map = dyndep::prefix_map;
@@ -1597,7 +1863,6 @@ namespace build2
// environment ctor).
//
size_t skip_count (0);
- auto& pts (t.prerequisite_targets[a]);
auto add = [this, &trace, what,
a, &bs, &t, &pts, pts_n = pts.size (),
@@ -1645,7 +1910,8 @@ namespace build2
//
if (!cache)
{
- // Skip if this is one of the static prerequisites.
+ // Skip if this is one of the static prerequisites provided it
+ // was updated.
//
for (size_t i (0); i != pts_n; ++i)
{
@@ -1653,10 +1919,10 @@ namespace build2
if (const target* pt =
(p.target != nullptr ? p.target :
- p.data != 0 ? reinterpret_cast<target*> (p.data) :
+ p.adhoc ? reinterpret_cast<target*> (p.data) :
nullptr))
{
- if (ft == pt)
+ if (ft == pt && (p.adhoc || p.data == 1))
return false;
}
}
@@ -1686,10 +1952,16 @@ namespace build2
}
}
+ // Note: mark the injected prerequisite target as updated (see
+ // execute_update_prerequisites() for details).
+ //
if (optional<bool> u = dyndep::inject_file (
trace, what,
a, t,
- *ft, mt, false /* fail */))
+ *ft, mt,
+ false /* fail */,
+ false /* adhoc */,
+ 1 /* data */))
{
if (!cache)
dd.expect (ft->path ()); // @@ Use fp (or verify match)?
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index d6f88f4..362c834 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -117,14 +117,14 @@ namespace build2
execute_depdb_preamble_dyndep (
action a, const scope& base, file& t,
environment& e, const script& s, runner& r,
- depdb& dd, bool& update, bool& deferred_failure, timestamp mt)
+ depdb& dd, bool& update, timestamp mt, bool& deferred_failure)
{
exec_depdb_preamble (
a, base, t,
e, s, r,
s.depdb_preamble.begin () + *s.depdb_dyndep,
s.depdb_preamble.end (),
- dd, &update, &deferred_failure, mt);
+ dd, &update, mt, &deferred_failure);
}
// This version doesn't actually execute the depdb-dyndep builtin (but
@@ -150,13 +150,12 @@ namespace build2
execute_depdb_preamble_dyndep_byproduct (
action a, const scope& base, const file& t,
environment& e, const script& s, runner& r,
- depdb& dd)
+ depdb& dd, bool& update, timestamp mt)
{
- // This is getting really ugly (we also don't really need to pass
+ // This is getting a bit ugly (we also don't really need to pass
// depdb here). One day we will find a better way...
//
- bool update, deferred_failure; // Dymmy.
- timestamp mt; // Dummy.
+ bool deferred_failure; // Dymmy.
dyndep_byproduct v;
exec_depdb_preamble (
@@ -164,7 +163,7 @@ namespace build2
e, s, r,
s.depdb_preamble.begin () + *s.depdb_dyndep,
s.depdb_preamble.end (),
- dd, &update, &deferred_failure, mt, &v);
+ dd, &update, mt, &deferred_failure, &v);
return v;
}
@@ -206,8 +205,8 @@ namespace build2
lines_iterator begin, lines_iterator end,
depdb&,
bool* update = nullptr,
- bool* deferred_failure = nullptr,
optional<timestamp> mt = nullopt,
+ bool* deferred_failure = nullptr,
dyndep_byproduct* = nullptr);
void
@@ -216,8 +215,8 @@ namespace build2
action, const scope& base, file&,
depdb&,
bool& update,
- bool& deferred_failure,
timestamp,
+ bool& deferred_failure,
dyndep_byproduct*);
// Helpers.
diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx
index ed41e0a..92e8903 100644
--- a/libbuild2/dyndep.cxx
+++ b/libbuild2/dyndep.cxx
@@ -81,7 +81,8 @@ namespace build2
const file& pt,
timestamp mt,
bool f,
- bool ah)
+ bool adhoc,
+ uintptr_t data)
{
// Even if failing we still use try_match() in order to issue consistent
// (with other places) diagnostics (rather than the generic "not rule to
@@ -104,7 +105,7 @@ namespace build2
// Add to our prerequisite target list.
//
- t.prerequisite_targets[a].push_back (prerequisite_target (&pt, ah));
+ t.prerequisite_targets[a].emplace_back (&pt, adhoc, data);
return r;
}
@@ -114,7 +115,9 @@ namespace build2
action a, target& t,
const file& pt,
timestamp mt,
- bool f)
+ bool f,
+ bool adhoc,
+ uintptr_t data)
{
if (!try_match (a, pt).first)
{
@@ -140,7 +143,7 @@ namespace build2
// Add to our prerequisite target list.
//
- t.prerequisite_targets[a].push_back (&pt);
+ t.prerequisite_targets[a].emplace_back (&pt, adhoc, data);
return r;
}
diff --git a/libbuild2/dyndep.hxx b/libbuild2/dyndep.hxx
index ad95df1..b285704 100644
--- a/libbuild2/dyndep.hxx
+++ b/libbuild2/dyndep.hxx
@@ -42,6 +42,7 @@ namespace build2
// hoc. But on the other hand, taking headers as an example, if the same
// header is listed as a static prerequisite, it will most definitely not
// going to be ad hoc. So we leave it to the caller to make this decision.
+ // Similarly, the data argument is passed to the prerequisite_target ctor.
//
static optional<bool>
inject_file (tracer&, const char* what,
@@ -49,7 +50,8 @@ namespace build2
const file& prerequiste,
timestamp,
bool fail,
- bool adhoc = false);
+ bool adhoc = false,
+ uintptr_t data = 0);
// As above but verify the file is matched with noop_recipe and issue
// diagnostics and fail otherwise (regardless of the fail flag).
@@ -64,7 +66,9 @@ namespace build2
action, target&,
const file& prerequiste,
timestamp,
- bool fail);
+ bool fail,
+ bool adhoc = false,
+ uintptr_t data = 0);
// Verify the file is matched with noop_recipe and issue diagnostics and
// fail otherwise. If the file is not matched, then fail if the target is
diff --git a/libbuild2/name.hxx b/libbuild2/name.hxx
index 216f207..1dd5a9f 100644
--- a/libbuild2/name.hxx
+++ b/libbuild2/name.hxx
@@ -178,6 +178,8 @@ namespace build2
// trailing directory separator then it is stored as a directory, otherwise
// as a simple name. Note that the returned name is never a pattern.
//
+ // NOTE: this function does not parse the full name syntax.
+ //
name
to_name (string);
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index 9c26f74..9f69117 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -979,7 +979,8 @@ namespace build2
// semantics is not immediately obvious. Whatever we decide, it
// should be consistent with the target type/pattern-specific
// variables where it is interpreted as a scope (and which doesn't
- // feel like the best option for pattern rules).
+ // feel like the best option for pattern rules). See also depdb
+ // dyndep --update-* patterns.
//
auto check_pattern = [this] (name& n, const location& loc)
{