aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/config
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/config')
-rw-r--r--libbuild2/config/init.cxx159
-rw-r--r--libbuild2/config/init.hxx36
-rw-r--r--libbuild2/config/module.cxx54
-rw-r--r--libbuild2/config/module.hxx93
-rw-r--r--libbuild2/config/operation.cxx997
-rw-r--r--libbuild2/config/operation.hxx29
-rw-r--r--libbuild2/config/utility.cxx307
-rw-r--r--libbuild2/config/utility.hxx179
-rw-r--r--libbuild2/config/utility.txx66
9 files changed, 1920 insertions, 0 deletions
diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx
new file mode 100644
index 0000000..73275c6
--- /dev/null
+++ b/libbuild2/config/init.cxx
@@ -0,0 +1,159 @@
+// file : libbuild2/config/init.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/config/init.hxx>
+
+#include <libbuild2/file.hxx>
+#include <libbuild2/rule.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/filesystem.hxx> // exists()
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/config/module.hxx>
+#include <libbuild2/config/utility.hxx>
+#include <libbuild2/config/operation.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace config
+ {
+ bool
+ boot (scope& rs, const location&, unique_ptr<module_base>& mod)
+ {
+ tracer trace ("config::boot");
+
+ l5 ([&]{trace << "for " << rs;});
+
+ const string& mname (current_mname);
+ const string& oname (current_oname);
+
+ // Only create the module if we are configuring or creating. This is a
+ // bit tricky since the build2 core may not yet know if this is the
+ // case. But we know.
+ //
+ if (( mname == "configure" || mname == "create") ||
+ (mname.empty () && (oname == "configure" || oname == "create")))
+ {
+ unique_ptr<module> m (new module);
+
+ // Adjust priority for the import pseudo-module so that
+ // config.import.* values come first in config.build.
+ //
+ m->save_module ("import", INT32_MIN);
+
+ mod = move (m);
+ }
+
+ // Register meta-operations. Note that we don't register create_id
+ // since it will be pre-processed into configure.
+ //
+ rs.insert_meta_operation (configure_id, mo_configure);
+ rs.insert_meta_operation (disfigure_id, mo_disfigure);
+
+ return true; // Initialize first (load config.build).
+ }
+
+ bool
+ init (scope& rs,
+ scope&,
+ const location& l,
+ unique_ptr<module_base>&,
+ bool first,
+ bool,
+ const variable_map& config_hints)
+ {
+ tracer trace ("config::init");
+
+ if (!first)
+ {
+ warn (l) << "multiple config module initializations";
+ return true;
+ }
+
+ const dir_path& out_root (rs.out_path ());
+ l5 ([&]{trace << "for " << out_root;});
+
+ assert (config_hints.empty ()); // We don't known any hints.
+
+ auto& vp (var_pool.rw (rs));
+
+ // Load config.build if one exists (we don't need to worry about
+ // disfigure since we will never be init'ed).
+ //
+ const variable& c_v (vp.insert<uint64_t> ("config.version", false));
+
+ {
+ path f (config_file (rs));
+
+ if (exists (f))
+ {
+ // Check the config version. We assume that old versions cannot
+ // understand new configs and new versions are incompatible with old
+ // configs.
+ //
+ // We extract the value manually instead of loading and then
+ // checking in order to be able to fixup/migrate the file which we
+ // may want to do in the future.
+ //
+ {
+ // Assume missing version is 0.
+ //
+ auto p (extract_variable (f, c_v));
+ uint64_t v (p.second ? cast<uint64_t> (p.first) : 0);
+
+ if (v != module::version)
+ fail (l) << "incompatible config file " << f <<
+ info << "config file version " << v
+ << (p.second ? "" : " (missing)") <<
+ info << "config module version " << module::version <<
+ info << "consider reconfiguring " << project (rs) << '@'
+ << out_root;
+ }
+
+ source (rs, rs, f);
+ }
+ }
+
+ // Register alias and fallback rule for the configure meta-operation.
+ //
+ // 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.
+ //
+ {
+ auto& r (rs.global ().rules);
+ r.insert<mtime_target> (
+ configure_id, 0, "config.file", file_rule::instance);
+ }
+ {
+ auto& r (rs.rules);
+
+ //@@ outer
+ r.insert<alias> (configure_id, 0, "config.alias", alias_rule::instance);
+
+ // This allows a custom configure rule while doing nothing by default.
+ //
+ r.insert<target> (configure_id, 0, "config", noop_rule::instance);
+ r.insert<file> (configure_id, 0, "config.file", noop_rule::instance);
+ }
+
+ return true;
+ }
+
+ module_functions
+ build2_config_load ()
+ {
+ // Initialize the config entry points in the build system core.
+ //
+ config_save_variable = &config::save_variable;
+ config_preprocess_create = &config::preprocess_create;
+
+ return module_functions {&boot, &init};
+ }
+ }
+}
diff --git a/libbuild2/config/init.hxx b/libbuild2/config/init.hxx
new file mode 100644
index 0000000..ff5e923
--- /dev/null
+++ b/libbuild2/config/init.hxx
@@ -0,0 +1,36 @@
+// file : libbuild2/config/init.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONFIG_INIT_HXX
+#define LIBBUILD2_CONFIG_INIT_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/module.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ namespace config
+ {
+ bool
+ boot (scope&, const location&, unique_ptr<module_base>&);
+
+ bool
+ init (scope&,
+ scope&,
+ const location&,
+ unique_ptr<module_base>&,
+ bool,
+ bool,
+ const variable_map&);
+
+ extern "C" LIBBUILD2_SYMEXPORT module_functions
+ build2_config_load ();
+ }
+}
+
+#endif // LIBBUILD2_CONFIG_INIT_HXX
diff --git a/libbuild2/config/module.cxx b/libbuild2/config/module.cxx
new file mode 100644
index 0000000..7e9b765
--- /dev/null
+++ b/libbuild2/config/module.cxx
@@ -0,0 +1,54 @@
+// file : libbuild2/config/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/config/module.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace config
+ {
+ void module::
+ save_variable (const variable& var, uint64_t flags)
+ {
+ const string& n (var.name);
+
+ // First try to find the module with the name that is the longest
+ // prefix of this variable name.
+ //
+ auto& sm (saved_modules);
+ auto i (sm.find_sup (n));
+
+ // If no module matched, then create one based on the variable name.
+ //
+ if (i == sm.end ())
+ {
+ // @@ For now with 'config.' prefix.
+ //
+ i = sm.insert (string (n, 0, n.find ('.', 7)));
+ }
+
+ // Don't insert duplicates. The config.import vars are particularly
+ // susceptible to duplication.
+ //
+ saved_variables& sv (i->second);
+ auto j (sv.find (var));
+
+ if (j == sv.end ())
+ sv.push_back (saved_variable {var, flags});
+ else
+ assert (j->flags == flags);
+ }
+
+ void module::
+ save_module (const char* name, int prio)
+ {
+ saved_modules.insert (string ("config.") += name, prio);
+ }
+
+ const string module::name ("config");
+ const uint64_t module::version (1);
+ }
+}
diff --git a/libbuild2/config/module.hxx b/libbuild2/config/module.hxx
new file mode 100644
index 0000000..6222319
--- /dev/null
+++ b/libbuild2/config/module.hxx
@@ -0,0 +1,93 @@
+// file : libbuild2/config/module.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONFIG_MODULE_HXX
+#define LIBBUILD2_CONFIG_MODULE_HXX
+
+#include <map>
+
+#include <libbutl/prefix-map.mxx>
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/module.hxx>
+#include <libbuild2/variable.hxx>
+
+namespace build2
+{
+ namespace config
+ {
+ // An ordered list of modules each with an ordered list of list of
+ // config.* variables and their "save flags" (see save_variable()) that
+ // are used (as opposed to just being specified) in this configuration.
+ // Populated by the config utility functions (required(), optional())
+ // and saved in the order populated.
+ //
+ struct saved_variable
+ {
+ reference_wrapper<const variable> var;
+ uint64_t flags;
+ };
+
+ struct saved_variables: vector<saved_variable>
+ {
+ // Normally each module only have a handful of config variables and we
+ // only do this during configuration so for now we do linear search
+ // instead of adding a map.
+ //
+ const_iterator
+ find (const variable& var) const
+ {
+ return std::find_if (
+ begin (),
+ end (),
+ [&var] (const saved_variable& v) {return var == v.var;});
+ }
+ };
+
+ struct saved_modules: butl::prefix_map<string, saved_variables, '.'>
+ {
+ // Priority order with INT32_MIN being the highest. Modules with the
+ // same priority are saved in the order inserted.
+ //
+ // Generally, the idea is that we want higher-level modules at the top
+ // of the file since that's the configuration that we usualy want to
+ // change. So we have the following priority bands/defaults:
+ //
+ // 101-200/150 - code generators (e.g., yacc, bison)
+ // 201-300/250 - compilers (e.g., C, C++),
+ // 301-400/350 - binutils (ar, ld)
+ //
+ std::multimap<std::int32_t, const_iterator> order;
+
+ iterator
+ insert (string name, int prio = 0)
+ {
+ auto p (emplace (move (name), saved_variables ()));
+
+ if (p.second)
+ order.emplace (prio, p.first);
+
+ return p.first;
+ }
+ };
+
+ struct module: module_base
+ {
+ config::saved_modules saved_modules;
+
+ void
+ save_variable (const variable&, uint64_t flags = 0);
+
+ void
+ save_module (const char* name, int prio = 0);
+
+ static const string name;
+ static const uint64_t version;
+ };
+ }
+}
+
+#endif // LIBBUILD2_CONFIG_MODULE_HXX
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
new file mode 100644
index 0000000..c3ce4b7
--- /dev/null
+++ b/libbuild2/config/operation.cxx
@@ -0,0 +1,997 @@
+// file : libbuild2/config/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/config/operation.hxx>
+
+#include <set>
+
+#include <libbuild2/file.hxx>
+#include <libbuild2/spec.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/algorithm.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/config/module.hxx>
+#include <libbuild2/config/utility.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace config
+ {
+ // configure
+ //
+ static void
+ save_src_root (const scope& root)
+ {
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ path f (out_root / root.root_extra->src_root_file);
+
+ if (verb >= 2)
+ text << "cat >" << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << "# Created automatically by the config module." << endl
+ << "#" << endl
+ << "src_root = ";
+ to_stream (ofs, name (src_root), true, '@'); // Quote.
+ ofs << endl;
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
+ static void
+ save_out_root (const scope& root)
+ {
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ path f (src_root / root.root_extra->out_root_file);
+
+ if (verb)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << "# Created automatically by the config module." << endl
+ << "#" << endl
+ << "out_root = ";
+ to_stream (ofs, name (out_root), true, '@'); // Quote.
+ ofs << endl;
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
+ using project_set = set<const scope*>; // Use pointers to get comparison.
+
+ static void
+ save_config (const scope& root, const project_set& projects)
+ {
+ path f (config_file (root));
+
+ if (verb)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ const module& mod (*root.lookup_module<const module> (module::name));
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << "# Created automatically by the config module, but feel " <<
+ "free to edit." << endl
+ << "#" << endl;
+
+ ofs << "config.version = " << module::version << endl;
+
+ if (auto l = root.vars[var_amalgamation])
+ {
+ const dir_path& d (cast<dir_path> (l));
+
+ ofs << endl
+ << "# Base configuration inherited from " << d << endl
+ << "#" << endl;
+ }
+
+ // Save config variables.
+ //
+ names storage;
+
+ 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.
+ for (const saved_variable& sv: svars)
+ {
+ const variable& var (sv.var);
+
+ pair<lookup, size_t> org (root.find_original (var));
+ pair<lookup, size_t> ovr (var.overrides == nullptr
+ ? org
+ : root.find_override (var, org));
+ const lookup& l (ovr.first);
+
+ // We definitely write values that are set on our root scope or
+ // are global overrides. Anything in-between is presumably
+ // inherited. We might also not have any value at all (see
+ // unconfigured()).
+ //
+ if (!l.defined ())
+ continue;
+
+ if (!(l.belongs (root) || l.belongs (*global_scope)))
+ {
+ // 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. Let's
+ // also warn the user just in case.
+ //
+ // 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);
+ const scope* r (&root);
+ while ((r = r->parent_scope ()->root_scope ()) != nullptr)
+ {
+ if (l.belongs (*r))
+ {
+ // Find the config module.
+ //
+ if (auto* m = r->lookup_module<const module> (module::name))
+ {
+ // Find the corresponding saved module.
+ //
+ auto i (m->saved_modules.find (sname));
+
+ if (i != m->saved_modules.end ())
+ {
+ // Find the variable.
+ //
+ const saved_variables& sv (i->second);
+ found = sv.find (var) != sv.end ();
+
+ // 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;
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (found) // Inherited.
+ continue;
+
+ location loc (&f);
+
+ // If this value is not defined in a project's root scope, then
+ // something is broken.
+ //
+ if (r == nullptr)
+ fail (loc) << "inherited variable " << var << " value "
+ << "is not from a root scope";
+
+ // If none of the outer project's configurations use this value,
+ // then we warn and save as our own. One special case where we
+ // don't want to warn the user is if the variable is overriden.
+ //
+ if (org.first == ovr.first)
+ {
+ diag_record dr;
+ dr << warn (loc) << "saving previously inherited variable "
+ << var;
+
+ dr << info (loc) << "because project " << *r
+ << " no longer uses it in its configuration";
+
+ if (verb >= 2)
+ {
+ dr << info (loc) << "variable value: ";
+
+ if (*l)
+ {
+ storage.clear ();
+ dr << "'" << reverse (*l, storage) << "'";
+ }
+ else
+ dr << "[null]";
+ }
+ }
+ }
+
+ const string& n (var.name);
+ const value& v (*l);
+
+ // We will only write config.*.configured if it is false (true is
+ // implied by its absence). We will also ignore false values if
+ // there is any other value for this module (see unconfigured()).
+ //
+ if (n.size () > 11 &&
+ n.compare (n.size () - 11, 11, ".configured") == 0)
+ {
+ if (cast<bool> (v) || svars.size () != 1)
+ continue;
+ }
+
+ // If we got here then we are saving this variable. Handle the
+ // blank line.
+ //
+ if (first)
+ {
+ ofs << endl;
+ first = false;
+ }
+
+ // Handle the save_commented flag.
+ //
+ if ((org.first.defined () && org.first->extra) && // Default value.
+ org.first == ovr.first && // Not overriden.
+ (sv.flags & save_commented) == save_commented)
+ {
+ ofs << '#' << n << " =" << endl;
+ continue;
+ }
+
+ if (v)
+ {
+ storage.clear ();
+ names_view ns (reverse (v, storage));
+
+ ofs << n;
+
+ if (ns.empty ())
+ ofs << " =";
+ else
+ {
+ ofs << " = ";
+ to_stream (ofs, ns, true, '@'); // Quote.
+ }
+
+ ofs << endl;
+ }
+ else
+ ofs << n << " = [null]" << endl;
+ }
+ }
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
+ static void
+ configure_project (action a, const scope& root, project_set& projects)
+ {
+ tracer trace ("configure_project");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already configured " << out_root;});
+ return;
+ }
+
+ // Make sure the directories exist.
+ //
+ if (out_root != src_root)
+ {
+ mkdir_p (out_root / root.root_extra->build_dir);
+ mkdir (out_root / root.root_extra->bootstrap_dir, 2);
+ }
+
+ // We distinguish between a complete configure and operation-
+ // specific.
+ //
+ if (a.operation () == default_id)
+ {
+ l5 ([&]{trace << "completely configuring " << out_root;});
+
+ // Save src-root.build unless out_root is the same as src.
+ //
+ if (out_root != src_root)
+ save_src_root (root);
+
+ // Save config.build.
+ //
+ save_config (root, projects);
+ }
+ else
+ {
+ }
+
+ // Configure subprojects that have been loaded.
+ //
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ const dir_path& pd (p.second);
+ dir_path out_nroot (out_root / pd);
+ const scope& nroot (scopes.find (out_nroot));
+
+ // @@ Strictly speaking we need to check whether the config
+ // module was loaded for this subproject.
+ //
+ if (nroot.out_path () != out_nroot) // This subproject not loaded.
+ continue;
+
+ configure_project (a, nroot, projects);
+ }
+ }
+ }
+
+ static void
+ configure_forward (const scope& root, project_set& projects)
+ {
+ tracer trace ("configure_forward");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already configured " << src_root;});
+ return;
+ }
+
+ mkdir (src_root / root.root_extra->bootstrap_dir, 2); // Make sure exists.
+ save_out_root (root);
+
+ // Configure subprojects. Since we don't load buildfiles if configuring
+ // a forward, we do it for all known subprojects.
+ //
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ dir_path out_nroot (out_root / p.second);
+ const scope& nroot (scopes.find (out_nroot));
+ assert (nroot.out_path () == out_nroot);
+
+ configure_forward (nroot, projects);
+ }
+ }
+ }
+
+ operation_id (*pre) (const values&, meta_operation_id, const location&);
+
+ static operation_id
+ configure_operation_pre (const values&, operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means configure everything.
+ //
+ return o;
+ }
+
+ // The (vague) idea is that in the future we may turn this into to some
+ // sort of key-value sequence (similar to the config initializer idea),
+ // for example:
+ //
+ // configure(out/@src/, forward foo bar@123)
+ //
+ // Though using commas instead spaces and '=' instead of '@' would have
+ // been nicer.
+ //
+ static bool
+ forward (const values& params,
+ const char* mo = nullptr,
+ const location& l = location ())
+ {
+ if (params.size () == 1)
+ {
+ const names& ns (cast<names> (params[0]));
+
+ if (ns.size () == 1 && ns[0].simple () && ns[0].value == "forward")
+ return true;
+ else if (!ns.empty ())
+ fail (l) << "unexpected parameter '" << ns << "' for "
+ << "meta-operation " << mo;
+ }
+ else if (!params.empty ())
+ fail (l) << "unexpected parameters for meta-operation " << mo;
+
+ return false;
+ }
+
+ static void
+ configure_pre (const values& params, const location& l)
+ {
+ forward (params, "configure", l); // Validate.
+ }
+
+ static void
+ configure_load (const values& params,
+ scope& root,
+ const path& buildfile,
+ const dir_path& out_base,
+ const dir_path& src_base,
+ const location& l)
+ {
+ if (forward (params))
+ {
+ // We don't need to load the buildfiles in order to configure
+ // forwarding but in order to configure subprojects we have to
+ // bootstrap them (similar to disfigure).
+ //
+ create_bootstrap_inner (root);
+
+ if (root.out_path () == root.src_path ())
+ fail (l) << "forwarding to source directory " << root.src_path ();
+ }
+ else
+ load (params, root, buildfile, out_base, src_base, l); // Normal load.
+ }
+
+ static void
+ configure_search (const values& params,
+ const scope& root,
+ const scope& base,
+ const path& bf,
+ const target_key& tk,
+ const location& l,
+ action_targets& ts)
+ {
+ if (forward (params))
+ {
+ // For forwarding we only collect the projects (again, similar to
+ // disfigure).
+ //
+ ts.push_back (&root);
+ }
+ else
+ search (params, root, base, bf, tk, l, ts); // Normal search.
+ }
+
+ static void
+ configure_match (const values&, action, action_targets&, uint16_t, bool)
+ {
+ // Don't match anything -- see execute ().
+ }
+
+ static void
+ configure_execute (const values& params,
+ action a,
+ action_targets& ts,
+ uint16_t,
+ bool)
+ {
+ bool fwd (forward (params));
+
+ project_set projects;
+
+ for (const action_target& at: ts)
+ {
+ if (fwd)
+ {
+ // Forward configuration.
+ //
+ const scope& root (*static_cast<const scope*> (at.target));
+ configure_forward (root, projects);
+ continue;
+ }
+
+ // Normal configuration.
+ //
+ // Match rules to configure every operation supported by each project.
+ // Note that we are not calling operation_pre/post() callbacks here
+ // since the meta operation is configure and we know what we are
+ // doing.
+ //
+ // Note that we cannot do this in parallel. We cannot parallelize the
+ // outer loop because we should match for a single action at a time.
+ // And we cannot swap the loops because the list of operations is
+ // target-specific. However, inside match(), things can proceed in
+ // parallel.
+ //
+ const target& t (at.as_target ());
+ const scope* rs (t.base_scope ().root_scope ());
+
+ if (rs == nullptr)
+ fail << "out of project target " << t;
+
+ const operations& ops (rs->root_extra->operations);
+
+ for (operation_id id (default_id + 1); // Skip default_id.
+ id < ops.size ();
+ ++id)
+ {
+ if (const operation_info* oif = ops[id])
+ {
+ // Skip aliases (e.g., update-for-install).
+ //
+ if (oif->id != id)
+ continue;
+
+ set_current_oif (*oif);
+
+ phase_lock pl (run_phase::match);
+ match (action (configure_id, id), t);
+ }
+ }
+
+ configure_project (a, *rs, projects);
+ }
+ }
+
+ const meta_operation_info mo_configure {
+ configure_id,
+ "configure",
+ "configure",
+ "configuring",
+ "configured",
+ "is configured",
+ true, // bootstrap_outer
+ &configure_pre, // meta-operation pre
+ &configure_operation_pre,
+ &configure_load, // normal load unless configuring forward
+ &configure_search, // normal search unless configuring forward
+ &configure_match,
+ &configure_execute,
+ nullptr, // operation post
+ nullptr, // meta-operation post
+ nullptr // include
+ };
+
+ // disfigure
+ //
+
+ static bool
+ disfigure_project (action a, const scope& root, project_set& projects)
+ {
+ tracer trace ("disfigure_project");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already disfigured " << out_root;});
+ return false;
+ }
+
+ bool r (false); // Keep track of whether we actually did anything.
+
+ // Disfigure subprojects. Since we don't load buildfiles during
+ // disfigure, we do it for all known subprojects.
+ //
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ const dir_path& pd (p.second);
+ dir_path out_nroot (out_root / pd);
+ const scope& nroot (scopes.find (out_nroot));
+ assert (nroot.out_path () == out_nroot); // See disfigure_load().
+
+ r = disfigure_project (a, nroot, projects) || r;
+
+ // We use mkdir_p() to create the out_root of a subproject
+ // which means there could be empty parent directories left
+ // behind. Clean them up.
+ //
+ if (!pd.simple () && out_root != src_root)
+ {
+ for (dir_path d (pd.directory ());
+ !d.empty ();
+ d = d.directory ())
+ {
+ rmdir_status s (rmdir (out_root / d, 2));
+
+ if (s == rmdir_status::not_empty)
+ break; // No use trying do remove parent ones.
+
+ r = (s == rmdir_status::success) || r;
+ }
+ }
+ }
+ }
+
+ // We distinguish between a complete disfigure and operation-
+ // specific.
+ //
+ if (a.operation () == default_id)
+ {
+ l5 ([&]{trace << "completely disfiguring " << out_root;});
+
+ r = rmfile (config_file (root)) || r;
+
+ if (out_root != src_root)
+ {
+ r = rmfile (out_root / root.root_extra->src_root_file, 2) || r;
+
+ // Clean up the directories.
+ //
+ // Note: try to remove the root/ hooks directory if it is empty.
+ //
+ r = rmdir (out_root / root.root_extra->root_dir, 2) || r;
+ r = rmdir (out_root / root.root_extra->bootstrap_dir, 2) || r;
+ r = rmdir (out_root / root.root_extra->build_dir, 2) || r;
+
+ switch (rmdir (out_root))
+ {
+ case rmdir_status::not_empty:
+ {
+ // We used to issue a warning but it is actually a valid usecase
+ // to leave the build output around in case, for example, of a
+ // reconfigure.
+ //
+ if (verb)
+ info << "directory " << out_root << " is "
+ << (out_root == work
+ ? "current working directory"
+ : "not empty") << ", not removing";
+ break;
+ }
+ case rmdir_status::success:
+ r = true;
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ }
+
+ return r;
+ }
+
+ static bool
+ disfigure_forward (const scope& root, project_set& projects)
+ {
+ // Pretty similar logic to disfigure_project().
+ //
+ tracer trace ("disfigure_forward");
+
+ const dir_path& out_root (root.out_path ());
+ const dir_path& src_root (root.src_path ());
+
+ if (!projects.insert (&root).second)
+ {
+ l5 ([&]{trace << "skipping already disfigured " << src_root;});
+ return false;
+ }
+
+ bool r (false);
+
+ if (auto l = root.vars[var_subprojects])
+ {
+ for (auto p: cast<subprojects> (l))
+ {
+ dir_path out_nroot (out_root / p.second);
+ const scope& nroot (scopes.find (out_nroot));
+ assert (nroot.out_path () == out_nroot);
+
+ r = disfigure_forward (nroot, projects) || r;
+ }
+ }
+
+ // Remove the out-root.build file and try to remove the bootstrap/
+ // directory if it is empty.
+ //
+ r = rmfile (src_root / root.root_extra->out_root_file) || r;
+ r = rmdir (src_root / root.root_extra->bootstrap_dir, 2) || r;
+
+ return r;
+ }
+
+ static void
+ disfigure_pre (const values& params, const location& l)
+ {
+ forward (params, "disfigure", l); // Validate.
+ }
+
+ static operation_id
+ disfigure_operation_pre (const values&, operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means disfigure everything.
+ //
+ return o;
+ }
+
+ static void
+ disfigure_load (const values&,
+ scope& root,
+ const path&,
+ const dir_path&,
+ const dir_path&,
+ const location&)
+ {
+ // Since we don't load buildfiles during disfigure but still want to
+ // disfigure all the subprojects (see disfigure_project() below), we
+ // bootstrap all the known subprojects.
+ //
+ create_bootstrap_inner (root);
+ }
+
+ static void
+ disfigure_search (const values&,
+ const scope& root,
+ const scope&,
+ const path&,
+ const target_key&,
+ const location&,
+ action_targets& ts)
+ {
+ ts.push_back (&root);
+ }
+
+ static void
+ disfigure_match (const values&, action, action_targets&, uint16_t, bool)
+ {
+ }
+
+ static void
+ disfigure_execute (const values& params,
+ action a,
+ action_targets& ts,
+ uint16_t diag,
+ bool)
+ {
+ tracer trace ("disfigure_execute");
+
+ bool fwd (forward (params));
+
+ project_set projects;
+
+ // Note: doing everything in the load phase (disfigure_project () does
+ // modify the build state).
+ //
+ for (const action_target& at: ts)
+ {
+ const scope& root (*static_cast<const scope*> (at.target));
+
+ if (!(fwd
+ ? disfigure_forward ( root, projects)
+ : disfigure_project (a, root, projects)))
+ {
+ // Create a dir{$out_root/} target to signify the project's root in
+ // diagnostics. Not very clean but seems harmless.
+ //
+ target& t (
+ targets.insert (dir::static_type,
+ fwd ? root.src_path () : root.out_path (),
+ dir_path (), // Out tree.
+ "",
+ nullopt,
+ true, // Implied.
+ trace).first);
+
+ if (verb != 0 && diag >= 2)
+ info << diag_done (a, t);
+ }
+ }
+ }
+
+ const meta_operation_info mo_disfigure {
+ disfigure_id,
+ "disfigure",
+ "disfigure",
+ "disfiguring",
+ "disfigured",
+ "is disfigured",
+ false, // bootstrap_outer
+ disfigure_pre, // meta-operation pre
+ &disfigure_operation_pre,
+ &disfigure_load,
+ &disfigure_search,
+ &disfigure_match,
+ &disfigure_execute,
+ nullptr, // operation post
+ nullptr, // meta-operation post
+ nullptr // include
+ };
+
+ // create
+ //
+ static void
+ save_config (const dir_path& d, const variable_overrides& var_ovs)
+ {
+ // Since there aren't any sub-projects yet, any config.import.* values
+ // that the user may want to specify won't be saved in config.build. So
+ // let's go ahead and mark them all to be saved. To do this, however, we
+ // need the config module (which is where this information is stored).
+ // And the module is created by init() during bootstrap. So what we are
+ // going to do is bootstrap the newly created project, similar to the
+ // way main() does it.
+ //
+ scope& gs (*scope::global_);
+ scope& rs (load_project (gs, d, d, false /* fwd */, false /* load */));
+ module& m (*rs.lookup_module<module> (module::name));
+
+ // Save all the global config.import.* variables.
+ //
+ variable_pool& vp (var_pool.rw (rs));
+ for (auto p (gs.vars.find_namespace (vp.insert ("config.import")));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable& var (p.first->first);
+
+ // Annoyingly, this can be (always is?) one of the overrides
+ // (__override, __prefix, etc).
+ //
+ size_t n (var.override ());
+ m.save_variable (n != 0 ? *vp.find (string (var.name, 0, n)) : var);
+ }
+
+ // Now project-specific. For now we just save all of them and let
+ // save_config() above weed out the ones that don't apply.
+ //
+ for (const variable_override& vo: var_ovs)
+ {
+ const variable& var (vo.var);
+
+ if (var.name.compare (0, 14, "config.import.") == 0)
+ m.save_variable (var);
+ }
+ }
+
+ const string&
+ preprocess_create (const variable_overrides& var_ovs,
+ values& params,
+ vector_view<opspec>& spec,
+ bool lifted,
+ const location& l)
+ {
+ tracer trace ("preprocess_create");
+
+ // The overall plan is to create the project(s), update the buildspec,
+ // clear the parameters, and then continue as if we were the configure
+ // meta-operation.
+
+ // Start with process parameters. The first parameter, if any, is a list
+ // of root.build modules. The second parameter, if any, is a list of
+ // bootstrap.build modules. If the second is not specified, then the
+ // default is test, dist, and install (config is mandatory).
+ //
+ strings bmod {"test", "dist", "install"};
+ strings rmod;
+ try
+ {
+ size_t n (params.size ());
+
+ if (n > 0)
+ rmod = convert<strings> (move (params[0]));
+
+ if (n > 1)
+ bmod = convert<strings> (move (params[1]));
+
+ if (n > 2)
+ fail (l) << "unexpected parameters for meta-operation create";
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid module name: " << e.what ();
+ }
+
+ current_oname = empty_string; // Make sure valid.
+
+ // Now handle each target in each operation spec.
+ //
+ for (const opspec& os: spec)
+ {
+ // First do some sanity checks: there should be no explicit operation
+ // and our targets should all be directories.
+ //
+ if (!lifted && !os.name.empty ())
+ fail (l) << "explicit operation specified for meta-operation create";
+
+ for (const targetspec& ts: os)
+ {
+ const name& tn (ts.name);
+
+ // Figure out the project directory. This logic must be consistent
+ // with find_target_type() and other places (grep for "..").
+ //
+ dir_path d;
+
+ if (tn.simple () &&
+ (tn.empty () || tn.value == "." || tn.value == ".."))
+ d = dir_path (tn.value);
+ else if (tn.directory ())
+ d = tn.dir;
+ else if (tn.typed () && tn.type == "dir")
+ d = tn.dir / dir_path (tn.value);
+ else
+ fail(l) << "non-directory target '" << ts << "' in "
+ << "meta-operation create";
+
+ if (d.relative ())
+ d = work / d;
+
+ d.normalize (true);
+
+ // If src_base was explicitly specified, make sure it is the same as
+ // the project directory.
+ //
+ if (!ts.src_base.empty ())
+ {
+ dir_path s (ts.src_base);
+
+ if (s.relative ())
+ s = work / s;
+
+ s.normalize (true);
+
+ if (s != d)
+ fail(l) << "different src/out directories for target '" << ts
+ << "' in meta-operation create";
+ }
+
+ l5 ([&]{trace << "creating project in " << d;});
+
+ // For now we disable amalgamating this project. Sooner or later
+ // someone will probably want to do this, though (i.e., nested
+ // configurations).
+ //
+ create_project (d,
+ dir_path (), /* amalgamation */
+ bmod,
+ "", /* root_pre */
+ rmod,
+ "", /* root_post */
+ true, /* config */
+ true, /* buildfile */
+ "the create meta-operation");
+
+ save_config (d, var_ovs);
+ }
+ }
+
+ params.clear ();
+ return mo_configure.name;
+ }
+ }
+}
diff --git a/libbuild2/config/operation.hxx b/libbuild2/config/operation.hxx
new file mode 100644
index 0000000..0a88f96
--- /dev/null
+++ b/libbuild2/config/operation.hxx
@@ -0,0 +1,29 @@
+// file : libbuild2/config/operation.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONFIG_OPERATION_HXX
+#define LIBBUILD2_CONFIG_OPERATION_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/operation.hxx>
+
+namespace build2
+{
+ namespace config
+ {
+ extern const meta_operation_info mo_configure;
+ extern const meta_operation_info mo_disfigure;
+
+ const string&
+ preprocess_create (const variable_overrides&,
+ values&,
+ vector_view<opspec>&,
+ bool,
+ const location&);
+ }
+}
+
+#endif // LIBBUILD2_CONFIG_OPERATION_HXX
diff --git a/libbuild2/config/utility.cxx b/libbuild2/config/utility.cxx
new file mode 100644
index 0000000..746639d
--- /dev/null
+++ b/libbuild2/config/utility.cxx
@@ -0,0 +1,307 @@
+// file : libbuild2/config/utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/config/utility.hxx>
+
+#include <libbuild2/file.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/config/module.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace config
+ {
+ pair<lookup, bool>
+ omitted (scope& r, const variable& var)
+ {
+ // This is a stripped-down version of the required() twisted
+ // implementation.
+
+ pair<lookup, size_t> org (r.find_original (var));
+
+ bool n (false); // New flag.
+ lookup l (org.first);
+
+ // Treat an inherited value that was set to default as new.
+ //
+ if (l.defined () && l->extra)
+ n = true;
+
+ if (var.overrides != nullptr)
+ {
+ pair<lookup, size_t> ovr (r.find_override (var, move (org)));
+
+ if (l != ovr.first) // Overriden?
+ {
+ // Override is always treated as new.
+ //
+ n = true;
+ l = move (ovr.first);
+ }
+ }
+
+ if (l.defined () && current_mif->id == configure_id)
+ save_variable (r, var);
+
+ return pair<lookup, bool> (l, n);
+ }
+
+ lookup
+ optional (scope& r, const variable& var)
+ {
+ if (current_mif->id == configure_id)
+ save_variable (r, var);
+
+ auto l (r[var]);
+ return l.defined ()
+ ? l
+ : lookup (r.assign (var), var, r); // NULL.
+ }
+
+ bool
+ specified (scope& r, const string& n)
+ {
+ // Search all outer scopes for any value in this namespace.
+ //
+ // What about "pure" overrides, i.e., those without any original values?
+ // Well, they will also be found since their names have the original
+ // variable as a prefix. But do they apply? Yes, since we haven't found
+ // any original values, they will be "visible"; see find_override() for
+ // details.
+ //
+ const variable& vns (var_pool.rw (r).insert ("config." + n));
+ for (scope* s (&r); s != nullptr; s = s->parent_scope ())
+ {
+ for (auto p (s->vars.find_namespace (vns));
+ p.first != p.second;
+ ++p.first)
+ {
+ const variable& var (p.first->first);
+
+ // Ignore config.*.configured.
+ //
+ if (var.name.size () < 11 ||
+ var.name.compare (var.name.size () - 11, 11, ".configured") != 0)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool
+ unconfigured (scope& rs, const string& n)
+ {
+ // Pattern-typed in boot() as bool.
+ //
+ const variable& var (
+ var_pool.rw (rs).insert ("config." + n + ".configured"));
+
+ if (current_mif->id == configure_id)
+ save_variable (rs, var);
+
+ auto l (rs[var]); // Include inherited values.
+ return l && !cast<bool> (l);
+ }
+
+ bool
+ unconfigured (scope& rs, const string& n, bool v)
+ {
+ // Pattern-typed in boot() as bool.
+ //
+ const variable& var (
+ var_pool.rw (rs).insert ("config." + n + ".configured"));
+
+ if (current_mif->id == configure_id)
+ save_variable (rs, var);
+
+ value& x (rs.assign (var));
+
+ if (x.null || cast<bool> (x) != !v)
+ {
+ x = !v;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void
+ save_variable (scope& r, const variable& var, uint64_t flags)
+ {
+ if (current_mif->id != configure_id)
+ return;
+
+ // The project might not be using the config module. But then how
+ // could we be configuring it? Good question.
+ //
+ if (module* m = r.lookup_module<module> (module::name))
+ m->save_variable (var, flags);
+ }
+
+ void
+ save_module (scope& r, const char* name, int prio)
+ {
+ if (current_mif->id != configure_id)
+ return;
+
+ if (module* m = r.lookup_module<module> (module::name))
+ m->save_module (name, prio);
+ }
+
+ void
+ create_project (const dir_path& d,
+ const build2::optional<dir_path>& amal,
+ const strings& bmod,
+ const string& rpre,
+ const strings& rmod,
+ const string& rpos,
+ bool config,
+ bool buildfile,
+ const char* who,
+ uint16_t verbosity)
+ {
+ string hdr ("# Generated by " + string (who) + ". Edit if you know"
+ " what you are doing.\n"
+ "#");
+
+ // If the directory exists, verify it's empty. Otherwise, create it.
+ //
+ if (exists (d))
+ {
+ if (!empty (d))
+ fail << "directory " << d << " exists and is not empty";
+ }
+ else
+ mkdir_p (d, verbosity);
+
+ // Create the build/ subdirectory.
+ //
+ // Note that for now we use the standard build file/directory scheme.
+ //
+ mkdir (d / std_build_dir, verbosity);
+
+ // Write build/bootstrap.build.
+ //
+ {
+ path f (d / std_bootstrap_file);
+
+ if (verb >= verbosity)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << hdr << endl
+ << "project =" << endl;
+
+ if (amal)
+ {
+ ofs << "amalgamation =";
+
+ if (!amal->empty ())
+ ofs << ' ' << amal->representation ();
+
+ ofs << endl;
+ }
+
+ ofs << endl;
+
+ if (config)
+ ofs << "using config" << endl;
+
+ for (const string& m: bmod)
+ {
+ if (!config || m != "config")
+ ofs << "using " << m << endl;
+ }
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
+ // Write build/root.build.
+ //
+ {
+ path f (d / std_root_file);
+
+ if (verb >= verbosity)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << hdr << endl;
+
+ if (!rpre.empty ())
+ ofs << rpre << endl
+ << endl;
+
+ for (const string& cm: rmod)
+ {
+ // If the module name start with '?', then use optional load.
+ //
+ bool opt (cm.front () == '?');
+ string m (cm, opt ? 1 : 0);
+
+ // Append .config unless the module name ends with '.', in which
+ // case strip it.
+ //
+ if (m.back () == '.')
+ m.pop_back ();
+ else
+ m += ".config";
+
+ ofs << "using" << (opt ? "?" : "") << " " << m << endl;
+ }
+
+ if (!rpos.empty ())
+ ofs << endl
+ << rpre << endl;
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+
+ // Write root buildfile.
+ //
+ if (buildfile)
+ {
+ path f (d / std_buildfile_file);
+
+ if (verb >= verbosity)
+ text << (verb >= 2 ? "cat >" : "save ") << f;
+
+ try
+ {
+ ofdstream ofs (f);
+
+ ofs << hdr << endl
+ << "./: {*/ -build/}" << endl;
+
+ ofs.close ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << f << ": " << e;
+ }
+ }
+ }
+ }
+}
diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx
new file mode 100644
index 0000000..e41aaa7
--- /dev/null
+++ b/libbuild2/config/utility.hxx
@@ -0,0 +1,179 @@
+// file : libbuild2/config/utility.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_CONFIG_UTILITY_HXX
+#define LIBBUILD2_CONFIG_UTILITY_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ class scope;
+
+ namespace config
+ {
+ // Set, if necessary, a required config.* variable.
+ //
+ // If override is true and the variable doesn't come from this root scope
+ // or from the command line (i.e., it is inherited from the amalgamtion),
+ // then its value is "overridden" to the default value on this root scope.
+ // See save_variable() for more information on save_flags.
+ //
+ // Return the reference to the value as well as the indication of whether
+ // the value is "new", that is, it was set to the default value (inherited
+ // or not, including overrides). We also treat command line overrides
+ // (inherited or not) as new. This flag is usually used to test that the
+ // new value is valid, print report, etc. We return the value as lookup
+ // (always defined) to pass alone its location (could be used to detect
+ // inheritance, etc).
+ //
+ // Note also that if save_flags has save_commented, then a default value
+ // is never considered "new" since for such variables absence of a value
+ // means the default value.
+ //
+ template <typename T>
+ pair<lookup, bool>
+ required (scope& root,
+ const variable&,
+ const T& default_value,
+ bool override = false,
+ uint64_t save_flags = 0);
+
+ // Note that the variable is expected to have already been registered.
+ //
+ template <typename T>
+ inline pair<lookup, bool>
+ required (scope& root,
+ const string& name,
+ const T& default_value,
+ bool override = false,
+ uint64_t save_flags = 0)
+ {
+ return required (
+ root, var_pool[name], default_value, override, save_flags);
+ }
+
+ inline pair<lookup, bool>
+ required (scope& root,
+ const string& name,
+ const char* default_value,
+ bool override = false,
+ uint64_t save_flags = 0)
+ {
+ return required (
+ root, name, string (default_value), override, save_flags);
+ }
+
+ // As above, but leave the unspecified value as undefined rather than
+ // setting it to the default value.
+ //
+ // This can be useful when we don't have a default value but may figure
+ // out some fallback. See config.bin.target for an example.
+ //
+ LIBBUILD2_SYMEXPORT pair<lookup, bool>
+ omitted (scope& root, const variable&);
+
+ // Note that the variable is expected to have already been registered.
+ //
+ inline pair<lookup, bool>
+ omitted (scope& root, const string& name)
+ {
+ return omitted (root, var_pool[name]);
+ }
+
+ // Set, if necessary, an optional config.* variable. In particular, an
+ // unspecified variable is set to NULL which is used to distinguish
+ // between the "configured as unspecified" and "not yet configured" cases.
+ //
+ // Return the value (as always defined lookup), which can be NULL.
+ //
+ // @@ Rename since clashes with the optional class template.
+ //
+ LIBBUILD2_SYMEXPORT lookup
+ optional (scope& root, const variable&);
+
+ // Note that the variable is expected to have already been registered.
+ //
+ inline lookup
+ optional (scope& root, const string& name)
+ {
+ return optional (root, var_pool[name]);
+ }
+
+ // Check whether there are any variables specified from the config
+ // namespace. The idea is that we can check if there are any, say,
+ // config.install.* values. If there are none, then we can assume
+ // this functionality is not (yet) used and omit writing a whole
+ // bunch of NULL config.install.* values to the config.build file.
+ // We call it omitted/delayed configuration.
+ //
+ // Note that this function detects and ignores the special
+ // config.*.configured variable which may be used by a module to
+ // "remember" that it is unconfigured (e.g., in order to avoid re-
+ // running the tests, etc).
+ //
+ LIBBUILD2_SYMEXPORT bool
+ specified (scope& root, const string& name);
+
+ // Check if there is a false config.*.configured value. This mechanism can
+ // be used to "remember" that the module is left unconfigured in order to
+ // avoid re-running the tests, etc.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ unconfigured (scope& root, const string& name);
+
+ // Set the config.*.configured value. Note that you only need to set it to
+ // false. It will be automatically ignored if there are any other config.*
+ // values for this module. Return true if this sets a new value.
+ //
+ LIBBUILD2_SYMEXPORT bool
+ unconfigured (scope& root, const string& name, bool);
+
+ // Enter the variable so that it is saved during configuration. See
+ // config::module for details.
+ //
+ const uint64_t save_commented = 0x01; // Save default value as commented.
+
+ LIBBUILD2_SYMEXPORT void
+ save_variable (scope& root, const variable&, uint64_t flags = 0);
+
+ // Establish module order/priority. See config::module for details.
+ //
+ LIBBUILD2_SYMEXPORT void
+ save_module (scope& root, const char* name, int prio = 0);
+
+ // Create a project in the specified directory.
+ //
+ LIBBUILD2_SYMEXPORT void
+ create_project (const dir_path& d,
+ const build2::optional<dir_path>& amalgamation,
+ const strings& boot_modules, // Bootstrap modules.
+ const string& root_pre, // Extra root.build text.
+ const strings& root_modules, // Root modules.
+ const string& root_post, // Extra root.build text.
+ bool config, // Load config module.
+ bool buildfile, // Create root buildfile.
+ const char* who, // Who is creating it.
+ uint16_t verbosity = 1); // Diagnostic verbosity.
+
+ inline path
+ config_file (const scope& root)
+ {
+ return (root.out_path () /
+ root.root_extra->build_dir /
+ "config." + root.root_extra->build_ext);
+ }
+ }
+}
+
+#include <libbuild2/config/utility.txx>
+
+#endif // LIBBUILD2_CONFIG_UTILITY_HXX
diff --git a/libbuild2/config/utility.txx b/libbuild2/config/utility.txx
new file mode 100644
index 0000000..d2ffa69
--- /dev/null
+++ b/libbuild2/config/utility.txx
@@ -0,0 +1,66 @@
+// file : libbuild2/config/utility.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/context.hxx>
+
+namespace build2
+{
+ namespace config
+ {
+ template <typename T>
+ pair<lookup, bool>
+ required (scope& root,
+ const variable& var,
+ const T& def_val,
+ bool def_ovr,
+ uint64_t save_flags)
+ {
+ // Note: see also omitted() if changing anything here.
+
+ if (current_mif->id == configure_id)
+ save_variable (root, var, save_flags);
+
+ pair<lookup, size_t> org (root.find_original (var));
+
+ bool n (false); // New flag.
+ lookup l (org.first);
+
+ // The interaction with command line overrides can get tricky. For
+ // example, the override to defaul value could make (non-recursive)
+ // command line override in the outer scope no longer apply. So what we
+ // are going to do is first ignore overrides and perform the normal
+ // logic on the original. Then we apply the overrides on the result.
+ //
+ if (!l.defined () || (def_ovr && !l.belongs (root)))
+ {
+ value& v (root.assign (var) = def_val);
+ v.extra = true; // Default value flag.
+
+ n = (save_flags & save_commented) == 0; // Absence means default.
+ l = lookup (v, var, root);
+ org = make_pair (l, 1); // Lookup depth is 1 since it's in root.vars.
+ }
+ // Treat an inherited value that was set to default as new.
+ //
+ else if (l->extra)
+ n = (save_flags & save_commented) == 0; // Absence means default.
+
+ if (var.overrides != nullptr)
+ {
+ pair<lookup, size_t> ovr (root.find_override (var, move (org)));
+
+ if (l != ovr.first) // Overriden?
+ {
+ // Override is always treated as new.
+ //
+ n = true;
+ l = move (ovr.first);
+ }
+ }
+
+ return pair<lookup, bool> (l, n);
+ }
+ }
+}