diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2023-05-23 09:23:16 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2023-05-29 10:21:12 +0200 |
commit | e05f7c7383cc48823bd408c0bc5187191a9a1c48 (patch) | |
tree | 024bb9c3be9c876b8822388baf1622cad03d7563 /libbuild2/adhoc-rule-buildscript.cxx | |
parent | 349b34108752e2fcf1ead648ffec8e5acfe4a91c (diff) |
Explicit group: static members
Diffstat (limited to 'libbuild2/adhoc-rule-buildscript.cxx')
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.cxx | 369 |
1 files changed, 299 insertions, 70 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 |