diff options
Diffstat (limited to 'libbuild2/config/init.cxx')
-rw-r--r-- | libbuild2/config/init.cxx | 518 |
1 files changed, 479 insertions, 39 deletions
diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index 1513a47..2f134c4 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -4,6 +4,7 @@ #include <libbuild2/config/init.hxx> #include <sstream> +#include <cstdlib> // getenv() #include <libbuild2/file.hxx> #include <libbuild2/rule.hxx> @@ -25,9 +26,66 @@ namespace build2 { namespace config { + static const file_rule file_rule_ (true /* check_type */); + void functions (function_map&); // functions.cxx + // Custom save function for config.config.environment. + // + // It tries to optimize the storage in subprojects by appending the + // difference (compared to the amalgamation's values) instead of storing + // the entire values. + // + static pair<names_view, const char*> + save_environment (const value& d, const value* b, names& storage) + { + if (b == nullptr) + return make_pair (reverse (d, storage, true /* reduce */), "="); + + // The plan is to iterator over environment variables adding those that + // are not in base to storage. There is, however, a complication: we may + // see multiple entries for the same variable and only the last entry + // should have effect. So we need to iterate in reverse and check if + // we've already seen this variable. + // + const strings& ds (d.as<strings> ()); + const strings& bs (b->as<strings> ()); + + for (auto i (ds.rbegin ()), e (ds.rend ()); i != e; ++i) + { + // Note that we must only consider variable names (up to first '=' if + // any). + // + const string& v (*i); + size_t p (v.find ('=')); + + // Check if we have already seen this variable. + // + if (find_if (ds.rbegin (), i, + [&v, p] (const string& v1) + { + return saved_environment::compare ( + v, v1, p, v1.find ('=')); + }) != i) + continue; + + // Check if there is the same value in base. + // + auto j (find_if (bs.rbegin (), bs.rend (), + [&v, p] (const string& v1) + { + return saved_environment::compare ( + v, v1, p, v1.find ('=')); + })); + + if (j == bs.rend () || *j != v) + storage.push_back (name (v)); + } + + return make_pair (names_view (storage), "+="); + } + void boot (scope& rs, const location&, module_boot_extra& extra) { @@ -44,7 +102,10 @@ namespace build2 // reserved to not be valid module names (`build`). We also currently // treat `import` as special. // - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); // NOTE: all config.** variables are by default made (via a pattern) to // be overridable with global visibility. So we must override this if a @@ -57,7 +118,7 @@ namespace build2 // as a command line override. // // Note: must be entered during bootstrap since we need it in - // configure_execute(). + // configure_execute() even for the forward case. // vp.insert<path> ("config.config.save", true /* ovr */); @@ -93,8 +154,11 @@ namespace build2 // // Use the NULL value to clear. // - auto& c_p (vp.insert<vector<pair<string, string>>> ( - "config.config.persist", true /* ovr */, v_p)); + // Note: must be entered during bootstrap since we need it in create's + // save_config(). + // + vp.insert<vector<pair<string, string>>> ( + "config.config.persist", true /* ovr */, v_p); // Only create the module if we are configuring, creating, or // disfiguring or if it was requested with config.config.module (useful @@ -116,17 +180,11 @@ namespace build2 if (!d) { - // Used as a variable prefix by configure_execute(). - // - vp.insert ("config"); - // Adjust priority for the config module and import pseudo-module so // that their variables come first in config.build. // m.save_module ("config", INT32_MIN); m.save_module ("import", INT32_MIN); - - m.save_variable (c_p, save_null_omitted); } } @@ -152,6 +210,9 @@ namespace build2 #ifndef BUILD2_BOOTSTRAP extern const char host_config[]; extern const char build2_config[]; + + extern const char host_config_no_warnings[]; + extern const char build2_config_no_warnings[]; #endif bool @@ -170,20 +231,165 @@ namespace build2 return true; } + context& ctx (rs.ctx); + l5 ([&]{trace << "for " << rs;}); - auto& vp (rs.var_pool ()); + // If we have the module, then we are configuring (or the project wishes + // to call $config.save(); we don't get here on disfigure). + // + module* m (extra.module != nullptr + ? &extra.module_as<module> () + : nullptr); + + auto& vp (rs.var_pool (true /* public */)); // Note: config.* is pattern-typed to global visibility. // const auto v_p (variable_visibility::project); - auto& c_l (vp.insert<paths> ("config.config.load", true /* ovr */)); auto& c_v (vp.insert<uint64_t> ("config.version", false /*ovr*/, v_p)); + auto& c_l (vp.insert<paths> ("config.config.load", true /* ovr */)); + + // Omit loading the configuration from the config.build file (it is + // still loaded from config.config.load if specified). Similar to + // config.config.load, only values specified on this project's root + // scope and global scope are considered. + // + // Note that this variable is not saved in config.build and is expected + // to always be specified as a command line override. + // + auto& c_u (vp.insert<bool> ("config.config.unload", true /*ovr*/)); - // Load config.build if one exists followed by extra files specified in - // config.config.load (we don't need to worry about disfigure since we - // will never be init'ed). + // Configuration variables to disfigure. + // + // The exact semantics is to ignore these variables when loading + // config.build (and any files specified in config.config.load), letting + // them to take on the default values (more precisely, the current + // implementation undefined them after loading config.build). See also + // config.config.unload. + // + // Besides names, variables can also be specified as patterns in the + // config.<prefix>.(*|**)[<suffix>] form where `*` matches single + // component names (i.e., `foo` but not `foo.bar`), and `**` matches + // single and multi-component names. Currently only single wildcard (`*` + // or `**`) is supported. Additionally, a pattern in the + // config.<prefix>(*|**) form (i.e., without `.` after <prefix>) matches + // config.<prefix>.(*|**) plus config.<prefix> itself (but not + // config.<prefix>foo). + // + // For example, to disfigure all the project configuration variables + // (while preserving all the module configuration variables; note + // quoting to prevent pattern expansion): + // + // b config.config.disfigure="'config.hello**'" + // + // Note that this variable is not saved in config.build and is expected + // to always be specified as a command line override. + // + // We also had the idea of using NULL values as a more natural way to + // undefine a configuration variable, which would only work for non- + // nullable variables (such as project configuration variables) or for + // those where NULL is the default value (most of the others). However, + // this cannot work in our model since we cannot reset a NULL override + // to a default value. So setting the variable itself to some special + // value does not seem to be an option and we have to convey this in + // some other way, such as in config.config.disfigure. Another idea is + // to invent a parallel set of variables, such as disfig.*, that can be + // used for that (though they would still have to be specified with some + // dummy value, for example disfig.hello.fancy=). On the other hand, + // this desire to disfigure individual variables does not seem to be + // very common (we lived without it for years without noticing). So + // it's not clear we need to do something like disfig.* which has a + // wiff of hack to it. + // + auto& c_d (vp.insert<strings> ("config.config.disfigure", true /*ovr*/)); + + // Hermetic configurations. + // + // A hermetic configuration stores environment variables that affect the + // project in config.config.environment. + // + // Note that this is essentially a tri-state value: true means keep + // hermetizing (save the environment in config.config.environment), + // false means keep de-hermetizing (clear config.config.environment) and + // undefined/NULL means don't touch config.config.environment. + // + // During reconfiguration things stay hermetic unless re-hermetization + // is explicitly requested with config.config.hermetic.reload=true (or + // de-hermetization is requested with config.config.hermetic=false). + // + // Use the NULL value to clear. + // + auto& c_h (vp.insert<bool> ("config.config.hermetic", true /* ovr */)); + + if (m != nullptr) + m->save_variable (c_h, save_null_omitted); + + // Request hermetic configuration re-hermetization. + // + // Note that this variable is not saved in config.build and is expected + // to always be specified as a command line override. + // + auto& c_h_r ( + vp.insert<bool> ("config.config.hermetic.reload", true /* ovr */)); + + // Hermetic configuration environment variables inclusion/exclusion. + // + // This configuration variable can be used to include additional or + // exclude existing environment variables into/from the list that should + // be saved in order to make the configuration hermetic. For example: + // + // config.config.hermetic.environment="LANG PATH@false" + // + // Use the NULL or empty value to clear. + // + auto& c_h_e ( + vp.insert<hermetic_environment> ("config.config.hermetic.environment")); + + if (m != nullptr) + m->save_variable (c_h_e, save_null_omitted | save_empty_omitted); + + // Configuration environment variables. + // + // Environment variables used by tools (e.g., compilers), buildfiles + // (e.g., $getenv()), and the build system itself (e.g., to locate + // tools) in ways that affect the build result are in essence part of + // the project configuration. + // + // This variable allows storing environment variable overrides that + // should be applied to the environment when executing tools, etc., as + // part of a project build. Specifically, it contains a list of + // environment variable "sets" (<name>=<value>) and "unsets" (<name>). + // If multiple entries are specified for the same environment variable, + // the last entry has effect. For example: + // + // config.config.environment="LC_ALL=C LANG" + // + // Note that a subproject inherits overrides from its amalgamation (this + // semantics is the result of the way we optimize the storage of this + // variable in subproject's config.build; the thinking is that if a + // variable is not overridden by the subproject then it doesn't affect + // the build result and therefore it's irrelevant whether it has a value + // that came from the original environment of from the amalgamation + // override). + // + // Use the NULL or empty value to clear. + // + // @@ We could use =<name> as a "pass-through" instruction (e.g., if + // we need to use original value in subproject). + // + auto& c_e (vp.insert<strings> ("config.config.environment", true /* ovr */)); + + if (m != nullptr) + m->save_variable (c_e, + save_null_omitted | save_empty_omitted | save_base, + &save_environment); + + // Load config.build if one exists (and unless config.config.unload is + // specified) followed by extra files specified in config.config.load + // (we don't need to worry about disfigure since we will never be + // init'ed). // auto load_config = [&rs, &c_v] (istream& is, const path_name& in, @@ -228,15 +434,37 @@ namespace build2 auto load_config_file = [&load_config] (const path& f, const location& l) { path_name fn (f); - ifdstream ifs; - load_config (open_file_or_stdin (fn, ifs), fn, l); + try + { + ifdstream ifs; + load_config (open_file_or_stdin (fn, ifs), fn, l); + } + catch (const io_error& e) + { + fail << "unable to read buildfile " << fn << ": " << e; + } }; + // Load config.build unless requested not to. + // { - path f (config_file (rs)); + // The same semantics as in config.config.load below. + // + bool u; + { + lookup l (rs[c_u]); + u = (l && + (l.belongs (rs) || l.belongs (ctx.global_scope)) && + cast_false<bool> (l)); + } + + if (!u) + { + path f (config_file (rs)); - if (exists (f)) - load_config_file (f, l); + if (exists (f)) + load_config_file (f, l); + } } if (lookup l = rs[c_l]) @@ -251,7 +479,7 @@ namespace build2 // b ./config.config.load=.../config.build # this project // b !config.config.load=.../config.build # every project // - if (l.belongs (rs) || l.belongs (rs.ctx.global_scope)) + if (l.belongs (rs) || l.belongs (ctx.global_scope)) { for (const path& f: cast<paths> (l)) { @@ -259,14 +487,23 @@ namespace build2 const string& s (f.string ()); - if (s[0] != '~') + if (s.empty ()) + fail << "empty path in config.config.load"; + else if (s[0] != '~') load_config_file (f, l); - else if (s == "~host" || s == "~build2") + else if (s == "~host" || s == "~host-no-warnings" || + s == "~build2" || s == "~build2-no-warnings") { #ifdef BUILD2_BOOTSTRAP assert (false); #else - istringstream is (s[1] == 'h' ? host_config : build2_config); + istringstream is (s[1] == 'h' + ? (s.size () == 5 + ? host_config + : host_config_no_warnings) + : (s.size () == 7 + ? build2_config + : build2_config_no_warnings)); load_config (is, path_name (s), l); #endif } @@ -277,33 +514,235 @@ namespace build2 } } - // Cache the config.config.persist value, if any. + // Undefine variables specified with config.config.disfigure. // - if (extra.module != nullptr) + if (const strings* ns = cast_null<strings> (rs[c_d])) { - auto& m (extra.module_as<module> ()); + auto p (rs.vars.lookup_namespace ("config")); + + for (auto i (p.first); i != p.second; ) + { + const variable& var (i->first); + + // This can be one of the overrides (__override, __prefix, etc), + // which we skip. + // + if (!var.override ()) + { + bool m (false); + + for (const string& n: *ns) + { + if (n.compare (0, 7, "config.") != 0) + fail << "config.* variable expected in " + << "config.config.disfigure instead of '" << n << "'"; + + size_t p (n.find ('*')); + + if (p == string::npos) + { + if ((m = var.name == n)) + break; + } + else + { + // Pattern in one of these forms: + // + // config.<prefix>.(*|**)[<suffix>] + // config.<prefix>(*|**) + // + // BTW, an alternative way to handle this would be to + // translate it to a path and use our path_match() machinery, + // similar to how we do it for build config include/exclude. + // Perhaps one day when/if we decide to support multiple + // wildcards. + // + if (p == 7) + fail << "config.<prefix>* pattern expected in " + << "config.config.disfigure instead of '" << n << "'"; + + bool r (n[p + 1] == '*'); // Recursive. + + size_t pe; // Prefix end/size. + if (n[p - 1] != '.') + { + // Second form should have no suffix. + // + if (p + (r ? 2 : 1) != n.size ()) + fail << "config.<prefix>(*|**) pattern expected in " + << "config.config.disfigure instead of '" << n << "'"; + + // Match just <prefix>. + // + if ((m = n.compare (0, p, var.name) == 0)) + break; + + pe = p; + } + else + pe = p - 1; + + // Match <prefix> followed by `.`. + // + if (n.compare (0, pe, var.name, 0, pe) != 0 || + var.name[pe] != '.') + continue; + + // Match suffix. + // + size_t sb (p + (r ? 2 : 1)); // Suffix begin. + size_t sn (n.size () - sb); // Suffix size. + + size_t te; // Stem end. + if (sn == 0) // No suffix. + te = var.name.size (); + else + { + if (var.name.size () < pe + 1 + sn) // Too short. + continue; + + te = var.name.size () - sn; + + if (n.compare (sb, sn, var.name, te, sn) != 0) + continue; + } + + // Match stem. + // + if ((m = r || var.name.find ('.', pe + 1) >= te)) + break; + } + } + + if (m) + { + i = rs.vars.erase (i); // Undefine. + continue; + } + } - m.persist = - cast_null<vector<pair<string, string>>> ( - rs["config.config.persist"]); + ++i; + } } - // Register alias and fallback rule for the configure meta-operation. + // Save and cache the config.config.persist value, if any. // - // We need this rule for out-of-any-project dependencies (e.g., - // libraries imported from /usr/lib). We are registring it on the - // global scope similar to builtin rules. + if (m != nullptr) + { + auto& c_p (*vp.find ("config.config.persist")); + m->save_variable (c_p, save_null_omitted); + m->persist = cast_null<vector<pair<string, string>>> (rs[c_p]); + } + + // If we are configuring, handle config.config.hermetic. + // + // The overall plan is to either clear config.config.environment (if + // c.c.h=false) or populate it with the values that affect this project + // (if c.c.h=true). We have to do it half here (because c.c.e is used as + // a source for the project environment and we would naturally want the + // semantics to be equivalent to what will be saved in config.build) and + // half in configure_execute() (because that's where we have the final + // list of all the environment variables we need to save). + // + // So here we must deal with the cases where the current c.c.e value + // will be changed: either cleared (c.c.h=false) or set to new values + // from the "outer" environment (c.c.h.reload=true). Note also that even + // then a c.c.e value from an amalgamation, if any, should be in effect. + // + if (ctx.current_mif->id == configure_id && + (!cast_true<bool> (rs[c_h]) || // c.c.h=false + cast_false<bool> (rs[c_h_r]))) // c.c.h.r=true + { + rs.vars.erase (c_e); // Undefine. + } + + // Copy config.config.environment to scope::root_extra::environment and + // calculate its checksum. // - rs.global_scope ().insert_rule<mtime_target> ( - configure_id, 0, "config.file", file_rule::instance); + // Note that we store shallow copies that point to the c.c.environment + // value which means it should not change. + // + if (const strings* src = cast_null<strings> (rs[c_e])) + { + sha256 cs; + vector<const char*>& dst (rs.root_extra->environment); + + // The idea is to only copy entries that are effective, that is those + // that actually override something in the environment. This should be + // both more efficient and less noisy (e.g., if we need to print this + // in diagnostics). + // + // Note that config.config.environment may contain duplicates and the + // last entry should have effect. + // + // Note also that we use std::getenv() instead of butl::getenv() to + // disregard any thread environment overrides. + // + for (auto i (src->rbegin ()), e (src->rend ()); i != e; ++i) + { + // Note that we must only consider variable names (up to first '=' + // if any). + // + const string& v (*i); + size_t p (v.find ('=')); + + // Check if we have already seen this variable. + // + if (find_if (src->rbegin (), i, + [&v, p] (const string& v1) + { + return saved_environment::compare ( + v, v1, p, v1.find ('=')); + }) != i) + continue; + + // If it's an unset, see if it actually unsets anything. + // + if (p == string::npos) + { + if (std::getenv (v.c_str ()) == nullptr) + continue; + } + // + // And if it's a set, see if it sets a different value. + // + else + { + const char* v1 (std::getenv (string (v, 0, p).c_str ())); + if (v1 != nullptr && v.compare (p + 1, string::npos, v1) == 0) + continue; + } + + dst.push_back (v.c_str ()); + cs.append (v); + } + + if (!dst.empty ()) + { + dst.push_back (nullptr); + rs.root_extra->environment_checksum = cs.string (); + } + } - //@@ outer + // Register alias and fallback rule for the configure meta-operation. + // rs.insert_rule<alias> (configure_id, 0, "config.alias", alias_rule::instance); // This allows a custom configure rule while doing nothing by default. // - rs.insert_rule<target> (configure_id, 0, "config", noop_rule::instance); - rs.insert_rule<file> (configure_id, 0, "config.file", noop_rule::instance); + rs.insert_rule<target> (configure_id, 0, "config.noop", noop_rule::instance); + + // We need this rule for out-of-any-project dependencies (for example, + // libraries imported from /usr/lib). We are registering it on the + // global scope similar to builtin rules. + // + // Note: use target instead of anything more specific (such as + // mtime_target) in order not to take precedence over the rules above. + // + // See a similar rule in the dist module. + // + rs.global_scope ().insert_rule<target> ( + configure_id, 0, "config.file", file_rule_); return true; } @@ -320,6 +759,7 @@ namespace build2 // Initialize the config entry points in the build system core. // config_save_variable = &module::save_variable; + config_save_environment = &module::save_environment; config_save_module = &module::save_module; config_preprocess_create = &preprocess_create; config_configure_post = &module::configure_post; |