aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/target.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/target.cxx')
-rw-r--r--libbuild2/target.cxx462
1 files changed, 362 insertions, 100 deletions
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index 78bc5ac..2a134a4 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -38,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.
@@ -109,7 +111,8 @@ 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};
}
@@ -140,7 +143,8 @@ namespace build2
pair<lookup, size_t> target::
lookup_original (const variable& var,
bool target_only,
- const scope* bs) const
+ const scope* bs,
+ bool locked) const
{
pair<lookup_type, size_t> r (lookup_type (), 0);
@@ -158,6 +162,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)
@@ -175,6 +184,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().
@@ -183,9 +205,14 @@ namespace build2
{
if (!target_only)
{
- target_key tk (key ());
- target_key g1k (g1 != nullptr ? g1->key () : target_key {});
- target_key g2k (g2 != nullptr ? g2->key () : target_key {});
+ auto key = [locked] (const target* t)
+ {
+ return locked ? t->key_locked () : t->key ();
+ };
+
+ target_key tk (key (this));
+ target_key g1k (g1 != nullptr ? key (g1) : target_key {});
+ target_key g2k (g2 != nullptr ? key (g2) : target_key {});
if (bs == nullptr)
bs = &base_scope ();
@@ -206,14 +233,30 @@ namespace build2
}
value& target::
- append (const variable& var)
+ append (const variable& var, const scope* bs)
{
// Note: see also prerequisite::append() if changing anything here.
// Note that here we want the original value without any overrides
// applied.
//
- auto l (lookup_original (var).first);
+ auto l (lookup_original (var, false, bs).first);
+
+ if (l.defined () && l.belongs (*this)) // Existing var in this target.
+ return vars.modify (l); // Ok since this is original.
+
+ value& r (assign (var)); // NULL.
+
+ if (l.defined ())
+ r = *l; // Copy value (and type) from the outer scope.
+
+ return r;
+ }
+
+ value& target::
+ append_locked (const variable& var, const scope* bs)
+ {
+ auto l (lookup_original (var, false, bs, true /* locked */).first);
if (l.defined () && l.belongs (*this)) // Existing var in this target.
return vars.modify (l); // Ok since this is original.
@@ -545,15 +588,33 @@ namespace build2
context& ctx (t.ctx);
include_type r (include_type::normal);
-
- if (const string* v = cast_null<string> (p.vars[ctx.var_include]))
{
- 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 << " variable value "
- << "'" << *v << "' specified for prerequisite " << p;
+ 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 (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
@@ -564,39 +625,55 @@ namespace build2
names storage;
names_view ns;
- const variable* current_ovar (nullptr);
+ const variable* ovar (nullptr);
if (r != include_type::excluded)
{
- current_ovar = a.outer ()
- ? ctx.current_outer_ovar
- : ctx.current_inner_ovar;
+ // 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 (current_ovar != nullptr && (l = p.vars[*current_ovar]))
+ if (ovar != nullptr)
{
- // 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);
+ l = p.vars[*ovar];
- if (ns.size () == 1)
+ if (l.defined ())
{
- const name& n (ns[0]);
+ 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 (n.simple ())
+ if (ns.size () == 1)
{
- const string& v (n.value);
+ 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 (v == "false")
+ r1 = false;
+ else if (v == "true")
+ r1 = true;
+ }
}
- }
- if (r1 && !*r1)
- r = include_type::excluded;
+ if (r1 && !*r1)
+ r = include_type::excluded;
+ }
}
}
@@ -617,8 +694,8 @@ namespace build2
// Note: we have to delay this until the meta-operation callback above
// had a chance to override it.
//
- fail << "unrecognized " << *current_ovar << " variable value "
- << "'" << ns << "' specified for prerequisite " << p;
+ fail << "unrecognized " << *ovar << " variable value '" << ns
+ << "' specified for prerequisite " << p;
}
}
@@ -733,10 +810,24 @@ 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 ();
@@ -792,9 +883,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);
@@ -804,22 +900,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.
+
+ if (!pd.empty ())
+ {
+ if (dv < 1)
+ os << diag_relative (pd);
+ else
+ to_stream (os, pd, true /* representation */);
+ }
- os << tt.name << '{';
+ os << tt.name << '{';
+ }
if (n)
{
@@ -862,37 +965,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));
@@ -913,14 +1026,19 @@ namespace build2
case run_phase::load: break;
case run_phase::match:
{
- // Similar logic to matched_state_impl().
+ // Similar logic to target::matched().
//
const opstate& s (state[action () /* inner */]);
- // Note: already synchronized.
- size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ());
+ // Note: use acquire for group_state().
+ //
+ size_t c (s.task_count.load (memory_order_acquire));
+ 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) ||
+ (c >= (b + offset_busy) &&
+ s.match_extra.cur_options_.load (memory_order_relaxed) != 0)))
break;
}
// Fall through.
@@ -1043,25 +1161,27 @@ namespace build2
//
const target*
- target_search (const target& t, const prerequisite_key& pk)
+ target_search (context& ctx, const target*, const prerequisite_key& pk)
{
// The default behavior is to look for an existing target in the
// prerequisite's directory scope.
//
- return search_existing_target (t.ctx, pk);
+ return search_existing_target (ctx, pk, true /* out_only */);
}
const target*
- file_search (const target& t, const prerequisite_key& pk)
+ file_search (context& ctx, const target* t, const prerequisite_key& pk)
{
- // First see if there is an existing target.
+ // First see if there is an existing target in the out or src tree.
//
- if (const target* e = search_existing_target (t.ctx, pk))
+ if (const target* e = search_existing_target (ctx,
+ pk,
+ false /* out_only */))
return e;
// Then look for an existing file in the src tree.
//
- return search_existing_file (t.ctx, pk);
+ return t != nullptr ? search_existing_file (ctx, pk) : nullptr;
}
extern const char target_extension_none_[] = "";
@@ -1081,20 +1201,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
@@ -1152,15 +1272,79 @@ 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",
+ &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)
+ alias_search (context& ctx, 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.
+ // nothing and it most likely not what the user intended (but omit this
+ // check when searching for an existing target since presumably a new one
+ // won't be created in this case).
+ //
+ // 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));
+ // Doesn't feel like an alias in the src tree makes much sense.
+ //
+ const target* e (search_existing_target (ctx, pk, true /* out_only */));
- if (e == nullptr || e->decl != target_decl::real)
+ if ((e == nullptr ||
+ !(operator>= (e->decl, target_decl::implied))) && t != nullptr)
fail << "no explicit target for " << pk;
return e;
@@ -1186,7 +1370,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 ())
{
@@ -1204,6 +1388,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;
}
@@ -1225,17 +1419,26 @@ 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,
+ prerequisite (dir::static_type,
dir_path (e.path ().representation ()), // Relative.
dir_path (), // In the out tree.
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)
@@ -1247,17 +1450,27 @@ namespace build2
}
static const target*
- dir_search (const target& t, const prerequisite_key& pk)
+ dir_search (context& ctx, const target* t, const prerequisite_key& pk)
{
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`).
+ //
+ // Likewise, dir{} in the src tree doesn't make much sense.
//
- const target* e (search_existing_target (t.ctx, pk));
+ const target* e (search_existing_target (ctx, pk, true /* out_only */));
if (e != nullptr && e->decl == target_decl::real)
return e;
+ // The search for an existing target can also be done during execute so
+ // none of the below code applied. Note: return implied instead of NULL
+ // (to be consistent with search_new(), for example).
+ //
+ if (t == nullptr)
+ return e;
+
// If not found (or is implied), then try to load the corresponding
// buildfile (which would normally define this target). Failed that, see
// if we can assume an implied buildfile which would be equivalent to:
@@ -1291,18 +1504,18 @@ namespace build2
//
bool retest (false);
- assert (t.ctx.phase == run_phase::match);
+ assert (ctx.phase == run_phase::match);
{
// Switch the phase to load.
//
- phase_switch ps (t.ctx, run_phase::load);
+ phase_switch ps (ctx, run_phase::load);
// This is subtle: while we were fussing around another thread may have
// loaded the buildfile. So re-test now that we are in an exclusive
// phase.
//
if (e == nullptr)
- e = search_existing_target (t.ctx, pk);
+ e = search_existing_target (ctx, pk, true);
if (e != nullptr && e->decl == target_decl::real)
retest = true;
@@ -1340,14 +1553,14 @@ namespace build2
}
}
- assert (t.ctx.phase == run_phase::match);
+ assert (ctx.phase == run_phase::match);
// If we loaded/implied the buildfile, examine the target again.
//
if (retest)
{
if (e == nullptr)
- e = search_existing_target (t.ctx, pk);
+ e = search_existing_target (ctx, pk, true);
if (e != nullptr && e->decl == target_decl::real)
return e;
@@ -1472,7 +1685,7 @@ namespace build2
nullptr,
#endif
nullptr,
- &file_search,
+ &file_search, // Note: can also be a script in src.
target_type::flag::none
};
@@ -1562,6 +1775,55 @@ namespace build2
target_type::flag::none
};
+ static const char*
+ buildscript_target_extension (const target_key& tk, const scope*)
+ {
+ // If the name is special 'buildscript', then there is no extension,
+ // otherwise it is .buildscript.
+ //
+ return *tk.name == "buildscript" ? "" : "buildscript";
+ }
+
+ static bool
+ buildscript_target_pattern (const target_type&,
+ const scope&,
+ string& v,
+ optional<string>& e,
+ const location& l,
+ bool r)
+ {
+ if (r)
+ {
+ assert (e);
+ e = nullopt;
+ }
+ else
+ {
+ e = target::split_name (v, l);
+
+ if (!e && v != "buildscript")
+ {
+ e = "buildscript";
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ const target_type buildscript::static_type
+ {
+ "buildscript",
+ &file::static_type,
+ &target_factory<buildscript>,
+ &buildscript_target_extension,
+ nullptr, /* default_extension */
+ &buildscript_target_pattern,
+ nullptr,
+ &file_search,
+ target_type::flag::none
+ };
+
const target_type doc::static_type
{
"doc",