From 5e51d523e71231cb190e9ed981962df527f4ee7e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 4 Apr 2021 13:11:56 +0200 Subject: Add base functionality for hermetic build configurations --- libbuild2/config/init.cxx | 206 ++++++++++++++++++++++++++++++---------------- 1 file changed, 133 insertions(+), 73 deletions(-) (limited to 'libbuild2/config/init.cxx') diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index 1e6b36b..950579a 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -5,7 +5,6 @@ #include #include // getenv() -#include // strncmp() #include #include @@ -30,24 +29,6 @@ namespace build2 void functions (function_map&); // functions.cxx - // Compare environment variable names. Note that on Windows they are - // case-insensitive. - // - static inline bool - compare_env (const string& x, size_t xn, const string& y, size_t yn) - { - if (xn == string::npos) xn = x.size (); - if (yn == string::npos) yn = y.size (); - - return xn == yn && -#ifdef _WIN32 - icasecmp (x.c_str (), y.c_str (), xn) == 0 -#else - strncmp (x.c_str (), y.c_str (), xn) == 0 -#endif - ; - } - // Custom save function for config.config.environment. // // It tries to optimize the storage in subprojects by appending the @@ -82,7 +63,8 @@ namespace build2 if (find_if (ds.rbegin (), i, [&v, p] (const string& v1) { - return compare_env (v, p, v1, v1.find ('=')); + return saved_environment::compare ( + v, v1, p, v1.find ('=')); }) != i) continue; @@ -91,7 +73,8 @@ namespace build2 auto j (find_if (bs.rbegin (), bs.rend (), [&v, p] (const string& v1) { - return compare_env (v, p, v1, v1.find ('=')); + return saved_environment::compare ( + v, v1, p, v1.find ('=')); })); if (j == bs.rend () || *j != v) @@ -130,7 +113,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 ("config.config.save", true /* ovr */); @@ -166,39 +149,11 @@ namespace build2 // // Use the NULL value to clear. // - auto& c_p (vp.insert>> ( - "config.config.persist", true /* ovr */, v_p)); - - // 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" (=) and "unsets" (). - // 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 = as a "pass-through" instruction (e.g., if - // we need to use original value in subproject). + // Note: must be entered during bootstrap since we need it in create's + // save_config(). // - auto& c_e (vp.insert ("config.config.environment", true /* ovr */)); + vp.insert>> ( + "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 @@ -229,11 +184,6 @@ namespace build2 // m.save_module ("config", INT32_MIN); m.save_module ("import", INT32_MIN); - - m.save_variable (c_p, save_null_omitted); - m.save_variable (c_e, - save_null_omitted | save_empty_omitted | save_base, - &save_environment); } } @@ -277,16 +227,106 @@ namespace build2 return true; } + context& ctx (rs.ctx); + l5 ([&]{trace << "for " << rs;}); + // 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 () + : nullptr); + auto& vp (rs.var_pool ()); // Note: config.* is pattern-typed to global visibility. // const auto v_p (variable_visibility::project); - auto& c_l (vp.insert ("config.config.load", true /* ovr */)); auto& c_v (vp.insert ("config.version", false /*ovr*/, v_p)); + auto& c_l (vp.insert ("config.config.load", 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 ("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 ("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 ("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" (=) and "unsets" (). + // 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 = as a "pass-through" instruction (e.g., if + // we need to use original value in subproject). + // + auto& c_e (vp.insert ("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 followed by extra files specified in // config.config.load (we don't need to worry about disfigure since we @@ -358,7 +398,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 (l)) { @@ -384,31 +424,50 @@ namespace build2 } } - // Cache the config.config.persist value, if any. + // Save and cache the config.config.persist value, if any. // - if (extra.module != nullptr) + if (m != nullptr) { - auto& m (extra.module_as ()); + auto& c_p (*vp.find ("config.config.persist")); + m->save_variable (c_p, save_null_omitted); + m->persist = cast_null>> (rs[c_p]); + } - m.persist = - cast_null>> ( - rs["config.config.persist"]); + // 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 (rs[c_h]) || // c.c.h=false + cast_false (rs[c_h_r]))) // c.c.h.r=true + { + rs.vars.erase (c_e); // Undefine. } // Copy config.config.environment to scope::root_extra::environment. // // Note that we store shallow copies that point to the c.c.environment - // value which means it shall not change. + // value which means it should not change. // - if (const strings* src = cast_null ( - rs["config.config.environment"])) + if (const strings* src = cast_null (rs[c_e])) { vector& 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 decide to print - // this in diagnostics). + // 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. @@ -429,7 +488,8 @@ namespace build2 if (find_if (src->rbegin (), i, [&v, p] (const string& v1) { - return compare_env (v, p, v1, v1.find ('=')); + return saved_environment::compare ( + v, v1, p, v1.find ('=')); }) != i) continue; -- cgit v1.1