aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/variable.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/variable.cxx
parent7161b24963dd9da4d218f92c736b77c35c328a2d (diff)
Split build system into library and driver
Diffstat (limited to 'libbuild2/variable.cxx')
-rw-r--r--libbuild2/variable.cxx1533
1 files changed, 1533 insertions, 0 deletions
diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx
new file mode 100644
index 0000000..beb169e
--- /dev/null
+++ b/libbuild2/variable.cxx
@@ -0,0 +1,1533 @@
+// file : libbuild2/variable.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/variable.hxx>
+
+#include <cstring> // memcmp()
+
+#include <libbutl/filesystem.mxx> // path_match()
+
+#include <libbuild2/context.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ // variable_visibility
+ //
+ ostream&
+ operator<< (ostream& o, variable_visibility v)
+ {
+ const char* s (nullptr);
+
+ switch (v)
+ {
+ case variable_visibility::normal: s = "normal"; break;
+ case variable_visibility::project: s = "project"; break;
+ case variable_visibility::scope: s = "scope"; break;
+ case variable_visibility::target: s = "target"; break;
+ case variable_visibility::prereq: s = "prerequisite"; break;
+ }
+
+ return o << s;
+ }
+
+ // value
+ //
+ void value::
+ reset ()
+ {
+ if (type == nullptr)
+ as<names> ().~names ();
+ else if (type->dtor != nullptr)
+ type->dtor (*this);
+
+ null = true;
+ }
+
+ value::
+ value (value&& v)
+ : type (v.type), null (v.null), extra (v.extra)
+ {
+ if (!null)
+ {
+ if (type == nullptr)
+ new (&data_) names (move (v).as<names> ());
+ else if (type->copy_ctor != nullptr)
+ type->copy_ctor (*this, v, true);
+ else
+ data_ = v.data_; // Copy as POD.
+ }
+ }
+
+ value::
+ value (const value& v)
+ : type (v.type), null (v.null), extra (v.extra)
+ {
+ if (!null)
+ {
+ if (type == nullptr)
+ new (&data_) names (v.as<names> ());
+ else if (type->copy_ctor != nullptr)
+ type->copy_ctor (*this, v, false);
+ else
+ data_ = v.data_; // Copy as POD.
+ }
+ }
+
+ value& value::
+ operator= (value&& v)
+ {
+ if (this != &v)
+ {
+ // Prepare the receiving value.
+ //
+ if (type != v.type)
+ {
+ *this = nullptr;
+ type = v.type;
+ }
+
+ // Now our types are the same. If the receiving value is NULL, then call
+ // copy_ctor() instead of copy_assign().
+ //
+ if (v)
+ {
+ if (type == nullptr)
+ {
+ if (null)
+ new (&data_) names (move (v).as<names> ());
+ else
+ 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.
+
+ null = v.null;
+ }
+ else
+ *this = nullptr;
+ }
+
+ return *this;
+ }
+
+ value& value::
+ operator= (const value& v)
+ {
+ if (this != &v)
+ {
+ // Prepare the receiving value.
+ //
+ if (type != v.type)
+ {
+ *this = nullptr;
+ type = v.type;
+ }
+
+ // Now our types are the same. If the receiving value is NULL, then call
+ // copy_ctor() instead of copy_assign().
+ //
+ if (v)
+ {
+ if (type == nullptr)
+ {
+ if (null)
+ new (&data_) names (v.as<names> ());
+ else
+ as<names> () = v.as<names> ();
+ }
+ else if (auto f = null ? type->copy_ctor : type->copy_assign)
+ f (*this, v, false);
+ else
+ data_ = v.data_; // Assign as POD.
+
+ null = v.null;
+ }
+ else
+ *this = nullptr;
+ }
+
+ return *this;
+ }
+
+ void value::
+ assign (names&& ns, const variable* var)
+ {
+ assert (type == nullptr || type->assign != nullptr);
+
+ if (type == nullptr)
+ {
+ if (null)
+ new (&data_) names (move (ns));
+ else
+ as<names> () = move (ns);
+ }
+ else
+ type->assign (*this, move (ns), var);
+
+ null = false;
+ }
+
+ void value::
+ append (names&& ns, const variable* var)
+ {
+ if (type == nullptr)
+ {
+ if (null)
+ new (&data_) names (move (ns));
+ else
+ {
+ names& p (as<names> ());
+
+ if (p.empty ())
+ p = move (ns);
+ else if (!ns.empty ())
+ {
+ p.insert (p.end (),
+ make_move_iterator (ns.begin ()),
+ make_move_iterator (ns.end ()));
+ }
+ }
+ }
+ else
+ {
+ if (type->append == nullptr)
+ {
+ diag_record dr (fail);
+
+ dr << "cannot append to " << type->name << " value";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ type->append (*this, move (ns), var);
+ }
+
+ null = false;
+ }
+
+ void value::
+ prepend (names&& ns, const variable* var)
+ {
+ if (type == nullptr)
+ {
+ if (null)
+ new (&data_) names (move (ns));
+ else
+ {
+ names& p (as<names> ());
+
+ if (p.empty ())
+ p = move (ns);
+ else if (!ns.empty ())
+ {
+ ns.insert (ns.end (),
+ make_move_iterator (p.begin ()),
+ make_move_iterator (p.end ()));
+ p = move (ns);
+ }
+ }
+ }
+ else
+ {
+ if (type->prepend == nullptr)
+ {
+ diag_record dr (fail);
+
+ dr << "cannot prepend to " << type->name << " value";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ type->prepend (*this, move (ns), var);
+ }
+
+ null = false;
+ }
+
+ bool
+ operator== (const value& x, const value& y)
+ {
+ bool xn (x.null);
+ bool yn (y.null);
+
+ assert (x.type == y.type ||
+ (xn && x.type == nullptr) ||
+ (yn && y.type == nullptr));
+
+ if (xn || yn)
+ return xn == yn;
+
+ if (x.type == nullptr)
+ return x.as<names> () == y.as<names> ();
+
+ if (x.type->compare == nullptr)
+ return memcmp (&x.data_, &y.data_, x.type->size) == 0;
+
+ return x.type->compare (x, y) == 0;
+ }
+
+ bool
+ operator< (const value& x, const value& y)
+ {
+ bool xn (x.null);
+ bool yn (y.null);
+
+ assert (x.type == y.type ||
+ (xn && x.type == nullptr) ||
+ (yn && y.type == nullptr));
+
+ // NULL value is always less than non-NULL.
+ //
+ if (xn || yn)
+ return xn > yn; // !xn < !yn
+
+ if (x.type == nullptr)
+ return x.as<names> () < y.as<names> ();
+
+ if (x.type->compare == nullptr)
+ return memcmp (&x.data_, &y.data_, x.type->size) < 0;
+
+ return x.type->compare (x, y) < 0;
+ }
+
+ bool
+ operator> (const value& x, const value& y)
+ {
+ bool xn (x.null);
+ bool yn (y.null);
+
+ assert (x.type == y.type ||
+ (xn && x.type == nullptr) ||
+ (yn && y.type == nullptr));
+
+ // NULL value is always less than non-NULL.
+ //
+ if (xn || yn)
+ return xn < yn; // !xn > !yn
+
+ if (x.type == nullptr)
+ return x.as<names> () > y.as<names> ();
+
+ if (x.type->compare == nullptr)
+ return memcmp (&x.data_, &y.data_, x.type->size) > 0;
+
+ return x.type->compare (x, y) > 0;
+ }
+
+ void
+ typify (value& v, const value_type& t, const variable* var, memory_order mo)
+ {
+ if (v.type == nullptr)
+ {
+ if (v)
+ {
+ // Note: the order in which we do things here is important.
+ //
+ names ns (move (v).as<names> ());
+ v = nullptr;
+
+ // Use value_type::assign directly to delay v.type change.
+ //
+ t.assign (v, move (ns), var);
+ v.null = false;
+ }
+ else
+ v.type = &t;
+
+ v.type.store (&t, mo);
+ }
+ else if (v.type != &t)
+ {
+ diag_record dr (fail);
+
+ dr << "type mismatch";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << info << "value type is " << v.type->name;
+ dr << info << (var != nullptr && &t == var->type ? "variable" : "new")
+ << " type is " << t.name;
+ }
+ }
+
+ void
+ typify_atomic (value& v, const value_type& t, const variable* var)
+ {
+ // Typification is kind of like caching so we reuse that mutex shard.
+ //
+ shared_mutex& m (
+ variable_cache_mutex_shard[
+ hash<value*> () (&v) % variable_cache_mutex_shard_size]);
+
+ // Note: v.type is rechecked by typify() under lock.
+ //
+ ulock l (m);
+ typify (v, t, var, memory_order_release);
+ }
+
+ void
+ untypify (value& v)
+ {
+ if (v.type == nullptr)
+ return;
+
+ if (v.null)
+ {
+ v.type = nullptr;
+ return;
+ }
+
+ names ns;
+ names_view nv (v.type->reverse (v, ns));
+
+ if (nv.empty () || nv.data () == ns.data ())
+ {
+ // If the data is in storage, then we are all set.
+ //
+ ns.resize (nv.size ()); // Just to be sure.
+ }
+ else
+ {
+ // If the data is somewhere in the value itself, then steal it.
+ //
+ auto b (const_cast<name*> (nv.data ()));
+ ns.assign (make_move_iterator (b),
+ make_move_iterator (b + nv.size ()));
+ }
+
+ v = nullptr; // Free old data.
+ v.type = nullptr; // Change type.
+ v.assign (move (ns), nullptr); // Assign new data.
+ }
+
+ // Throw invalid_argument for an invalid simple value.
+ //
+ [[noreturn]] static void
+ throw_invalid_argument (const name& n, const name* r, const char* type)
+ {
+ string m;
+ string t (type);
+
+ if (r != nullptr)
+ m = "pair in " + t + " value";
+ else
+ {
+ m = "invalid " + t + " value: ";
+
+ if (n.simple ())
+ m += "'" + n.value + "'";
+ else if (n.directory ())
+ m += "'" + n.dir.representation () + "'";
+ else
+ m += "complex name";
+ }
+
+ throw invalid_argument (m);
+ }
+
+ // names
+ //
+ const names& value_traits<names>::empty_instance = empty_names;
+
+ // bool value
+ //
+ bool value_traits<bool>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr && n.simple ())
+ {
+ const string& s (n.value);
+
+ if (s == "true")
+ return true;
+
+ if (s == "false")
+ return false;
+
+ // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "bool");
+ }
+
+ const char* const value_traits<bool>::type_name = "bool";
+
+ const value_type value_traits<bool>::value_type
+ {
+ type_name,
+ sizeof (bool),
+ nullptr, // No base.
+ nullptr, // No element.
+ nullptr, // No dtor (POD).
+ nullptr, // No copy_ctor (POD).
+ nullptr, // No copy_assign (POD).
+ &simple_assign<bool>,
+ &simple_append<bool>,
+ &simple_append<bool>, // Prepend same as append.
+ &simple_reverse<bool>,
+ nullptr, // No cast (cast data_ directly).
+ nullptr, // No compare (compare as POD).
+ nullptr // Never empty.
+ };
+
+ // uint64_t value
+ //
+ uint64_t value_traits<uint64_t>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr && n.simple ())
+ {
+ try
+ {
+ // May throw invalid_argument or out_of_range.
+ //
+ return stoull (n.value);
+ }
+ catch (const std::exception&)
+ {
+ // Fall through.
+ }
+ }
+
+ throw_invalid_argument (n, r, "uint64");
+ }
+
+ const char* const value_traits<uint64_t>::type_name = "uint64";
+
+ const value_type value_traits<uint64_t>::value_type
+ {
+ type_name,
+ sizeof (uint64_t),
+ nullptr, // No base.
+ nullptr, // No element.
+ nullptr, // No dtor (POD).
+ nullptr, // No copy_ctor (POD).
+ nullptr, // No copy_assign (POD).
+ &simple_assign<uint64_t>,
+ &simple_append<uint64_t>,
+ &simple_append<uint64_t>, // Prepend same as append.
+ &simple_reverse<uint64_t>,
+ nullptr, // No cast (cast data_ directly).
+ nullptr, // No compare (compare as POD).
+ nullptr // Never empty.
+ };
+
+ // string value
+ //
+ string value_traits<string>::
+ convert (name&& n, name* r)
+ {
+ // The goal is to reverse the name into its original representation. The
+ // code is a bit convoluted because we try to avoid extra allocations for
+ // the common cases (unqualified, unpaired simple name or directory).
+ //
+
+ // We can only convert project-qualified simple and directory names.
+ //
+ if (!(n.simple (true) || n.directory (true)) ||
+ !(r == nullptr || r->simple (true) || r->directory (true)))
+ throw_invalid_argument (n, r, "string");
+
+ string s;
+
+ if (n.directory (true))
+ // 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);
+
+ // Convert project qualification to its string representation.
+ //
+ if (n.qualified ())
+ {
+ string p (move (*n.proj).string ());
+ p += '%';
+ p += s;
+ p.swap (s);
+ }
+
+ // The same for the RHS of a pair, if we have one.
+ //
+ if (r != nullptr)
+ {
+ s += '@';
+
+ if (r->qualified ())
+ {
+ s += r->proj->string ();
+ s += '%';
+ }
+
+ if (r->directory (true))
+ s += move (r->dir).representation ();
+ else
+ s += r->value;
+ }
+
+ return s;
+ }
+
+ const string& value_traits<string>::empty_instance = empty_string;
+
+ const char* const value_traits<string>::type_name = "string";
+
+ const value_type value_traits<string>::value_type
+ {
+ type_name,
+ sizeof (string),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<string>,
+ &default_copy_ctor<string>,
+ &default_copy_assign<string>,
+ &simple_assign<string>,
+ &simple_append<string>,
+ &simple_prepend<string>,
+ &simple_reverse<string>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<string>,
+ &default_empty<string>
+ };
+
+ // path value
+ //
+ path value_traits<path>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr)
+ {
+ // A directory path is a path.
+ //
+ if (n.directory ())
+ return move (n.dir);
+
+ if (n.simple ())
+ {
+ try
+ {
+ return path (move (n.value));
+ }
+ catch (invalid_path& e)
+ {
+ n.value = move (e.path); // Restore the name object for diagnostics.
+ // Fall through.
+ }
+ }
+
+ // Reassemble split dir/value.
+ //
+ if (n.untyped () && n.unqualified ())
+ {
+ try
+ {
+ return n.dir / n.value;
+ }
+ catch (const invalid_path&)
+ {
+ // Fall through.
+ }
+ }
+
+ // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "path");
+ }
+
+ const path& value_traits<path>::empty_instance = empty_path;
+
+ const char* const value_traits<path>::type_name = "path";
+
+ const value_type value_traits<path>::value_type
+ {
+ type_name,
+ sizeof (path),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<path>,
+ &default_copy_ctor<path>,
+ &default_copy_assign<path>,
+ &simple_assign<path>,
+ &simple_append<path>,
+ &simple_prepend<path>,
+ &simple_reverse<path>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<path>,
+ &default_empty<path>
+ };
+
+ // dir_path value
+ //
+ dir_path value_traits<dir_path>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr)
+ {
+ if (n.directory ())
+ return move (n.dir);
+
+ if (n.simple ())
+ {
+ try
+ {
+ return dir_path (move (n.value));
+ }
+ catch (invalid_path& e)
+ {
+ n.value = move (e.path); // Restore the name object for diagnostics.
+ // Fall through.
+ }
+ }
+
+ // Reassemble split dir/value.
+ //
+ if (n.untyped () && n.unqualified ())
+ {
+ try
+ {
+ n.dir /= n.value;
+ return move (n.dir);
+ }
+ catch (const invalid_path&)
+ {
+ // Fall through.
+ }
+ }
+
+ // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "dir_path");
+ }
+
+ const dir_path& value_traits<dir_path>::empty_instance = empty_dir_path;
+
+ const char* const value_traits<dir_path>::type_name = "dir_path";
+
+ const value_type value_traits<dir_path>::value_type
+ {
+ type_name,
+ sizeof (dir_path),
+ &value_traits<path>::value_type, // Base (assuming direct cast works for
+ // both).
+ nullptr, // No element.
+ &default_dtor<dir_path>,
+ &default_copy_ctor<dir_path>,
+ &default_copy_assign<dir_path>,
+ &simple_assign<dir_path>,
+ &simple_append<dir_path>,
+ &simple_prepend<dir_path>,
+ &simple_reverse<dir_path>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<dir_path>,
+ &default_empty<dir_path>
+ };
+
+ // abs_dir_path value
+ //
+ abs_dir_path value_traits<abs_dir_path>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr && (n.simple () || n.directory ()))
+ {
+ try
+ {
+ dir_path d (n.simple () ? dir_path (move (n.value)) : move (n.dir));
+
+ if (!d.empty ())
+ {
+ if (d.relative ())
+ d.complete ();
+
+ d.normalize (true); // Actualize.
+ }
+
+ return abs_dir_path (move (d));
+ }
+ catch (const invalid_path&) {} // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "abs_dir_path");
+ }
+
+ const char* const value_traits<abs_dir_path>::type_name = "abs_dir_path";
+
+ const value_type value_traits<abs_dir_path>::value_type
+ {
+ type_name,
+ sizeof (abs_dir_path),
+ &value_traits<dir_path>::value_type, // Base (assuming direct cast works
+ // for both).
+ nullptr, // No element.
+ &default_dtor<abs_dir_path>,
+ &default_copy_ctor<abs_dir_path>,
+ &default_copy_assign<abs_dir_path>,
+ &simple_assign<abs_dir_path>,
+ &simple_append<abs_dir_path>,
+ nullptr, // No prepend.
+ &simple_reverse<abs_dir_path>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<abs_dir_path>,
+ &default_empty<abs_dir_path>
+ };
+
+ // name value
+ //
+ name value_traits<name>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr)
+ return move (n);
+
+ throw_invalid_argument (n, r, "name");
+ }
+
+ static names_view
+ name_reverse (const value& v, names&)
+ {
+ const name& n (v.as<name> ());
+ return n.empty () ? names_view (nullptr, 0) : names_view (&n, 1);
+ }
+
+ const char* const value_traits<name>::type_name = "name";
+
+ const value_type value_traits<name>::value_type
+ {
+ type_name,
+ sizeof (name),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<name>,
+ &default_copy_ctor<name>,
+ &default_copy_assign<name>,
+ &simple_assign<name>,
+ nullptr, // Append not supported.
+ nullptr, // Prepend not supported.
+ &name_reverse,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<name>,
+ &default_empty<name>
+ };
+
+ // name_pair
+ //
+ name_pair value_traits<name_pair>::
+ convert (name&& n, name* r)
+ {
+ n.pair = '\0'; // Keep "unpaired" in case r is empty.
+ return name_pair (move (n), r != nullptr ? move (*r) : name ());
+ }
+
+ void
+ name_pair_assign (value& v, names&& ns, const variable* var)
+ {
+ using traits = value_traits<name_pair>;
+
+ size_t n (ns.size ());
+
+ if (n <= 2)
+ {
+ try
+ {
+ traits::assign (
+ v,
+ (n == 0
+ ? name_pair ()
+ : traits::convert (move (ns[0]), n == 2 ? &ns[1] : nullptr)));
+ return;
+ }
+ catch (const invalid_argument&) {} // Fall through.
+ }
+
+ diag_record dr (fail);
+ dr << "invalid name_pair value '" << ns << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ static names_view
+ name_pair_reverse (const value& v, names& ns)
+ {
+ const name_pair& p (v.as<name_pair> ());
+ const name& f (p.first);
+ const name& s (p.second);
+
+ if (f.empty () && s.empty ())
+ return names_view (nullptr, 0);
+
+ if (f.empty ())
+ return names_view (&s, 1);
+
+ if (s.empty ())
+ return names_view (&f, 1);
+
+ ns.push_back (f);
+ ns.back ().pair = '@';
+ ns.push_back (s);
+ return ns;
+ }
+
+ const char* const value_traits<name_pair>::type_name = "name_pair";
+
+ const value_type value_traits<name_pair>::value_type
+ {
+ type_name,
+ sizeof (name_pair),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<name_pair>,
+ &default_copy_ctor<name_pair>,
+ &default_copy_assign<name_pair>,
+ &name_pair_assign,
+ nullptr, // Append not supported.
+ nullptr, // Prepend not supported.
+ &name_pair_reverse,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<name_pair>,
+ &default_empty<name_pair>
+ };
+
+ // process_path value
+ //
+ process_path value_traits<process_path>::
+ convert (name&& n, name* r)
+ {
+ if ( n.untyped () && n.unqualified () && !n.empty () &&
+ (r == nullptr || (r->untyped () && r->unqualified () && !r->empty ())))
+ {
+ path rp (move (n.dir));
+ if (rp.empty ())
+ rp = path (move (n.value));
+ else
+ rp /= n.value;
+
+ path ep;
+ if (r != nullptr)
+ {
+ ep = move (r->dir);
+ if (ep.empty ())
+ ep = path (move (r->value));
+ else
+ ep /= r->value;
+ }
+
+ process_path pp (nullptr, move (rp), move (ep));
+ pp.initial = pp.recall.string ().c_str ();
+ return pp;
+ }
+
+ throw_invalid_argument (n, r, "process_path");
+ }
+
+ void
+ process_path_assign (value& v, names&& ns, const variable* var)
+ {
+ using traits = value_traits<process_path>;
+
+ size_t n (ns.size ());
+
+ if (n <= 2)
+ {
+ try
+ {
+ traits::assign (
+ v,
+ (n == 0
+ ? process_path ()
+ : traits::convert (move (ns[0]), n == 2 ? &ns[1] : nullptr)));
+ return;
+ }
+ catch (const invalid_argument&) {} // Fall through.
+ }
+
+ diag_record dr (fail);
+ dr << "invalid process_path value '" << ns << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ void
+ process_path_copy_ctor (value& l, const value& r, bool m)
+ {
+ const auto& rhs (r.as<process_path> ());
+
+ if (m)
+ new (&l.data_) process_path (move (const_cast<process_path&> (rhs)));
+ else
+ {
+ auto& lhs (
+ *new (&l.data_) process_path (
+ nullptr, path (rhs.recall), path (rhs.effect)));
+ lhs.initial = lhs.recall.string ().c_str ();
+ }
+ }
+
+ void
+ process_path_copy_assign (value& l, const value& r, bool m)
+ {
+ auto& lhs (l.as<process_path> ());
+ const auto& rhs (r.as<process_path> ());
+
+ if (m)
+ lhs = move (const_cast<process_path&> (rhs));
+ else
+ {
+ lhs.recall = rhs.recall;
+ lhs.effect = rhs.effect;
+ lhs.initial = lhs.recall.string ().c_str ();
+ }
+ }
+
+ static names_view
+ process_path_reverse (const value& v, names& s)
+ {
+ const process_path& x (v.as<process_path> ());
+
+ if (!x.empty ())
+ {
+ s.reserve (x.effect.empty () ? 1 : 2);
+
+ s.push_back (name (x.recall.directory (),
+ string (),
+ x.recall.leaf ().string ()));
+
+ if (!x.effect.empty ())
+ {
+ s.back ().pair = '@';
+ s.push_back (name (x.effect.directory (),
+ string (),
+ x.effect.leaf ().string ()));
+ }
+ }
+
+ return s;
+ }
+
+ const char* const value_traits<process_path>::type_name = "process_path";
+
+ const value_type value_traits<process_path>::value_type
+ {
+ type_name,
+ sizeof (process_path),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<process_path>,
+ &process_path_copy_ctor,
+ &process_path_copy_assign,
+ &process_path_assign,
+ nullptr, // Append not supported.
+ nullptr, // Prepend not supported.
+ &process_path_reverse,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<process_path>,
+ &default_empty<process_path>
+ };
+
+ // target_triplet value
+ //
+ target_triplet value_traits<target_triplet>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr)
+ {
+ if (n.simple ())
+ {
+ try
+ {
+ return n.empty () ? target_triplet () : target_triplet (n.value);
+ }
+ catch (const invalid_argument& e)
+ {
+ throw invalid_argument (
+ string ("invalid target_triplet value: ") + e.what ());
+ }
+ }
+
+ // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "target_triplet");
+ }
+
+ const char* const value_traits<target_triplet>::type_name = "target_triplet";
+
+ const value_type value_traits<target_triplet>::value_type
+ {
+ type_name,
+ sizeof (target_triplet),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<target_triplet>,
+ &default_copy_ctor<target_triplet>,
+ &default_copy_assign<target_triplet>,
+ &simple_assign<target_triplet>,
+ nullptr, // Append not supported.
+ nullptr, // Prepend not supported.
+ &simple_reverse<target_triplet>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<target_triplet>,
+ &default_empty<target_triplet>
+ };
+
+ // project_name value
+ //
+ project_name value_traits<project_name>::
+ convert (name&& n, name* r)
+ {
+ if (r == nullptr)
+ {
+ if (n.simple ())
+ {
+ try
+ {
+ return n.empty () ? project_name () : project_name (move (n.value));
+ }
+ catch (const invalid_argument& e)
+ {
+ throw invalid_argument (
+ string ("invalid project_name value: ") + e.what ());
+ }
+ }
+
+ // Fall through.
+ }
+
+ throw_invalid_argument (n, r, "project_name");
+ }
+
+ const project_name&
+ value_traits<project_name>::empty_instance = empty_project_name;
+
+ const char* const value_traits<project_name>::type_name = "project_name";
+
+ const value_type value_traits<project_name>::value_type
+ {
+ type_name,
+ sizeof (project_name),
+ nullptr, // No base.
+ nullptr, // No element.
+ &default_dtor<project_name>,
+ &default_copy_ctor<project_name>,
+ &default_copy_assign<project_name>,
+ &simple_assign<project_name>,
+ nullptr, // Append not supported.
+ nullptr, // Prepend not supported.
+ &simple_reverse<project_name>,
+ nullptr, // No cast (cast data_ directly).
+ &simple_compare<project_name>,
+ &default_empty<project_name>
+ };
+
+ // variable_pool
+ //
+ void variable_pool::
+ update (variable& var,
+ const build2::value_type* t,
+ const variable_visibility* v,
+ const bool* o) const
+ {
+ // Check overridability (all overrides, if any, should already have
+ // been entered (see context.cxx:reset()).
+ //
+ if (var.overrides != nullptr && (o == nullptr || !*o))
+ fail << "variable " << var.name << " cannot be overridden";
+
+ bool ut (t != nullptr && var.type != t);
+ bool uv (v != nullptr && var.visibility != *v);
+
+ // Variable should not be updated post-aliasing.
+ //
+ assert (var.aliases == &var || (!ut && !uv));
+
+ // Update type?
+ //
+ if (ut)
+ {
+ assert (var.type == nullptr);
+ var.type = t;
+ }
+
+ // Change visibility? While this might at first seem like a bad idea,
+ // it can happen that the variable lookup happens before any values
+ // were set, in which case the variable will be entered with the
+ // default visibility.
+ //
+ if (uv)
+ {
+ assert (var.visibility == variable_visibility::normal); // Default.
+ var.visibility = *v;
+ }
+ }
+
+ static bool
+ match_pattern (const string& n, const string& p, const string& s, bool multi)
+ {
+ size_t nn (n.size ()), pn (p.size ()), sn (s.size ());
+
+ if (nn < pn + sn + 1)
+ return false;
+
+ if (pn != 0)
+ {
+ if (n.compare (0, pn, p) != 0)
+ return false;
+ }
+
+ if (sn != 0)
+ {
+ if (n.compare (nn - sn, sn, s) != 0)
+ return false;
+ }
+
+ // Make sure the stem is a single name unless instructed otherwise.
+ //
+ return multi || string::traits_type::find (n.c_str () + pn,
+ nn - pn - sn,
+ '.') == nullptr;
+ }
+
+ static inline void
+ merge_pattern (const variable_pool::pattern& p,
+ const build2::value_type*& t,
+ const variable_visibility*& v,
+ const bool*& o)
+ {
+ if (p.type)
+ {
+ if (t == nullptr)
+ t = *p.type;
+ else if (p.match)
+ assert (t == *p.type);
+ }
+
+ if (p.visibility)
+ {
+ if (v == nullptr)
+ v = &*p.visibility;
+ else if (p.match)
+ assert (*v == *p.visibility);
+ }
+
+ if (p.overridable)
+ {
+ if (o == nullptr)
+ o = &*p.overridable;
+ else if (p.match)
+ {
+ // Allow the pattern to restrict but not relax.
+ //
+ if (*o)
+ o = &*p.overridable;
+ else
+ assert (*o == *p.overridable);
+ }
+ }
+ }
+
+ variable& variable_pool::
+ insert (string n,
+ const build2::value_type* t,
+ const variable_visibility* v,
+ const bool* o,
+ bool pat)
+ {
+ assert (!global_ || phase == run_phase::load);
+
+ // Apply pattern.
+ //
+ if (pat)
+ {
+ if (n.find ('.') != string::npos)
+ {
+ // Reverse means from the "largest" (most specific).
+ //
+ for (const pattern& p: reverse_iterate (patterns_))
+ {
+ if (match_pattern (n, p.prefix, p.suffix, p.multi))
+ {
+ merge_pattern (p, t, v, o);
+ break;
+ }
+ }
+ }
+ }
+
+ auto p (
+ insert (
+ variable {
+ move (n),
+ nullptr,
+ t,
+ nullptr,
+ v != nullptr ? *v : variable_visibility::normal}));
+
+ variable& r (p.first->second);
+
+ if (p.second)
+ r.aliases = &r;
+ else // Note: overridden variable will always exist.
+ {
+ if (t != nullptr || v != nullptr || o != nullptr)
+ update (r, t, v, o); // Not changing the key.
+ else if (r.overrides != nullptr)
+ fail << "variable " << r.name << " cannot be overridden";
+ }
+
+ return r;
+ }
+
+ const variable& variable_pool::
+ insert_alias (const variable& var, string n)
+ {
+ assert (var.aliases != nullptr && var.overrides == nullptr);
+
+ variable& a (insert (move (n),
+ var.type,
+ &var.visibility,
+ nullptr /* override */,
+ false /* pattern */));
+
+ if (a.aliases == &a) // Not aliased yet.
+ {
+ a.aliases = var.aliases;
+ const_cast<variable&> (var).aliases = &a;
+ }
+ else
+ assert (a.alias (var)); // Make sure it is already an alias of var.
+
+ 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)
+ {
+ assert (!global_ || phase == run_phase::load);
+
+ size_t pn (p.size ());
+
+ size_t w (p.find ('*'));
+ assert (w != string::npos);
+
+ bool multi (w + 1 != pn && p[w + 1] == '*');
+
+ // Extract prefix and suffix.
+ //
+ string pfx, sfx;
+
+ if (w != 0)
+ {
+ assert (p[w - 1] == '.' && w != 1);
+ pfx.assign (p, 0, w);
+ }
+
+ w += multi ? 2 : 1; // First suffix character.
+ size_t sn (pn - w); // Suffix length.
+
+ if (sn != 0)
+ {
+ assert (p[w] == '.' && sn != 1);
+ sfx.assign (p, w, sn);
+ }
+
+ auto i (
+ patterns_.insert (
+ pattern {move (pfx), move (sfx), multi, match, t, v, o}));
+
+ // Apply retrospectively to existing variables.
+ //
+ if (retro)
+ {
+ for (auto& p: map_)
+ {
+ variable& var (p.second);
+
+ if (match_pattern (var.name, i->prefix, i->suffix, i->multi))
+ {
+ // Make sure that none of the existing more specific patterns
+ // match.
+ //
+ auto j (i), e (patterns_.end ());
+ for (++j; j != e; ++j)
+ {
+ if (match_pattern (var.name, j->prefix, j->suffix, j->multi))
+ break;
+ }
+
+ if (j == e)
+ update (var,
+ t ? *t : nullptr,
+ v ? &*v : nullptr,
+ o ? &*o : nullptr); // Not changing the key.
+ }
+ }
+ }
+ }
+
+ variable_pool variable_pool::instance (true);
+ const variable_pool& variable_pool::cinstance = variable_pool::instance;
+ const variable_pool& var_pool = variable_pool::cinstance;
+
+ // variable_map
+ //
+ auto variable_map::
+ find (const variable& var, bool typed) const ->
+ pair<const value_data*, const variable&>
+ {
+ const variable* v (&var);
+ const value_data* r (nullptr);
+ do
+ {
+ // @@ Should we verify that there are no distinct values for aliases?
+ // This can happen if the values were entered before the variables
+ // were aliased. Possible but probably highly unlikely.
+ //
+ auto i (m_.find (*v));
+ if (i != m_.end ())
+ {
+ r = &i->second;
+ break;
+ }
+
+ v = v->aliases;
+
+ } while (v != &var && v != nullptr);
+
+ // Check if this is the first access after being assigned a type.
+ //
+ if (r != nullptr && typed && v->type != nullptr)
+ typify (*r, *v);
+
+ return pair<const value_data*, const variable&> (
+ r, r != nullptr ? *v : var);
+ }
+
+ auto variable_map::
+ find_to_modify (const variable& var, bool typed) ->
+ pair<value_data*, const variable&>
+ {
+ auto p (find (var, typed));
+ auto* r (const_cast<value_data*> (p.first));
+
+ if (r != nullptr)
+ r->version++;
+
+ return pair<value_data*, const variable&> (r, p.second);
+ }
+
+ pair<reference_wrapper<value>, bool> variable_map::
+ insert (const variable& var, bool typed)
+ {
+ assert (!global_ || phase == run_phase::load);
+
+ auto p (m_.emplace (var, value_data (typed ? var.type : nullptr)));
+ value_data& r (p.first->second);
+
+ if (!p.second)
+ {
+ // 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.
+ //
+ if (typed && var.type != nullptr)
+ typify (r, var);
+ }
+
+ r.version++;
+
+ return make_pair (reference_wrapper<value> (r), p.second);
+ }
+
+ // variable_type_map
+ //
+ lookup variable_type_map::
+ find (const target_type& type,
+ const string& name,
+ const variable& var) const
+ {
+ // Search across target type hierarchy.
+ //
+ for (auto tt (&type); tt != nullptr; tt = tt->base)
+ {
+ auto i (map_.find (*tt));
+
+ if (i == end ())
+ continue;
+
+ // Try to match the pattern, starting from the longest values
+ // so that the more "specific" patterns (i.e., those that cover
+ // fewer characters with the wildcard) take precedence. See
+ // tests/variable/type-pattern.
+ //
+ const variable_pattern_map& m (i->second);
+
+ for (auto j (m.rbegin ()); j != m.rend (); ++j)
+ {
+ const string& pat (j->first);
+
+ //@@ TODO: should we detect ambiguity? 'foo-*' '*-foo' and 'foo-foo'?
+ // Right now the last defined will be used.
+ //
+ if (pat != "*")
+ {
+ if (name.size () < pat.size () - 1 || // One for '*' or '?'.
+ !butl::path_match (pat, name))
+ continue;
+ }
+
+ // Ok, this pattern matches. But is there a variable?
+ //
+ // Since we store append/prepend values untyped, instruct find() not
+ // to automatically type it. And if it is assignment, then typify it
+ // ourselves.
+ //
+ const variable_map& vm (j->second);
+ {
+ auto p (vm.find (var, false));
+ if (const variable_map::value_data* v = p.first)
+ {
+ // Check if this is the first access after being assigned a type.
+ //
+ if (v->extra == 0 && var.type != nullptr)
+ vm.typify (*v, var);
+
+ return lookup (*v, p.second, vm);
+ }
+ }
+ }
+ }
+
+ return lookup ();
+ }
+
+ size_t variable_cache_mutex_shard_size;
+ unique_ptr<shared_mutex[]> variable_cache_mutex_shard;
+
+ template struct LIBBUILD2_DEFEXPORT value_traits<strings>;
+ template struct LIBBUILD2_DEFEXPORT value_traits<vector<name>>;
+ template struct LIBBUILD2_DEFEXPORT value_traits<paths>;
+ template struct LIBBUILD2_DEFEXPORT value_traits<dir_paths>;
+ template struct LIBBUILD2_DEFEXPORT value_traits<uint64s>;
+
+ template struct LIBBUILD2_DEFEXPORT value_traits<std::map<string, string>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<std::map<project_name, dir_path>>;
+}