aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-05-23 09:23:16 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-05-29 10:21:12 +0200
commite05f7c7383cc48823bd408c0bc5187191a9a1c48 (patch)
tree024bb9c3be9c876b8822388baf1622cad03d7563
parent349b34108752e2fcf1ead648ffec8e5acfe4a91c (diff)
Explicit group: static members
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx369
-rw-r--r--libbuild2/adhoc-rule-buildscript.hxx12
-rw-r--r--libbuild2/adhoc-rule-cxx.cxx17
-rw-r--r--libbuild2/adhoc-rule-cxx.hxx3
-rw-r--r--libbuild2/adhoc-rule-regex-pattern.cxx23
-rw-r--r--libbuild2/adhoc-rule-regex-pattern.hxx2
-rw-r--r--libbuild2/algorithm.cxx81
-rw-r--r--libbuild2/algorithm.ixx5
-rw-r--r--libbuild2/build/script/parser.cxx20
-rw-r--r--libbuild2/build/script/parser.hxx18
-rw-r--r--libbuild2/build/script/runner.cxx27
-rw-r--r--libbuild2/build/script/script.cxx24
-rw-r--r--libbuild2/parser.cxx3
-rw-r--r--libbuild2/rule.cxx7
-rw-r--r--libbuild2/rule.hxx14
-rw-r--r--libbuild2/target.cxx38
-rw-r--r--libbuild2/target.hxx36
-rw-r--r--libbuild2/target.ixx4
18 files changed, 580 insertions, 123 deletions
diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx
index f3e3560..62f3594 100644
--- a/libbuild2/adhoc-rule-buildscript.cxx
+++ b/libbuild2/adhoc-rule-buildscript.cxx
@@ -211,11 +211,13 @@ namespace build2
bool adhoc_buildscript_rule::
reverse_fallback (action a, const target_type& tt) const
{
- // We can provide clean for a file target if we are providing update.
+ // We can provide clean for a file or group target if we are providing
+ // update.
//
- return a == perform_clean_id && tt.is_a<file> () &&
- find (actions.begin (), actions.end (),
- perform_update_id) != actions.end ();
+ return (a == perform_clean_id &&
+ (tt.is_a<file> () || tt.is_a<group> ()) &&
+ find (actions.begin (), actions.end (),
+ perform_update_id) != actions.end ());
}
struct adhoc_buildscript_rule::match_data
@@ -256,22 +258,27 @@ namespace build2
};
bool adhoc_buildscript_rule::
- match (action a, target& t, const string& h, match_extra& me) const
+ match (action a, target& xt, const string& h, match_extra& me) const
{
+ const target& t (xt); // See adhoc_rule::match().
+
// We pre-parsed the script with the assumption it will be used on a
- // non/file-based target. Note that this should not be possible with
- // patterns.
+ // non/file-based (or file group-based) target. Note that this should not
+ // be possible with patterns.
//
if (pattern == nullptr)
{
- if ((t.is_a<file> () != nullptr) != ttype->is_a<file> ())
- {
+ // Let's not allow mixing file/group.
+ //
+ if ((t.is_a<file> () != nullptr) == ttype->is_a<file> () ||
+ (t.is_a<group> () != nullptr) == ttype->is_a<group> ())
+ ;
+ else
fail (loc) << "incompatible target types used with shared recipe" <<
- info << "all targets must be file-based or non-file-based";
- }
+ info << "all targets must be file- or file group-based or non";
}
- return adhoc_rule::match (a, t, h, me);
+ return adhoc_rule::match (a, xt, h, me);
}
recipe adhoc_buildscript_rule::
@@ -282,17 +289,27 @@ namespace build2
recipe adhoc_buildscript_rule::
apply (action a,
- target& xt,
+ target& t,
match_extra& me,
- const optional<timestamp>& d) const
+ const optional<timestamp>& deadline) const
{
tracer trace ("adhoc_buildscript_rule::apply");
+ // Handle matching explicit group members (see adhoc_rule::match() for
+ // background).
+ //
+ if (const group* g = t.group != nullptr ? t.group->is_a<group> () : nullptr)
+ {
+ match_sync (a, *g);
+ return group_recipe; // Execute the group's recipe.
+ }
+
// We don't support deadlines for any of these cases (see below).
//
- if (d && (a.outer () ||
- me.fallback ||
- (a == perform_update_id && xt.is_a<file> ())))
+ if (deadline && (a.outer () ||
+ me.fallback ||
+ (a == perform_update_id &&
+ (t.is_a<file> () || t.is_a<group> ()))))
return empty_recipe;
// If this is an outer operation (e.g., update-for-test), then delegate to
@@ -300,26 +317,80 @@ namespace build2
//
if (a.outer ())
{
- match_inner (a, xt);
+ match_inner (a, t);
return execute_inner;
}
- context& ctx (xt.ctx);
- const scope& bs (xt.base_scope ());
+ context& ctx (t.ctx);
+ const scope& bs (t.base_scope ());
+
+ group* g (t.is_a<group> ()); // Explicit group.
// Inject pattern's ad hoc group members, if any.
//
if (pattern != nullptr)
- pattern->apply_adhoc_members (a, xt, bs, me);
+ {
+ pattern->apply_adhoc_members (a, t, bs, me);
- // Derive file names for the target and its ad hoc group members, if any.
+ // A pattern rule that matches an explicit group should not inject any
+ // ad hoc members.
+ //
+ if (g != nullptr)
+ assert (t.adhoc_member == nullptr);
+ }
+
+ // Derive file names for the target and its static/ad hoc group members,
+ // if any.
//
if (a == perform_update_id || a == perform_clean_id)
{
- for (target* m (&xt); m != nullptr; m = m->adhoc_member)
+ if (g != nullptr)
+ {
+ g->reset_members (a); // See group::group_members() for background.
+
+ for (const target& m: g->static_members)
+ {
+ if (auto* p = m.is_a<path_target> ())
+ p->derive_path ();
+
+ g->members.push_back (&m);
+ }
+
+ if (g->members.empty ())
+ {
+ if (!script.depdb_dyndep_dyn_target) // @@ TODO: expl: first must file
+ fail << "group " << *g << " has no static or dynamic members";
+ }
+ else if (!g->members.front ()->is_a<file> ())
+ {
+ // We use the first static member to derive depdb path, get mtime,
+ // etc. So it must be file-based.
+ //
+ fail << "first static member " << g->members.front () << " of group "
+ << *g << " is not a file";
+ }
+ }
+ else
+ {
+ for (target* m (&t); m != nullptr; m = m->adhoc_member)
+ {
+ if (auto* p = m->is_a<path_target> ())
+ p->derive_path ();
+ }
+ }
+ }
+ else if (g != nullptr)
+ {
+ // This could be, for example, configure/dist update which could need a
+ // "representative sample" of members (in order to be able to match the
+ // rules). So add static members if there aren't any.
+ //
+ if (g->group_members (a).members == nullptr) // Note: not g->member.
{
- if (auto* p = m->is_a<path_target> ())
- p->derive_path ();
+ g->reset_members (a);
+
+ for (const target& m: g->static_members)
+ g->members.push_back (&m);
}
}
@@ -332,22 +403,22 @@ namespace build2
// prerequisites injected by the pattern. So we have to handle this ad hoc
// below.
//
- const fsdir* dir (inject_fsdir (a, xt, false /* prereq */));
+ const fsdir* dir (inject_fsdir (a, t, false /* prereq */));
// Match prerequisites.
//
// This is essentially match_prerequisite_members() but with support
// for update=unmatch|match.
//
- auto& pts (xt.prerequisite_targets[a]);
+ auto& pts (t.prerequisite_targets[a]);
{
// Re-create the clean semantics as in match_prerequisite_members().
//
- bool clean (a.operation () == clean_id && !xt.is_a<alias> ());
+ bool clean (a.operation () == clean_id && !t.is_a<alias> ());
// Add target's prerequisites.
//
- for (prerequisite_member p: group_prerequisite_members (a, xt))
+ for (prerequisite_member p: group_prerequisite_members (a, t))
{
// Note that we have to recognize update=unmatch|match for *(update),
// not just perform(update). But only actually do anything about it
@@ -355,7 +426,7 @@ namespace build2
//
lookup l; // The `update` variable value, if any.
include_type pi (
- include (a, xt, p, a.operation () == update_id ? &l : nullptr));
+ include (a, t, p, a.operation () == update_id ? &l : nullptr));
// Use prerequisite_target::include to signal update during match or
// unmatch.
@@ -387,7 +458,7 @@ namespace build2
if (!pi)
continue;
- const target& pt (p.search (xt));
+ const target& pt (p.search (t));
if (&pt == dir) // Don't add injected fsdir{} twice.
continue;
@@ -406,19 +477,19 @@ namespace build2
// Inject pattern's prerequisites, if any.
//
if (pattern != nullptr)
- pattern->apply_prerequisites (a, xt, bs, me);
+ pattern->apply_prerequisites (a, t, bs, me);
// Start asynchronous matching of prerequisites. Wait with unlocked
// phase to allow phase switching.
//
- wait_guard wg (ctx, ctx.count_busy (), xt[a].task_count, true);
+ wait_guard wg (ctx, ctx.count_busy (), t[a].task_count, true);
for (const prerequisite_target& pt: pts)
{
if (pt.target == dir) // Don't match injected fsdir{} twice.
continue;
- match_async (a, *pt.target, ctx.count_busy (), xt[a].task_count);
+ match_async (a, *pt.target, ctx.count_busy (), t[a].task_count);
}
wg.wait ();
@@ -525,9 +596,9 @@ namespace build2
{
// For depdb-dyndep --dyn-target use depdb to clean dynamic targets.
//
- if (script.depdb_dyndep && script.depdb_dyndep_dyn_target)
+ if (script.depdb_dyndep_dyn_target)
{
- file& t (xt.as<file> ());
+ file& ft (t.as<file> ()); // @@ TODO: expl
// Note that only removing the relevant filesystem entries is not
// enough: we actually have to populate the group with members since
@@ -543,8 +614,13 @@ namespace build2
return dyndep::map_extension (bs, n, e, nullptr);
});
- for (path& f: read_dyn_targets (t.path () + ".d"))
+ // @@ TODO: expl
+ //
+ // - depdb name?
+
+ for (path& f: read_dyn_targets (ft.path () + ".d"))
{
+
// Note that this logic should be consistent with what we have in
// exec_depdb_dyndep().
//
@@ -567,13 +643,13 @@ namespace build2
// type mapping in order to resolve ambiguities.
//
dyndep::inject_adhoc_group_member ("file",
- a, bs, t,
+ a, bs, ft,
move (f),
map_ext, file::static_type);
}
}
- return perform_clean_file;
+ return g == nullptr ? perform_clean_file : perform_clean_group;
}
// If we have any update during match prerequisites, now is the time to
@@ -586,17 +662,17 @@ namespace build2
// prerequisite_target::data.
//
if (a == perform_update_id)
- update_during_match_prerequisites (trace, a, xt);
+ update_during_match_prerequisites (trace, a, t);
- // See if this is not update or not on a file-based target.
+ // See if this is not update or not on a file/group-based target.
//
- if (a != perform_update_id || !xt.is_a<file> ())
+ if (a != perform_update_id || !(g != nullptr || t.is_a<file> ()))
{
// Make sure we get small object optimization.
//
- if (d)
+ if (deadline)
{
- return [dv = *d, this] (action a, const target& t)
+ return [dv = *deadline, this] (action a, const target& t)
{
return default_action (a, t, dv);
};
@@ -616,14 +692,14 @@ namespace build2
{
return [this] (action a, const target& t)
{
- return perform_update_file (a, t);
+ return perform_update_file_or_group (a, t);
};
}
- // This is a perform update on a file target with extraction of dynamic
- // dependency information either in the depdb preamble (depdb-dyndep
- // without --byproduct) or as a byproduct of the recipe body execution
- // (depdb-dyndep with --byproduct).
+ // This is a perform update on a file or group target with extraction of
+ // dynamic dependency information either in the depdb preamble
+ // (depdb-dyndep without --byproduct) or as a byproduct of the recipe body
+ // execution (depdb-dyndep with --byproduct).
//
// For the former case, we may need to add additional prerequisites (or
// even target group members). We also have to save any such additional
@@ -641,9 +717,6 @@ namespace build2
// example and all this logic is based on the prior work in the cc module
// where you can often find more detailed rationale for some of the steps
// performed (like the fsdir update below).
- //
- file& t (xt.as<file> ());
- const path& tp (t.path ());
// Re-acquire fsdir{} specified by the user, similar to inject_fsdir()
// (which we have disabled; see above).
@@ -689,6 +762,11 @@ namespace build2
}
}
+ assert (g != nullptr); // @@ TODO: expl
+
+ file& ft (t.as<file> ());
+ const path& tp (ft.path ());
+
// Note that while it's tempting to turn match_data* into recipes, some of
// their members are not movable. And in the end we will have the same
// result: one dynamic memory allocation.
@@ -821,8 +899,10 @@ namespace build2
update = true;
else
{
- if ((mt = t.mtime ()) == timestamp_unknown)
- t.mtime (mt = mtime (tp)); // Cache.
+ // @@ TODO: expl mtime
+
+ if ((mt = ft.mtime ()) == timestamp_unknown)
+ ft.mtime (mt = mtime (tp)); // Cache.
update = dd.mtime > mt;
}
@@ -854,7 +934,7 @@ namespace build2
{
build::script::parser p (ctx);
mdb->byp = p.execute_depdb_preamble_dyndep_byproduct (
- a, bs, t,
+ a, bs, ft, // @@ TODO: expl
env, script, run,
dd, update, mt);
}
@@ -978,8 +1058,8 @@ namespace build2
else
{
// Run the second half of the preamble (depdb-dyndep commands) to update
- // our prerequisite targets and extract dynamic dependencies (targets and
- // prerequisites).
+ // our prerequisite targets and extract dynamic dependencies (targets
+ // and prerequisites).
//
// Note that this should be the last update to depdb (the invalidation
// order semantics).
@@ -987,7 +1067,7 @@ namespace build2
md->deferred_failure = false;
{
build::script::parser p (ctx);
- p.execute_depdb_preamble_dyndep (a, bs, t,
+ p.execute_depdb_preamble_dyndep (a, bs, ft, // @@ TODO: expl
env, script, run,
dd,
md->dyn_targets,
@@ -1341,21 +1421,25 @@ namespace build2
}
target_state adhoc_buildscript_rule::
- perform_update_file (action a, const target& xt) const
+ perform_update_file_or_group (action a, const target& t) const
{
- tracer trace ("adhoc_buildscript_rule::perform_update_file");
+ tracer trace ("adhoc_buildscript_rule::perform_update_file_or_group");
- context& ctx (xt.ctx);
+ context& ctx (t.ctx);
+ const scope& bs (t.base_scope ());
- const file& t (xt.as<file> ());
- const path& tp (t.path ());
+ // For a group we use the first (static) member to derive depdb path, as a
+ // source of mtime, etc.
+ //
+ const group* g (t.is_a<group> ());
- const scope& bs (t.base_scope ());
+ const file& ft ((g == nullptr ? t : *g->members.front ()).as<file> ());
+ const path& tp (ft.path ());
// Update prerequisites and determine if any of them render this target
// out-of-date.
//
- timestamp mt (t.load_mtime ());
+ timestamp mt (g == nullptr ? ft.load_mtime () : g->load_mtime (tp));
// This is essentially ps=execute_prerequisites(a, t, mt) which we
// cannot use because we need to see ad hoc prerequisites.
@@ -1449,8 +1533,18 @@ namespace build2
//
{
sha256 tcs;
- for (const target* m (&t); m != nullptr; m = m->adhoc_member)
- hash_target (tcs, *m, storage);
+ if (g == nullptr)
+ {
+ for (const target* m (&t); m != nullptr; m = m->adhoc_member)
+ hash_target (tcs, *m, storage);
+ }
+ else
+ {
+ // Feels like there is not much sense in hashing the group itself.
+ //
+ for (const target* m: g->members)
+ hash_target (tcs, *m, storage);
+ }
if (dd.expect (tcs.string ()) != nullptr)
l4 ([&]{trace << "target set change forcing update of " << t;});
@@ -1534,7 +1628,11 @@ namespace build2
{
// Prepare to execute the script diag preamble and/or body.
//
- if ((r = execute_update_file (bs, a, t, env, run)))
+ r = g == nullptr
+ ? execute_update_file (bs, a, ft, env, run)
+ : execute_update_group (bs, a, *g, env, run);
+
+ if (r)
{
if (!ctx.dry_run)
dd.check_mtime (tp);
@@ -1544,7 +1642,8 @@ namespace build2
if (r || depdb_preamble)
run.leave (env, script.end_loc);
- t.mtime (system_clock::now ());
+ const auto& m (g == nullptr ? static_cast<const mtime_target&> (ft) : *g);
+ m.mtime (system_clock::now ());
return target_state::changed;
}
@@ -1653,6 +1752,8 @@ namespace build2
build::script::default_runner& run,
bool deferred_failure) const
{
+ // NOTE: similar to execute_update_group() below.
+ //
context& ctx (t.ctx);
const scope& rs (*bs.root_scope ());
@@ -1787,6 +1888,115 @@ namespace build2
return exec_diag || exec_body;
}
+ bool adhoc_buildscript_rule::
+ execute_update_group (const scope& bs,
+ action a, const group& g,
+ build::script::environment& env,
+ build::script::default_runner& run,
+ bool deferred_failure) const
+ {
+ // Note: similar to execute_update_file() above (see there for comments).
+ //
+ context& ctx (g.ctx);
+
+ const scope& rs (*bs.root_scope ());
+
+ build::script::parser p (ctx);
+
+ bool exec_body (!ctx.dry_run || verb >= 2);
+ bool exec_diag (!script.diag_preamble.empty () && (exec_body || verb == 1));
+ bool exec_depdb (!script.depdb_preamble.empty ());
+
+ if (script.diag_name)
+ {
+ if (verb == 1)
+ {
+ const file* pt (nullptr);
+ for (const prerequisite_target& p: g.prerequisite_targets[a])
+ {
+ if (p.target != nullptr && !p.adhoc ())
+ {
+ pt = p.target->is_a<file> ();
+ break;
+ }
+ }
+
+ if (pt != nullptr)
+ print_diag (script.diag_name->c_str (), *pt, g);
+ else
+ print_diag (script.diag_name->c_str (), g);
+ }
+ }
+ else if (exec_diag)
+ {
+ if (script.diag_preamble_temp_dir && !script.depdb_preamble_temp_dir)
+ env.set_temp_dir_variable ();
+
+ pair<names, location> diag (
+ p.execute_diag_preamble (rs, bs,
+ env, script, run,
+ verb == 1 /* diag */,
+ !exec_depdb /* enter */,
+ false /* leave */));
+ if (verb == 1)
+ print_custom_diag (bs, move (diag.first), diag.second);
+ }
+
+ if (exec_body)
+ {
+ // On failure remove the target files that may potentially exist but
+ // be invalid.
+ //
+ small_vector<auto_rmfile, 8> rms;
+
+ if (!ctx.dry_run)
+ {
+ for (const target* m: g.members)
+ {
+ if (auto* f = m->is_a<file> ())
+ rms.emplace_back (f->path ());
+ }
+ }
+
+ if (script.body_temp_dir &&
+ !script.depdb_preamble_temp_dir &&
+ !script.diag_preamble_temp_dir)
+ env.set_temp_dir_variable ();
+
+ p.execute_body (rs, bs,
+ env, script, run,
+ !exec_depdb && !exec_diag /* enter */,
+ false /* leave */);
+
+ if (!ctx.dry_run)
+ {
+ if (deferred_failure)
+ fail << "expected error exit status from recipe body";
+
+#ifndef _WIN32
+ auto chmod = [] (const path& p)
+ {
+ path_perms (p,
+ (path_perms (p) |
+ permissions::xu |
+ permissions::xg |
+ permissions::xo));
+ };
+
+ for (const target* m: g.members)
+ {
+ if (auto* p = m->is_a<exe> ())
+ chmod (p->path ());
+ }
+#endif
+ for (auto& rm: rms)
+ rm.cancel ();
+ }
+ }
+
+ return exec_diag || exec_body;
+ }
+
target_state adhoc_buildscript_rule::
perform_clean_file (action a, const target& t)
{
@@ -1802,7 +2012,7 @@ namespace build2
// Finally, we print the entire ad hoc group at verbosity level 1, similar
// to the default update diagnostics.
//
- // @@ TODO: .t may also be a temporary directory.
+ // @@ TODO: .t may also be a temporary directory (and below).
//
return perform_clean_extra (a,
t.as<file> (),
@@ -1812,6 +2022,25 @@ namespace build2
}
target_state adhoc_buildscript_rule::
+ perform_clean_group (action a, const target& xt)
+ {
+ const group& g (xt.as<group> ());
+
+ path d, t;
+ if (!g.static_members.empty ())
+ {
+ const path& p (g.static_members.front ().get ().as<file> ().path ());
+ d = p + ".d";
+ t = p + ".t";
+ }
+ else
+ assert (false); // @@ TODO: expl
+
+ return perform_clean_group_extra (a, g, {d.string ().c_str (),
+ t.string ().c_str ()});
+ }
+
+ target_state adhoc_buildscript_rule::
default_action (action a,
const target& t,
const optional<timestamp>& deadline) const
diff --git a/libbuild2/adhoc-rule-buildscript.hxx b/libbuild2/adhoc-rule-buildscript.hxx
index 02939c1..13b272f 100644
--- a/libbuild2/adhoc-rule-buildscript.hxx
+++ b/libbuild2/adhoc-rule-buildscript.hxx
@@ -36,7 +36,7 @@ namespace build2
const optional<timestamp>&) const override;
target_state
- perform_update_file (action, const target&) const;
+ perform_update_file_or_group (action, const target&) const;
struct match_data;
struct match_data_byproduct;
@@ -58,9 +58,19 @@ namespace build2
build::script::default_runner&,
bool deferred_failure = false) const;
+ bool
+ execute_update_group (const scope&,
+ action a, const group&,
+ build::script::environment&,
+ build::script::default_runner&,
+ bool deferred_failure = false) const;
+
static target_state
perform_clean_file (action, const target&);
+ static target_state
+ perform_clean_group (action, const target&);
+
target_state
default_action (action, const target&, const optional<timestamp>&) const;
diff --git a/libbuild2/adhoc-rule-cxx.cxx b/libbuild2/adhoc-rule-cxx.cxx
index db5c5ab..ad19481 100644
--- a/libbuild2/adhoc-rule-cxx.cxx
+++ b/libbuild2/adhoc-rule-cxx.cxx
@@ -95,8 +95,10 @@ namespace build2
load_module_library (const path& lib, const string& sym, string& err);
bool adhoc_cxx_rule::
- match (action a, target& t, const string& hint, match_extra& me) const
+ match (action a, target& xt, const string& hint, match_extra& me) const
{
+ const target& t (xt); // See adhoc_rule::match() for background.
+
if (pattern != nullptr && !pattern->match (a, t, hint, me))
return false;
@@ -674,13 +676,24 @@ namespace build2
}
}
- return impl->match (a, t, hint, me);
+ return impl->match (a, xt, hint, me);
}
#endif // BUILD2_BOOTSTRAP || LIBBUILD2_STATIC_BUILD
recipe adhoc_cxx_rule::
apply (action a, target& t, match_extra& me) const
{
+ // Handle matching explicit group member (see adhoc_rule::match() for
+ // background).
+ //
+ if (const group* g = (t.group != nullptr
+ ? t.group->is_a<group> ()
+ : nullptr))
+ {
+ match_sync (a, *g);
+ return group_recipe; // Execute the group's recipe.
+ }
+
return impl.load (memory_order_relaxed)->apply (a, t, me);
}
}
diff --git a/libbuild2/adhoc-rule-cxx.hxx b/libbuild2/adhoc-rule-cxx.hxx
index 9a17447..b563881 100644
--- a/libbuild2/adhoc-rule-cxx.hxx
+++ b/libbuild2/adhoc-rule-cxx.hxx
@@ -52,6 +52,9 @@ namespace build2
// Return true by default.
//
+ // Note: must treat target as const (unless known to match a non-group).
+ // See adhoc_rule::match() for background.
+ //
virtual bool
match (action, target&) const override;
};
diff --git a/libbuild2/adhoc-rule-regex-pattern.cxx b/libbuild2/adhoc-rule-regex-pattern.cxx
index 59a63bc..354b859 100644
--- a/libbuild2/adhoc-rule-regex-pattern.cxx
+++ b/libbuild2/adhoc-rule-regex-pattern.cxx
@@ -126,10 +126,13 @@ namespace build2
}
bool adhoc_rule_regex_pattern::
- match (action a, target& t, const string&, match_extra& me) const
+ match (action a, const target& t, const string&, match_extra& me) const
{
tracer trace ("adhoc_rule_regex_pattern::match");
+ // Note: target may not be locked in which case we should not modify
+ // target or match_extra (see adhoc_rule::match() for background).
+
// The plan is as follows: First check the "type signature" of the target
// and its prerequisites (the primary target type has already been matched
// by the rule matching machinery). If there is a match, then concatenate
@@ -161,12 +164,17 @@ namespace build2
// implementation. Except we support the unmatch and match values in
// the update variable.
//
+ // Note: assuming group prerequisites are immutable (not locked).
+ //
for (prerequisite_member p: group_prerequisite_members (a, t))
{
// Note that here we don't validate the update operation override
// value (since we may not match). Instead the rule does this in
// apply().
//
+ // Note: assuming include()'s use of target only relied on immutable
+ // data (not locked).
+ //
lookup l;
if (include (a, t, p, a.operation () == update_id ? &l : nullptr) ==
include_type::normal && p.is_a (tt))
@@ -205,10 +213,13 @@ namespace build2
// So the plan is to store the string in match_extra::data() and
// regex_match_results (which we can move) in the auxiliary data storage.
//
+ // Note: only cache if locked.
+ //
static_assert (sizeof (string) <= match_extra::data_size,
"match data too large");
- string& ns (me.data (string ()));
+ string tmp;
+ string& ns (me.locked ? me.data (string ()) : tmp);
auto append_name = [&ns,
first = true,
@@ -226,10 +237,12 @@ namespace build2
// Primary target (always a pattern).
//
auto te (targets_.end ()), ti (targets_.begin ());
- append_name (t.key (), *ti);
+ append_name (t.key (), *ti); // Immutable (not locked).
// Match ad hoc group members.
//
+ // Note: shouldn't be in effect for an explicit group (not locked).
+ //
while ((ti = find_if (ti + 1, te, pattern)) != te)
{
const target* at (find_adhoc_member (t, ti->type));
@@ -279,7 +292,9 @@ namespace build2
return false;
}
- t.data (a, move (mr));
+ if (me.locked)
+ t.data (a, move (mr));
+
return true;
}
diff --git a/libbuild2/adhoc-rule-regex-pattern.hxx b/libbuild2/adhoc-rule-regex-pattern.hxx
index 597f30d..eb75ea0 100644
--- a/libbuild2/adhoc-rule-regex-pattern.hxx
+++ b/libbuild2/adhoc-rule-regex-pattern.hxx
@@ -32,7 +32,7 @@ namespace build2
names&&, const location&);
virtual bool
- match (action, target&, const string&, match_extra&) const override;
+ match (action, const target&, const string&, match_extra&) const override;
virtual void
apply_adhoc_members (action, target&,
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
index 4489e2b..966c7a8 100644
--- a/libbuild2/algorithm.cxx
+++ b/libbuild2/algorithm.cxx
@@ -504,8 +504,65 @@ namespace build2
// Return the matching rule or NULL if no match and try_match is true.
//
const rule_match*
- match_rule (action a, target& t, const rule* skip, bool try_match)
+ match_rule (action a, target& t,
+ const rule* skip,
+ bool try_match,
+ match_extra* pme)
{
+ using fallback_rule = adhoc_rule_pattern::fallback_rule;
+
+ auto adhoc_rule_match = [] (const rule_match& r)
+ {
+ return dynamic_cast<const adhoc_rule*> (&r.second.get ());
+ };
+
+ auto fallback_rule_match = [] (const rule_match& r)
+ {
+ return dynamic_cast<const fallback_rule*> (&r.second.get ());
+ };
+
+ // If this is a member of group-based target, then first try to find a
+ // matching ad hoc recipe/rule by matching (to an ad hoc recipe/rule) the
+ // group but applying to the member. See adhoc_rule::match() for
+ // background, including for why const_cast should be safe.
+ //
+ // To put it another way, if a group is matched by an ad hoc recipe/rule,
+ // then we want all the member to be matched to the same recipe/rule.
+ //
+ if (const group* g = t.group != nullptr ? t.group->is_a<group> () : nullptr)
+ {
+ assert (pme == nullptr);
+
+ // As an optimization, check if the group is already matched for this
+ // action. Note that this can become important when matching adhoc regex
+ // rules since we can potentially call match() for many members. This is
+ // probably ok for static members (of which we don't expect more than a
+ // handful) but can become an issue for dynamic members.
+ //
+ if (g->matched (a, memory_order_acquire))
+ {
+ const rule_match* r (g->state[a].rule);
+
+ if (r != nullptr && adhoc_rule_match (*r))
+ return r;
+
+ // Fall through: if some rule matched the group then it must also deal
+ // with the members.
+ }
+ else
+ {
+ // We cannot init match_extra from the target if it's unlocked so use
+ // a temporary (it shouldn't be modified if unlocked).
+ //
+ match_extra me (false /* locked */);
+ if (const rule_match* r = match_rule (
+ a, const_cast<group&> (*g), skip, true /* try_match */, &me))
+ return r;
+
+ // Fall through to normal match of the member.
+ }
+ }
+
const scope& bs (t.base_scope ());
// Match rules in project environment.
@@ -514,7 +571,7 @@ namespace build2
if (const scope* rs = bs.root_scope ())
penv = auto_project_env (*rs);
- match_extra& me (t[a].match_extra);
+ match_extra& me (pme == nullptr ? t[a].match_extra : *pme);
// First check for an ad hoc recipe.
//
@@ -619,8 +676,6 @@ namespace build2
// reverse_fallback() rather than it returning (a list) of
// reverse actions, which would be necessary to register them.
//
- using fallback_rule = adhoc_rule_pattern::fallback_rule;
-
auto find_fallback = [mo, o, tt] (const fallback_rule& fr)
-> const rule_match*
{
@@ -633,14 +688,19 @@ namespace build2
if (oi == 0)
{
- if (auto* fr =
- dynamic_cast<const fallback_rule*> (&r->second.get ()))
+ if (const fallback_rule* fr = fallback_rule_match (*r))
{
if ((r = find_fallback (*fr)) == nullptr)
continue;
}
}
+ // Skip non-ad hoc rules if the target is not locked (see
+ // above).
+ //
+ if (!me.locked && !adhoc_rule_match (*r))
+ continue;
+
const string& n (r->first);
const rule& ru (r->second);
@@ -672,14 +732,16 @@ namespace build2
if (oi == 0)
{
- if (auto* fr =
- dynamic_cast<const fallback_rule*> (&r1->second.get ()))
+ if (const fallback_rule* fr = fallback_rule_match (*r1))
{
if ((r1 = find_fallback (*fr)) == nullptr)
continue;
}
}
+ if (!me.locked && !adhoc_rule_match (*r1))
+ continue;
+
const string& n1 (r1->first);
const rule& ru1 (r1->second);
@@ -698,8 +760,7 @@ namespace build2
//
// @@ Can't we temporarily swap things out in target?
//
- match_extra me1;
- me1.init (oi == 0);
+ match_extra me1 (me.locked, oi == 0 /* fallback */);
if (!ru1.match (a, t, *hint, me1))
continue;
}
diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx
index 6029290..d2cd018 100644
--- a/libbuild2/algorithm.ixx
+++ b/libbuild2/algorithm.ixx
@@ -392,7 +392,10 @@ namespace build2
}
LIBBUILD2_SYMEXPORT const rule_match*
- match_rule (action, target&, const rule* skip, bool try_match = false);
+ match_rule (action, target&,
+ const rule* skip,
+ bool try_match = false,
+ match_extra* = nullptr);
LIBBUILD2_SYMEXPORT recipe
apply_impl (action, target&, const rule_match&);
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index e7268f9..c71f218 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -61,7 +61,7 @@ namespace build2
pbase_ = scope_->src_path_;
- file_based_ = tt.is_a<file> ();
+ file_based_ = tt.is_a<file> () || tt.is_a<group> ();
perform_update_ = find (as.begin (), as.end (), perform_update_id) !=
as.end ();
@@ -748,8 +748,8 @@ namespace build2
}
if (!file_based_)
- fail (l) << "'depdb' builtin can only be used for file-based "
- << "targets";
+ fail (l) << "'depdb' builtin can only be used for file- or "
+ << "file group-based targets";
if (!diag_preamble_.empty ())
fail (diag_loc ()) << "'diag' builtin call before 'depdb' call" <<
@@ -1287,7 +1287,7 @@ namespace build2
}
void parser::
- exec_depdb_preamble (action a, const scope& bs, const file& t,
+ exec_depdb_preamble (action a, const scope& bs, const target& t,
environment& e, const script& s, runner& r,
lines_iterator begin, lines_iterator end,
depdb& dd,
@@ -1314,7 +1314,7 @@ namespace build2
action a;
const scope& bs;
- const file& t;
+ const target& t;
environment& env;
const script& scr;
@@ -1360,7 +1360,7 @@ namespace build2
//
exec_depdb_dyndep (t, tt,
li, ll,
- data.a, data.bs, const_cast<file&> (data.t),
+ data.a, data.bs, const_cast<target&> (data.t),
data.dd,
*data.dyn_targets,
*data.update,
@@ -1645,7 +1645,7 @@ namespace build2
void parser::
exec_depdb_dyndep (token& lt, build2::script::token_type& ltt,
size_t li, const location& ll,
- action a, const scope& bs, file& t,
+ action a, const scope& bs, target& t,
depdb& dd,
paths& dyn_targets,
bool& update,
@@ -2455,6 +2455,8 @@ namespace build2
//
if (ops.drop_cycles ())
{
+ // @@ TODO: expl
+
for (const target* m (&t); m != nullptr; m = m->adhoc_member)
{
if (ft == m)
@@ -2874,7 +2876,7 @@ namespace build2
// that there are only real targets to start with.
//
optional<vector<const target*>> dts;
- for (const target* m (&t); m != nullptr; m = m->adhoc_member)
+ for (const target* m (&t); m != nullptr; m = m->adhoc_member) // @@ TODO: expl
{
if (m->decl != target_decl::real)
dts = vector<const target*> ();
@@ -2914,7 +2916,7 @@ namespace build2
//
if (dts)
{
- for (target* p (&t); p->adhoc_member != nullptr; )
+ for (target* p (&t); p->adhoc_member != nullptr; ) // @@ TODO: expl
{
target* m (p->adhoc_member);
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index b615a90..856ad64 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -103,8 +103,10 @@ namespace build2
// runner's enter() function is called before the first preamble/body
// command execution and leave() -- after the last command.
//
+ // Note: target must be file or group.
+ //
void
- execute_depdb_preamble (action a, const scope& base, const file& t,
+ execute_depdb_preamble (action a, const scope& base, const target& t,
environment& e, const script& s, runner& r,
depdb& dd)
{
@@ -219,8 +221,10 @@ namespace build2
names
exec_special (token&, build2::script::token_type&, bool skip_first);
+ // Note: target must be file or group.
+ //
void
- exec_depdb_preamble (action, const scope& base, const file&,
+ exec_depdb_preamble (action, const scope& base, const target&,
environment&, const script&, runner&,
lines_iterator begin, lines_iterator end,
depdb&,
@@ -230,10 +234,12 @@ namespace build2
bool* deferred_failure = nullptr,
dyndep_byproduct* = nullptr);
+ // Note: target must be file or group.
+ //
void
exec_depdb_dyndep (token&, build2::script::token_type&,
size_t line_index, const location&,
- action, const scope& base, file&,
+ action, const scope& base, target&,
depdb&,
paths& dyn_targets,
bool& update,
@@ -276,9 +282,9 @@ namespace build2
script* script_;
const small_vector<action, 1>* actions_; // Non-NULL during pre-parse.
- // True if this script is for file-based targets and performing update
- // is one of the actions, respectively. Only set for the pre-parse
- // mode.
+ // True if this script is for file- or file group-based targets and
+ // performing update is one of the actions, respectively. Only set for
+ // the pre-parse mode.
//
bool file_based_;
bool perform_update_;
diff --git a/libbuild2/build/script/runner.cxx b/libbuild2/build/script/runner.cxx
index c52ef66..e08ebbf 100644
--- a/libbuild2/build/script/runner.cxx
+++ b/libbuild2/build/script/runner.cxx
@@ -28,12 +28,29 @@ namespace build2
//
for (auto i (env.cleanups.begin ()); i != env.cleanups.end (); )
{
- const target* m (&env.target);
- for (; m != nullptr; m = m->adhoc_member)
+ const target* m (nullptr);
+ if (const group* g = env.target.is_a<group> ())
{
- if (const path_target* pm = m->is_a<path_target> ())
- if (i->path == pm->path ())
- break;
+ for (const target* gm: g->members)
+ {
+ if (const path_target* pm = gm->is_a<path_target> ())
+ {
+ if (i->path == pm->path ())
+ {
+ m = gm;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (m = &env.target; m != nullptr; m = m->adhoc_member)
+ {
+ if (const path_target* pm = m->is_a<path_target> ())
+ if (i->path == pm->path ())
+ break;
+ }
}
if (m != nullptr)
diff --git a/libbuild2/build/script/script.cxx b/libbuild2/build/script/script.cxx
index 9d9b5a8..0f31e7f 100644
--- a/libbuild2/build/script/script.cxx
+++ b/libbuild2/build/script/script.cxx
@@ -58,11 +58,27 @@ namespace build2
{
// $>
//
+ // What should it contain for an explicit group? While it may seem
+ // that just the members should be enough (and analogous to the ad
+ // hoc case), this won't let us get the group name for diagnostics.
+ // So the group name followed by all the members seems like the
+ // logical choice.
+ //
names ns;
- for (const target_type* m (&target);
- m != nullptr;
- m = m->adhoc_member)
- m->as_name (ns);
+
+ if (const group* g = target.is_a<group> ())
+ {
+ g->as_name (ns);
+ for (const target_type* m: g->members)
+ m->as_name (ns);
+ }
+ else
+ {
+ for (const target_type* m (&target);
+ m != nullptr;
+ m = m->adhoc_member)
+ m->as_name (ns);
+ }
assign (var_ts) = move (ns);
}
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index a00f6a5..45c56af 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -1267,6 +1267,9 @@ namespace build2
// rule for an explicit group that wishes to match based on some of
// its members feels far fetched.
//
+ // @@ TODO: expl: this can be used to inject static members (which
+ // otherwise would be tedious to repeat).
+ //
const location& mloc (gns.empty () ? location () : gns[0].member_loc);
if (!gns.empty () && gns[0].expl)
diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx
index 0b401c2..097e15a 100644
--- a/libbuild2/rule.cxx
+++ b/libbuild2/rule.cxx
@@ -44,7 +44,7 @@ namespace build2
// Use scratch match_extra since if there is no recipe, then we don't
// want to keep any changes and if there is, then we want it discarded.
//
- match_extra s;
+ match_extra s (true /* locked */); // Not called from adhoc_rule::match().
if (match_adhoc_recipe (action (a.meta_operation (), o), t, s) != nullptr)
return false;
}
@@ -73,7 +73,7 @@ namespace build2
{
if (!t.adhoc_recipes.empty ())
{
- match_extra s;
+ match_extra s (true /* locked */); // Not called from adhoc_rule::match().
if (match_adhoc_recipe (action (a.meta_operation (), o), t, s) != nullptr)
return false;
}
@@ -403,8 +403,9 @@ namespace build2
}
bool adhoc_rule::
- match (action a, target& t, const string& h, match_extra& me) const
+ match (action a, target& xt, const string& h, match_extra& me) const
{
+ const target& t (xt);
return pattern == nullptr || pattern->match (a, t, h, me);
}
diff --git a/libbuild2/rule.hxx b/libbuild2/rule.hxx
index 913c597..b89821b 100644
--- a/libbuild2/rule.hxx
+++ b/libbuild2/rule.hxx
@@ -252,6 +252,16 @@ namespace build2
// The default implementation forwards to the pattern's match() if there
// is a pattern and returns true otherwise.
//
+ // Note also that in case of a member of a group-based target, match() is
+ // called on the group while apply() on the member (see match_rule() in
+ // algorithms.cxx for details). This means that match() may be called
+ // without having the target locked and as a result match() should (unless
+ // known to only match a non-group) treat the target as const and only
+ // rely on immutable information (type, name, etc) since the group could
+ // be matched concurrenly. This case can be detected by examining
+ // match_extra::locked (see adhoc_rule_regex_pattern::match() for a
+ // use-case).
+ //
virtual bool
match (action, target&, const string&, match_extra&) const override;
@@ -318,8 +328,10 @@ namespace build2
~adhoc_rule_pattern ();
public:
+ // Note: the adhoc_rule::match() restrictions apply here as well.
+ //
virtual bool
- match (action, target&, const string&, match_extra&) const = 0;
+ match (action, const target&, const string&, match_extra&) const = 0;
virtual void
apply_adhoc_members (action, target&,
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index a9ae47a..c1002b9 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -1217,6 +1217,42 @@ namespace build2
target_type::flag::none
};
+ // group
+ //
+ group_view group::
+ group_members (action a) const
+ {
+ if (members_on == 0) // Not yet discovered.
+ return group_view {nullptr, 0};
+
+ // Members discovered during anything other than perform_update are only
+ // good for that operation. For example, we only return the static members
+ // ("representative sample") for perform_configure.
+ //
+ // We also re-discover the members on each update and clean not to
+ // overcomplicate the already twisted adhoc_buildscript_rule::apply()
+ // logic.
+ //
+ if (members_on != ctx.current_on)
+ {
+ if (members_action != perform_update_id ||
+ a == perform_update_id ||
+ a == perform_clean_id)
+ return group_view {nullptr, 0};
+ }
+
+ // Note that we may have no members (e.g., perform_configure and there are
+ // no static members). However, whether std::vector returns a non-NULL
+ // pointer in this case is undefined.
+ //
+ size_t n (members.size ());
+ return group_view {
+ n != 0
+ ? members.data ()
+ : reinterpret_cast<const target* const*> (this),
+ n};
+ }
+
const target_type group::static_type
{
"group",
@@ -1230,6 +1266,8 @@ namespace build2
target_type::flag::group
};
+ // alias
+ //
static const target*
alias_search (const target& t, const prerequisite_key& pk)
{
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx
index 01e82bb..428b4a0 100644
--- a/libbuild2/target.hxx
+++ b/libbuild2/target.hxx
@@ -170,6 +170,7 @@ namespace build2
//
struct match_extra
{
+ bool locked; // Normally true (see adhoc_rule::match() for background).
bool fallback; // True if matching a fallback rule (see match_rule()).
// Auxiliary data storage.
@@ -237,7 +238,12 @@ namespace build2
// Implementation details.
//
+ // NOTE: see match_rule() in algorithms.cxx if changing anything here.
+ //
public:
+ explicit
+ match_extra (bool l = true, bool f = false): locked (l), fallback (f) {}
+
void
init (bool fallback);
@@ -852,8 +858,11 @@ namespace build2
// Return true if the target has been matched for the specified action.
// This function can only be called during the match or execute phases.
//
+ // If you need to observe something in the matched target (e.g., the
+ // matched rule), use memory_order_acquire.
+ //
bool
- matched (action) const;
+ matched (action, memory_order mo = memory_order_relaxed) const;
// This function can only be called during match if we have observed
// (synchronization-wise) that this target has been matched (i.e., the
@@ -2160,15 +2169,34 @@ namespace build2
// Note that it is not see-through but a derived group can be made see-
// through via the [see_through] attribute.
//
- // Note that normally you wouldn't use it as a base for a custom group
- // defined in C++, instead deriving from mtime_target directly and using a
- // custom members layout more appropriate for the group's semantics.
+ // Note also that you shouldn't use it as a base for a custom group defined
+ // in C++, instead deriving from mtime_target directly and using a custom
+ // members layout more appropriate for the group's semantics. To put it
+ // another way, a group-based target should only be matched by an ad hoc
+ // recipe/rule (see match_rule() in algorithms.cxx for details).
//
class LIBBUILD2_SYMEXPORT group: public mtime_target
{
public:
vector<reference_wrapper<const target>> static_members;
+ // Note: we expect no NULL entries in members.
+ //
+ vector<const target*> members; // Layout compatible with group_view.
+ action members_action; // Action on which members were resolved.
+ size_t members_on = 0; // Operation number on which members were resolved.
+
+ void
+ reset_members (action a)
+ {
+ members.clear ();
+ members_action = a;
+ members_on = ctx.current_on;
+ }
+
+ virtual group_view
+ group_members (action) const override;
+
group (context& c, dir_path d, dir_path o, string n)
: mtime_target (c, move (d), move (o), move (n))
{
diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx
index 899e829..3f005c3 100644
--- a/libbuild2/target.ixx
+++ b/libbuild2/target.ixx
@@ -235,13 +235,13 @@ namespace build2
}
inline bool target::
- matched (action a) const
+ matched (action a, memory_order mo) const
{
assert (ctx.phase == run_phase::match ||
ctx.phase == run_phase::execute);
const opstate& s (state[a]);
- size_t c (s.task_count.load (memory_order_relaxed) - ctx.count_base ());
+ size_t c (s.task_count.load (mo) - ctx.count_base ());
if (ctx.phase == run_phase::match)
{