aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/scope.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-06-24 12:01:19 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2019-07-01 18:13:55 +0300
commit977d07a3ae47ef204665d1eda2d642e5064724f3 (patch)
tree525a3d6421f61ce789b690191d3c30fc09be3517 /libbuild2/scope.cxx
parent7161b24963dd9da4d218f92c736b77c35c328a2d (diff)
Split build system into library and driver
Diffstat (limited to 'libbuild2/scope.cxx')
-rw-r--r--libbuild2/scope.cxx911
1 files changed, 911 insertions, 0 deletions
diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx
new file mode 100644
index 0000000..1ad7455
--- /dev/null
+++ b/libbuild2/scope.cxx
@@ -0,0 +1,911 @@
+// file : libbuild2/scope.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/scope.hxx>
+
+#include <libbuild2/target.hxx>
+#include <libbuild2/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;
+ }
+}