From b262d2c9c56eed18d043dccefac02b54a6ae2f95 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 30 Jan 2017 11:12:25 +0200 Subject: Implement pattern-based variable typing, tighten variable type update --- build2/cli/init.cxx | 6 +- build2/config/init.cxx | 4 + build2/config/utility | 6 +- build2/config/utility.cxx | 18 +++-- build2/context.cxx | 17 ++++- build2/dist/init.cxx | 2 +- build2/file.cxx | 12 ++- build2/install/init.cxx | 2 +- build2/module.cxx | 12 ++- build2/variable | 69 ++++++++++++++++- build2/variable.cxx | 183 ++++++++++++++++++++++++++++++++-------------- 11 files changed, 246 insertions(+), 85 deletions(-) diff --git a/build2/cli/init.cxx b/build2/cli/init.cxx index 2650c1b..1cf248d 100644 --- a/build2/cli/init.cxx +++ b/build2/cli/init.cxx @@ -81,13 +81,13 @@ namespace build2 // Otherwise we will only honor optional if the user didn't specify // any cli configuration explicitly. // - optional = optional && !config::specified (rs, "config.cli"); + optional = optional && !config::specified (rs, "cli"); // If the configuration says we are unconfigured, then we should't // re-run tests, etc. But we may still need to print the config // report. // - conf = !optional || !config::unconfigured (rs, "config.cli"); + conf = !optional || !config::unconfigured (rs, "cli"); } if (first) @@ -214,7 +214,7 @@ namespace build2 // on each run. // if (!conf) - nv = config::unconfigured (rs, "config.cli", true) || nv; + nv = config::unconfigured (rs, "cli", true) || nv; // If this is a new value (e.g., we are configuring), then print the // report at verbosity level 2 and up (-v). diff --git a/build2/config/init.cxx b/build2/config/init.cxx index 1f0b391..cfb69e0 100644 --- a/build2/config/init.cxx +++ b/build2/config/init.cxx @@ -40,6 +40,10 @@ namespace build2 auto& vp (var_pool.rw (rs)); + // utility.cxx:unconfigured() + // + vp.insert_pattern ("config.*.configured"); + // Load config.build if one exists. // // Note that we have to do this during bootstrap since the order in diff --git a/build2/config/utility b/build2/config/utility index e4e463d..041277a 100644 --- a/build2/config/utility +++ b/build2/config/utility @@ -116,21 +116,21 @@ namespace build2 // running the tests, etc). // bool - specified (scope& root, const string& ns); + specified (scope& root, const string& name); // Check if there is a false config.*.configured value. This mechanism can // be used to "remember" that the module is left unconfigured in order to // avoid re-running the tests, etc. // bool - unconfigured (scope& root, const string& ns); + unconfigured (scope& root, const string& name); // Set the config.*.configured value. Note that you only need to set it to // false. It will be automatically ignored if there are any other config.* // values for this module. Return true if this sets a new value. // bool - unconfigured (scope& root, const string& ns, bool); + unconfigured (scope& root, const string& name, bool); // Enter the variable so that it is saved during configuration. See // config::module for details. diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx index 7db4555..08faeeb 100644 --- a/build2/config/utility.cxx +++ b/build2/config/utility.cxx @@ -62,7 +62,7 @@ namespace build2 } bool - specified (scope& r, const string& ns) + specified (scope& r, const string& n) { // Search all outer scopes for any value in this namespace. // @@ -72,7 +72,7 @@ namespace build2 // any original values, they will be "visible"; see find_override() for // details. // - const variable& vns (var_pool.rw (r).insert (ns)); + const variable& vns (var_pool.rw (r).insert ("config." + n)); for (scope* s (&r); s != nullptr; s = s->parent_scope ()) { for (auto p (s->vars.find_namespace (vns)); @@ -93,11 +93,12 @@ namespace build2 } bool - unconfigured (scope& rs, const string& ns) + unconfigured (scope& rs, const string& n) { - // Note: not overridable. + // Pattern-typed in boot() as bool. // - const variable& var (var_pool.rw (rs).insert (ns + ".configured")); + const variable& var ( + var_pool.rw (rs).insert ("config." + n + ".configured")); if (current_mif->id == configure_id) save_variable (rs, var); @@ -107,11 +108,12 @@ namespace build2 } bool - unconfigured (scope& rs, const string& ns, bool v) + unconfigured (scope& rs, const string& n, bool v) { - // Note: not overridable. + // Pattern-typed in boot() as bool. // - const variable& var (var_pool.rw (rs).insert (ns + ".configured")); + const variable& var ( + var_pool.rw (rs).insert ("config." + n + ".configured")); if (current_mif->id == configure_id) save_variable (rs, var); diff --git a/build2/context.cxx b/build2/context.cxx index 0f13f27..a8fe2df 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -231,8 +231,23 @@ namespace build2 vos.emplace_back (variable_override {var, *o, move (r.first)}); } - // Enter builtin variables. + // Enter builtin variables and patterns. // + + // file.cxx:import() + // + // Note that the order is important (reverse application). + // + vp.insert_pattern ("config.import.**", true); + vp.insert_pattern ("config.import.*", true); + + // module.cxx:load_module(). + // + vp.insert_pattern ("**.loaded", false, variable_visibility::project); + vp.insert_pattern ("**.configured", + false, + variable_visibility::project); + var_src_root = &vp.insert ("src_root"); var_out_root = &vp.insert ("out_root"); var_src_base = &vp.insert ("src_base"); diff --git a/build2/dist/init.cxx b/build2/dist/init.cxx index 9ee2d46..98f8a9a 100644 --- a/build2/dist/init.cxx +++ b/build2/dist/init.cxx @@ -97,7 +97,7 @@ namespace build2 // must be explicitly specified or we will complain if and when // we try to dist. // - bool s (config::specified (rs, "config.dist")); + bool s (config::specified (rs, "dist")); // Adjust module priority so that the config.dist.* values are saved at // the end of config.build. diff --git a/build2/file.cxx b/build2/file.cxx index 72d2807..755ff0f 100644 --- a/build2/file.cxx +++ b/build2/file.cxx @@ -900,10 +900,11 @@ namespace build2 // config.import. // - // Note: overridable variable with path auto-completion. - // { - const variable& var (vp.insert (n, true)); + // Note: pattern-typed in context.cxx:reset() as an overridable + // variable of type abs_dir_path (path auto-completion). + // + const variable& var (vp.insert (n)); if (auto l = iroot[var]) { @@ -934,7 +935,10 @@ namespace build2 { auto lookup = [&iroot, &vp, &loc] (string name) -> path { - const variable& var (vp.insert (name, true)); + // Note: pattern-typed in context.cxx:reset() as an overridable + // variable of type path. + // + const variable& var (vp.insert (move (name))); path r; if (auto l = iroot[var]) diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 122eb54..0966d82 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -215,7 +215,7 @@ namespace build2 { using build2::path; - bool s (config::specified (rs, "config.install")); + bool s (config::specified (rs, "install")); // Adjust module priority so that the (numerous) config.install.* // values are saved at the end of config.build. diff --git a/build2/module.cxx b/build2/module.cxx index 031ae45..6fc33fa 100644 --- a/build2/module.cxx +++ b/build2/module.cxx @@ -102,13 +102,11 @@ namespace build2 auto& vp (var_pool.rw (rs)); - const variable& lv (vp.insert (name + ".loaded", - variable_visibility::project)); - - const variable& cv (vp.insert (name + ".configured", - variable_visibility::project)); - bs.assign (lv) = l; - bs.assign (cv) = c; + // Note: pattern-typed in context.cxx:reset() as project-visibility + // variables of type bool. + // + bs.assign (vp.insert (name + ".loaded")) = l; + bs.assign (vp.insert (name + ".configured")) = c; return l && c; } diff --git a/build2/variable b/build2/variable index d31d20f..876ae2b 100644 --- a/build2/variable +++ b/build2/variable @@ -797,10 +797,13 @@ namespace build2 find (const string& name) const; // Find existing or insert new (untyped, non-overridable, normal - // visibility). + // visibility; but may be overridden by a pattern). // const variable& - insert (string name); + insert (string name) + { + return insert (move (name), nullptr, nullptr, nullptr); + } // Insert or override (type/visibility). Note that by default the // variable is not overridable. @@ -853,6 +856,37 @@ namespace build2 move (name), &value_traits::value_type, &v, &overridable); } + // Insert a variable pattern. Any variable that matches this pattern + // will have the specified type, visibility, and overridability. + // + // The pattern must be in the form [.](*|**)[.] where + // '*' matches single component stems (i.e., 'foo' but not 'foo.bar') + // and '**' matches single and multi-component stems. Note that only + // multi-component variables are considered for pattern matching (so + // just '*' won't match anything). + // + // Note that patterns are matched in the reverse order of insertion (as + // opposed to, say, more specific first) with the first match used. A + // newly inserted pattern is also applied retrospectively to all the + // existing variables that match. + // + public: + void + insert_pattern (const string& pattern, + const build2::value_type* type, + bool overridable, + variable_visibility); + + template + void + insert_pattern (const string& p, + bool overridable = false, + variable_visibility v = variable_visibility::normal) + { + insert_pattern (p, &value_traits::value_type, overridable, v); + } + + public: void clear () {map_.clear ();} @@ -875,6 +909,12 @@ namespace build2 const variable_visibility* = nullptr, const bool* overridable = nullptr); + void + update (variable&, + const build2::value_type*, + const variable_visibility*, + const bool*) const; + // Entities that can access bypassing the lock. // friend class scope; @@ -883,6 +923,8 @@ namespace build2 public: static const variable_pool& cinstance; // For var_pool initialization. + // Variable map. + // private: using key = butl::map_key; using map = std::unordered_map; @@ -904,11 +946,32 @@ namespace build2 return r; } + map map_; + + // Patterns. + // + public: + struct pattern + { + string prefix; + string suffix; + bool multi; // Match multi-component stems. + + const build2::value_type* type; + variable_visibility visibility; + bool overridable; + }; + + private: + vector patterns_; + + // Global pool flag. + // + private: explicit variable_pool (bool global): global_ (global) {} bool global_; - map map_; }; extern const variable_pool& var_pool; diff --git a/build2/variable.cxx b/build2/variable.cxx index bbe08d0..8695623 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -906,23 +906,82 @@ namespace build2 // variable_pool // - const variable& variable_pool:: - insert (string n) + void variable_pool:: + update (variable& var, + const build2::value_type* t, + const variable_visibility* v, + const bool* o) const { - assert (!global_ || phase == run_phase::load); + // Check overridability (all overrides, if any, should already have + // been entered (see context.cxx:reset()). + // + if (var.override != nullptr && (o == nullptr || !*o)) + fail << "variable " << var.name << " cannot be overridden"; - // We are not overriding anything so skip the insert_() checks. + bool ut (t != nullptr && var.type != t); + bool uv (v != nullptr && var.visibility != *v); + + // In the global pool existing variables can only be updated during + // serial load. // - auto p ( - insert ( - variable {move (n), nullptr, nullptr, variable_visibility::normal})); + assert (!global_ || !(ut || uv) || model_lock == nullptr); - const variable& r (p.first->second); + // Update type? + // + if (ut) + { + assert (var.type == nullptr); + var.type = t; + } - if (r.override != nullptr) - fail << "variable " << r.name << " cannot be overridden"; + // 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; + } + } - return r; + 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 (t == nullptr) t = p.type; else assert ( t == p.type); + if (v == nullptr) v = &p.visibility; else assert (*v == p.visibility); + if (o == nullptr) o = &p.overridable; else assert (*o == p.overridable); } const variable& variable_pool:: @@ -933,6 +992,20 @@ namespace build2 { assert (!global_ || phase == run_phase::load); + // Apply pattern. + // + if (n.find ('.') != string::npos) + { + 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 { @@ -941,61 +1014,63 @@ namespace build2 nullptr, v != nullptr ? *v : variable_visibility::normal})); - const variable& r (p.first->second); + variable& r (p.first->second); if (!p.second) { - // Check overridability (all overrides, if any, should already have - // been entered (see context.cxx:reset()). - // - if (r.override != nullptr && (o == nullptr || !*o)) - fail << "variable " << r.name << " cannot be overridden"; + if (t != nullptr || v != nullptr || o != nullptr) + update (r, t, v, o); // Not changing the key. + else + if (r.override != nullptr) + fail << "variable " << r.name << " cannot be overridden"; + } - bool ut (t != nullptr && r.type != t); - bool uv (v != nullptr && r.visibility != *v); + return r; + } - // In the global pool existing variables can only be updated during - // serial load. - // - /* - @@ MT + void variable_pool:: + insert_pattern (const string& p, + const build2::value_type* t, + bool o, + variable_visibility v) + { + size_t pn (p.size ()); - if (global_) - { - //assert (!(ut || uv) || model_lock == nullptr); + size_t w (p.find ('*')); + assert (w != string::npos); - if (model_lock != nullptr) - { - if (ut) - text << r.name << " type update during exclusive load"; + bool multi (w + 1 != pn && p[w + 1] == '*'); - if (uv) - text << r.name << " visibility update during exclusive load"; - } - } - */ + // Extract prefix and suffix. + // + string pfx, sfx; - // Update type? - // - if (ut) - { - assert (r.type == nullptr); - const_cast (r).type = t; // Not changing the key. - } + if (w != 0) + { + assert (p[w - 1] == '.' && w != 1); + pfx.assign (p, 0, w); + } - // 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 (r.visibility == variable_visibility::normal); // Default. - const_cast (r).visibility = *v; // Not changing the key. - } + 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); } - return r; + // Apply retrospectively to existing variables. + // + for (auto& p: map_) + { + variable& var (p.second); + + if (match_pattern (var.name, pfx, sfx, multi)) + update (var, t, &v, &o); // Not changing the key. + } + + patterns_.push_back (pattern {move (pfx), move (sfx), multi, t, v, o}); } variable_pool variable_pool::instance (true); -- cgit v1.1