aboutsummaryrefslogtreecommitdiff
path: root/build2/scope.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build2/scope.cxx')
-rw-r--r--build2/scope.cxx911
1 files changed, 0 insertions, 911 deletions
diff --git a/build2/scope.cxx b/build2/scope.cxx
deleted file mode 100644
index a6ebe1f..0000000
--- a/build2/scope.cxx
+++ /dev/null
@@ -1,911 +0,0 @@
-// file : build2/scope.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <build2/scope.hxx>
-
-#include <build2/target.hxx>
-#include <build2/context.hxx>
-
-using namespace std;
-
-namespace build2
-{
- // scope
- //
- pair<lookup, size_t> scope::
- find_original (const variable& var,
- const target_type* tt, const string* tn,
- const target_type* gt, const string* gn,
- size_t start_d) const
- {
- assert (tt != nullptr || var.visibility != variable_visibility::target);
-
- size_t d (0);
-
- if (var.visibility == variable_visibility::prereq)
- return make_pair (lookup (), d);
-
- // Process target type/pattern-specific prepend/append values.
- //
- auto pre_app = [&var] (lookup& l,
- const scope* s,
- const target_type* tt, const string* tn,
- const target_type* gt, const string* gn)
- {
- const value& v (*l);
- assert ((v.extra == 1 || v.extra == 2) && v.type == nullptr);
-
- // First we need to look for the stem value starting from the "next
- // lookup point". That is, if we have the group, then from the
- // s->target_vars (for the group), otherwise from s->vars, and then
- // continuing looking in the outer scopes (for both target and group).
- // Note that this may have to be repeated recursively, i.e., we may have
- // prepents/appends in outer scopes. Also, if the value is for the
- // 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 stem (s->find_original (var, tt, tn, gt, gn, 2).first);
-
- // Check the cache.
- //
- pair<value&, ulock> entry (
- s->target_vars.cache.insert (
- make_tuple (&v, tt, *tn),
- stem,
- static_cast<const variable_map::value_data&> (v).version,
- var));
-
- value& cv (entry.first);
-
- // If cache miss/invalidation, update the value.
- //
- if (entry.second.owns_lock ())
- {
- // Un-typify the cache. This can be necessary, for example, if we are
- // changing from one value-typed stem to another.
- //
- // Note: very similar logic as in the override cache population code
- // below.
- //
- if (!stem.defined () || cv.type != stem->type)
- {
- cv = nullptr;
- cv.type = nullptr; // Un-typify.
- }
-
- // Copy the stem.
- //
- if (stem.defined ())
- cv = *stem;
-
- // Typify the cache value in case there is no stem (we still want to
- // prepend/append things in type-aware way).
- //
- if (cv.type == nullptr && var.type != nullptr)
- typify (cv, *var.type, &var);
-
- // Now prepend/append the value, unless it is NULL.
- //
- if (v)
- {
- if (v.extra == 1)
- cv.prepend (names (cast<names> (v)), &var);
- else
- cv.append (names (cast<names> (v)), &var);
- }
- }
-
- // Return cache as the resulting value but retain l.var/vars, so it
- // looks as if the value came from s->target_vars.
- //
- l.value = &cv;
- };
-
- for (const scope* s (this); s != nullptr; )
- {
- if (tt != nullptr) // This started from the target.
- {
- bool f (!s->target_vars.empty ());
-
- // Target.
- //
- if (++d >= start_d)
- {
- if (f)
- {
- lookup l (s->target_vars.find (*tt, *tn, var));
-
- if (l.defined ())
- {
- if (l->extra != 0) // Prepend/append?
- pre_app (l, s, tt, tn, gt, gn);
-
- return make_pair (move (l), d);
- }
- }
- }
-
- // Group.
- //
- if (++d >= start_d)
- {
- if (f && gt != nullptr)
- {
- lookup l (s->target_vars.find (*gt, *gn, var));
-
- if (l.defined ())
- {
- if (l->extra != 0) // Prepend/append?
- pre_app (l, s, gt, gn, nullptr, nullptr);
-
- return make_pair (move (l), d);
- }
- }
- }
- }
-
- // Note that we still increment the lookup depth so that we can compare
- // depths of variables with different visibilities.
- //
- if (++d >= start_d && var.visibility != variable_visibility::target)
- {
- auto p (s->vars.find (var));
- if (p.first != nullptr)
- return make_pair (lookup (*p.first, p.second, s->vars), d);
- }
-
- switch (var.visibility)
- {
- case variable_visibility::scope:
- s = nullptr;
- break;
- case variable_visibility::target:
- case variable_visibility::project:
- s = s->root () ? nullptr : s->parent_scope ();
- break;
- case variable_visibility::normal:
- s = s->parent_scope ();
- break;
- case variable_visibility::prereq:
- assert (false);
- }
- }
-
- return make_pair (lookup (), size_t (~0));
- }
-
- pair<lookup, size_t> scope::
- find_override (const variable& var,
- pair<lookup, size_t> original,
- bool target,
- bool rule) const
- {
- assert (!rule || target); // Rule-specific is target-specific.
-
- // Normally there would be no overrides and if there are, there will only
- // be a few of them. As a result, here we concentrate on keeping the logic
- // as straightforward as possible without trying to optimize anything.
- //
- // Note also that we rely (e.g., in the config module) on the fact that if
- // no overrides apply, then we return the original value and not its copy
- // in the cache (this is used to detect if the value was overriden).
- //
- assert (var.overrides != nullptr);
-
- const lookup& orig (original.first);
- size_t orig_depth (original.second);
-
- // The first step is to find out where our cache will reside. After some
- // meditation you will see it should be next to the innermost (scope-wise)
- // value of this variable (override or original).
- //
- // We also keep track of the root scope of the project from which this
- // innermost value comes. This is used to decide whether a non-recursive
- // project-wise override applies. And also where our variable cache is.
- //
- const variable_map* inner_vars (nullptr);
- const scope* inner_proj (nullptr);
-
- // One special case is if the original is target/rule-specific, which is
- // the most innermost. Or is it innermostest?
- //
- bool targetspec (false);
- if (target)
- {
- targetspec = orig.defined () && (orig_depth == 1 ||
- orig_depth == 2 ||
- (rule && orig_depth == 3));
- if (targetspec)
- {
- inner_vars = orig.vars;
- inner_proj = root_scope ();
- }
- }
-
- const scope* s;
-
- // Return true if the override applies to a value from vars/proj. Note
- // that it expects vars and proj to be not NULL; if there is nothing "more
- // inner", then any override will still be "visible".
- //
- auto applies = [&s] (const variable* o,
- const variable_map* vars,
- const scope* proj) -> bool
- {
- switch (o->visibility)
- {
- case variable_visibility::scope:
- {
- // Does not apply if in a different scope.
- //
- if (vars != &s->vars)
- return false;
-
- break;
- }
- case variable_visibility::project:
- {
- // Does not apply if in a subproject.
- //
- // Note that before we used to require the same project but that
- // missed values that are "visible" from the outer projects.
- //
- // If root scope is NULL, then we are looking at the global scope.
- //
- const scope* rs (s->root_scope ());
- if (rs != nullptr && rs->sub_root (*proj))
- return false;
-
- break;
- }
- case variable_visibility::normal:
- break;
- case variable_visibility::target:
- case variable_visibility::prereq:
- assert (false);
- }
-
- return true;
- };
-
- // Return the override value if present in scope s and (optionally) of
- // the specified kind (__override, __prefix, etc).
- //
- auto find = [&s, &var] (const variable* o,
- const char* k = nullptr) -> lookup
- {
- if (k != nullptr && !o->override (k))
- return lookup ();
-
- // Note: using the original as storage variable.
- //
- return lookup (s->vars.find (*o).first, &var, &s->vars);
- };
-
- // Return true if a value is from this scope (either target type/pattern-
- // specific or ordinary).
- //
- auto belongs = [&s, target] (const lookup& l) -> bool
- {
- if (target)
- {
- for (auto& p1: s->target_vars)
- for (auto& p2: p1.second)
- if (l.vars == &p2.second)
- return true;
- }
-
- return l.vars == &s->vars;
- };
-
- // While looking for the cache we also detect if none of the overrides
- // apply. In this case the result is simply the original value (if any).
- //
- bool apply (false);
-
- for (s = this; s != nullptr; s = s->parent_scope ())
- {
- // 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()).
- //
- if (inner_vars == nullptr && orig.defined () && belongs (orig))
- {
- inner_vars = orig.vars;
- inner_proj = s->root_scope ();
- }
-
- for (const variable* o (var.overrides.get ());
- o != nullptr;
- o = o->overrides.get ())
- {
- if (inner_vars != nullptr && !applies (o, inner_vars, inner_proj))
- continue;
-
- auto l (find (o));
-
- if (l.defined ())
- {
- if (inner_vars == nullptr)
- {
- inner_vars = l.vars;
- inner_proj = s->root_scope ();
- }
-
- apply = true;
- break;
- }
- }
-
- // We can stop if we found the cache and at least one override applies.
- //
- if (inner_vars != nullptr && apply)
- break;
- }
-
- if (!apply)
- return original;
-
- assert (inner_vars != nullptr);
-
- // If for some reason we are not in a project, use the cache from the
- // global scope.
- //
- if (inner_proj == nullptr)
- inner_proj = global_scope;
-
- // Now find our "stem", that is, the value to which we will be appending
- // suffixes and prepending prefixes. This is either the original or the
- // __override, provided it applies. We may also not have either.
- //
- lookup stem;
- size_t stem_depth (0);
- const scope* stem_proj (nullptr);
- const variable* stem_ovr (nullptr); // __override if found and applies.
-
- // Again the special case of a target/rule-specific variable.
- //
- if (targetspec)
- {
- stem = orig;
- stem_depth = orig_depth;
- stem_proj = root_scope ();
- }
-
- // Depth at which we found the override (with implied target/rule-specific
- // lookup counts).
- //
- size_t ovr_depth (target ? (rule ? 3 : 2) : 0);
-
- for (s = this; s != nullptr; s = s->parent_scope ())
- {
- bool done (false);
-
- // First check if the original is from this scope.
- //
- if (orig.defined () && belongs (orig))
- {
- stem = orig;
- stem_depth = orig_depth;
- stem_proj = s->root_scope ();
- // Keep searching.
- }
-
- ++ovr_depth;
-
- // Then look for an __override that applies.
- //
- // Note that the override list is in the reverse order of appearance and
- // so we will naturally see the most recent override first.
- //
- for (const variable* o (var.overrides.get ());
- o != nullptr;
- o = o->overrides.get ())
- {
- // If we haven't yet found anything, then any override will still be
- // "visible" even if it doesn't apply.
- //
- if (stem.defined () && !applies (o, stem.vars, stem_proj))
- continue;
-
- auto l (find (o, "__override"));
-
- if (l.defined ())
- {
- stem = move (l);
- stem_depth = ovr_depth;
- stem_proj = s->root_scope ();
- stem_ovr = o;
- done = true;
- break;
- }
- }
-
- if (done)
- break;
- }
-
- // Check the cache.
- //
- variable_override_cache& cache (
- inner_proj == global_scope
- ? global_override_cache
- : inner_proj->root_extra->override_cache);
-
- pair<value&, ulock> entry (
- cache.insert (
- make_pair (&var, inner_vars),
- stem,
- 0, // Overrides are immutable.
- var));
-
- value& cv (entry.first);
- bool cl (entry.second.owns_lock ());
-
- // If cache miss/invalidation, update the value.
- //
- if (cl)
- {
- // Note: very similar logic as in the target type/pattern specific cache
- // population code above.
- //
-
- // Un-typify the cache. This can be necessary, for example, if we are
- // changing from one value-typed stem to another.
- //
- if (!stem.defined () || cv.type != stem->type)
- {
- cv = nullptr;
- cv.type = nullptr; // Un-typify.
- }
-
- if (stem.defined ())
- cv = *stem;
-
- // Typify the cache value. If the stem is the original, then the type
- // would get propagated automatically. But the stem could also be the
- // override, which is kept untyped. Or the stem might not be there at
- // all while we still need to apply prefixes/suffixes in the type-aware
- // way.
- //
- if (cv.type == nullptr && var.type != nullptr)
- typify (cv, *var.type, &var);
- }
-
- // Now apply override prefixes and suffixes (if updating the cache). Also
- // calculate the vars and depth of the result, which will be those of the
- // stem or prefix/suffix that applies, whichever is the innermost.
- //
- // Note: we could probably cache this information instead of recalculating
- // it every time.
- //
- size_t depth (stem_depth);
- const variable_map* vars (stem.vars);
- const scope* proj (stem_proj);
-
- ovr_depth = target ? (rule ? 3 : 2) : 0;
-
- for (s = this; s != nullptr; s = s->parent_scope ())
- {
- ++ovr_depth;
-
- // The override list is in the reverse order of appearance so we need to
- // iterate backwards in order to apply things in the correct order.
- //
- // We also need to skip any append/prepend overrides that appear before
- // __override (in the command line order), provided it is from this
- // scope.
- //
- bool skip (stem_ovr != nullptr && stem_depth == ovr_depth);
-
- for (const variable* o (var.overrides->aliases); // Last override.
- o != nullptr;
- o = (o->aliases != var.overrides->aliases ? o->aliases : nullptr))
- {
- if (skip)
- {
- if (stem_ovr == o) // Keep skipping until after we see __override.
- skip = false;
-
- continue;
- }
-
- // First see if this override applies. This is tricky: what if the
- // stem is a "visible" override from an outer project? Shouldn't its
- // overrides apply? Sure sounds logical. So we use the project of the
- // stem's scope.
- //
- if (vars != nullptr && !applies (o, vars, proj))
- continue;
-
- // Note that we keep override values as untyped names even if the
- // variable itself is typed. We also pass the original variable for
- // diagnostics.
- //
- auto lp (find (o, "__prefix"));
- auto ls (find (o, "__suffix"));
-
- if (cl)
- {
- // Note: if we have both, then one is already in the stem.
- //
- if (lp) // No sense to prepend/append if NULL.
- {
- cv.prepend (names (cast<names> (lp)), &var);
- }
- else if (ls)
- {
- cv.append (names (cast<names> (ls)), &var);
- }
- }
-
- if (lp.defined () || ls.defined ())
- {
- // If we had no stem, use the first override as a surrogate stem.
- //
- if (vars == nullptr)
- {
- depth = ovr_depth;
- vars = &s->vars;
- proj = s->root_scope ();
- }
- // Otherwise, pick the innermost location between the stem and
- // prefix/suffix.
- //
- else if (ovr_depth < depth)
- {
- depth = ovr_depth;
- vars = &s->vars;
- }
- }
- }
- }
-
- // Use the location of the innermost value that contributed as the
- // location of the result.
- //
- return make_pair (lookup (&cv, &var, vars), depth);
- }
-
- value& scope::
- append (const variable& var)
- {
- // Note that here we want the original value without any overrides
- // applied.
- //
- lookup l (find_original (var).first);
-
- if (l.defined () && l.belongs (*this)) // Existing var in this scope.
- 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;
- }
-
- const target_type* scope::
- find_target_type (const string& tt, const scope** rs) const
- {
- // Search scopes outwards, stopping at the project root.
- //
- for (const scope* s (this);
- s != nullptr;
- s = s->root () ? global_scope : s->parent_scope ())
- {
- if (s->target_types.empty ())
- continue;
-
- if (const target_type* r = s->target_types.find (tt))
- {
- if (rs != nullptr)
- *rs = s;
-
- return r;
- }
- }
-
- return nullptr;
- }
-
- // Find target type from file name.
- //
- static const target_type*
- find_file_target_type (const scope* s, const string& n)
- {
- // Pretty much the same logic as in find_target_type() above.
- //
- for (; s != nullptr; s = s->root () ? global_scope : s->parent_scope ())
- {
- if (s->target_types.empty ())
- continue;
-
- if (const target_type* r = s->target_types.find_file (n))
- return r;
- }
-
- return nullptr;
- }
-
- pair<const target_type*, optional<string>> scope::
- find_target_type (name& n, const location& loc) const
- {
- const target_type* tt (nullptr);
- 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 (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;
- }
-
- // Directories require special name processing. If we find that more
- // targets deviate, then we should make this target type-specific.
- //
- if (tt != nullptr && (tt->is_a<dir> () || tt->is_a<fsdir> ()))
- {
- // The canonical representation of a directory name is with empty
- // value.
- //
- if (!v.empty ())
- {
- n.dir /= dir_path (v); // Move name value to dir.
- v.clear ();
- }
- }
- 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.
- //
- // See also parser::expand_name_pattern() if changing anything here.
- //
- size_t p (path::traits_type::rfind_separator (v));
-
- if (p != string::npos)
- {
- 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?
- //
- if (++p == v.size ())
- fail (loc) << "invalid name '" << v << "'";
-
- v.erase (0, p);
- }
-
- // Extract the extension.
- //
- ext = target::split_name (v, loc);
- }
-
- // If the target type is still unknown, map it using the name/extension,
- // falling back to file{}.
- //
- if (tt == nullptr)
- {
- // We only consider files without extension for file name mapping.
- //
- if (!ext)
- tt = find_file_target_type (this, v);
-
- //@@ TODO: derive type from extension.
-
- if (tt == nullptr)
- tt = &file::static_type;
- }
-
- // If the target type does not use extensions but one was specified,
- // factor it back into the name (this way we won't assert when printing
- // diagnostics; see to_stream(target_key) for details).
- //
- if (ext &&
- tt->fixed_extension == nullptr &&
- tt->default_extension == nullptr)
- {
- v += '.';
- v += *ext;
- ext = nullopt;
- }
-
- return make_pair (tt, move (ext));
- }
-
- static target*
- derived_tt_factory (const target_type& t, dir_path d, dir_path o, string n)
- {
- // Pass our type to the base factory so that it can detect that it is
- // being called to construct a derived target. This can be used, for
- // example, to decide whether to "link up" to the group.
- //
- // One exception: if we are derived from a derived target type, then this
- // logic would lead to infinite recursion. So in this case get the
- // ultimate base.
- //
- const target_type* bt (t.base);
- for (; bt->factory == &derived_tt_factory; bt = bt->base) ;
-
- target* r (bt->factory (t, move (d), move (o), move (n)));
- r->derived_type = &t;
- return r;
- }
-
- pair<reference_wrapper<const target_type>, bool> scope::
- derive_target_type (const string& name, const target_type& base)
- {
- // Base target type uses extensions.
- //
- bool ext (base.fixed_extension != nullptr ||
- base.default_extension != nullptr);
-
- // @@ Looks like we may need the ability to specify a fixed extension
- // (which will be used to compare existing targets and not just
- // search for existing files that is handled by the target_type::
- // extension hook). See the file_factory() for details. We will
- // probably need to specify it as part of the define directive (and
- // have the ability to specify empty and NULL).
- //
- // 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;
-
- // @@ 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
- // a fixed extension target and not overriding its extension? Some kind of
- // alias. Fuzzy.
- //
- dt->fixed_extension = nullptr /*&target_extension_fix<???>*/; // @@ TODO
-
- // Override default extension/pattern derivation function: we most likely
- // don't want to use the same default as our base (think cli: file). But,
- // if our base doesn't use extensions, then most likely neither do we
- // (think foo: alias).
- //
- dt->default_extension =
- ext && dt->fixed_extension == nullptr
- ? &target_extension_var<var_extension, nullptr>
- : nullptr;
-
- dt->pattern =
- dt->fixed_extension != nullptr ? nullptr /*&target_pattern_fix<???>*/ :
- dt->default_extension != nullptr ? &target_pattern_var<var_extension, nullptr> :
- nullptr;
-
- // There is actually a difference between "fixed fixed" (like man1{}) and
- // "fixed but overridable" (like file{}). Fuzzy: feels like there are
- // different kinds of "fixed" (file{} vs man{} vs man1{}).
- //
- dt->print =
- dt->fixed_extension != nullptr
- ? &target_print_0_ext_verb // Fixed extension, no use printing.
- : nullptr; // Normal.
-
- return target_types.insert (name, move (dt));
- }
-
- scope* scope::global_;
- scope::variable_override_cache scope::global_override_cache;
-
- // scope_map
- //
- scope_map scope_map::instance;
- const scope_map& scope_map::cinstance = scope_map::instance;
- const scope_map& scopes = scope_map::cinstance;
-
- const scope* global_scope;
-
- auto scope_map::
- insert (const dir_path& k, bool root) -> iterator
- {
- scope_map_base& m (*this);
-
- auto er (m.emplace (k, scope (true))); // Global.
- scope& s (er.first->second);
-
- // If this is a new scope, update the parent chain.
- //
- if (er.second)
- {
- scope* p (nullptr);
-
- // 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)
- {
- // The first entry is ourselves.
- //
- auto r (m.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;
- }
-
- // 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 ());
- }
-
- s.parent_ = p;
- s.root_ = root ? &s : (p != nullptr ? p->root_ : nullptr);
- }
- else if (root && !s.root ())
- {
- // Upgrade to root scope.
- //
- auto r (m.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;
- }
-
- s.root_ = &s;
- }
-
- return er.first;
- }
-
- scope& scope_map::
- find (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;
- }
-}