diff options
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.cxx | 290 | ||||
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.hxx | 3 | ||||
-rw-r--r-- | libbuild2/algorithm.hxx | 4 | ||||
-rw-r--r-- | libbuild2/build/script/builtin.cli | 20 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 326 | ||||
-rw-r--r-- | libbuild2/build/script/parser.hxx | 17 | ||||
-rw-r--r-- | libbuild2/dyndep.cxx | 11 | ||||
-rw-r--r-- | libbuild2/dyndep.hxx | 8 | ||||
-rw-r--r-- | libbuild2/name.hxx | 2 | ||||
-rw-r--r-- | libbuild2/parser.cxx | 3 |
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) { |