aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)
{