aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/dyndep.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/dyndep.cxx')
-rw-r--r--libbuild2/dyndep.cxx425
1 files changed, 369 insertions, 56 deletions
diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx
index 61fa8cb..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>
@@ -56,9 +57,45 @@ 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,
@@ -81,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));
@@ -96,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";
}
@@ -119,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,
@@ -384,6 +424,7 @@ 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,
@@ -392,14 +433,26 @@ namespace build2
// NOTE: see enter_header() caching logic if changing anyting here with
// regards to the target and base scope usage.
- // Find or maybe insert the target. The directory is only moved from if
- // insert is true. Note that it must be normalized.
+ 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.
//
@@ -422,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).
//
@@ -435,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;
+ }
}
}
@@ -453,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 << "'";});
@@ -487,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
@@ -499,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;
@@ -510,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;});
@@ -549,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);
};
@@ -653,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;
@@ -713,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);
}
}
@@ -732,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);
}
@@ -740,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,
@@ -748,52 +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);
}
- const file& dyndep_rule::
- inject_group_member (action a, const scope& bs, mtime_target& g,
- path p, const target_type& tt)
+ 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)
{
- path n (p.leaf ());
- string e (n.extension ());
+ // NOTE: see adhoc_rule_regex_pattern::apply_group_members() for a variant
+ // of the same code.
- // Assume nobody else can insert these members (seems reasonable seeing
- // that their names are dynamically discovered).
+ // 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,
- p.directory (),
+ f.directory (),
dir_path (), // Always in out.
- move (n.make_base ()).string (),
+ 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 (p)); // Only do this once.
+ t.path (move (f));
+ return pair<const file&, bool> (t, true);
}
else
- // Must have been already done (e.g., on previous operation in a
- // batch).
+ {
+ 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.
//
- assert (t.group == &g);
+ 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 ();
- // This shouldn't fail since we are the only ones that should be matching
- // this target.
+ // Map extension to the target type, falling back to the fallback type.
//
- target_lock tl (lock (a, t));
- assert (tl);
+ const target_type& tt (
+ map_target_type (what, bs, f, n.string (), e, map_ext, fallback));
- match_inc_dependents (a, g);
- match_recipe (tl, group_recipe);
- return t;
+ return inject_adhoc_group_member_impl (
+ a, bs, t, move (f), move (n).string (), move (e), tt);
}
}