aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/scope.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/scope.cxx')
-rw-r--r--libbuild2/scope.cxx421
1 files changed, 312 insertions, 109 deletions
diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx
index 53c859f..23781a8 100644
--- a/libbuild2/scope.cxx
+++ b/libbuild2/scope.cxx
@@ -3,6 +3,7 @@
#include <libbuild2/scope.hxx>
+#include <libbuild2/rule.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -22,7 +23,7 @@ namespace build2
? empty_project_name
: i->first);
- os << (i != b ? " " : "") << n << '@' << i->second;
+ os << (i != b ? " " : "") << n << '@' << i->second.string ();
}
return os;
@@ -30,13 +31,27 @@ namespace build2
// scope
//
+ scope::
+ scope (context& c, bool shared)
+ : ctx (c), vars (*this, shared), target_vars (c, shared)
+ {
+ }
+
+ scope::
+ ~scope ()
+ {
+ // Definition of adhoc_rule_pattern.
+ }
+
pair<lookup, size_t> scope::
lookup_original (const variable& var,
- const target_type* tt, const string* tn,
- const target_type* gt, const string* gn,
+ const target_key* tk,
+ const target_key* g1k,
+ const target_key* g2k,
size_t start_d) const
{
- assert (tt != nullptr || var.visibility != variable_visibility::target);
+ assert (tk != nullptr || var.visibility != variable_visibility::target);
+ assert (g2k == nullptr || g1k != nullptr);
size_t d (0);
@@ -47,8 +62,10 @@ namespace build2
//
auto pre_app = [&var, this] (lookup_type& l,
const scope* s,
- const target_type* tt, const string* tn,
- const target_type* gt, const string* gn)
+ const target_key* tk,
+ const target_key* g1k,
+ const target_key* g2k,
+ string n)
{
const value& v (*l);
assert ((v.extra == 1 || v.extra == 2) && v.type == nullptr);
@@ -62,14 +79,14 @@ namespace build2
// group, then we shouldn't be looking for stem in the target's
// variables. In other words, once we "jump" to group, we stay there.
//
- lookup_type stem (s->lookup_original (var, tt, tn, gt, gn, 2).first);
+ lookup_type stem (s->lookup_original (var, tk, g1k, g2k, 2).first);
// Check the cache.
//
pair<value&, ulock> entry (
s->target_vars.cache.insert (
ctx,
- make_tuple (&v, tt, *tn),
+ make_tuple (&v, tk->type, !n.empty () ? move (n) : *tk->name),
stem,
static_cast<const variable_map::value_data&> (v).version,
var));
@@ -120,9 +137,18 @@ namespace build2
l.value = &cv;
};
+ // Most of the time we match against the target name directly but
+ // sometimes we may need to match against the directory leaf (dir{} or
+ // fsdir{}) or incorporate the extension. We therefore try hard to avoid
+ // the copy.
+ //
+ optional<string> tn;
+ optional<string> g1n;
+ optional<string> g2n;
+
for (const scope* s (this); s != nullptr; )
{
- if (tt != nullptr) // This started from the target.
+ if (tk != nullptr) // This started from the target.
{
bool f (!s->target_vars.empty ());
@@ -132,12 +158,12 @@ namespace build2
{
if (f)
{
- lookup_type l (s->target_vars.find (*tt, *tn, var));
+ lookup_type l (s->target_vars.find (*tk, var, tn));
if (l.defined ())
{
if (l->extra != 0) // Prepend/append?
- pre_app (l, s, tt, tn, gt, gn);
+ pre_app (l, s, tk, g1k, g2k, move (*tn));
return make_pair (move (l), d);
}
@@ -148,17 +174,30 @@ namespace build2
//
if (++d >= start_d)
{
- if (f && gt != nullptr)
+ if (f && g1k != nullptr)
{
- lookup_type l (s->target_vars.find (*gt, *gn, var));
+ lookup_type l (s->target_vars.find (*g1k, var, g1n));
if (l.defined ())
{
if (l->extra != 0) // Prepend/append?
- pre_app (l, s, gt, gn, nullptr, nullptr);
+ pre_app (l, s, g1k, g2k, nullptr, move (*g1n));
return make_pair (move (l), d);
}
+
+ if (g2k != nullptr)
+ {
+ l = s->target_vars.find (*g2k, var, g2n);
+
+ if (l.defined ())
+ {
+ if (l->extra != 0) // Prepend/append?
+ pre_app (l, s, g2k, nullptr, nullptr, move (*g2n));
+
+ return make_pair (move (l), d);
+ }
+ }
}
}
}
@@ -297,8 +336,12 @@ namespace build2
return lookup_type ();
// Note: using the original as storage variable.
+ // Note: have to suppress aliases since used for something else.
//
- return lookup_type (s->vars.lookup (*o).first, &var, &s->vars);
+ return lookup_type (
+ s->vars.lookup (*o, true /* typed */, false /* aliased */).first,
+ &var,
+ &s->vars);
};
// Return true if a value is from this scope (either target type/pattern-
@@ -327,7 +370,7 @@ namespace build2
// If we are still looking for the cache, see if the original comes from
// this scope. We check this before the overrides since it can come from
// the target type/patter-specific variables, which is "more inner" than
- // normal scope variables (see find_original()).
+ // normal scope variables (see lookup_original()).
//
if (inner_vars == nullptr && orig.defined () && belongs (orig))
{
@@ -640,31 +683,36 @@ namespace build2
}
pair<const target_type*, optional<string>> scope::
- find_target_type (name& n, const location& loc) const
+ find_target_type (name& n, const location& loc, const target_type* tt) const
{
- const target_type* tt (nullptr);
+ // NOTE: see also functions-name.cxx:filter() if changing anything here.
+
optional<string> ext;
string& v (n.value);
- // If the target type is specified, resolve it and bail out if not found.
- // Otherwise, we know in the end it will resolve to something (if nothing
- // else, either dir{} or file{}), so we can go ahead and process the name.
+ // If the name is typed, resolve the target type it and bail out if not
+ // found. Otherwise, we know in the end it will resolve to something (if
+ // nothing else, either dir{} or file{}), so we can go ahead and process
+ // the name.
//
- if (n.typed ())
+ if (tt == nullptr)
{
- tt = find_target_type (n.type);
+ if (n.typed ())
+ {
+ tt = find_target_type (n.type);
- if (tt == nullptr)
- return make_pair (tt, move (ext));
- }
- else
- {
- // Empty name as well as '.' and '..' signify a directory. Note that
- // this logic must be consistent with other places (grep for "..").
- //
- if (v.empty () || v == "." || v == "..")
- tt = &dir::static_type;
+ if (tt == nullptr)
+ return make_pair (tt, move (ext));
+ }
+ else
+ {
+ // Empty name as well as '.' and '..' signify a directory. Note that
+ // this logic must be consistent with other places (grep for "..").
+ //
+ if (v.empty () || v == "." || v == "..")
+ tt = &dir::static_type;
+ }
}
// Directories require special name processing. If we find that more
@@ -684,33 +732,26 @@ namespace build2
else if (!v.empty ())
{
// Split the path into its directory part (if any) the name part, and
- // the extension (if any). We cannot assume the name part is a valid
- // filesystem name so we will have to do the splitting manually.
+ // the extension (if any).
//
// See also parser::expand_name_pattern() if changing anything here.
//
- size_t p (path::traits_type::rfind_separator (v));
-
- if (p != string::npos)
+ try
{
- try
- {
- n.dir /= dir_path (v, p != 0 ? p : 1); // Special case: "/".
- }
- catch (const invalid_path& e)
- {
- fail (loc) << "invalid path '" << e.path << "'";
- }
-
- // This is probably too general of a place to ignore multiple trailing
- // slashes and treat it as a directory (e.g., we don't want to
- // encourage this sloppiness in buildfiles). We could, however, do it
- // for certain contexts, such as buildspec. Maybe a lax flag?
+ n.canonicalize ();
+ }
+ catch (const invalid_path& e)
+ {
+ fail (loc) << "invalid path '" << e.path << "'";
+ }
+ catch (const invalid_argument&)
+ {
+ // This is probably too general of a place to ignore multiple
+ // trailing slashes and treat it as a directory (e.g., we don't want
+ // to encourage this sloppiness in buildfiles). We could, however,
+ // do it for certain contexts, such as buildspec. Maybe a lax flag?
//
- if (++p == v.size ())
- fail (loc) << "invalid name '" << v << "'";
-
- v.erase (0, p);
+ fail (loc) << "invalid name '" << v << "'";
}
// Extract the extension.
@@ -751,9 +792,11 @@ namespace build2
}
pair<const target_type&, optional<string>> scope::
- find_target_type (name& n, name& o, const location& loc) const
+ find_target_type (name& n, name& o,
+ const location& loc,
+ const target_type* tt) const
{
- auto r (find_target_type (n, loc));
+ auto r (find_target_type (n, loc, tt));
if (r.first == nullptr)
fail (loc) << "unknown target type " << n.type << " in " << n;
@@ -767,26 +810,68 @@ namespace build2
fail (loc) << "expected directory after '@'";
}
- dir_path& d (n.dir);
+ dir_path& dir (n.dir);
const dir_path& sd (src_path ());
const dir_path& od (out_path ());
- if (d.empty ())
- d = src ? sd : od; // Already dormalized.
+ bool nabs (false);
+
+ if (dir.empty ())
+ dir = src ? sd : od; // Already normalized.
else
{
- if (d.relative ())
- d = (src ? sd : od) / d;
+ if (dir.relative ())
+ dir = (src ? sd : od) / dir;
+ else if (src)
+ nabs = true;
- d.normalize ();
+ dir.normalize ();
}
dir_path out;
- if (src && sd != od) // If in-source build, then out must be empty.
+ if (src)
{
- out = o.dir.relative () ? od / o.dir : move (o.dir);
+ bool oabs (o.dir.absolute ());
+
+ out = oabs ? move (o.dir) : od / o.dir;
out.normalize ();
+
+ // Make sure out and src are parallel unless both were specified as
+ // absolute. We make an exception for this case because out may be used
+ // to "tag" imported targets (see cc::search_library()). So it's sort of
+ // the "I know what I am doing" escape hatch (it would have been even
+ // better to verify such a target is outside any project but that won't
+ // be cheap).
+ //
+ // See similar code for prerequisites in parser::parse_dependency().
+ //
+ if (nabs && oabs)
+ ;
+ else if (root_->out_eq_src ()
+ ? out == dir
+ //
+ // @@ PERF: could just compare leafs in place.
+ //
+ : (out.sub (root_->out_path ()) &&
+ dir.sub (root_->src_path ()) &&
+ out.leaf (root_->out_path ()) == dir.leaf (root_->src_path ())))
+ ;
+ else
+ // @@ TMP change warn to fail after 0.16.0 release.
+ //
+ warn (loc) << "target output directory " << out
+ << " must be parallel to source directory " << dir;
+
+ // If this target is in this project, then out must be empty if this is
+ // in source build. We assume that if either src or out are relative,
+ // then it belongs to this project.
+ //
+ if (root_->out_eq_src ())
+ {
+ if (!nabs || !oabs || out.sub (root_->out_path ()))
+ out.clear ();
+ }
}
o.dir = move (out); // Result.
@@ -795,14 +880,16 @@ namespace build2
}
target_key scope::
- find_target_key (names& ns, const location& loc) const
+ find_target_key (names& ns,
+ const location& loc,
+ const target_type* tt) const
{
if (size_t n = ns.size ())
{
if (n == (ns[0].pair ? 2 : 1))
{
name dummy;
- return find_target_key (ns[0], n == 1 ? dummy : ns[1], loc);
+ return find_target_key (ns[0], n == 1 ? dummy : ns[1], loc, tt);
}
}
@@ -810,9 +897,11 @@ namespace build2
}
pair<const target_type&, optional<string>> scope::
- find_prerequisite_type (name& n, name& o, const location& loc) const
+ find_prerequisite_type (name& n, name& o,
+ const location& loc,
+ const target_type* tt) const
{
- auto r (find_target_type (n, loc));
+ auto r (find_target_type (n, loc, tt));
if (r.first == nullptr)
fail (loc) << "unknown target type " << n.type << " in " << n;
@@ -836,14 +925,16 @@ namespace build2
}
prerequisite_key scope::
- find_prerequisite_key (names& ns, const location& loc) const
+ find_prerequisite_key (names& ns,
+ const location& loc,
+ const target_type* tt) const
{
if (size_t n = ns.size ())
{
if (n == (ns[0].pair ? 2 : 1))
{
name dummy;
- return find_prerequisite_key (ns[0], n == 1 ? dummy : ns[1], loc);
+ return find_prerequisite_key (ns[0], n == 1 ? dummy : ns[1], loc, tt);
}
}
@@ -871,7 +962,9 @@ namespace build2
}
pair<reference_wrapper<const target_type>, bool> scope::
- derive_target_type (const string& name, const target_type& base)
+ derive_target_type (const string& name,
+ const target_type& base,
+ target_type::flag flags)
{
assert (root_scope () == this);
@@ -889,11 +982,22 @@ namespace build2
//
// Currently, if we define myfile{}: file{}, then myfile{foo} and
// myfile{foo.x} are the same target.
- //
- unique_ptr<target_type> dt (new target_type (base));
- dt->base = &base;
- dt->factory = &derived_tt_factory;
+ // Note: copies flags.
+ //
+ unique_ptr<target_type> dt (
+ new target_type {
+ nullptr, // Will be patched in by insert() below.
+ &base,
+ &derived_tt_factory,
+ base.fixed_extension,
+ base.default_extension,
+ base.pattern,
+ base.print,
+ base.search,
+ base.flags | flags});
+
+#if 0
// @@ We should probably inherit the fixed extension unless overriden with
// another fixed? But then any derivation from file{} will have to specify
// (or override) the fixed extension? But what is the use of deriving from
@@ -925,6 +1029,43 @@ namespace build2
dt->fixed_extension != nullptr
? &target_print_0_ext_verb // Fixed extension, no use printing.
: nullptr; // Normal.
+#endif
+
+ // An attempt to clarify the above mess:
+ //
+ // 1. If we have a "really fixed" extension (like man1{}) then we keep
+ // it (including pattern and print functions).
+ //
+ // 2. Otherwise, we make it target_extension_var.
+ //
+ // Note that this still mis-fires for the following scenarios:
+ //
+ // file{} -- What if the user does not set the default extension expecting
+ // similar semantics as file{} or man{} itself. Maybe explicit
+ // via attribute (i.e., inherit from base)?
+ //
+ // @@ Get the fallback extension from base target_extension_var
+ // somehow (we know the base target type so could just call it)?
+ //
+ if (ext)
+ {
+ if (dt->fixed_extension == nullptr ||
+ dt->fixed_extension == &target_extension_none ||
+ dt->fixed_extension == &target_extension_must)
+ {
+ dt->fixed_extension = nullptr;
+ dt->default_extension = &target_extension_var<nullptr>;
+ dt->pattern = &target_pattern_var<nullptr>;
+ dt->print = nullptr;
+ }
+ }
+ else
+ {
+ dt->fixed_extension = nullptr;
+ dt->default_extension = nullptr;
+ dt->pattern = nullptr;
+ dt->print = nullptr;
+ }
return root_extra->target_types.insert (name, move (dt));
}
@@ -933,8 +1074,17 @@ namespace build2
derive_target_type (const target_type& et)
{
assert (root_scope () == this);
- unique_ptr<target_type> dt (new target_type (et));
- dt->factory = &derived_tt_factory;
+ unique_ptr<target_type> dt (
+ new target_type {
+ nullptr, // Will be patched in by insert() below.
+ et.base,
+ &derived_tt_factory,
+ et.fixed_extension,
+ et.default_extension,
+ et.pattern,
+ et.print,
+ et.search,
+ et.flags});
return root_extra->target_types.insert (et.name, move (dt)).first;
}
@@ -942,12 +1092,20 @@ namespace build2
//
auto scope_map::
- insert (const dir_path& k, bool root) -> iterator
+ insert_out (const dir_path& k, bool root) -> iterator
{
- scope_map_base& m (*this);
+ auto er (map_.emplace (k, scopes ()));
+
+ if (er.second)
+ er.first->second.push_back (nullptr);
+
+ if (er.first->second.front () == nullptr)
+ {
+ er.first->second.front () = new scope (ctx, true /* shared */);
+ er.second = true;
+ }
- auto er (m.emplace (k, scope (ctx, true /* global */)));
- scope& s (er.first->second);
+ scope& s (*er.first->second.front ());
// If this is a new scope, update the parent chain.
//
@@ -958,34 +1116,35 @@ namespace build2
// Update scopes of which we are a new parent/root (unless this is the
// global scope). Also find our parent while at it.
//
- if (m.size () > 1)
+ if (map_.size () > 1)
{
// The first entry is ourselves.
//
- auto r (m.find_sub (k));
+ auto r (map_.find_sub (k));
for (++r.first; r.first != r.second; ++r.first)
{
- scope& c (r.first->second);
-
- // The first scope of which we are a parent is the least (shortest)
- // one which means there is no other scope between it and our
- // parent.
- //
- if (p == nullptr)
- p = c.parent_;
-
- if (root && c.root_ == p->root_) // No intermediate root.
- c.root_ = &s;
-
- if (p == c.parent_) // No intermediate parent.
- c.parent_ = &s;
+ if (scope* c = r.first->second.front ())
+ {
+ // The first scope of which we are a parent is the least
+ // (shortest) one which means there is no other scope between it
+ // and our parent.
+ //
+ if (p == nullptr)
+ p = c->parent_;
+
+ if (root && c->root_ == p->root_) // No intermediate root.
+ c->root_ = &s;
+
+ if (p == c->parent_) // No intermediate parent.
+ c->parent_ = &s;
+ }
}
// We couldn't get the parent from one of its old children so we have
// to find it ourselves.
//
if (p == nullptr)
- p = &find (k.directory ());
+ p = &find_out (k.directory ());
}
s.parent_ = p;
@@ -995,13 +1154,14 @@ namespace build2
{
// Upgrade to root scope.
//
- auto r (m.find_sub (k));
+ auto r (map_.find_sub (k));
for (++r.first; r.first != r.second; ++r.first)
{
- scope& c (r.first->second);
-
- if (c.root_ == s.root_) // No intermediate root.
- c.root_ = &s;
+ if (scope* c = r.first->second.front ())
+ {
+ if (c->root_ == s.root_) // No intermediate root.
+ c->root_ = &s;
+ }
}
s.root_ = &s;
@@ -1010,14 +1170,57 @@ namespace build2
return er.first;
}
+ auto scope_map::
+ insert_src (scope& s, const dir_path& k) -> iterator
+ {
+ auto er (map_.emplace (k, scopes ()));
+
+ if (er.second)
+ er.first->second.push_back (nullptr); // Owning out path entry.
+
+ // It doesn't feel like this function can possibly be called multiple
+ // times for the same scope and path so we skip the duplicate check.
+ //
+ er.first->second.push_back (&s);
+
+ return er.first;
+ }
+
scope& scope_map::
- find (const dir_path& k)
+ find_out (const dir_path& k)
{
assert (k.normalized (false)); // Allow non-canonical dir separators.
- scope_map_base& m (*this);
- auto i (m.find_sup (k));
- assert (i != m.end ()); // Should have global scope.
- return i->second;
+ // This one is tricky: if we found an entry that doesn't contain the
+ // out path scope, then we need to consider outer scopes.
+ //
+ auto i (map_.find_sup_if (k,
+ [] (const pair<const dir_path, scopes>& v)
+ {
+ return v.second.front () != nullptr;
+ }));
+
+ assert (i != map_.end ()); // Should have at least global scope.
+ return *i->second.front ();
+ }
+
+ auto scope_map::
+ find (const dir_path& k, bool sno) const -> pair<scopes::const_iterator,
+ scopes::const_iterator>
+ {
+ assert (k.normalized (false));
+ auto i (map_.find_sup (k));
+ assert (i != map_.end ());
+
+ auto b (i->second.begin ());
+ auto e (i->second.end ());
+
+ // Skip NULL first element if requested.
+ //
+ if (sno && *b == nullptr)
+ ++b;
+
+ assert (b != e);
+ return make_pair (b, e);
}
}