diff options
Diffstat (limited to 'libbuild2/variable.cxx')
-rw-r--r-- | libbuild2/variable.cxx | 527 |
1 files changed, 426 insertions, 101 deletions
diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index 6f2812c..8c52e22 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -3,7 +3,7 @@ #include <libbuild2/variable.hxx> -#include <cstring> // memcmp() +#include <cstring> // memcmp(), memcpy() #include <libbutl/path-pattern.hxx> @@ -47,7 +47,7 @@ namespace build2 } value:: - value (value&& v) + value (value&& v) noexcept : type (v.type), null (v.null), extra (v.extra) { if (!null) @@ -57,7 +57,7 @@ namespace build2 else if (type->copy_ctor != nullptr) type->copy_ctor (*this, v, true); else - data_ = v.data_; // Copy as POD. + memcpy (data_, v.data_, size_); // Copy as POD. } } @@ -72,7 +72,7 @@ namespace build2 else if (type->copy_ctor != nullptr) type->copy_ctor (*this, v, false); else - data_ = v.data_; // Copy as POD. + memcpy (data_, v.data_, size_); // Copy as POD. } } @@ -99,12 +99,14 @@ namespace build2 if (null) new (&data_) names (move (v).as<names> ()); else + // Note: can throw (see small_vector for details). + // as<names> () = move (v).as<names> (); } else if (auto f = null ? type->copy_ctor : type->copy_assign) f (*this, v, true); else - data_ = v.data_; // Assign as POD. + memcpy (data_, v.data_, size_); // Assign as POD. null = v.null; } @@ -143,7 +145,7 @@ namespace build2 else if (auto f = null ? type->copy_ctor : type->copy_assign) f (*this, v, false); else - data_ = v.data_; // Assign as POD. + memcpy (data_, v.data_, size_); // Assign as POD. null = v.null; } @@ -367,8 +369,8 @@ namespace build2 // Typification is kind of like caching so we reuse that mutex shard. // shared_mutex& m ( - ctx.mutexes.variable_cache[ - hash<value*> () (&v) % ctx.mutexes.variable_cache_size]); + ctx.mutexes->variable_cache[ + hash<value*> () (&v) % ctx.mutexes->variable_cache_size]); // Note: v.type is rechecked by typify() under lock. // @@ -377,7 +379,7 @@ namespace build2 } void - untypify (value& v) + untypify (value& v, bool reduce) { if (v.type == nullptr) return; @@ -389,7 +391,7 @@ namespace build2 } names ns; - names_view nv (v.type->reverse (v, ns)); + names_view nv (v.type->reverse (v, ns, reduce)); if (nv.empty () || nv.data () == ns.data ()) { @@ -451,11 +453,11 @@ namespace build2 m = "invalid " + t + " value "; if (n.simple ()) - m += "'" + n.value + "'"; + m += '\'' + n.value + '\''; else if (n.directory ()) - m += "'" + n.dir.representation () + "'"; + m += '\'' + n.dir.representation () + '\''; else - m += "name '" + to_string (n) + "'"; + m += "name '" + to_string (n) + '\''; } throw invalid_argument (m); @@ -470,7 +472,7 @@ namespace build2 bool value_traits<bool>:: convert (const name& n, const name* r) { - if (r == nullptr && !n.pattern && n.simple () ) + if (r == nullptr && !n.pattern && n.simple ()) { const string& s (n.value); @@ -493,6 +495,7 @@ namespace build2 type_name, sizeof (bool), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -515,13 +518,22 @@ namespace build2 { try { - // May throw invalid_argument or out_of_range. - // - size_t i; - int64_t r (stoll (n.value, &i)); + const string& v (n.value); + + if (!wspace (v[0])) + { + // Note that unlike uint64, we don't support hex notation for int64. + + // May throw invalid_argument or out_of_range. + // + size_t i; + int64_t r (stoll (v, &i)); + + if (i == v.size ()) + return r; - if (i == n.value.size ()) - return r; + // Fall through. + } // Fall through. } @@ -541,6 +553,7 @@ namespace build2 type_name, sizeof (int64_t), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -563,13 +576,22 @@ namespace build2 { try { - // May throw invalid_argument or out_of_range. - // - size_t i; - uint64_t r (stoull (n.value, &i)); + const string& v (n.value); + + if (!wspace (v[0])) + { + int b (v[0] == '0' && (v[1] == 'x' || v[1] == 'X') ? 16 : 10); + + // May throw invalid_argument or out_of_range. + // + size_t i; + uint64_t r (stoull (v, &i, b)); - if (i == n.value.size ()) - return r; + if (i == v.size ()) + return r; + + // Fall through. + } // Fall through. } @@ -589,6 +611,7 @@ namespace build2 type_name, sizeof (uint64_t), nullptr, // No base. + false, // Not container. nullptr, // No element. nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). @@ -612,28 +635,31 @@ namespace build2 // the common cases (unqualified, unpaired simple name or directory). // - // We can only convert project-qualified simple and directory names. + // We can only convert project-qualified untyped names. // - if (n.pattern || - !(n.simple (true) || n.directory (true))) + if (n.pattern || n.typed ()) throw_invalid_argument (n, nullptr, "string"); if (r != nullptr) { - if (r->pattern || - !(r->simple (true) || r->directory (true))) + if (r->pattern || r->typed ()) throw_invalid_argument (*r, nullptr, "string"); } string s; - if (n.directory (true)) + if (n.simple (true)) + s.swap (n.value); + else + { // Note that here we cannot assume what's in dir is really a // path (think s/foo/bar/) so we have to reverse it exactly. // s = move (n.dir).representation (); // Move out of path. - else - s.swap (n.value); + + if (!n.value.empty ()) + s += n.value; // Separator is already there. + } // Convert project qualification to its string representation. // @@ -657,10 +683,15 @@ namespace build2 s += '%'; } - if (r->directory (true)) - s += move (r->dir).representation (); - else + if (r->simple (true)) s += r->value; + else + { + s += move (r->dir).representation (); + + if (!r->value.empty ()) + s += r->value; + } } return s; @@ -675,6 +706,7 @@ namespace build2 type_name, sizeof (string), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<string>, &default_copy_ctor<string>, @@ -742,6 +774,7 @@ namespace build2 type_name, sizeof (path), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<path>, &default_copy_ctor<path>, @@ -809,6 +842,7 @@ namespace build2 sizeof (dir_path), &value_traits<path>::value_type, // Base (assuming direct cast works for // both). + false, // Not container. nullptr, // No element. &default_dtor<dir_path>, &default_copy_ctor<dir_path>, @@ -857,6 +891,7 @@ namespace build2 sizeof (abs_dir_path), &value_traits<dir_path>::value_type, // Base (assuming direct cast works // for both). + false, // Not container. nullptr, // No element. &default_dtor<abs_dir_path>, &default_copy_ctor<abs_dir_path>, @@ -882,10 +917,10 @@ namespace build2 } static names_view - name_reverse (const value& v, names&) + name_reverse (const value& v, names&, bool reduce) { const name& n (v.as<name> ()); - return n.empty () ? names_view (nullptr, 0) : names_view (&n, 1); + return reduce && n.empty () ? names_view (nullptr, 0) : names_view (&n, 1); } const char* const value_traits<name>::type_name = "name"; @@ -895,6 +930,7 @@ namespace build2 type_name, sizeof (name), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<name>, &default_copy_ctor<name>, @@ -949,13 +985,13 @@ namespace build2 } static names_view - name_pair_reverse (const value& v, names& ns) + name_pair_reverse (const value& v, names& ns, bool reduce) { const name_pair& p (v.as<name_pair> ()); const name& f (p.first); const name& s (p.second); - if (f.empty () && s.empty ()) + if (reduce && f.empty () && s.empty ()) return names_view (nullptr, 0); if (f.empty ()) @@ -977,6 +1013,7 @@ namespace build2 type_name, sizeof (name_pair), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<name_pair>, &default_copy_ctor<name_pair>, @@ -1107,10 +1144,14 @@ namespace build2 } static names_view - process_path_reverse (const value& v, names& s) + process_path_reverse (const value& v, names& s, bool) { const auto& x (v.as<process_path> ()); + // Note that strictly speaking process_path doesn't have empty + // representation (see convert() above). Thus we always return reduced + // representation. + // if (!x.empty ()) { s.reserve (x.effect.empty () ? 1 : 2); @@ -1127,6 +1168,7 @@ namespace build2 type_name, sizeof (process_path), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<process_path>, &process_path_copy_ctor<process_path>, @@ -1275,10 +1317,13 @@ namespace build2 } static names_view - process_path_ex_reverse (const value& v, names& s) + process_path_ex_reverse (const value& v, names& s, bool) { const auto& x (v.as<process_path_ex> ()); + // Note that process_path_ex only has reduced empty representation (see + // convert() above). + // if (!x.empty ()) { s.reserve ((x.effect.empty () ? 1 : 2) + @@ -1322,6 +1367,7 @@ namespace build2 sizeof (process_path_ex), &value_traits< // Base (assuming direct cast works process_path>::value_type, // for both). + false, // Not container. nullptr, // No element. &default_dtor<process_path_ex>, &process_path_ex_copy_ctor, @@ -1363,6 +1409,7 @@ namespace build2 type_name, sizeof (target_triplet), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<target_triplet>, &default_copy_ctor<target_triplet>, @@ -1407,6 +1454,7 @@ namespace build2 type_name, sizeof (project_name), nullptr, // No base. + false, // Not container. nullptr, // No element. &default_dtor<project_name>, &default_copy_ctor<project_name>, @@ -1420,6 +1468,139 @@ namespace build2 &default_empty<project_name> }; + // cmdline + // + cmdline value_traits<cmdline>:: + convert (names&& ns) + { + return cmdline (make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + void value_traits<cmdline>:: + assign (value& v, cmdline&& x) + { + if (v) + v.as<cmdline> () = move (x); + else + new (&v.data_) cmdline (move (x)); + } + + void value_traits<cmdline>:: + append (value& v, cmdline&& x) + { + if (v) + { + cmdline& p (v.as<cmdline> ()); + + if (p.empty ()) + p.swap (x); + else + p.insert (p.end (), + make_move_iterator (x.begin ()), + make_move_iterator (x.end ())); + } + else + new (&v.data_) cmdline (move (x)); + } + + void value_traits<cmdline>:: + prepend (value& v, cmdline&& x) + { + if (v) + { + cmdline& p (v.as<cmdline> ()); + + if (!p.empty ()) + x.insert (x.end (), + make_move_iterator (p.begin ()), + make_move_iterator (p.end ())); + + p.swap (x); + } + else + new (&v.data_) cmdline (move (x)); + } + + void + cmdline_assign (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + v.as<cmdline> ().assign (make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + void + cmdline_append (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + auto& x (v.as<cmdline> ()); + x.insert (x.end (), + make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + void + cmdline_prepend (value& v, names&& ns, const variable*) + { + if (!v) + { + new (&v.data_) cmdline (); + v.null = false; + } + + auto& x (v.as<cmdline> ()); + x.insert (x.begin (), + make_move_iterator (ns.begin ()), + make_move_iterator (ns.end ())); + } + + static names_view + cmdline_reverse (const value& v, names&, bool) + { + const auto& x (v.as<cmdline> ()); + return names_view (x.data (), x.size ()); + } + + static int + cmdline_compare (const value& l, const value& r) + { + return vector_compare<name> (l, r); + } + + const cmdline value_traits<cmdline>::empty_instance; + + const char* const value_traits<cmdline>::type_name = "cmdline"; + + const value_type value_traits<cmdline>::value_type + { + type_name, + sizeof (cmdline), + nullptr, // No base. + true, // Container. + &value_traits<string>::value_type, // Element type. + &default_dtor<cmdline>, + &default_copy_ctor<cmdline>, + &default_copy_assign<cmdline>, + &cmdline_assign, + &cmdline_append, + &cmdline_prepend, + &cmdline_reverse, + nullptr, // No cast (cast data_ directly). + &cmdline_compare, + &default_empty<cmdline> + }; + // variable_pool // void variable_pool:: @@ -1428,6 +1609,17 @@ namespace build2 const variable_visibility* v, const bool* o) const { + assert (var.owner == this); + + if (outer_ != nullptr) + { + // Project-private variable. Assert visibility/overridability, the same + // as in insert(). + // + assert ((o == nullptr || !*o) && + (v == nullptr || *v >= variable_visibility::project)); + } + // Check overridability (all overrides, if any, should already have // been entered; see context ctor for details). // @@ -1509,7 +1701,7 @@ namespace build2 } static inline void - merge_pattern (const variable_pool::pattern& p, + merge_pattern (const variable_patterns::pattern& p, const build2::value_type*& t, const variable_visibility*& v, const bool*& o) @@ -1560,20 +1752,68 @@ namespace build2 const bool* o, bool pat) { - assert (!global_ || global_->phase == run_phase::load); + if (outer_ != nullptr) + { + // Project-private pool. + // + if (n.find ('.') != string::npos) // Qualified. + return outer_->insert (move (n), t, v, o, pat); + + // Unqualified. + // + // The pool chaining semantics for insertion: first check the outer pool + // then, if not found, insert in own pool. + // + if (const variable* var = outer_->find (n)) + { + // Verify type/visibility/overridability. + // + // Should we assert or fail? Currently the buildfile parser goes + // through update() to set these so let's do assert for now. We also + // require equality (these are a handful of special variables). + // + assert ((t == nullptr || t == var->type) && + (v == nullptr || *v == var->visibility) && + (o == nullptr || *o || var->overrides == nullptr)); + + return pair<variable&, bool> (const_cast<variable&> (*var), false); + } + + // Project-private variable. Assert visibility/overridability and fall + // through. Again, we expect the buildfile parser to verify and diagnose + // these. + // + // Note: similar code in update(). + // + assert ((o == nullptr || !*o) && + (v == nullptr || *v >= variable_visibility::project)); + } + else if (shared_) + { + // Public pool. + // + // Make sure all the unqualified variables are pre-entered during + // initialization. + // + assert (shared_->load_generation == 0 || n.find ('.') != string::npos); + } + + assert (!shared_ || shared_->phase == run_phase::load); // Apply pattern. // + using pattern = variable_patterns::pattern; + const pattern* pa (nullptr); auto pt (t); auto pv (v); auto po (o); - if (pat) + if (pat && patterns_ != nullptr) { if (n.find ('.') != string::npos) { // Reverse means from the "largest" (most specific). // - for (const pattern& p: reverse_iterate (patterns_)) + for (const pattern& p: reverse_iterate (patterns_->patterns_)) { if (match_pattern (n, p.prefix, p.suffix, p.multi)) { @@ -1590,6 +1830,7 @@ namespace build2 variable { move (n), nullptr, + nullptr, pt, nullptr, pv != nullptr ? *pv : variable_visibility::project})); @@ -1597,7 +1838,10 @@ namespace build2 variable& var (r.first->second); if (r.second) + { + var.owner = this; var.aliases = &var; + } else // Note: overridden variable will always exist. { // This is tricky: if the pattern does not require a match, then we @@ -1625,7 +1869,15 @@ namespace build2 const variable& variable_pool:: insert_alias (const variable& var, string n) { - assert (var.aliases != nullptr && var.overrides == nullptr); + if (outer_ != nullptr) + { + assert (n.find ('.') != string::npos); // Qualified. + return outer_->insert_alias (var, move (n)); + } + + assert (var.owner == this && + var.aliases != nullptr && + var.overrides == nullptr); variable& a (insert (move (n), var.type, @@ -1646,15 +1898,15 @@ namespace build2 return a; } - void variable_pool:: - insert_pattern (const string& p, - optional<const value_type*> t, - optional<bool> o, - optional<variable_visibility> v, - bool retro, - bool match) + void variable_patterns:: + insert (const string& p, + optional<const value_type*> t, + optional<bool> o, + optional<variable_visibility> v, + bool retro, + bool match) { - assert (!global_ || global_->phase == run_phase::load); + assert (!shared_ || shared_->phase == run_phase::load); size_t pn (p.size ()); @@ -1688,9 +1940,9 @@ namespace build2 // Apply retrospectively to existing variables. // - if (retro) + if (retro && pool_ != nullptr) { - for (auto& p: map_) + for (auto& p: pool_->map_) { variable& var (p.second); @@ -1707,10 +1959,10 @@ namespace build2 } if (j == e) - update (var, - t ? *t : nullptr, - v ? &*v : nullptr, - o ? &*o : nullptr); // Not changing the key. + pool_->update (var, + t ? *t : nullptr, + v ? &*v : nullptr, + o ? &*o : nullptr); // Not changing the key. } } } @@ -1718,7 +1970,66 @@ namespace build2 // variable_map // - const variable_map empty_variable_map (nullptr /* context */); + const variable_map empty_variable_map (variable_map::owner::empty); + + // Need scope/target definition thus not inline. + // + variable_map:: + variable_map (const scope& s, bool shared) + : shared_ (shared), owner_ (owner::scope), scope_ (&s), ctx (&s.ctx) + { + } + + variable_map:: + variable_map (const target& t, bool shared) + : shared_ (shared), owner_ (owner::target), target_ (&t), ctx (&t.ctx) + { + } + + variable_map:: + variable_map (const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::prereq), prereq_ (&p), + ctx (&p.scope.ctx) + { + } + + variable_map:: + variable_map (variable_map&& v, const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::scope), prereq_ (&p), + ctx (&p.scope.ctx), + m_ (move (v.m_)) + { + } + + variable_map:: + variable_map (const variable_map& v, const prerequisite& p, bool shared) + : shared_ (shared), + owner_ (owner::scope), prereq_ (&p), + ctx (&p.scope.ctx), + m_ (v.m_) + { + } + + lookup variable_map:: + lookup (const string& name) const + { + lookup_type r; + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + if (const variable* var = bs->var_pool ().find (name)) + { + auto p (lookup (*var)); + r = lookup_type (p.first, &p.second, this); + } + + return r; + } auto variable_map:: lookup (const variable& var, bool typed, bool aliased) const -> @@ -1761,24 +2072,43 @@ namespace build2 auto* r (const_cast<value_data*> (p.first)); if (r != nullptr) + { + r->extra = 0; r->version++; + } return pair<value_data*, const variable&> (r, p.second); } + value& variable_map:: + assign (const string& name) + { + assert (owner_ != owner::context); + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + return insert (bs->var_pool ()[name]).first; + } + pair<value&, bool> variable_map:: - insert (const variable& var, bool typed) + insert (const variable& var, bool typed, bool reset_extra) { - assert (!global_ || ctx->phase == run_phase::load); + assert (!shared_ || ctx->phase == run_phase::load); auto p (m_.emplace (var, value_data (typed ? var.type : nullptr))); value_data& r (p.first->second); if (!p.second) { + if (reset_extra) + r.extra = 0; + // Check if this is the first access after being assigned a type. // - // Note: we still need atomic in case this is not a global state. + // Note: we still need atomic in case this is not a shared state. // if (typed && var.type != nullptr) typify (r, var); @@ -1789,21 +2119,47 @@ namespace build2 return pair<value&, bool> (r, p.second); } + auto variable_map:: + find (const string& name) const -> const_iterator + { + assert (owner_ != owner::context); + + const scope* bs (owner_ == owner::scope ? scope_ : + owner_ == owner::target ? &target_->base_scope () : + owner_ == owner::prereq ? &prereq_->scope : + nullptr); + + + const variable* var (bs->var_pool ().find (name)); + return var != nullptr ? find (*var) : end (); + } + bool variable_map:: erase (const variable& var) { - assert (!global_ || ctx->phase == run_phase::load); + assert (!shared_ || ctx->phase == run_phase::load); return m_.erase (var) != 0; } + variable_map::const_iterator variable_map:: + erase (const_iterator i) + { + assert (!shared_ || ctx->phase == run_phase::load); + + return const_iterator (m_.erase (i), *this); + } + // variable_pattern_map // variable_map& variable_pattern_map:: insert (pattern_type type, string&& text) { + // Note that this variable map is special and we use context as its owner + // (see variable_map for details). + // auto r (map_.emplace (pattern {type, false, move (text), {}}, - variable_map (ctx, global_))); + variable_map (ctx, shared_))); // Compile the regex. // @@ -1857,39 +2213,8 @@ namespace build2 { if (!oname) { - const target_type& tt (*tk.type); - - // Note that if the name is not empty, then we always use that, even - // if the type is dir/fsdir. - // - if (tk.name->empty () && (tt.is_a<dir> () || tt.is_a<fsdir> ())) - { - oname = tk.dir->leaf ().string (); - } - // If we have the extension and the type expects the extension to be - // always specified explicitly by the user, then add it to the name. - // - // Overall, we have the following cases: - // - // 1. Extension is fixed: man1{}. - // - // 2. Extension is always specified by the user: file{}. - // - // 3. Default extension that may be overridden by the user: hxx{}. - // - // 4. Extension assigned by the rule but may be overridden by the - // user: obje{}. - // - // By default we only match the extension for (2). - // - else if (tk.ext && !tk.ext->empty () && - (tt.fixed_extension == &target_extension_none || - tt.fixed_extension == &target_extension_must)) - { - oname = *tk.name + '.' + *tk.ext; - } - else - oname = string (); // Use tk.name as is. + oname = string (); + tk.effective_name (*oname); } return oname->empty () ? *tk.name : *oname; |