aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/config
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/config')
-rw-r--r--libbuild2/config/functions.cxx62
-rw-r--r--libbuild2/config/host-config.cxx.in3
-rw-r--r--libbuild2/config/init.cxx518
-rw-r--r--libbuild2/config/module.cxx13
-rw-r--r--libbuild2/config/module.hxx98
-rw-r--r--libbuild2/config/operation.cxx522
-rw-r--r--libbuild2/config/operation.hxx15
-rw-r--r--libbuild2/config/types.hxx25
-rw-r--r--libbuild2/config/utility.cxx72
-rw-r--r--libbuild2/config/utility.hxx182
-rw-r--r--libbuild2/config/utility.ixx13
-rw-r--r--libbuild2/config/utility.txx4
12 files changed, 1284 insertions, 243 deletions
diff --git a/libbuild2/config/functions.cxx b/libbuild2/config/functions.cxx
index 5e85238..b1a61a2 100644
--- a/libbuild2/config/functions.cxx
+++ b/libbuild2/config/functions.cxx
@@ -7,6 +7,7 @@
#include <libbuild2/function.hxx>
#include <libbuild2/variable.hxx>
+#include <libbuild2/config/module.hxx>
#include <libbuild2/config/operation.hxx>
using namespace std;
@@ -20,6 +21,58 @@ namespace build2
{
function_family f (m, "config");
+ // $config.origin()
+ //
+ // Return the origin of the value of the specified configuration
+ // variable. Possible result values and their semantics are as follows:
+ //
+ // undefined
+ // The variable is undefined.
+ //
+ // default
+ // The variable has the default value from the config directive (or
+ // as specified by a module).
+ //
+ // buildfile
+ // The variable has the value from a buildfile, normally config.build
+ // but could also be from file(s) specified with config.config.load.
+ //
+ // override
+ // The variable has the command line override value. Note that if
+ // the override happens to be append/prepend, then the value could
+ // incorporate the original value.
+ //
+ // Note that the variable must be specified as a name and not as an
+ // expansion (i.e., without $).
+ //
+ // Note that this function is not pure.
+ //
+ f.insert (".origin", false) += [] (const scope* s, names name)
+ {
+ if (s == nullptr)
+ fail << "config.origin() called out of scope" << endf;
+
+ // Only look in the root scope since that's the only config.*
+ // variables we generally consider.
+ //
+ s = s->root_scope ();
+
+ if (s == nullptr)
+ fail << "config.origin() called out of project" << endf;
+
+ switch (origin (*s, convert<string> (move (name))).first)
+ {
+ case variable_origin::undefined: return "undefined";
+ case variable_origin::default_: return "default";
+ case variable_origin::buildfile: return "buildfile";
+ case variable_origin::override_: return "override";
+ }
+
+ return ""; // Should not reach.
+ };
+
+ // $config.save()
+ //
// Return the configuration file contents as a string, similar to the
// config.config.save variable functionality.
//
@@ -39,6 +92,14 @@ namespace build2
if (s == nullptr)
fail << "config.save() called out of project" << endf;
+ // See save_config() for details.
+ //
+ assert (s->ctx.phase == run_phase::load);
+ const module* mod (s->find_module<module> (module::name));
+
+ if (mod == nullptr)
+ fail << "config.save() called without config module";
+
ostringstream os;
// Empty project set should is ok as long as inherit is false.
@@ -47,6 +108,7 @@ namespace build2
save_config (*s,
os, path_name ("config.save()"),
false /* inherit */,
+ *mod,
ps);
return os.str ();
diff --git a/libbuild2/config/host-config.cxx.in b/libbuild2/config/host-config.cxx.in
index 9e3e0c2..6b1ce77 100644
--- a/libbuild2/config/host-config.cxx.in
+++ b/libbuild2/config/host-config.cxx.in
@@ -9,5 +9,8 @@ namespace build2
//
extern const char host_config[] = R"###($host_config$)###";
extern const char build2_config[] = R"###($build2_config$)###";
+
+ extern const char host_config_no_warnings[] = R"###($host_config_no_warnings$)###";
+ extern const char build2_config_no_warnings[] = R"###($build2_config_no_warnings$)###";
}
}
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;
diff --git a/libbuild2/config/module.cxx b/libbuild2/config/module.cxx
index bf68c4e..713d30c 100644
--- a/libbuild2/config/module.cxx
+++ b/libbuild2/config/module.cxx
@@ -12,7 +12,9 @@ namespace build2
namespace config
{
bool module::
- save_variable (const variable& var, optional<uint64_t> flags)
+ save_variable (const variable& var,
+ optional<uint64_t> flags,
+ save_variable_function* save)
{
const string& n (var.name);
@@ -43,7 +45,7 @@ namespace build2
return false;
}
- sv.push_back (saved_variable {var, flags});
+ sv.push_back (saved_variable {var, flags, save});
return true;
}
@@ -54,6 +56,13 @@ namespace build2
m->save_variable (var, flags);
}
+ void module::
+ save_environment (scope& rs, const char* var)
+ {
+ if (module* m = rs.find_module<module> (module::name))
+ m->save_environment (var);
+ }
+
bool module::
save_module (const char* name, int prio)
{
diff --git a/libbuild2/config/module.hxx b/libbuild2/config/module.hxx
index 857a30c..8d3ff67 100644
--- a/libbuild2/config/module.hxx
+++ b/libbuild2/config/module.hxx
@@ -4,9 +4,9 @@
#ifndef LIBBUILD2_CONFIG_MODULE_HXX
#define LIBBUILD2_CONFIG_MODULE_HXX
-#include <map>
+#include <cstring> // strncmp()
-#include <libbutl/prefix-map.mxx>
+#include <libbutl/prefix-map.hxx>
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
@@ -28,10 +28,19 @@ namespace build2
// saved in the order populated. If flags are absent, then this variable
// was marked as "unsaved" (always transient).
//
+ // The optional save function can be used to implement custom variable
+ // saving, for example, as a difference appended to the base value. The
+ // second half of the result is the assignment operator to use.
+ //
+ using save_variable_function =
+ pair<names_view, const char*> (const value&,
+ const value* base,
+ names& storage);
struct saved_variable
{
reference_wrapper<const variable> var;
optional<uint64_t> flags;
+ save_variable_function* save;
};
struct saved_variables: vector<saved_variable>
@@ -43,7 +52,7 @@ namespace build2
const_iterator
find (const variable& var) const
{
- return std::find_if (
+ return find_if (
begin (),
end (),
[&var] (const saved_variable& v) {return var == v.var;});
@@ -55,7 +64,7 @@ namespace build2
// Priority order with INT32_MIN being the highest. Modules with the
// same priority are saved in the order inserted.
//
- std::multimap<std::int32_t, const_iterator> order;
+ multimap<std::int32_t, const_iterator> order;
pair<iterator, bool>
insert (string name, int prio = 0)
@@ -69,14 +78,77 @@ namespace build2
}
};
- struct module: build2::module
+ // List of environment variable names that effect this project.
+ //
+ // Note that on Windows environment variable names are case-insensitive.
+ //
+ struct saved_environment: vector<string>
+ {
+ // Compare environment variable names.
+ //
+ static inline bool
+ compare (const string& x,
+ const string& y,
+ size_t xn = string::npos,
+ size_t yn = string::npos)
+ {
+ 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
+ ;
+ }
+
+ iterator
+ find (const string& v)
+ {
+ return find_if (
+ begin (),
+ end (),
+ [&v] (const string& v1) {return compare (v, v1);});
+ }
+
+ const_iterator
+ find (const string& v) const
+ {
+ return find_if (
+ begin (),
+ end (),
+ [&v] (const string& v1) {return compare (v, v1);});
+ }
+
+ void
+ insert (string v)
+ {
+ if (find (v) == end ())
+ push_back (move (v));
+ }
+
+ void
+ erase (const string& v)
+ {
+ auto i (find (v));
+ if (i != end ())
+ vector<string>::erase (i);
+ }
+ };
+
+ class module: public build2::module
{
+ public:
config::saved_modules saved_modules;
// Return true if variable/module were newly inserted.
//
bool
- save_variable (const variable&, optional<uint64_t> flags);
+ save_variable (const variable&,
+ optional<uint64_t> flags,
+ save_variable_function* = nullptr);
static void
save_variable (scope&, const variable&, optional<uint64_t>);
@@ -88,7 +160,7 @@ namespace build2
save_module (scope&, const char*, int);
const saved_variable*
- find_variable (const variable& var)
+ find_variable (const variable& var) const
{
auto i (saved_modules.find_sup (var.name));
if (i != saved_modules.end ())
@@ -101,6 +173,18 @@ namespace build2
return nullptr;
}
+ void
+ save_environment (const char* var)
+ {
+ saved_environment.insert (var);
+ }
+
+ static void
+ save_environment (scope&, const char*);
+
+ config::saved_environment saved_environment;
+ strings old_environment;
+
// Configure/disfigure hooks.
//
static bool
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index b9856be..150bf1a 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -42,7 +42,7 @@ namespace build2
ofs << "# Created automatically by the config module." << endl
<< "#" << endl
<< "src_root = ";
- to_stream (ofs, name (src_root), true /* quote */, '@');
+ to_stream (ofs, name (src_root), quote_mode::normal, '@');
ofs << endl;
ofs.close ();
@@ -61,8 +61,10 @@ namespace build2
path f (src_root / rs.root_extra->out_root_file);
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ if (verb >= 2)
+ text << "cat >" << f;
+ else if (verb)
+ print_diag ("save", f);
try
{
@@ -71,7 +73,7 @@ namespace build2
ofs << "# Created automatically by the config module." << endl
<< "#" << endl
<< "out_root = ";
- to_stream (ofs, name (out_root), true /* quote */, '@');
+ to_stream (ofs, name (out_root), quote_mode::normal, '@');
ofs << endl;
ofs.close ();
@@ -82,8 +84,6 @@ namespace build2
}
}
- using project_set = set<const scope*>; // Use pointers to get comparison.
-
// Return (first) whether an unused/inherited variable should be saved
// according to the config.config.persist value and (second) whether the
// user should be warned about it.
@@ -134,7 +134,8 @@ namespace build2
bool r;
if (c.compare (p, 4 , "save") == 0) r = true;
else if (c.compare (p, 4 , "drop") == 0) r = false;
- else fail << "invalid config.config.persist action '" << c << "'";
+ else fail << "invalid config.config.persist action '" << c << "'"
+ << endf;
bool w (false);
if ((p += 4) != c.size ())
@@ -159,24 +160,26 @@ namespace build2
// If inherit is false, then don't rely on inheritance from outer scopes.
//
+ // @@ We are modifying the module (marking additional variables as saved)
+ // and this function can be called from a buildfile (probably only
+ // during serial execution but still).
+ //
+ // We could also be configuring multiple projects (including from
+ // pkg_configure() in bpkg) but feels like we should be ok since we
+ // only modify this project's root scope data which should not affect
+ // any other project.
+ //
+ // See also save_environment() for a similar issue.
+ //
void
save_config (const scope& rs,
ostream& os, const path_name& on,
bool inherit,
+ const module& mod,
const project_set& projects)
{
context& ctx (rs.ctx);
- // @@ We are modifying the module (marking additional variables as
- // saved) and this function can be called from a buildfile (probably
- // only during serial execution but still).
- //
- module* mod (rs.find_module<module> (module::name));
-
- if (mod == nullptr)
- fail (on) << "no configuration information available during this "
- << "meta-operation";
-
names storage;
auto info_value = [&storage] (diag_record& dr, const value& v) mutable
@@ -186,7 +189,7 @@ namespace build2
if (v)
{
storage.clear ();
- dr << "'" << reverse (v, storage) << "'";
+ dr << "'" << reverse (v, storage, true /* reduce */) << "'";
}
else
dr << "[null]";
@@ -214,9 +217,11 @@ namespace build2
// saved according to config.config.persist potentially warning if the
// variable would otherwise be dropped.
//
+ // Note: go straight for the public variable pool.
+ //
auto& vp (ctx.var_pool);
- for (auto p (rs.vars.lookup_namespace (*vp.find ("config")));
+ for (auto p (rs.vars.lookup_namespace ("config"));
p.first != p.second;
++p.first)
{
@@ -228,26 +233,59 @@ namespace build2
if (size_t n = var->override ())
var = vp.find (string (var->name, 0, n));
+ const string& name (var->name);
+
// Skip special variables.
//
- if (var->name == "config.booted" ||
- var->name == "config.loaded" ||
- var->name == "config.configured" ||
- var->name.compare (0, 14, "config.config.") == 0)
+ if (name == "config.booted" ||
+ name == "config.loaded" ||
+ name == "config.configured" ||
+ name.compare (0, 14, "config.config.") == 0)
continue;
- if (mod->find_variable (*var)) // Saved or unsaved.
+ if (mod.find_variable (*var)) // Saved or unsaved.
continue;
+ // Skip config.**.develop variables (see parser::parse_config() for
+ // details).
+ //
+ // In a sense, this variable is always "available" but if the
+ // package does not distinguish between development and consumption,
+ // then specifying config.*.develop=true should be noop.
+ //
+ {
+ size_t p (name.rfind ('.'));
+ if (p != 6 && name.compare (p + 1, string::npos, "develop") == 0)
+ continue;
+ }
+
+ // A common reason behind an unused config.import.* value is an
+ // unused dependency. That is, there is depends in manifest but no
+ // import in buildfile (or import could be conditional in which case
+ // depends should also be conditional). So let's suggest this
+ // possibility. Note that the project name may have been sanitized
+ // to a variable name. Oh, well, better than nothing.
+ //
+ auto info_import = [] (diag_record& dr, const string& var)
+ {
+ if (var.compare (0, 14, "config.import.") == 0)
+ {
+ size_t p (var.find ('.', 14));
+
+ dr << info << "potentially unused dependency on "
+ << string (var, 14, p == string::npos ? p : p - 14);
+ }
+ };
+
const value& v (p.first->second);
pair<bool, bool> r (save_config_variable (*var,
- mod->persist,
+ mod.persist,
false /* inherited */,
true /* unused */));
if (r.first) // save
{
- mod->save_variable (*var, 0);
+ const_cast<module&> (mod).save_variable (*var, 0);
if (r.second) // warn
{
@@ -266,6 +304,7 @@ namespace build2
diag_record dr;
dr << warn (on) << "saving no longer used variable " << *var;
+ info_import (dr, var->name);
if (verb >= 2)
info_value (dr, v);
}
@@ -276,6 +315,7 @@ namespace build2
{
diag_record dr;
dr << warn (on) << "dropping no longer used variable " << *var;
+ info_import (dr, var->name);
info_value (dr, v);
}
}
@@ -283,12 +323,23 @@ namespace build2
// Save config variables.
//
- for (auto p: mod->saved_modules.order)
+ for (auto p: mod.saved_modules.order)
{
const string& sname (p.second->first);
const saved_variables& svars (p.second->second);
- bool first (true); // Separate modules with a blank line.
+ // Separate modules with a blank line.
+ //
+ auto first = [v = true] () mutable
+ {
+ if (v)
+ {
+ v = false;
+ return "\n";
+ }
+ return "";
+ };
+
for (const saved_variable& sv: svars)
{
if (!sv.flags) // unsaved
@@ -308,7 +359,13 @@ namespace build2
// inherited. We might also not have any value at all (see
// unconfigured()).
//
- if (!l.defined () || (l->null && flags & save_null_omitted))
+ // Note that we must check for null() before attempting any
+ // further tests.
+ //
+ if (!l.defined () ||
+ (l->null ? flags & save_null_omitted :
+ l->empty () ? flags & save_empty_omitted :
+ (flags & save_false_omitted) != 0 && !cast<bool> (*l)))
continue;
// Handle inherited from outer scope values.
@@ -317,90 +374,97 @@ namespace build2
// we save the inherited values regardless of whether they are
// used or not.
//
- if (inherit && !(l.belongs (rs) || l.belongs (ctx.global_scope)))
+ const value* base (nullptr);
+ if (inherit)
{
- // This is presumably an inherited value. But it could also be
- // some left-over garbage. For example, an amalgamation could
- // have used a module but then dropped it while its config
- // values are still lingering in config.build. They are probably
- // still valid and we should probably continue using them but we
- // definitely want to move them to our config.build since they
- // will be dropped from the amalgamation's config.build on the
- // next reconfigure. Let's also warn the user just in case,
- // unless there is no module and thus we couldn't really check
- // (the latter could happen when calling $config.save() during
- // other meta-operations, though it passes false for inherit).
+ // Return true if the specified value can be inherited from.
//
- // There is also another case that falls under this now that
- // overrides are by default amalgamation-wide rather than just
- // "project and subprojects": we may be (re-)configuring a
- // subproject but the override is now set on the outer project's
- // root.
- //
- bool found (false), checked (true);
- const scope* r (&rs);
- while ((r = r->parent_scope ()->root_scope ()) != nullptr)
+ auto find_inherited = [&on, &projects,
+ &info_value,
+ &sname, &rs, &var] (const lookup& org,
+ const lookup& ovr)
{
- if (l.belongs (*r))
+ const lookup& l (ovr);
+
+ // This is presumably an inherited value. But it could also be
+ // some left-over garbage. For example, an amalgamation could
+ // have used a module but then dropped it while its config
+ // values are still lingering in config.build. They are
+ // probably still valid and we should probably continue using
+ // them but we definitely want to move them to our
+ // config.build since they will be dropped from the
+ // amalgamation's config.build on the next reconfigure. Let's
+ // also warn the user just in case, unless there is no module
+ // and thus we couldn't really check (the latter could happen
+ // when calling $config.save() during other meta-operations,
+ // though it passes false for inherit).
+ //
+ // There is also another case that falls under this now that
+ // overrides are by default amalgamation-wide rather than just
+ // "project and subprojects": we may be (re-)configuring a
+ // subproject but the override is now set on the outer
+ // project's root.
+ //
+ bool found (false), checked (true);
+ const scope* r (&rs);
+ while ((r = r->parent_scope ()->root_scope ()) != nullptr)
{
- // Find the config module (might not be there).
- //
- if (auto* m = r->find_module<const module> (module::name))
+ if (l.belongs (*r))
{
- // Find the corresponding saved module.
+ // Find the config module (might not be there).
//
- auto i (m->saved_modules.find (sname));
-
- if (i != m->saved_modules.end ())
+ if (auto* m = r->find_module<const module> (module::name))
{
- // Find the variable.
+ // Find the corresponding saved module.
//
- const saved_variables& sv (i->second);
- found = sv.find (var) != sv.end ();
+ auto i (m->saved_modules.find (sname));
- // If not marked as saved, check whether overriden via
- // config.config.persist.
- //
- if (!found && m->persist != nullptr)
+ if (i != m->saved_modules.end ())
{
- found = save_config_variable (
- var,
- m->persist,
- false /* inherited */,
- true /* unused */).first;
+ // Find the variable.
+ //
+ const saved_variables& sv (i->second);
+ found = sv.find (var) != sv.end ();
+
+ // If not marked as saved, check whether overriden via
+ // config.config.persist.
+ //
+ if (!found && m->persist != nullptr)
+ {
+ found = save_config_variable (
+ var,
+ m->persist,
+ false /* inherited */,
+ true /* unused */).first;
+ }
+
+ // Handle that other case: if this is an override but
+ // the outer project itself is not being configured,
+ // then we need to save this override.
+ //
+ // One problem with using the already configured
+ // project set is that the outer project may be
+ // configured only after us in which case both
+ // projects will save the value. But perhaps this is a
+ // feature, not a bug since this is how project-local
+ // (%) override behaves.
+ //
+ if (found &&
+ org != ovr &&
+ projects.find (r) == projects.end ())
+ found = false;
}
-
- // Handle that other case: if this is an override but
- // the outer project itself is not being configured,
- // then we need to save this override.
- //
- // One problem with using the already configured project
- // set is that the outer project may be configured only
- // after us in which case both projects will save the
- // value. But perhaps this is a feature, not a bug since
- // this is how project-local (%) override behaves.
- //
- if (found &&
- org.first != ovr.first &&
- projects.find (r) == projects.end ())
- found = false;
}
- }
- else
- checked = false;
+ else
+ checked = false;
- break;
+ break;
+ }
}
- }
- if (found)
- {
- // Inherited.
- //
- continue;
- }
- else
- {
+ if (found)
+ return true;
+
// If this value is not defined in a project's root scope,
// then something is broken.
//
@@ -413,7 +477,7 @@ namespace build2
// our own. One special case where we don't want to warn the
// user is if the variable is overriden.
//
- if (checked && org.first == ovr.first)
+ if (checked && org == ovr)
{
diag_record dr;
dr << warn (on) << "saving previously inherited variable "
@@ -425,6 +489,39 @@ namespace build2
if (verb >= 2)
info_value (dr, *l);
}
+
+ return false;
+ };
+
+ // Inherit as-is.
+ //
+ if (!l.belongs (rs) &&
+ !l.belongs (ctx.global_scope) &&
+ find_inherited (org.first, ovr.first))
+ continue;
+ else if (flags & save_base)
+ {
+ // See if we can base our value on inherited.
+ //
+ if (const scope* ors = rs.parent_scope ()->root_scope ())
+ {
+ pair<lookup, size_t> org (ors->lookup_original (var));
+ pair<lookup, size_t> ovr (var.overrides == nullptr
+ ? org
+ : ors->lookup_override (var, org));
+ const lookup& l (ovr.first);
+
+ // We cannot base anything on an empty value.
+ //
+ if (l && !l->empty ())
+ {
+ // @@ It's not clear we want the checks/diagnostics in
+ // this case.
+ //
+ if (find_inherited (org.first, ovr.first))
+ base = l.value;
+ }
+ }
}
}
@@ -442,44 +539,42 @@ namespace build2
continue;
}
- // If we got here then we are saving this variable. Handle the
- // blank line.
- //
- if (first)
- {
- os << endl;
- first = false;
- }
-
// Handle the save_default_commented flag.
//
- if ((org.first.defined () && org.first->extra) && // Default value.
- org.first == ovr.first && // Not overriden.
+ if (org.first.defined () && org.first->extra == 1 && // Default.
+ org.first == ovr.first && // No override.
(flags & save_default_commented) != 0)
{
- os << '#' << n << " =" << endl;
+ os << first () << '#' << n << " =" << endl;
continue;
}
- if (v)
+ if (v.null)
{
- storage.clear ();
- names_view ns (reverse (v, storage));
+ os << first () << n << " = [null]" << endl;
+ continue;
+ }
- os << n;
+ storage.clear ();
+ pair<names_view, const char*> p (
+ sv.save != nullptr
+ ? sv.save (v, base, storage)
+ : make_pair (reverse (v, storage, true /* reduce */), "="));
- if (ns.empty ())
- os << " =";
- else
- {
- os << " = ";
- to_stream (os, ns, true /* quote */, '@');
- }
+ // Might becomes empty after a custom save function had at it.
+ //
+ if (p.first.empty () && (flags & save_empty_omitted))
+ continue;
+
+ os << first () << n << ' ' << p.second;
- os << endl;
+ if (!p.first.empty ())
+ {
+ os << ' ';
+ to_stream (os, p.first, quote_mode::normal, '@');
}
- else
- os << n << " = [null]" << endl;
+
+ os << endl;
}
}
}
@@ -493,6 +588,7 @@ namespace build2
save_config (const scope& rs,
const path& f,
bool inherit,
+ const module& mod,
const project_set& projects)
{
path_name fn (f);
@@ -500,13 +596,16 @@ namespace build2
if (f.string () == "-")
fn.name = "<stdout>";
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << fn;
+ if (verb >= 2)
+ text << "cat >" << fn;
+ else if (verb)
+ print_diag ("save", fn);
try
{
ofdstream ofs;
- save_config (rs, open_file_or_stdout (fn, ofs), fn, inherit, projects);
+ save_config (
+ rs, open_file_or_stdout (fn, ofs), fn, inherit, mod, projects);
ofs.close ();
}
catch (const io_error& e)
@@ -515,10 +614,86 @@ namespace build2
}
}
+ // Update config.config.environment value for a hermetic configuration.
+ //
+ // @@ We are modifying the module. See also save_config() for a similar
+ // issue.
+ //
+ static void
+ save_environment (scope& rs, module& mod)
+ {
+ // Here we have two parts: (1) get the list of environment variables we
+ // need to save and (2) save their values in config.config.environment.
+ //
+ // The saved_environment list should by now contain all the project-
+ // specific environment variables. To that we add builtin defaults and
+ // then filter the result against config.config.hermetic.environment
+ // inclusions/exclusions.
+ //
+ auto& vars (mod.saved_environment);
+
+ vars.insert ("PATH");
+
+#if defined(_WIN32)
+#elif defined(__APPLE__)
+ vars.insert ("DYLD_LIBRARY_PATH");
+#else // Linux, FreeBSD, NetBSD, OpenBSD
+ vars.insert ("LD_LIBRARY_PATH");
+#endif
+
+ for (const pair<string, optional<bool>>& p:
+ cast_empty<hermetic_environment> (
+ rs["config.config.hermetic.environment"]))
+ {
+ if (!p.second || *p.second)
+ vars.insert (p.first);
+ else
+ vars.erase (p.first);
+ }
+
+ // Get the values.
+ //
+ strings vals;
+ {
+ // Set the project environment before querying the values. Note that
+ // the logic in init() makes sure that this is all we need to do to
+ // handle reload (in which case we should still be using
+ // config.config.environment from amalgamation, if any).
+ //
+ auto_project_env penv (rs);
+
+ for (const string& var: vars)
+ {
+ if (optional<string> val = getenv (var))
+ {
+ vals.push_back (var + '=' + *val);
+ }
+ else
+ vals.push_back (var); // Unset.
+ }
+ }
+
+ // Note: go straight for the public variable pool.
+ //
+ value& v (rs.assign (*rs.ctx.var_pool.find ("config.config.environment")));
+
+ // Note that setting new config.config.environment value invalidates the
+ // project's environment (scope::root_extra::environment) which could be
+ // queried in the post-configuration hook. We could re-initialize it but
+ // the c.c.e value from amalgamation could be referenced by subprojects.
+ // So instead it seems easier to just save the old value in the module.
+ //
+ if (v)
+ mod.old_environment = move (v.as<strings> ());
+
+ v = move (vals);
+ }
+
static void
configure_project (action a,
const scope& rs,
const variable* c_s, // config.config.save
+ const module& mod,
project_set& projects)
{
tracer trace ("configure_project");
@@ -538,7 +713,7 @@ namespace build2
//
if (out_root != src_root)
{
- mkdir_p (out_root / rs.root_extra->build_dir);
+ mkdir_p (out_root / rs.root_extra->build_dir, 1);
mkdir (out_root / rs.root_extra->bootstrap_dir, 2);
}
@@ -548,6 +723,12 @@ namespace build2
{
l5 ([&]{trace << "completely configuring " << out_root;});
+ // Save the environment if this configuration is hermetic (see init()
+ // for the other half of this logic).
+ //
+ if (cast_false<bool> (rs["config.config.hermetic"]))
+ save_environment (const_cast<scope&> (rs), const_cast<module&> (mod));
+
// Save src-root.build unless out_root is the same as src.
//
if (c_s == nullptr && out_root != src_root)
@@ -572,12 +753,17 @@ namespace build2
// may end up leaving it in partially configured state.
//
if (c_s == nullptr)
- save_config (rs, config_file (rs), true /* inherit */, projects);
+ save_config (rs, config_file (rs), true /* inherit */, mod, projects);
else
{
lookup l (rs[*c_s]);
if (l && (l.belongs (rs) || l.belongs (ctx.global_scope)))
{
+ const path& f (cast<path> (l));
+
+ if (f.empty ())
+ fail << "empty path in " << *c_s;
+
// While writing the complete configuration seems like a natural
// default, there might be a desire to take inheritance into
// account (if, say, we are exporting at multiple levels). One can
@@ -585,7 +771,7 @@ namespace build2
// still want to support this mode somehow in the future (it seems
// like an override of config.config.persist should do the trick).
//
- save_config (rs, cast<path> (l), false /* inherit */, projects);
+ save_config (rs, f, false /* inherit */, mod, projects);
}
}
}
@@ -596,11 +782,8 @@ namespace build2
if (c_s == nullptr)
{
- if (module* m = rs.find_module<module> (module::name))
- {
- for (auto hook: m->configure_post_)
- hook (a, rs);
- }
+ for (auto hook: mod.configure_post_)
+ hook (a, rs);
}
// Configure subprojects that have been loaded.
@@ -611,15 +794,19 @@ namespace build2
{
const dir_path& pd (p.second);
dir_path out_nroot (out_root / pd);
- const scope& nrs (ctx.scopes.find (out_nroot));
+ const scope& nrs (ctx.scopes.find_out (out_nroot));
- // @@ Strictly speaking we need to check whether the config module
- // was loaded for this subproject.
+ // Skip this subproject if it is not loaded or doesn't use the
+ // config module.
//
- if (nrs.out_path () != out_nroot) // This subproject not loaded.
- continue;
+ if (nrs.out_path () == out_nroot)
+ {
+ if (const module* m = nrs.find_module<module> (module::name))
+ {
+ configure_project (a, nrs, c_s, *m, projects);
+ }
+ }
- configure_project (a, nrs, c_s, projects);
}
}
}
@@ -651,7 +838,7 @@ namespace build2
for (auto p: *ps)
{
dir_path out_nroot (out_root / p.second);
- const scope& nrs (ctx.scopes.find (out_nroot));
+ const scope& nrs (ctx.scopes.find_out (out_nroot));
assert (nrs.out_path () == out_nroot);
configure_forward (nrs, projects);
@@ -662,11 +849,13 @@ namespace build2
operation_id (*pre) (const values&, meta_operation_id, const location&);
static operation_id
- configure_operation_pre (const values&, operation_id o)
+ configure_operation_pre (context&, const values&, operation_id o)
{
// Don't translate default to update. In our case unspecified
// means configure everything.
//
+ // Note: see pkg_configure() in bpkg if changing anything here.
+ //
return o;
}
@@ -701,8 +890,10 @@ namespace build2
}
static void
- configure_pre (const values& params, const location& l)
+ configure_pre (context&, const values& params, const location& l)
{
+ // Note: see pkg_configure() in bpkg if changing anything here.
+ //
forward (params, "configure", l); // Validate.
}
@@ -722,11 +913,13 @@ namespace build2
//
create_bootstrap_inner (rs);
- if (rs.out_path () == rs.src_path ())
+ if (rs.out_eq_src ())
fail (l) << "forwarding to source directory " << rs.src_path ();
}
else
- load (params, rs, buildfile, out_base, src_base, l); // Normal load.
+ // Normal load.
+ //
+ perform_load (params, rs, buildfile, out_base, src_base, l);
}
static void
@@ -746,7 +939,7 @@ namespace build2
ts.push_back (&rs);
}
else
- search (params, rs, bs, bf, tk, l, ts); // Normal search.
+ perform_search (params, rs, bs, bf, tk, l, ts); // Normal search.
}
static void
@@ -766,6 +959,8 @@ namespace build2
context& ctx (fwd ? ts[0].as<scope> ().ctx : ts[0].as<target> ().ctx);
+ // Note: go straight for the public variable pool.
+ //
const variable* c_s (ctx.var_pool.find ("config.config.save"));
if (c_s->overrides == nullptr)
@@ -820,16 +1015,28 @@ namespace build2
ctx.current_operation (*oif);
+ if (oif->operation_pre != nullptr)
+ oif->operation_pre (ctx, {}, true /* inner */, location ());
+
phase_lock pl (ctx, run_phase::match);
- match (action (configure_id, id), t);
+ match_sync (action (configure_id, id), t);
+
+ if (oif->operation_post != nullptr)
+ oif->operation_post (ctx, {}, true /* inner */);
}
}
- configure_project (a, *rs, c_s, projects);
+ configure_project (a,
+ *rs,
+ c_s,
+ *rs->find_module<module> (module::name),
+ projects);
}
}
}
+ // NOTE: see pkg_configure() in bpkg if changing anything here.
+ //
const meta_operation_info mo_configure {
configure_id,
"configure",
@@ -879,7 +1086,7 @@ namespace build2
{
const dir_path& pd (p.second);
dir_path out_nroot (out_root / pd);
- const scope& nrs (ctx.scopes.find (out_nroot));
+ const scope& nrs (ctx.scopes.find_out (out_nroot));
assert (nrs.out_path () == out_nroot); // See disfigure_load().
r = disfigure_project (a, nrs, projects) || r;
@@ -905,7 +1112,7 @@ namespace build2
}
}
- if (module* m = rs.find_module<module> (module::name))
+ if (const module* m = rs.find_module<module> (module::name))
{
for (auto hook: m->disfigure_pre_)
r = hook (a, rs) || r;
@@ -987,7 +1194,7 @@ namespace build2
for (auto p: *ps)
{
dir_path out_nroot (out_root / p.second);
- const scope& nrs (ctx.scopes.find (out_nroot));
+ const scope& nrs (ctx.scopes.find_out (out_nroot));
assert (nrs.out_path () == out_nroot);
r = disfigure_forward (nrs, projects) || r;
@@ -1004,13 +1211,13 @@ namespace build2
}
static void
- disfigure_pre (const values& params, const location& l)
+ disfigure_pre (context&, const values& params, const location& l)
{
forward (params, "disfigure", l); // Validate.
}
static operation_id
- disfigure_operation_pre (const values&, operation_id o)
+ disfigure_operation_pre (context&, const values&, operation_id o)
{
// Don't translate default to update. In our case unspecified
// means disfigure everything.
@@ -1128,6 +1335,8 @@ namespace build2
// Add the default config.config.persist value unless there is a custom
// one (specified as a command line override).
//
+ // Note: go straight for the public variable pool.
+ //
const variable& var (*ctx.var_pool.find ("config.config.persist"));
if (!rs[var].defined ())
@@ -1244,7 +1453,8 @@ namespace build2
string ("config"), /* config_module */
nullopt, /* config_file */
true, /* buildfile */
- "the create meta-operation");
+ "the create meta-operation",
+ 1 /* verbosity */);
save_config (ctx, d);
}
diff --git a/libbuild2/config/operation.hxx b/libbuild2/config/operation.hxx
index a887eb7..1662941 100644
--- a/libbuild2/config/operation.hxx
+++ b/libbuild2/config/operation.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_CONFIG_OPERATION_HXX
#define LIBBUILD2_CONFIG_OPERATION_HXX
-#include <set>
-
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
@@ -15,8 +13,10 @@ namespace build2
{
namespace config
{
- extern const meta_operation_info mo_configure;
- extern const meta_operation_info mo_disfigure;
+ class module;
+
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info mo_configure;
+ LIBBUILD2_SYMEXPORT extern const meta_operation_info mo_disfigure;
const string&
preprocess_create (context&,
@@ -27,7 +27,7 @@ namespace build2
// Configuration exporting.
//
- using project_set = std::set<const scope*>; // Pointers for comparison.
+ using project_set = set<const scope*>; // Pointers for comparison.
// If inherit is false, then don't rely on inheritance from outer scopes
// (used for config.config.save/$config.save()). In this case the already
@@ -37,7 +37,12 @@ namespace build2
save_config (const scope& rs,
ostream&, const path_name&,
bool inherit,
+ const module&,
const project_set&);
+
+ // See config.config.hermetic.environment.
+ //
+ using hermetic_environment = vector<pair<string, optional<bool>>>;
}
}
diff --git a/libbuild2/config/types.hxx b/libbuild2/config/types.hxx
new file mode 100644
index 0000000..3cdc5e3
--- /dev/null
+++ b/libbuild2/config/types.hxx
@@ -0,0 +1,25 @@
+// file : libbuild2/config/types.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONFIG_TYPES_HXX
+#define LIBBUILD2_CONFIG_TYPES_HXX
+
+#include <libbuild2/types.hxx>
+
+namespace build2
+{
+ namespace config
+ {
+ // The origin of the value of a configuration variable.
+ //
+ enum class variable_origin
+ {
+ undefined, // Undefined.
+ default_, // Default value from the config directive.
+ buildfile, // Value from a buildfile, normally config.build.
+ override_ // Value from a command line override.
+ };
+ }
+}
+
+#endif // LIBBUILD2_CONFIG_TYPES_HXX
diff --git a/libbuild2/config/utility.cxx b/libbuild2/config/utility.cxx
index f777c08..6574367 100644
--- a/libbuild2/config/utility.cxx
+++ b/libbuild2/config/utility.cxx
@@ -8,6 +8,7 @@ using namespace std;
namespace build2
{
void (*config_save_variable) (scope&, const variable&, optional<uint64_t>);
+ void (*config_save_environment) (scope&, const char*);
void (*config_save_module) (scope&, const char*, int);
const string& (*config_preprocess_create) (context&,
values&,
@@ -20,7 +21,7 @@ namespace build2
namespace config
{
pair<lookup, bool>
- lookup_config_impl (scope& rs, const variable& var)
+ lookup_config_impl (scope& rs, const variable& var, uint64_t sflags)
{
// This is a stripped-down version of the default value case.
@@ -31,7 +32,7 @@ namespace build2
// Treat an inherited value that was set to default as new.
//
- if (l.defined () && l->extra)
+ if (l.defined () && l->extra == 1)
n = true;
if (var.overrides != nullptr)
@@ -70,7 +71,7 @@ namespace build2
}
if (l.defined ())
- save_variable (rs, var);
+ save_variable (rs, var, sflags);
return pair<lookup, bool> (l, n);
}
@@ -80,7 +81,9 @@ namespace build2
const string& n,
initializer_list<const char*> ig)
{
- auto& vp (rs.var_pool ());
+ // Note: go straight for the public variable pool.
+ //
+ auto& vp (rs.ctx.var_pool);
// Search all outer scopes for any value in this namespace.
//
@@ -90,7 +93,7 @@ namespace build2
// any original values, they will be "visible"; see find_override() for
// details.
//
- const variable& ns (vp.insert ("config." + n));
+ const string ns ("config." + n);
for (scope* s (&rs); s != nullptr; s = s->parent_scope ())
{
for (auto p (s->vars.lookup_namespace (ns));
@@ -106,12 +109,12 @@ namespace build2
auto match_tail = [&ns, v] (const char* t)
{
- return v->name.compare (ns.name.size () + 1, string::npos, t) == 0;
+ return v->name.compare (ns.size () + 1, string::npos, t) == 0;
};
// Ignore config.*.configured and user-supplied names.
//
- if (v->name.size () <= ns.name.size () ||
+ if (v->name.size () <= ns.size () ||
(!match_tail ("configured") &&
find_if (ig.begin (), ig.end (), match_tail) == ig.end ()))
return true;
@@ -127,7 +130,7 @@ namespace build2
// Pattern-typed as bool.
//
const variable& var (
- rs.var_pool ().insert ("config." + n + ".configured"));
+ rs.var_pool (true).insert ("config." + n + ".configured"));
save_variable (rs, var);
@@ -141,7 +144,7 @@ namespace build2
// Pattern-typed as bool.
//
const variable& var (
- rs.var_pool ().insert ("config." + n + ".configured"));
+ rs.var_pool (true).insert ("config." + n + ".configured"));
save_variable (rs, var);
@@ -155,5 +158,56 @@ namespace build2
else
return false;
}
+
+ pair<variable_origin, lookup>
+ origin (const scope& rs, const string& n)
+ {
+ // Note: go straight for the public variable pool.
+ //
+ const variable* var (rs.ctx.var_pool.find (n));
+
+ if (var == nullptr)
+ {
+ if (n.compare (0, 7, "config.") != 0)
+ throw invalid_argument ("config.* variable expected");
+
+ return make_pair (variable_origin::undefined, lookup ());
+ }
+
+ return origin (rs, *var);
+ }
+
+ pair<variable_origin, lookup>
+ origin (const scope& rs, const variable& var)
+ {
+ // Make sure this is a config.* variable. This could matter since we
+ // rely on the semantics of value::extra. We could also detect
+ // special variables like config.booted, some config.config.*, etc.,
+ // (see config_save() for details) but that seems harmless.
+ //
+ if (var.name.compare (0, 7, "config.") != 0)
+ throw invalid_argument ("config.* variable expected");
+
+ return origin (rs, var, rs.lookup_original (var));
+ }
+
+ pair<variable_origin, lookup>
+ origin (const scope& rs, const variable& var, pair<lookup, size_t> org)
+ {
+ pair<lookup, size_t> ovr (var.overrides == nullptr
+ ? org
+ : rs.lookup_override (var, org));
+
+ if (!ovr.first.defined ())
+ return make_pair (variable_origin::undefined, lookup ());
+
+ if (org.first != ovr.first)
+ return make_pair (variable_origin::override_, ovr.first);
+
+ return make_pair (org.first->extra == 1
+ ? variable_origin::default_
+ : variable_origin::buildfile,
+ org.first);
+ }
}
}
diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx
index 0429555..1e2ff53 100644
--- a/libbuild2/config/utility.hxx
+++ b/libbuild2/config/utility.hxx
@@ -11,6 +11,8 @@
#include <libbuild2/scope.hxx>
#include <libbuild2/variable.hxx>
+#include <libbuild2/config/types.hxx>
+
#include <libbuild2/export.hxx>
namespace build2
@@ -36,6 +38,9 @@ namespace build2
(*config_save_variable) (scope&, const variable&, optional<uint64_t>);
LIBBUILD2_SYMEXPORT extern void
+ (*config_save_environment) (scope&, const char*);
+
+ LIBBUILD2_SYMEXPORT extern void
(*config_save_module) (scope&, const char*, int);
LIBBUILD2_SYMEXPORT extern const string&
@@ -53,10 +58,22 @@ namespace build2
namespace config
{
- // Mark the variable to be saved during configuration.
+ // Mark a variable to be saved during configuration.
+ //
+ // Note: the save_*_omitted flags work best when undefined or (one of) the
+ // omitted value(s) is the default (see a note in lookup_config()
+ // documentation for details).
+ //
+ // The below lookup_*() functions mark the default value by setting
+ // value::extra to 1. Note that it's exactly 1 and not "not 0" since other
+ // values could have other meaning (see, for example, package skeleton
+ // in bpkg).
//
const uint64_t save_default_commented = 0x01; // Based on value::extra.
const uint64_t save_null_omitted = 0x02; // Treat NULL as undefined.
+ const uint64_t save_empty_omitted = 0x04; // Treat empty as undefined.
+ const uint64_t save_false_omitted = 0x08; // Treat false as undefined.
+ const uint64_t save_base = 0x10; // Custom save with base.
inline void
save_variable (scope& rs, const variable& var, uint64_t flags = 0)
@@ -65,7 +82,7 @@ namespace build2
config_save_variable (rs, var, flags);
}
- // Mark the variable as "unsaved" (always transient).
+ // Mark a variable as "unsaved" (always transient).
//
// Such variables are not very common and are usually used to control the
// process of configuration itself.
@@ -77,6 +94,93 @@ namespace build2
config_save_variable (rs, var, nullopt);
}
+ // Mark an environment variable to be saved during hermetic configuration.
+ //
+ // Some notes/suggestions on saving environment variables for tools (e.g.,
+ // compilers, etc):
+ //
+ // 1. We want to save variables that affect the result (e.g., build
+ // output) rather than byproducts (e.g., diagnostics).
+ //
+ // 2. Environment variables are often poorly documented (and not always in
+ // the ENVIRONMENT section; sometimes they are mentioned together with
+ // the corresponding option). A sensible approach in this case is to
+ // save documented (and perhaps well-known undocumented) variables --
+ // the user can always save additional variables if necessary. The way
+ // to discover undocumented environment variables is to grep the source
+ // code.
+ //
+ // 3. Sometime environment variables only affect certain modes of a tool.
+ // If such modes are not used, then there is no need to save the
+ // corresponding variables.
+ //
+ // 4. Finally, there could be environment variables that are incompatible
+ // with what we are doing (e.g., they change the mode of operation or
+ // some such; see GCC's DEPENDENCIES_OUTPUT for example). The two ways
+ // to deal with this is either clear them for each invocation or, if
+ // that's too burdensome and there is no good reason to have the build
+ // system invoked with such variables, detect their presence and fail.
+ // Note that unsetting them for the entire build system process is not
+ // an option since that would be racy.
+ //
+ // See also build2::hash_environment().
+ //
+ inline void
+ save_environment (scope& rs, const string& var)
+ {
+ if (config_save_environment != nullptr)
+ config_save_environment (rs, var.c_str ());
+ }
+
+ inline void
+ save_environment (scope& rs, const char* var)
+ {
+ if (config_save_environment != nullptr)
+ config_save_environment (rs, var);
+ }
+
+ inline void
+ save_environment (scope& rs, initializer_list<const char*> vars)
+ {
+ if (config_save_environment != nullptr)
+ {
+ for (const char* var: vars)
+ config_save_environment (rs, var);
+ }
+ }
+
+ inline void
+ save_environment (scope& rs, const cstrings& vars)
+ {
+ if (config_save_environment != nullptr)
+ {
+ for (const char* var: vars)
+ config_save_environment (rs, var);
+ }
+ }
+
+ inline void
+ save_environment (scope& rs, const strings& vars)
+ {
+ if (config_save_environment != nullptr)
+ {
+ for (const string& var: vars)
+ config_save_environment (rs, var.c_str ());
+ }
+ }
+
+ // A NULL-terminated list of variables (may itself be NULL).
+ //
+ inline void
+ save_environment (scope& rs, const char* const* vars)
+ {
+ if (vars != nullptr && config_save_environment != nullptr)
+ {
+ for (; *vars != nullptr; ++vars)
+ config_save_environment (rs, *vars);
+ }
+ }
+
// Establish module save order/priority with INT32_MIN being the highest.
// Modules with the same priority are saved in the order inserted.
//
@@ -136,9 +240,11 @@ namespace build2
//
// Unlike the rest of the lookup_config() versions, this one leaves the
// unspecified value as undefined rather than setting it to a default
- // value. This can be useful when we don't have a default value or in case
- // we want the mentioning of the variable to be omitted from persistent
- // storage (e.g., a config file) if the default value is used.
+ // value (in this case it also doesn't mark the variable for saving with
+ // the specified flags). This can be useful when we don't have a default
+ // value or in case we want the mentioning of the variable to be omitted
+ // from persistent storage (e.g., a config file) if the default value is
+ // used.
//
// Note also that we can first do the lookup without the default value and
// then, if there is no value, call the version with the default value and
@@ -147,27 +253,38 @@ namespace build2
// expensive. It is also ok to call both versions multiple times provided
// the flags are the same.
//
- // @@ Should we pass flags and interpret save_null_omitted to treat null
- // as undefined? Sounds logical.
- //
lookup
- lookup_config (scope& rs, const variable&);
+ lookup_config (scope& rs,
+ const variable&,
+ uint64_t save_flags = 0);
lookup
- lookup_config (bool& new_value, scope& rs, const variable&);
+ lookup_config (bool& new_value,
+ scope& rs,
+ const variable&,
+ uint64_t save_flags = 0);
// Note that the variable is expected to have already been entered.
//
inline lookup
- lookup_config (scope& rs, const string& var)
+ lookup_config (scope& rs,
+ const string& var,
+ uint64_t save_flags = 0)
{
- return lookup_config (rs, rs.ctx.var_pool[var]);
+ // Note: go straight for the public variable pool.
+ //
+ return lookup_config (rs, rs.ctx.var_pool[var], save_flags);
}
inline lookup
- lookup_config (bool& new_value, scope& rs, const string& var)
+ lookup_config (bool& new_value,
+ scope& rs,
+ const string& var,
+ uint64_t save_flags = 0)
{
- return lookup_config (new_value, rs, rs.ctx.var_pool[var]);
+ // Note: go straight for the public variable pool.
+ //
+ return lookup_config (new_value, rs, rs.ctx.var_pool[var], save_flags);
}
// Lookup a config.* variable value and, if the value is undefined, set it
@@ -195,8 +312,14 @@ namespace build2
// or from the command line (i.e., it is inherited from the amalgamation),
// then its value is "overridden" to the default value on this root scope.
//
- // @@ Should save_null_omitted be interpreted to treat null as undefined?
- // Sounds logical.
+ // Note that while it may seem logical, these functions do not
+ // "reinterpret" defined values according to the save_*_omitted flags (for
+ // example, by returning the default value if the defined value is NULL
+ // and the save_null_omitted flag is specified). This is because such a
+ // reinterpretation may cause a diversion between the returned value and
+ // the re-queried config.* variable value if the defined value came from
+ // an override. To put another way, the save_*_omitted flags are purely to
+ // reduce the noise in config.build.
//
template <typename T>
lookup
@@ -248,6 +371,8 @@ namespace build2
uint64_t save_flags = 0,
bool override = false)
{
+ // Note: go straight for the public variable pool.
+ //
return lookup_config (rs,
rs.ctx.var_pool[var],
std::forward<T> (default_value), // VC14
@@ -264,6 +389,8 @@ namespace build2
uint64_t save_flags = 0,
bool override = false)
{
+ // Note: go straight for the public variable pool.
+ //
return lookup_config (new_value,
rs,
rs.ctx.var_pool[var],
@@ -308,7 +435,7 @@ namespace build2
const V* cv (
cast_null<V> (
lookup_config (rs,
- rs.var_pool ().insert<V> ("config." + var),
+ rs.var_pool (true).insert<V> ("config." + var),
std::forward<T> (default_value)))); // VC14
value& v (bs.assign<V> (move (var)));
@@ -326,7 +453,7 @@ namespace build2
const V* cv (
cast_null<V> (
lookup_config (rs,
- rs.var_pool ().insert<V> ("config." + var),
+ rs.var_pool (true).insert<V> ("config." + var),
std::forward<T> (default_value)))); // VC14
value& v (bs.append<V> (move (var)));
@@ -389,6 +516,25 @@ namespace build2
//
LIBBUILD2_SYMEXPORT bool
unconfigured (scope& rs, const string& var, bool value);
+
+ // Return the origin of the value of the specified configuration variable
+ // plus the value itself. See $config.origin() for details.
+ //
+ // Throws invalid_argument if the passed variable is not config.*.
+ //
+ LIBBUILD2_SYMEXPORT pair<variable_origin, lookup>
+ origin (const scope& rs, const string& name);
+
+ LIBBUILD2_SYMEXPORT pair<variable_origin, lookup>
+ origin (const scope& rs, const variable&);
+
+ // As above but using the result of scope::lookup_original() or
+ // semantically equivalent (e.g., lookup_namespace()).
+ //
+ // Note that this version does not check that the variable is config.*.
+ //
+ LIBBUILD2_SYMEXPORT pair<variable_origin, lookup>
+ origin (const scope& rs, const variable&, pair<lookup, size_t> original);
}
}
diff --git a/libbuild2/config/utility.ixx b/libbuild2/config/utility.ixx
index 79d5470..d8348bd 100644
--- a/libbuild2/config/utility.ixx
+++ b/libbuild2/config/utility.ixx
@@ -6,22 +6,25 @@ namespace build2
namespace config
{
LIBBUILD2_SYMEXPORT pair<lookup, bool>
- lookup_config_impl (scope&, const variable&);
+ lookup_config_impl (scope&, const variable&, uint64_t);
template <typename T>
pair<lookup, bool>
lookup_config_impl (scope&, const variable&, T&&, uint64_t, bool);
inline lookup
- lookup_config (scope& rs, const variable& var)
+ lookup_config (scope& rs, const variable& var, uint64_t sflags)
{
- return lookup_config_impl (rs, var).first;
+ return lookup_config_impl (rs, var, sflags).first;
}
inline lookup
- lookup_config (bool& new_value, scope& rs, const variable& var)
+ lookup_config (bool& new_value,
+ scope& rs,
+ const variable& var,
+ uint64_t sflags)
{
- auto r (lookup_config_impl (rs, var));
+ auto r (lookup_config_impl (rs, var, sflags));
new_value = new_value || r.second;
return r.first;
}
diff --git a/libbuild2/config/utility.txx b/libbuild2/config/utility.txx
index b88f76c..71e41fd 100644
--- a/libbuild2/config/utility.txx
+++ b/libbuild2/config/utility.txx
@@ -58,7 +58,7 @@ namespace build2
if (!l.defined () || (def_ovr && !l.belongs (rs)))
{
value& v (rs.assign (var) = std::forward<T> (def_val)); // VC14
- v.extra = true; // Default value flag.
+ v.extra = 1; // Default value flag.
n = (sflags & save_default_commented) == 0; // Absence means default.
l = lookup (v, var, rs);
@@ -66,7 +66,7 @@ namespace build2
}
// Treat an inherited value that was set to default as new.
//
- else if (l->extra)
+ else if (l->extra == 1)
n = (sflags & save_default_commented) == 0; // Absence means default.
if (var.overrides != nullptr)