aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/target.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/target.cxx')
-rw-r--r--libbuild2/target.cxx451
1 files changed, 341 insertions, 110 deletions
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index bc5dbba..fb47b6d 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -22,26 +22,13 @@ namespace build2
bool target_type::
is_a (const char* n) const
{
- if (strcmp (name, n) == 0)
- return true;
-
- for (const target_type* b (base); b != nullptr; b = b->base)
+ for (const target_type* b (this); b != nullptr; b = b->base)
if (strcmp (b->name, n) == 0)
return true;
return false;
}
- bool target_type::
- is_a_base (const target_type& tt) const
- {
- for (const target_type* b (base); b != nullptr; b = b->base)
- if (*b == tt)
- return true;
-
- return false;
- }
-
// target_key
//
void target_key::
@@ -51,7 +38,9 @@ namespace build2
if (!name->empty ())
{
v = *name;
- target::combine_name (v, ext, false /* @@ TODO: what to do? */);
+ // @@ TMP: see also other calls to combine_name() -- need to fix.
+ //
+ target::combine_name (v, ext, false /* @@ TMP: what to do? */);
}
else
assert (!ext || ext->empty ()); // Unspecified or none.
@@ -69,6 +58,7 @@ namespace build2
//
static const char* const target_state_[] =
{
+ "<invalid>", // Absent/invalid (see target_state for details).
"unknown",
"unchanged",
"postponed",
@@ -78,10 +68,10 @@ namespace build2
"group"
};
- ostream&
- operator<< (ostream& os, target_state ts)
+ string
+ to_string (target_state ts)
{
- return os << target_state_[static_cast<uint8_t> (ts)];
+ return target_state_[static_cast<uint8_t> (ts)];
}
// target
@@ -91,7 +81,6 @@ namespace build2
target::
~target ()
{
- clear_data ();
}
const string& target::
@@ -122,27 +111,33 @@ namespace build2
group_view target::
group_members (action) const
{
- assert (false); // Not a group or doesn't expose its members.
+ // Not a group or doesn't expose its members.
+ //
return group_view {nullptr, 0};
}
const scope& target::
- base_scope () const
+ base_scope_impl () const
{
// If this target is from the src tree, use its out directory to find
// the scope.
//
- return ctx.scopes.find_out (out_dir ());
- }
+ const scope& s (ctx.scopes.find_out (out_dir ()));
- const scope& target::
- root_scope () const
- {
- // This is tricky to cache so we do the lookup for now.
+ // Cache unless we are in the load phase.
//
- const scope* r (base_scope ().root_scope ());
- assert (r != nullptr);
- return *r;
+ if (ctx.phase != run_phase::load)
+ {
+ const scope* e (nullptr);
+ if (!base_scope_.compare_exchange_strong (
+ e,
+ &s,
+ memory_order_release,
+ memory_order_consume))
+ assert (e == &s);
+ }
+
+ return s;
}
pair<lookup, size_t> target::
@@ -166,6 +161,11 @@ namespace build2
{
++r.second;
+ // While we went back to not treating the first member as a group for
+ // variable lookup, let's keep this logic in case one day we end up with
+ // a separate ad hoc group target.
+ //
+#if 0
// In case of an ad hoc group, we may have to look in two groups.
//
if ((g1 = group) != nullptr)
@@ -183,6 +183,19 @@ namespace build2
}
}
}
+#else
+ // Skip looking up in the ad hoc group, which is semantically the
+ // first/primary member.
+ //
+ if ((g1 = group == nullptr
+ ? nullptr
+ : group->adhoc_group () ? group->group : group))
+ {
+ auto p (g1->vars.lookup (var));
+ if (p.first != nullptr)
+ r.first = lookup_type (*p.first, p.second, g1->vars);
+ }
+#endif
}
// Delegate to scope's lookup_original().
@@ -541,40 +554,127 @@ namespace build2
// include()
//
+ // See var_include documentation for details on what's going on here.
+ //
include_type
include_impl (action a,
const target& t,
const prerequisite& p,
- const target* m)
+ const target* m,
+ lookup* rl)
{
context& ctx (t.ctx);
include_type r (include_type::normal);
+ {
+ lookup l (p.vars[ctx.var_include]);
+
+ if (l.defined ())
+ {
+ if (l->null)
+ {
+ // @@ TMP (added in 0.16.0).
+ //
+ warn << "null " << *ctx.var_include << " variable value specified "
+ << "for prerequisite " << p <<
+ info << "treated as undefined for backwards compatibility" <<
+ info << "this warning will become error in the future";
+ }
+ else
+ {
+ const string& v (cast<string> (*l));
- // If var_clean is defined, then it takes precedence over include for
- // the clean operation.
+ if (v == "false") r = include_type::excluded;
+ else if (v == "true") r = include_type::normal;
+ else if (v == "adhoc") r = include_type::adhoc;
+ else if (v == "posthoc") r = include_type::posthoc;
+ else
+ fail << "invalid " << *ctx.var_include << " variable value '"
+ << v << "' specified for prerequisite " << p;
+ }
+ }
+ }
+
+ // Handle operation-specific override (see var_include documentation
+ // for details).
//
lookup l;
- if (a.operation () == clean_id && (l = p.vars[ctx.var_clean]))
- {
- r = cast<bool> (l) ? include_type::normal : include_type::excluded;
- }
- else if (const string* v = cast_null<string> (p.vars[ctx.var_include]))
+ optional<bool> r1; // Absent means something other than true|false.
+
+ names storage;
+ names_view ns;
+ const variable* ovar (nullptr);
+
+ if (r != include_type::excluded)
{
- if (*v == "false") r = include_type::excluded;
- else if (*v == "adhoc") r = include_type::adhoc;
- else if (*v == "true") r = include_type::normal;
- else
- fail << "invalid " << ctx.var_include->name << " variable value "
- << "'" << *v << "' specified for prerequisite " << p;
+ // Instead of going via potentially expensive target::base_scope(), use
+ // the prerequisite's scope; while it may not be the same as the
+ // targets's base scope, they must have the same root scope.
+ //
+ const scope& rs (*p.scope.root_scope ());
+
+ ovar = rs.root_extra->operations[
+ (a.outer ()
+ ? ctx.current_outer_oif
+ : ctx.current_inner_oif)->id].ovar;
+
+ if (ovar != nullptr)
+ {
+ l = p.vars[*ovar];
+
+ if (l.defined ())
+ {
+ if (l->null)
+ fail << "null " << *ovar << " variable value specified for "
+ << "prerequisite " << p;
+
+ // Maybe we should optimize this for the common cases (bool, path,
+ // name)? But then again we don't expect many such overrides. Plus
+ // will complicate the diagnostics below.
+ //
+ ns = reverse (*l, storage, true /* reduce */);
+
+ if (ns.size () == 1)
+ {
+ const name& n (ns[0]);
+
+ if (n.simple ())
+ {
+ const string& v (n.value);
+
+ if (v == "false")
+ r1 = false;
+ else if (v == "true")
+ r1 = true;
+ }
+ }
+
+ if (r1 && !*r1)
+ r = include_type::excluded;
+ }
+ }
}
// Call the meta-operation override, if any (currently used by dist).
//
- if (r != include_type::normal)
+ if (r != include_type::normal || l)
{
if (auto f = ctx.current_mif->include)
- r = f (a, t, prerequisite_member {p, m}, r);
+ r = f (a, t, prerequisite_member {p, m}, r, l);
+ }
+
+ if (l)
+ {
+ if (rl != nullptr)
+ *rl = l;
+ else if (!r1)
+ {
+ // Note: we have to delay this until the meta-operation callback above
+ // had a chance to override it.
+ //
+ fail << "unrecognized " << *ovar << " variable value '" << ns
+ << "' specified for prerequisite " << p;
+ }
}
return r;
@@ -585,7 +685,9 @@ namespace build2
const target* target_set::
find (const target_key& k, tracer& trace) const
{
- slock sl (mutex_);
+ bool load (ctx.phase == run_phase::load);
+
+ slock sl (mutex_, defer_lock); if (!load) sl.lock ();
map_type::const_iterator i (map_.find (k));
if (i == map_.end ())
@@ -604,14 +706,18 @@ namespace build2
// Between us releasing the shared lock and acquiring unique the
// extension could change and possibly a new target that matches the
// key could be inserted. In this case we simply re-run find ().
+ // Naturally, can't happen during load.
//
- sl.unlock ();
- ul = ulock (mutex_);
-
- if (ext) // Someone set the extension.
+ if (!load)
{
- ul.unlock ();
- return find (k, trace);
+ sl.unlock ();
+ ul = ulock (mutex_);
+
+ if (ext) // Someone set the extension.
+ {
+ ul.unlock ();
+ return find (k, trace);
+ }
}
}
@@ -645,10 +751,12 @@ namespace build2
string name,
optional<string> ext,
target_decl decl,
- tracer& trace)
+ tracer& trace,
+ bool skip_find,
+ bool need_lock)
{
target_key tk {&tt, &dir, &out, &name, move (ext)};
- target* t (const_cast<target*> (find (tk, trace)));
+ target* t (skip_find ? nullptr : const_cast<target*> (find (tk, trace)));
if (t == nullptr)
{
@@ -669,7 +777,9 @@ namespace build2
// case we proceed pretty much like find() except already under the
// exclusive lock.
//
- ulock ul (mutex_);
+ ulock ul (mutex_, defer_lock);
+ if (ctx.phase != run_phase::load || need_lock)
+ ul.lock ();
auto p (map_.emplace (target_key {&tt, &t->dir, &t->out, &t->name, e},
unique_ptr<target> (t)));
@@ -678,10 +788,28 @@ namespace build2
if (p.second)
{
+#if 0
+ {
+ size_t n (map_.bucket_count ());
+ if (n > buckets_)
+ {
+ text << "target_set buckets: " << buckets_ << " -> " << n
+ << " (" << map_.size () << ")";
+ buckets_ = n;
+ }
+ }
+#endif
+
t->ext_ = &i->first.ext;
t->decl = decl;
t->state.inner.target_ = t;
t->state.outer.target_ = t;
+ t->state.inner.vars.target_ = t;
+ t->state.outer.vars.target_ = t;
+
+ if (ctx.phase != run_phase::load && !need_lock)
+ ul.unlock ();
+
return pair<target&, ulock> (*t, move (ul));
}
@@ -733,9 +861,14 @@ namespace build2
static const optional<string> unknown_ext ("?");
- ostream&
- to_stream (ostream& os, const target_key& k, optional<stream_verbosity> osv)
+ bool
+ to_stream (ostream& os,
+ const target_key& k,
+ optional<stream_verbosity> osv,
+ bool name_only)
{
+ // Note: similar code in print_diag_impl(vector<target_key>).
+
stream_verbosity sv (osv ? *osv : stream_verb (os));
uint16_t dv (sv.path);
uint16_t ev (sv.extension);
@@ -745,22 +878,29 @@ namespace build2
//
bool n (!k.name->empty ());
- // Note: relative() returns empty for './'.
- //
- const dir_path& rd (dv < 1 ? relative (*k.dir) : *k.dir); // Relative.
- const dir_path& pd (n ? rd : rd.directory ()); // Parent.
+ const target_type& tt (*k.type);
- if (!pd.empty ())
+ dir_path rds; // Storage.
+ if (!name_only)
{
+ // Note: relative() returns empty for './'.
+ //
if (dv < 1)
- os << diag_relative (pd);
- else
- to_stream (os, pd, true /* representation */);
- }
+ rds = relative (*k.dir);
- const target_type& tt (*k.type);
+ const dir_path& rd (dv < 1 ? rds : *k.dir); // Relative.
+ const dir_path& pd (n ? rd : rd.directory ()); // Parent.
- os << tt.name << '{';
+ if (!pd.empty ())
+ {
+ if (dv < 1)
+ os << diag_relative (pd);
+ else
+ to_stream (os, pd, true /* representation */);
+ }
+
+ os << tt.name << '{';
+ }
if (n)
{
@@ -803,37 +943,47 @@ namespace build2
}
}
else
+ {
+ if (name_only && dv < 1) // Already done if !name_only.
+ rds = relative (*k.dir);
+
+ const dir_path& rd (dv < 1 ? rds : *k.dir);
+
to_stream (os,
rd.empty () ? dir_path (".") : rd.leaf (),
true /* representation */);
+ }
- os << '}';
-
- // If this target is from src, print its out.
- //
- if (!k.out->empty ())
+ if (!name_only)
{
- if (dv < 1)
+ os << '}';
+
+ // If this target is from src, print its out.
+ //
+ if (!k.out->empty ())
{
- // Don't print '@./'.
- //
- const string& o (diag_relative (*k.out, false));
+ if (dv < 1)
+ {
+ // Don't print '@./'.
+ //
+ const string& o (diag_relative (*k.out, false));
- if (!o.empty ())
- os << '@' << o;
+ if (!o.empty ())
+ os << '@' << o;
+ }
+ else
+ os << '@' << *k.out;
}
- else
- os << '@' << *k.out;
}
- return os;
+ return n; // Regular if we had the name.
}
ostream&
operator<< (ostream& os, const target_key& k)
{
if (auto p = k.type->print)
- p (os, k);
+ p (os, k, false /* name_only */);
else
to_stream (os, k, stream_verb (os));
@@ -859,9 +1009,10 @@ namespace build2
const opstate& s (state[action () /* inner */]);
// Note: already synchronized.
- size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ());
+ size_t c (s.task_count.load (memory_order_relaxed));
+ size_t b (ctx.count_base ()); // Note: cannot do (c - b)!
- if (o != offset_applied && o != offset_executed)
+ if (c != (b + offset_applied) && c != (b + offset_executed))
break;
}
// Fall through.
@@ -1022,20 +1173,20 @@ namespace build2
return tk.ext->c_str ();
}
- void
- target_print_0_ext_verb (ostream& os, const target_key& k)
+ bool
+ target_print_0_ext_verb (ostream& os, const target_key& k, bool no)
{
stream_verbosity sv (stream_verb (os));
if (sv.extension == 1) sv.extension = 0; // Remap 1 to 0.
- to_stream (os, k, sv);
+ return to_stream (os, k, sv, no);
}
- void
- target_print_1_ext_verb (ostream& os, const target_key& k)
+ bool
+ target_print_1_ext_verb (ostream& os, const target_key& k, bool no)
{
stream_verbosity sv (stream_verb (os));
if (sv.extension == 0) sv.extension = 1; // Remap 0 to 1.
- to_stream (os, k, sv);
+ return to_stream (os, k, sv, no);
}
// type info
@@ -1051,7 +1202,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none,
};
const target_type mtime_target::static_type
@@ -1064,7 +1215,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
const target_type path_target::static_type
@@ -1077,7 +1228,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
const target_type file::static_type
@@ -1090,18 +1241,77 @@ namespace build2
nullptr, /* pattern */
&target_print_1_ext_verb, // Print extension even at verbosity level 0.
&file_search,
- false
+ 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",
+ &mtime_target::static_type,
+ &target_factory<group>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ &target_search,
+ //
+ // Note that the dyn_members semantics is used not only to handle
+ // depdb-dyndep --dyn-target, but also pattern rule-static members.
+ //
+ target_type::flag::group | target_type::flag::dyn_members
+ };
+
+ // alias
+ //
static const target*
alias_search (const target& t, const prerequisite_key& pk)
{
// For an alias we don't want to silently create a target since it will do
// nothing and it most likely not what the user intended.
//
+ // But, allowing implied aliases seems harmless since all the alias does
+ // is pull its prerequisites. And they are handy to use as metadata
+ // carriers.
+ //
const target* e (search_existing_target (t.ctx, pk));
- if (e == nullptr || e->decl != target_decl::real)
+ if (e == nullptr || !(operator>= (e->decl, target_decl::implied)))
fail << "no explicit target for " << pk;
return e;
@@ -1117,7 +1327,7 @@ namespace build2
nullptr,
nullptr,
&alias_search,
- false
+ target_type::flag::none
};
// dir
@@ -1127,7 +1337,7 @@ namespace build2
{
try
{
- for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */))
+ for (const dir_entry& e: dir_iterator (d, dir_iterator::detect_dangling))
{
switch (e.type ())
{
@@ -1145,6 +1355,16 @@ namespace build2
break;
}
+ case entry_type::unknown:
+ {
+ bool sl (e.ltype () == entry_type::symlink);
+
+ warn << "skipping "
+ << (sl ? "dangling symlink" : "inaccessible entry") << ' '
+ << d / e.path ();
+
+ break;
+ }
default:
break;
}
@@ -1166,9 +1386,10 @@ namespace build2
try
{
- for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */))
+ for (const dir_entry& e: dir_iterator (d, dir_iterator::detect_dangling))
{
if (e.type () == entry_type::directory)
+ {
r.push_back (
prerequisite (nullopt,
dir::static_type,
@@ -1177,6 +1398,15 @@ namespace build2
string (),
nullopt,
bs));
+ }
+ else if (e.type () == entry_type::unknown)
+ {
+ bool sl (e.ltype () == entry_type::symlink);
+
+ warn << "skipping "
+ << (sl ? "dangling symlink" : "inaccessible entry") << ' '
+ << d / e.path ();
+ }
}
}
catch (const system_error& e)
@@ -1192,7 +1422,8 @@ namespace build2
{
tracer trace ("dir_search");
- // The first step is like in search_alias(): looks for an existing target.
+ // The first step is like in alias_search(): looks for an existing target
+ // (but unlike alias, no implied, think `test/: install=false`).
//
const target* e (search_existing_target (t.ctx, pk));
@@ -1333,7 +1564,7 @@ namespace build2
&dir_pattern,
nullptr,
&dir_search,
- false
+ target_type::flag::none
};
const target_type fsdir::static_type
@@ -1346,7 +1577,7 @@ namespace build2
&dir_pattern,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
static optional<string>
@@ -1414,7 +1645,7 @@ namespace build2
#endif
nullptr,
&file_search,
- false
+ target_type::flag::none
};
static const char*
@@ -1500,7 +1731,7 @@ namespace build2
&buildfile_target_pattern,
nullptr,
&file_search,
- false
+ target_type::flag::none
};
const target_type doc::static_type
@@ -1513,7 +1744,7 @@ namespace build2
nullptr, /* pattern */ // Same as file.
&target_print_1_ext_verb, // Same as file.
&file_search,
- false
+ target_type::flag::none
};
const target_type legal::static_type
@@ -1526,7 +1757,7 @@ namespace build2
nullptr, /* pattern */ // Same as file.
&target_print_1_ext_verb, // Same as file.
&file_search,
- false
+ target_type::flag::none
};
const target_type man::static_type
@@ -1539,7 +1770,7 @@ namespace build2
nullptr,
&target_print_1_ext_verb, // Print extension even at verbosity level 0.
&file_search,
- false
+ target_type::flag::none
};
extern const char man1_ext[] = "1"; // VC14 rejects constexpr.
@@ -1554,7 +1785,7 @@ namespace build2
&target_pattern_fix<man1_ext>,
&target_print_0_ext_verb, // Fixed extension, no use printing.
&file_search,
- false
+ target_type::flag::none
};
static const char*
@@ -1603,6 +1834,6 @@ namespace build2
&manifest_target_pattern,
nullptr,
&file_search,
- false
+ target_type::flag::none
};
}