diff options
Diffstat (limited to 'libbuild2/dyndep.cxx')
-rw-r--r-- | libbuild2/dyndep.cxx | 508 |
1 files changed, 409 insertions, 99 deletions
diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx index 73fc8eb..dbeb47e 100644 --- a/libbuild2/dyndep.cxx +++ b/libbuild2/dyndep.cxx @@ -5,6 +5,7 @@ #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> +#include <libbuild2/search.hxx> #include <libbuild2/context.hxx> #include <libbuild2/algorithm.hxx> #include <libbuild2/filesystem.hxx> @@ -18,61 +19,7 @@ namespace build2 bool dyndep_rule:: update (tracer& trace, action a, const target& t, timestamp ts) { - // In particular, this function is used to make sure header dependencies - // are up to date. - // - // There would normally be a lot of headers for every source file (think - // all the system headers) and just calling execute_direct() on all of - // them can get expensive. At the same time, most of these headers are - // existing files that we will never be updating (again, system headers, - // for example) and the rule that will match them is the fallback - // file_rule. That rule has an optimization: it returns noop_recipe (which - // causes the target state to be automatically set to unchanged) if the - // file is known to be up to date. So we do the update "smartly". - // - const path_target* pt (t.is_a<path_target> ()); - - if (pt == nullptr) - ts = timestamp_unknown; - - target_state os (t.matched_state (a)); - - if (os == target_state::unchanged) - { - if (ts == timestamp_unknown) - return false; - else - { - // We expect the timestamp to be known (i.e., existing file). - // - timestamp mt (pt->mtime ()); - assert (mt != timestamp_unknown); - return mt > ts; - } - } - else - { - // We only want to return true if our call to execute() actually caused - // an update. In particular, the target could already have been in - // target_state::changed because of the dynamic dependency extraction - // run for some other target. - // - // @@ MT perf: so we are going to switch the phase and execute for - // any generated header. - // - phase_switch ps (t.ctx, run_phase::execute); - target_state ns (execute_direct (a, t)); - - if (ns != os && ns != target_state::unchanged) - { - l6 ([&]{trace << "updated " << t - << "; old state " << os - << "; new state " << ns;}); - return true; - } - else - return ts != timestamp_unknown ? pt->newer (ts, ns) : false; - } + return update_during_match (trace, a, t, ts); } optional<bool> dyndep_rule:: @@ -84,11 +31,11 @@ namespace build2 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 - // update ..."). + // Even if failing we still use try_match_sync() in order to issue + // consistent (with other places) diagnostics (rather than the generic + // "not rule to update ..."). // - if (!try_match (a, pt).first) + if (!try_match_sync (a, pt).first) { if (!f) return nullopt; @@ -110,16 +57,52 @@ namespace build2 return r; } + // Check if the specified prerequisite is updated during match by any other + // prerequisites of the specified target, recursively. + // + static bool + updated_during_match (action a, const target& t, size_t pts_n, + const target& pt) + { + const auto& pts (t.prerequisite_targets[a]); + + for (size_t i (0); i != pts_n; ++i) + { + const prerequisite_target& p (pts[i]); + + // If include_target flag is specified, then p.data contains the + // target pointer. + // + if (const target* xt = + (p.target != nullptr ? p.target : + ((p.include & prerequisite_target::include_target) != 0 + ? reinterpret_cast<target*> (p.data) + : nullptr))) + { + if (xt == &pt && (p.include & prerequisite_target::include_udm) != 0) + return true; + + if (size_t n = xt->prerequisite_targets[a].size ()) + { + if (updated_during_match (a, *xt, n, pt)) + return true; + } + } + } + + return false; + } + optional<bool> dyndep_rule:: inject_existing_file (tracer& trace, const char* what, - action a, target& t, + action a, target& t, size_t pts_n, const file& pt, timestamp mt, bool f, bool adhoc, uintptr_t data) { - if (!try_match (a, pt).first) + if (!try_match_sync (a, pt).first) { if (!f) return nullopt; @@ -135,8 +118,11 @@ namespace build2 recipe_function* const* rf (pt[a].recipe.target<recipe_function*> ()); if (rf == nullptr || *rf != &noop_action) { - fail << what << ' ' << pt << " has non-noop recipe" << - info << "consider listing it as static prerequisite of " << t; + if (pts_n == 0 || !updated_during_match (a, t, pts_n, pt)) + { + fail << what << ' ' << pt << " has non-noop recipe" << + info << "consider listing it as static prerequisite of " << t; + } } bool r (update (trace, a, pt, mt)); @@ -150,21 +136,27 @@ namespace build2 void dyndep_rule:: verify_existing_file (tracer&, const char* what, - action a, const target& t, + action a, const target& t, size_t pts_n, const file& pt) { diag_record dr; - if (pt.matched (a)) + if (pt.matched (a, memory_order_acquire)) { recipe_function* const* rf (pt[a].recipe.target<recipe_function*> ()); if (rf == nullptr || *rf != &noop_action) { - dr << fail << what << ' ' << pt << " has non-noop recipe"; + if (pts_n == 0 || !updated_during_match (a, t, pts_n, pt)) + { + dr << fail << what << ' ' << pt << " has non-noop recipe"; + } } } else if (pt.decl == target_decl::real) { + // Note that this target could not possibly be updated during match + // since it's not matched. + // dr << fail << what << ' ' << pt << " is explicitly declared as " << "target and may have non-noop recipe"; } @@ -173,12 +165,6 @@ namespace build2 dr << info << "consider listing it as static prerequisite of " << t; } - // Reverse-lookup target type(s) from file name/extension. - // - // If the list of base target types is specified, then only these types and - // those derived from them are considered. Otherwise, any file-based type is - // considered but not the file type itself. - // small_vector<const target_type*, 2> dyndep_rule:: map_extension (const scope& bs, const string& n, const string& e, @@ -404,7 +390,7 @@ namespace build2 prev_ = nullptr; } - // See if this path is inside a project with an out-of-tree build and is + // See if this path is inside a project with an out of source build and is // in the out directory tree. // const scope& bs (ctx_.scopes.find_out (d)); @@ -438,19 +424,35 @@ namespace build2 action a, const scope& bs, const target& t, path& fp, bool cache, bool norm, bool insert, + bool dynamic, const function<dyndep_rule::map_extension_func>& map_extension, const target_type& fallback, const function<dyndep_rule::prefix_map_func>& get_pfx_map, const dyndep_rule::srcout_map& so_map) { - // Find or maybe insert the target. The directory is only moved from if - // insert is true. Note that it must be normalized. + // NOTE: see enter_header() caching logic if changing anyting here with + // regards to the target and base scope usage. + + assert (!insert || t.ctx.phase == run_phase::match); + + // Find or maybe insert the target. + // + // If insert is false, then don't consider dynamically-created targets + // (i.e., those that are not real or implied) unless dynamic is true, in + // which case return the target that would have been inserted. + // + // The directory is only moved from if insert is true. Note that it must + // be absolute and normalized. // - auto find = [&trace, what, &t, - &map_extension, &fallback] (dir_path&& d, - path&& f, - bool insert) -> const file* + auto find = [&trace, what, &bs, &t, + &map_extension, + &fallback] (dir_path&& d, + path&& f, + bool insert, + bool dynamic = false) -> const file* { + context& ctx (t.ctx); + // Split the file into its name part and extension. Here we can assume // the name part is a valid filesystem name. // @@ -473,7 +475,7 @@ namespace build2 dir_path out; // It's possible the extension-to-target type mapping is ambiguous (for - // example, because both C and X-language headers use the same .h + // example, because both C and C++-language headers use the same .h // extension). In this case we will first try to find one that matches // an explicit target (similar logic to when insert is false). // @@ -486,15 +488,40 @@ namespace build2 // pick the first one (it's highly unlikely the source file extension // mapping will differ based on the configuration). // + // Note that we also need to remember the base scope for search() below + // (failed that, search_existing_file() will refuse to look). + // + const scope* s (nullptr); { - const scope& bs (**t.ctx.scopes.find (d).first); - if (const scope* rs = bs.root_scope ()) + // While we cannot accurately associate in the general case, we can do + // so if the path belongs to this project. + // + const scope& rs (*bs.root_scope ()); + bool src (false); + if (d.sub (rs.out_path ()) || + (src = (!rs.out_eq_src () && d.sub (rs.src_path ())))) { if (map_extension != nullptr) tts = map_extension (bs, n, e); - if (!bs.out_eq_src () && d.sub (bs.src_path ())) - out = out_src (d, *rs); + if (src) + out = out_src (d, rs); + + s = &bs; + } + else + { + const scope& bs (**ctx.scopes.find (d).first); + if (const scope* rs = bs.root_scope ()) + { + if (map_extension != nullptr) + tts = map_extension (bs, n, e); + + if (!rs->out_eq_src () && d.sub (rs->src_path ())) + out = out_src (d, *rs); + + s = &bs; + } } } @@ -504,9 +531,9 @@ namespace build2 if (tts.empty ()) { // If the project doesn't "know" this extension then we can't possibly - // find an explicit target of this type. + // find a real or implied target of this type. // - if (!insert) + if (!insert && !dynamic) { l6 ([&]{trace << "unknown " << what << ' ' << n << " extension '" << e << "'";}); @@ -538,7 +565,7 @@ namespace build2 { const target_type& tt (*tts[i]); - if (const target* x = t.ctx.targets.find (tt, d, out, n, e, trace)) + if (const target* x = ctx.targets.find (tt, d, out, n, e, trace)) { // What would be the harm in reusing a dynamically-inserted target // if there is no buildfile-mentioned one? Probably none (since it @@ -550,8 +577,7 @@ namespace build2 // implied ones because pre-entered members of a target group // (e.g., cli.cxx) are implied. // - if (x->decl == target_decl::real || - x->decl == target_decl::implied) + if (operator>= (x->decl, target_decl::implied)) // @@ VC14 { r = x; break; @@ -561,7 +587,7 @@ namespace build2 // Cache the dynamic target corresponding to tts[0] since that's // what we will be inserting (see below). // - if (insert && i == 0) + if ((insert || dynamic) && i == 0) f = x; l6 ([&]{trace << "dynamic target with target type " << tt.name;}); @@ -571,7 +597,7 @@ namespace build2 l6 ([&]{trace << "no target with target type " << tt.name;}); } - // Note: we can't do this because of the in-source builds where there + // Note: we can't do this because of the in source builds where there // won't be explicit targets for non-generated files. // // This should be harmless, however, since in our world generated file @@ -600,10 +626,29 @@ namespace build2 r = f; } - // @@ OPT: move d, out, n - // if (r == nullptr && insert) - r = &search (t, *tts[0], d, out, n, &e, nullptr); + { + // Like search(t, pk) but don't fail if the target is in src. + // + // While it may seem like there is not much difference, the caller may + // actually do more than just issue more specific diagnostics. For + // example, it may defer the failure to the tool diagnostics. + // +#if 0 + r = &search (t, *tts[0], d, out, n, &e, s); +#else + prerequisite_key pk {nullopt, {tts[0], &d, &out, &n, move (e)}, s}; + + r = pk.tk.type->search (ctx, &t, pk); + + if (r == nullptr && pk.tk.out->empty ()) + { + auto p (ctx.scopes.find (d, false)); + if (*p.first != nullptr || ++p.first == p.second) + r = &create_new_target (ctx, pk); + } +#endif + } return static_cast<const file*> (r); }; @@ -615,6 +660,9 @@ namespace build2 // Note: we now always use absolute path to the translation unit so this // no longer applies. But let's keep it for posterity. // + // Also note that we now assume (see cc::compile_rule::enter_header()) a + // relative path signifies a generated header. + // #if 0 if (f.relative () && rels.relative ()) { @@ -644,7 +692,7 @@ namespace build2 const file* pt (nullptr); bool remapped (false); - // If still relative then it does not exist. + // If relative then it does not exist. // if (fp.relative ()) { @@ -701,7 +749,11 @@ namespace build2 // Maybe for diagnostics (i.e., we will actually try to build // something there instead of just saying no mapping). // - pt = find (pd / d, fp.leaf (), insert && !i->first.empty ()); + if (i->first.empty ()) + pt = find (pd / d, fp.leaf (), false); + else + pt = find (pd / d, fp.leaf (), insert, dynamic); + if (pt != nullptr) { fp = pd / fp; @@ -761,7 +813,7 @@ namespace build2 if (pt == nullptr) { l6 ([&]{trace << (insert ? "entering " : "finding ") << fp;}); - pt = find (fp.directory (), fp.leaf (), insert); + pt = find (fp.directory (), fp.leaf (), insert, dynamic); } } @@ -780,7 +832,7 @@ namespace build2 return enter_file_impl (trace, what, a, bs, t, fp, cache, norm, - true /* insert */, + true /* insert */, false, map_ext, fallback, pfx_map, so_map); } @@ -788,6 +840,7 @@ namespace build2 find_file (tracer& trace, const char* what, action a, const scope& bs, const target& t, path& fp, bool cache, bool norm, + bool dynamic, const function<map_extension_func>& map_ext, const target_type& fallback, const function<prefix_map_func>& pfx_map, @@ -796,7 +849,264 @@ namespace build2 return enter_file_impl (trace, what, a, bs, t, fp, cache, norm, - false /* insert */, + false /* insert */, dynamic, map_ext, fallback, pfx_map, so_map); } + + static pair<const file&, bool> + inject_group_member_impl (action a, const scope& bs, mtime_target& g, + path f, string n, string e, + const target_type& tt, + const function<dyndep_rule::group_filter_func>& fl) + { + // NOTE: see adhoc_rule_regex_pattern::apply_group_members() for a variant + // of the same code. + + // Note that we used to directly match such a member with group_recipe. + // But that messes up our dependency counts since we don't really know + // whether someone will execute such a member. + // + // So instead we now just link the member up to the group and rely on the + // special semantics in match_rule_impl() for groups with the dyn_members + // flag. + // + assert ((g.type ().flags & target_type::flag::dyn_members) == + target_type::flag::dyn_members); + + // We expect that nobody else can insert these members (seems reasonable + // seeing that their names are dynamically discovered). + // + auto l (search_new_locked ( + bs.ctx, + tt, + f.directory (), + dir_path (), // Always in out. + move (n), + &e, + &bs)); + + const file& t (l.first.as<file> ()); // Note: non-const only if have lock. + + // We don't need to match the group recipe directy from ad hoc + // recipes/rules due to the special semantics for explicit group members + // in match_rule_impl(). This is what skip_match is for. + // + if (l.second) + { + l.first.group = &g; + l.second.unlock (); + t.path (move (f)); + return pair<const file&, bool> (t, true); + } + else + { + if (fl != nullptr && !fl (g, t)) + return pair<const file&, bool> (t, false); + } + + // Check if we already belong to this group. Note that this not a mere + // optimization since we may be in the member->group->member chain and + // trying to lock the member the second time would deadlock (this can be + // triggered, for example, by dist, which sort of depends on such members + // directly... which was not quite correct and is now fixed). + // + if (t.group == &g) // Note: atomic. + t.path (move (f)); + else + { + // This shouldn't normally fail since we are the only ones that should + // know about this target (otherwise why is it dynamicaly discovered). + // However, nothing prevents the user from depending on such a target, + // however misguided. + // + target_lock tl (lock (a, t)); + + if (!tl) + fail << "group " << g << " member " << t << " is already matched" << + info << "dynamically extracted group members cannot be used as " + << "prerequisites directly, only via group"; + + if (t.group == nullptr) + tl.target->group = &g; + else if (t.group != &g) + fail << "group " << g << " member " << t + << " is already member of group " << *t.group; + + t.path (move (f)); + } + + return pair<const file&, bool> (t, true); + } + + pair<const file&, bool> dyndep_rule:: + inject_group_member (action a, const scope& bs, mtime_target& g, + path f, + const target_type& tt, + const function<group_filter_func>& filter) + { + path n (f.leaf ()); + string e (n.extension ()); + n.make_base (); + + return inject_group_member_impl (a, bs, g, + move (f), move (n).string (), move (e), + tt, + filter); + } + + static const target_type& + map_target_type (const char* what, + const scope& bs, + const path& f, const string& n, const string& e, + const function<dyndep_rule::map_extension_func>& map_ext, + const target_type& fallback) + { + // Map extension to the target type, falling back to the fallback type. + // + small_vector<const target_type*, 2> tts; + if (map_ext != nullptr) + tts = map_ext (bs, n, e); + + // Not sure what else we can do in this case. + // + if (tts.size () > 1) + { + diag_record dr (fail); + + dr << "mapping of " << what << " target path " << f + << " to target type is ambiguous"; + + for (const target_type* tt: tts) + dr << info << "can be " << tt->name << "{}"; + } + + const target_type& tt (tts.empty () ? fallback : *tts.front ()); + + if (!tt.is_a<file> ()) + { + fail << what << " target path " << f << " mapped to non-file-based " + << "target type " << tt.name << "{}"; + } + + return tt; + } + + pair<const file&, bool> dyndep_rule:: + inject_group_member (const char* what, + action a, const scope& bs, mtime_target& g, + path f, + const function<map_extension_func>& map_ext, + const target_type& fallback, + const function<group_filter_func>& filter) + { + path n (f.leaf ()); + string e (n.extension ()); + n.make_base (); + + // Map extension to the target type, falling back to the fallback type. + // + const target_type& tt ( + map_target_type (what, bs, f, n.string (), e, map_ext, fallback)); + + return inject_group_member_impl (a, bs, g, + move (f), move (n).string (), move (e), + tt, + filter); + } + + pair<const file&, bool> + inject_adhoc_group_member_impl (action, const scope& bs, target& t, + path f, string n, string e, + const target_type& tt) + { + // Assume nobody else can insert these members (seems reasonable seeing + // that their names are dynamically discovered). + // + auto l (search_new_locked ( + bs.ctx, + tt, + f.directory (), + dir_path (), // Always in out. + move (n), + &e, + &bs)); + + file* ft (&l.first.as<file> ()); // Note: non-const only if locked. + + // Skip if this is one of the static targets (or a duplicate of the + // dynamic target). + // + // In particular, we expect to skip all the targets that we could not lock + // (e.g., in case all of this has already been done for the previous + // operation in a batch; make sure to test `update update update` and + // `update clean update ...` batches if changing anything here). + // + // While at it also find the ad hoc members list tail. + // + const_ptr<target>* tail (&t.adhoc_member); + for (target* m (&t); m != nullptr; m = m->adhoc_member) + { + if (ft == m) + { + tail = nullptr; + break; + } + + tail = &m->adhoc_member; + } + + if (tail == nullptr) + return pair<const file&, bool> (*ft, false); + + if (!l.second) + fail << "dynamic target " << *ft << " already exists and cannot be " + << "made ad hoc member of group " << t; + + ft->group = &t; + l.second.unlock (); + + // We need to be able to distinguish static targets from dynamic (see the + // static set hashing in adhoc_buildscript_rule::apply() for details). + // + assert (ft->decl != target_decl::real); + + *tail = ft; + ft->path (move (f)); + + return pair<const file&, bool> (*ft, true); + } + + pair<const file&, bool> dyndep_rule:: + inject_adhoc_group_member (action a, const scope& bs, target& t, + path f, + const target_type& tt) + { + path n (f.leaf ()); + string e (n.extension ()); + n.make_base (); + + return inject_adhoc_group_member_impl ( + a, bs, t, move (f), move (n).string (), move (e), tt); + } + + pair<const file&, bool> dyndep_rule:: + inject_adhoc_group_member (const char* what, + action a, const scope& bs, target& t, + path f, + const function<map_extension_func>& map_ext, + const target_type& fallback) + { + path n (f.leaf ()); + string e (n.extension ()); + n.make_base (); + + // Map extension to the target type, falling back to the fallback type. + // + const target_type& tt ( + map_target_type (what, bs, f, n.string (), e, map_ext, fallback)); + + + return inject_adhoc_group_member_impl ( + a, bs, t, move (f), move (n).string (), move (e), tt); + } } |