diff options
Diffstat (limited to 'libbuild2/scope.cxx')
-rw-r--r-- | libbuild2/scope.cxx | 421 |
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); } } |