aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/build
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/build')
-rw-r--r--libbuild2/build/script/builtin.cli20
-rw-r--r--libbuild2/build/script/parser.cxx326
-rw-r--r--libbuild2/build/script/parser.hxx17
3 files changed, 327 insertions, 36 deletions
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.