aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/config/operation.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-11-04 09:37:30 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-11-04 15:14:42 +0200
commit41a31b0a61464fd506166887f621100364e67276 (patch)
tree2f2cfaad943ee956fb412ec7f1929a6b493f4aab /libbuild2/config/operation.cxx
parent3ca5421a788cb50004fe62f452c43132492778e1 (diff)
Add support for configuration exporting and importing
The new config.export variable specifies the alternative file to write the configuration to as part of the configure meta-operation. For example: $ b configure: proj/ config.export=proj-config.build The config.export value "applies" only to the projects on whose root scope it is specified or if it is a global override (the latter is a bit iffy but we allow it, for example, to dump everything to stdout). This means that in order to save a subproject's configuration we will have to use a scope-specific override (since the default will apply to the outermost amalgamation). For example: $ b configure: subproj/ subproj/config.export=.../subproj-config.build This could be somewhat unnatural but then it will be the amalgamation whose configuration we normally want to export. The new config.import variable specifies additional configuration files to be loaded after the project's default config.build, if any. For example: $ b create: cfg/,cc config.import=my-config.build Similar to config.export, the config.import value "applies" only to the project on whose root scope it is specified or if it is a global override. This allows the use of the standard override "positioning" machinery (i.e., where the override applies) to decide where the extra configuration files are loaded. The resulting semantics is quite natural and consistent with command line variable overrides, for example: $ b config.import=.../config.build # outermost amalgamation $ b ./config.import=.../config.build # this project $ b !config.import=.../config.build # every project Both config.export and config.import recognize the special `-` file name as an instruction to write/read to/from stdout/stdin, respectively. For example: $ b configure: src-prj/ config.export=- | b configure: dst-prj/ config.import=-
Diffstat (limited to 'libbuild2/config/operation.cxx')
-rw-r--r--libbuild2/config/operation.cxx264
1 files changed, 165 insertions, 99 deletions
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index 535018e..9050645 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -87,35 +87,45 @@ namespace build2
using project_set = set<const scope*>; // Use pointers to get comparison.
+ // If inherit is false, then don't rely on inheritance from outer scopes
+ // (used for config.export).
+ //
static void
- save_config (const scope& rs, const project_set& projects)
+ save_config (const scope& rs,
+ const path& f,
+ bool inherit,
+ const project_set& projects)
{
context& ctx (rs.ctx);
- path f (config_file (rs));
+ const module& mod (*rs.lookup_module<const module> (module::name));
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << f;
+ const string& df (f.string () != "-" ? f.string () : "<stdout>");
- const module& mod (*rs.lookup_module<const module> (module::name));
+ if (verb)
+ text << (verb >= 2 ? "cat >" : "save ") << df;
try
{
- ofdstream ofs (f);
+ ofdstream ofs;
+ ostream& os (open_file_or_stdout (f, ofs));
- ofs << "# Created automatically by the config module, but feel " <<
+ os << "# Created automatically by the config module, but feel " <<
"free to edit." << endl
- << "#" << endl;
+ << "#" << endl;
- ofs << "config.version = " << module::version << endl;
+ os << "config.version = " << module::version << endl;
- if (auto l = rs.vars[ctx.var_amalgamation])
+ if (inherit)
{
- const dir_path& d (cast<dir_path> (l));
+ if (auto l = rs.vars[ctx.var_amalgamation])
+ {
+ const dir_path& d (cast<dir_path> (l));
- ofs << endl
- << "# Base configuration inherited from " << d << endl
- << "#" << endl;
+ os << endl
+ << "# Base configuration inherited from " << d << endl
+ << "#" << endl;
+ }
}
// Save config variables.
@@ -146,6 +156,11 @@ namespace build2
if (!l.defined ())
continue;
+ // Handle inherited from outer scope values.
+ //
+ // Note that we keep this logic (with warnings and all) even if
+ // inherit is false to make things easier to reason about.
+ //
if (!(l.belongs (rs) || l.belongs (ctx.global_scope)))
{
// This is presumably an inherited value. But it could also be
@@ -205,42 +220,50 @@ namespace build2
}
}
- 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)
+ if (found)
{
- 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)
+ // Inherited.
+ //
+ if (inherit)
+ continue;
+ }
+ else
+ {
+ 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)
{
- dr << info (loc) << "variable value: ";
+ diag_record dr;
+ dr << warn (loc) << "saving previously inherited variable "
+ << var;
- if (*l)
+ dr << info (loc) << "because project " << *r
+ << " no longer uses it in its configuration";
+
+ if (verb >= 2)
{
- storage.clear ();
- dr << "'" << reverse (*l, storage) << "'";
+ dr << info (loc) << "variable value: ";
+
+ if (*l)
+ {
+ storage.clear ();
+ dr << "'" << reverse (*l, storage) << "'";
+ }
+ else
+ dr << "[null]";
}
- else
- dr << "[null]";
}
}
}
@@ -264,7 +287,7 @@ namespace build2
//
if (first)
{
- ofs << endl;
+ os << endl;
first = false;
}
@@ -274,7 +297,7 @@ namespace build2
org.first == ovr.first && // Not overriden.
(sv.flags & save_commented) == save_commented)
{
- ofs << '#' << n << " =" << endl;
+ os << '#' << n << " =" << endl;
continue;
}
@@ -283,20 +306,20 @@ namespace build2
storage.clear ();
names_view ns (reverse (v, storage));
- ofs << n;
+ os << n;
if (ns.empty ())
- ofs << " =";
+ os << " =";
else
{
- ofs << " = ";
- to_stream (ofs, ns, true, '@'); // Quote.
+ os << " = ";
+ to_stream (os, ns, true, '@'); // Quote.
}
- ofs << endl;
+ os << endl;
}
else
- ofs << n << " = [null]" << endl;
+ os << n << " = [null]" << endl;
}
}
@@ -304,12 +327,15 @@ namespace build2
}
catch (const io_error& e)
{
- fail << "unable to write " << f << ": " << e;
+ fail << "unable to write " << df << ": " << e;
}
}
static void
- configure_project (action a, const scope& rs, project_set& projects)
+ configure_project (action a,
+ const scope& rs,
+ const variable* c_e, // config.export
+ project_set& projects)
{
tracer trace ("configure_project");
@@ -332,8 +358,7 @@ namespace build2
mkdir (out_root / rs.root_extra->bootstrap_dir, 2);
}
- // We distinguish between a complete configure and operation-
- // specific.
+ // We distinguish between a complete configure and operation-specific.
//
if (a.operation () == default_id)
{
@@ -341,15 +366,48 @@ namespace build2
// Save src-root.build unless out_root is the same as src.
//
- if (out_root != src_root)
+ if (c_e == nullptr && out_root != src_root)
save_src_root (rs);
- // Save config.build.
+ // Save config.build unless an alternative is specified with
+ // config.export. Similar to config.import we will only save to that
+ // file if it is specified on our root scope or as a global override
+ // (the latter is a bit iffy but let's allow it, for example, to dump
+ // everything to stdout). Note that to save a subproject's config we
+ // will have to use a scope-specific override (since the default will
+ // apply to the amalgamation):
//
- save_config (rs, projects);
+ // b configure: subproj/ subproj/config.export=.../config.build
+ //
+ // Could be confusing but then normally it will be the amalgamation
+ // whose configuration we want to export.
+ //
+ // Note also that if config.export is specified we do not rewrite
+ // config.build files (say, of subprojects) as well as src-root.build
+ // above. Failed that, if we are running in a disfigured project, we
+ // may end up leaving it in partially configured state.
+ //
+ if (c_e == nullptr)
+ save_config (rs, config_file (rs), true /* inherit */, projects);
+ else
+ {
+ lookup l (rs[*c_e]);
+ if (l && (l.belongs (rs) || l.belongs (ctx.global_scope)))
+ {
+ // 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
+ // of course just copy the relevant config.build files, but we may
+ // still want to support this mode somehow in the future (maybe
+ // using `+` as a modifier, say config.export=+.../config.build).
+ //
+ save_config (rs, cast<path> (l), false /* inherit */, projects);
+ }
+ }
}
else
{
+ fail << "operation-specific configuration not yet supported";
}
// Configure subprojects that have been loaded.
@@ -368,7 +426,7 @@ namespace build2
if (nrs.out_path () != out_nroot) // This subproject not loaded.
continue;
- configure_project (a, nrs, projects);
+ configure_project (a, nrs, c_e, projects);
}
}
}
@@ -513,6 +571,15 @@ namespace build2
{
bool fwd (forward (params));
+ context& ctx (fwd ? ts[0].as<scope> ().ctx : ts[0].as<target> ().ctx);
+
+ const variable* c_e (ctx.var_pool.find ("config.export"));
+
+ if (c_e->overrides == nullptr)
+ c_e = nullptr;
+ else if (fwd)
+ fail << "config.export specified for forward configuration";
+
project_set projects;
for (const action_target& at: ts)
@@ -521,53 +588,52 @@ namespace build2
{
// Forward configuration.
//
- const scope& rs (*static_cast<const scope*> (at.target));
+ const scope& rs (at.as<scope> ());
configure_forward (rs, projects);
- continue;
}
+ else
+ {
+ // 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 ());
- // 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;
-
- context& ctx (t.ctx);
+ if (rs == nullptr)
+ fail << "out of project target " << t;
- const operations& ops (rs->root_extra->operations);
+ 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])
+ for (operation_id id (default_id + 1); // Skip default_id.
+ id < ops.size ();
+ ++id)
{
- // Skip aliases (e.g., update-for-install).
- //
- if (oif->id != id)
- continue;
+ if (const operation_info* oif = ops[id])
+ {
+ // Skip aliases (e.g., update-for-install).
+ //
+ if (oif->id != id)
+ continue;
- ctx.current_operation (*oif);
+ ctx.current_operation (*oif);
- phase_lock pl (ctx, run_phase::match);
- match (action (configure_id, id), t);
+ phase_lock pl (ctx, run_phase::match);
+ match (action (configure_id, id), t);
+ }
}
- }
- configure_project (a, *rs, projects);
+ configure_project (a, *rs, c_e, projects);
+ }
}
}
@@ -802,7 +868,7 @@ namespace build2
//
for (const action_target& at: ts)
{
- const scope& rs (*static_cast<const scope*> (at.target));
+ const scope& rs (at.as<scope> ());
if (!(fwd
? disfigure_forward ( rs, projects)