aboutsummaryrefslogtreecommitdiff
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
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=-
-rw-r--r--build2/b.cxx2
-rw-r--r--libbuild2/config/init.cxx108
-rw-r--r--libbuild2/config/module.cxx2
-rw-r--r--libbuild2/config/operation.cxx264
-rw-r--r--libbuild2/context.cxx2
-rw-r--r--libbuild2/dist/operation.cxx6
-rw-r--r--libbuild2/file.cxx95
-rw-r--r--libbuild2/file.hxx26
-rw-r--r--libbuild2/lexer.hxx2
-rw-r--r--libbuild2/module.cxx2
-rw-r--r--libbuild2/operation.cxx14
-rw-r--r--libbuild2/operation.hxx7
-rw-r--r--libbuild2/parser.cxx11
-rw-r--r--libbuild2/parser.hxx3
-rw-r--r--libbuild2/utility.cxx32
-rw-r--r--libbuild2/utility.hxx13
-rw-r--r--libbuild2/version/init.cxx3
17 files changed, 416 insertions, 176 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index 4571567..1ebcc53 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -106,7 +106,7 @@ namespace build2
if (ops.structured_result ())
{
- const target& t (at.as_target ());
+ const target& t (at.as<target> ());
context& ctx (t.ctx);
cout << at.state
diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx
index 6998017..4e1890a 100644
--- a/libbuild2/config/init.cxx
+++ b/libbuild2/config/init.cxx
@@ -6,6 +6,7 @@
#include <libbuild2/file.hxx>
#include <libbuild2/rule.hxx>
+#include <libbuild2/lexer.hxx>
#include <libbuild2/scope.hxx>
#include <libbuild2/context.hxx>
#include <libbuild2/filesystem.hxx> // exists()
@@ -32,9 +33,18 @@ namespace build2
const string& mname (rs.ctx.current_mname);
const string& oname (rs.ctx.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.
+ // While config.import (see below) could theoretically be specified in a
+ // buildfile, config.export is expected to always be specified as a
+ // command line override.
+ //
+ // Note: must be entered during bootstrap since we need it in
+ // configure_execute().
+ //
+ vp.insert<path> ("config.export", true /* ovr */);
+
+ // Only create the module if we are configuring or creating or if it was
+ // forced with config.module (useful if we need to call $config.export()
+ // during other meta-operations).
//
if (( mname == "configure" || mname == "create") ||
(mname.empty () && (oname == "configure" || oname == "create")))
@@ -80,42 +90,80 @@ namespace build2
assert (config_hints.empty ()); // We don't known any hints.
+ // Note that the config.<name>* variables belong to the module <name>.
+ // So the only "special" variables we can allocate in config.* are
+ // config.config.*, names that have been "gifted" to us by other modules
+ // (like config.version) as well as names that we have reserved to not
+ // be valid module names (build, import, export).
+ //
auto& vp (rs.ctx.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).
+ auto& c_v (vp.insert<uint64_t> ("config.version", false /*ovr*/));
+ auto& c_i (vp.insert<paths> ("config.import", true /* ovr */));
+
+ // Load config.build if one exists followed by extra files specified in
+ // config.import (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));
+ auto load_config = [&rs, &c_v] (const path& f, const location& l)
+ {
+ // 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.
+ //
+
+ // This is tricky for stdin since we cannot reopen it (or put more
+ // than one character back). So what we are going to do is continue
+ // reading after extracting the variable. One side effect of this is
+ // that we won't have the config.version variable entered in the scope
+ // but that is harmless (we could do it manually if necessary).
+ //
+ ifdstream ifs;
+ lexer lex (open_file_or_stdin (f, ifs), f);
+
+ // Assume missing version is 0.
+ //
+ auto p (extract_variable (rs.ctx, lex, 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) << '@'
+ << rs.out_path ();
+
+ source (rs, rs, lex);
+ };
{
path f (config_file (rs));
if (exists (f))
+ load_config (f, l);
+ }
+
+ if (lookup l = rs[c_i])
+ {
+ // Only load files that were specified on our root scope as well as
+ // global overrides. This way we can use our override "positioning"
+ // machinery (i.e., where the override applies) to decide where the
+ // extra config is loaded. The resulting semantics feels quite natural
+ // and consistent with command line variable overrides:
+ //
+ // b config.import=.../config.build # outermost amalgamation
+ // b ./config.import=.../config.build # this project
+ // b !config.import=.../config.build # every project
+ //
+ if (l.belongs (rs) || l.belongs (rs.ctx.global_scope))
{
- // 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 (rs.ctx, 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);
+ for (const path& f: cast<paths> (l))
+ load_config (f, location (&f));
}
}
diff --git a/libbuild2/config/module.cxx b/libbuild2/config/module.cxx
index 7e9b765..b43f1d9 100644
--- a/libbuild2/config/module.cxx
+++ b/libbuild2/config/module.cxx
@@ -30,7 +30,7 @@ namespace build2
i = sm.insert (string (n, 0, n.find ('.', 7)));
}
- // Don't insert duplicates. The config.import vars are particularly
+ // Don't insert duplicates. The config.import.* vars are particularly
// susceptible to duplication.
//
saved_variables& sv (i->second);
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)
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 292feed..b4a3e93 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -473,7 +473,7 @@ namespace build2
// Enter builtin variables and patterns.
//
- // All config. variables are by default overridable.
+ // All config.* variables are by default overridable.
//
vp.insert_pattern ("config.**", nullopt, true, nullopt, true, false);
diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx
index 391f7d8..ad829df 100644
--- a/libbuild2/dist/operation.cxx
+++ b/libbuild2/dist/operation.cxx
@@ -75,7 +75,7 @@ namespace build2
// For now we assume all the targets are from the same project.
//
- const target& t (ts[0].as_target ());
+ const target& t (ts[0].as<target> ());
const scope* rs (t.base_scope ().root_scope ());
if (rs == nullptr)
@@ -118,7 +118,7 @@ namespace build2
//
for (const action_target& at: ts)
{
- const target& t (at.as_target ());
+ const target& t (at.as<target> ());
if (rs != t.base_scope ().root_scope ())
fail << "target " << t << " is from a different project" <<
@@ -353,7 +353,7 @@ namespace build2
for (size_t i (0), n (files.size ()); i != n; ++i)
{
- const file& t (*files[i].as_target ().is_a<file> ());
+ const file& t (*files[i].as<target> ().is_a<file> ());
// Figure out where this file is inside the target directory.
//
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 603943c..b55d576 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -4,8 +4,6 @@
#include <libbuild2/file.hxx>
-#include <iostream> // cin
-
#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -161,27 +159,39 @@ namespace build2
}
static void
- source (scope& root, scope& base, const path& bf, bool boot)
+ source (scope& root, scope& base, lexer& l, bool boot)
{
tracer trace ("source");
+ const path& bf (l.name ());
+
try
{
- bool sin (bf.string () == "-");
-
- ifdstream ifs;
-
- if (!sin)
- ifs.open (bf);
- else
- cin.exceptions (ifdstream::failbit | ifdstream::badbit);
-
- istream& is (sin ? cin : ifs);
-
l5 ([&]{trace << "sourcing " << bf;});
parser p (root.ctx, boot);
- p.parse_buildfile (is, bf, root, base);
+ p.parse_buildfile (l, root, base);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e;
+ }
+ }
+
+ static void
+ source (scope& root, scope& base, istream& is, const path& bf, bool boot)
+ {
+ lexer l (is, bf);
+ source (root, base, l, boot);
+ }
+
+ static void
+ source (scope& root, scope& base, const path& bf, bool boot)
+ {
+ try
+ {
+ ifdstream ifs;
+ return source (root, base, open_file_or_stdin (bf, ifs), bf, boot);
}
catch (const io_error& e)
{
@@ -195,6 +205,18 @@ namespace build2
source (root, base, bf, false);
}
+ void
+ source (scope& root, scope& base, istream& is, const path& bf)
+ {
+ source (root, base, is, bf, false);
+ }
+
+ void
+ source (scope& root, scope& base, lexer& l)
+ {
+ source (root, base, l, false);
+ }
+
bool
source_once (scope& root, scope& base, const path& bf, scope& once)
{
@@ -206,7 +228,7 @@ namespace build2
return false;
}
- source (root, base, bf);
+ source (root, base, bf, false);
return true;
}
@@ -498,18 +520,17 @@ namespace build2
}
pair<value, bool>
- extract_variable (context& ctx, const path& bf, const variable& var)
+ extract_variable (context& ctx, lexer& l, const variable& var)
{
+ const path& bf (l.name ());
+
try
{
- ifdstream ifs (bf);
-
- lexer lex (ifs, bf);
- token t (lex.next ());
+ token t (l.next ());
token_type tt;
if (t.type != token_type::word || t.value != var.name ||
- ((tt = lex.next ().type) != token_type::assign &&
+ ((tt = l.next ().type) != token_type::assign &&
tt != token_type::prepend &&
tt != token_type::append))
{
@@ -518,7 +539,7 @@ namespace build2
parser p (ctx);
temp_scope tmp (ctx.global_scope.rw ());
- p.parse_variable (lex, tmp, var, tt);
+ p.parse_variable (l, tmp, var, tt);
value* v (tmp.vars.find_to_modify (var).first);
assert (v != nullptr);
@@ -533,6 +554,32 @@ namespace build2
}
}
+ pair<value, bool>
+ extract_variable (context& ctx,
+ istream& is,
+ const path& bf,
+ const variable& var)
+ {
+ lexer l (is, bf);
+ return extract_variable (ctx, l, var);
+ }
+
+ pair<value, bool>
+ extract_variable (context& ctx, const path& bf, const variable& var)
+ {
+ try
+ {
+ ifdstream ifs (bf);
+ return extract_variable (ctx, ifs, bf, var);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to read buildfile " << bf << ": " << e << endf;
+ }
+ }
+
+
+
// Extract the project name from bootstrap.build.
//
static project_name
@@ -745,7 +792,7 @@ namespace build2
// root scope multiple time.
//
if (rs.buildfiles.insert (f).second)
- source (rs, rs, f, true);
+ source (rs, rs, f, true /* boot */);
else
l5 ([&]{trace << "skipping already sourced " << f;});
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
index 3d3c38b..78bd049 100644
--- a/libbuild2/file.hxx
+++ b/libbuild2/file.hxx
@@ -18,6 +18,8 @@
namespace build2
{
+ class lexer;
+
using subprojects = std::map<project_name, dir_path>;
LIBBUILD2_SYMEXPORT ostream&
@@ -71,6 +73,18 @@ namespace build2
LIBBUILD2_SYMEXPORT void
source (scope& root, scope& base, const path&);
+ // As above, but extract from a stream. The name argument is used for
+ // diagnostics.
+ //
+ LIBBUILD2_SYMEXPORT void
+ source (scope& root, scope& base, istream&, const path& name);
+
+ // As above, but extract from a lexer (this could be useful for sourcing
+ // stdin that requires parse_variable()).
+ //
+ LIBBUILD2_SYMEXPORT void
+ source (scope& root, scope& base, lexer&);
+
// As above but first check if this buildfile has already been sourced for
// the base scope. Return false if the file has already been sourced.
//
@@ -195,6 +209,18 @@ namespace build2
LIBBUILD2_SYMEXPORT pair<value, bool>
extract_variable (context&, const path&, const variable&);
+ // As above, but extract from a stream. The name argument is used for
+ // diagnostics.
+ //
+ LIBBUILD2_SYMEXPORT pair<value, bool>
+ extract_variable (context&, istream&, const path& name, const variable&);
+
+ // As above, but extract from a lexer (this could be useful for extracting
+ // from stdin).
+ //
+ LIBBUILD2_SYMEXPORT pair<value, bool>
+ extract_variable (context&, lexer&, const variable&);
+
// Import has two phases: the first is triggered by the import directive in
// the buildfile. It will try to find and load the project. Failed that, it
// will return the project-qualified name of the target which will be used
diff --git a/libbuild2/lexer.hxx b/libbuild2/lexer.hxx
index 90d546d..a2c7431 100644
--- a/libbuild2/lexer.hxx
+++ b/libbuild2/lexer.hxx
@@ -194,7 +194,7 @@ namespace build2
mode (lexer_mode::normal, '@', escapes);
}
- const path name_;
+ const path name_; // @@ TODO: why not shallow (like istream)?
std::stack<state> state_;
bool sep_; // True if we skipped spaces in peek().
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
index 50c6d53..a8304ff 100644
--- a/libbuild2/module.cxx
+++ b/libbuild2/module.cxx
@@ -304,7 +304,7 @@ namespace build2
}
assert (tgs.size () == 1);
- const target& l (tgs[0].as_target ());
+ const target& l (tgs[0].as<target> ());
if (!l.is_a ("libs"))
fail (loc) << "wrong export from build system module " << mod;
diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx
index a9b6107..86cd571 100644
--- a/libbuild2/operation.cxx
+++ b/libbuild2/operation.cxx
@@ -136,7 +136,7 @@ namespace build2
if (ts.empty ())
return;
- context& ctx (ts[0].as_target ().ctx);
+ context& ctx (ts[0].as<target> ().ctx);
{
phase_lock l (ctx, run_phase::match);
@@ -176,7 +176,7 @@ namespace build2
for (; i != n; ++i)
{
- const target& t (ts[i].as_target ());
+ const target& t (ts[i].as<target> ());
l5 ([&]{trace << diag_doing (a, t);});
target_state s (match_async (a, t, 0, task_count, false));
@@ -208,7 +208,7 @@ namespace build2
for (size_t j (0); j != n; ++j)
{
action_target& at (ts[j]);
- const target& t (at.as_target ());
+ const target& t (at.as<target> ());
target_state s (j < i
? match (a, t, false)
@@ -264,7 +264,7 @@ namespace build2
if (ts.empty ())
return;
- context& ctx (ts[0].as_target ().ctx);
+ context& ctx (ts[0].as<target> ().ctx);
// Reverse the order of targets if the execution mode is 'last'.
//
@@ -338,7 +338,7 @@ namespace build2
for (const action_target& at: ts)
{
- const target& t (at.as_target ());
+ const target& t (at.as<target> ());
l5 ([&]{trace << diag_doing (a, t);});
@@ -389,7 +389,7 @@ namespace build2
bool fail (false);
for (action_target& at: ts)
{
- const target& t (at.as_target ());
+ const target& t (at.as<target> ());
switch ((at.state = t.executed_state (a, false)))
{
@@ -521,7 +521,7 @@ namespace build2
if (i != 0)
cout << endl;
- const scope& rs (*static_cast<const scope*> (ts[i].target));
+ const scope& rs (ts[i].as<scope> ());
context& ctx (rs.ctx);
diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx
index 7b6310f..921b77a 100644
--- a/libbuild2/operation.hxx
+++ b/libbuild2/operation.hxx
@@ -29,16 +29,15 @@ namespace build2
//
struct action_target
{
- using target_type = build2::target;
-
const void* target = nullptr;
target_state state = target_state::unknown;
action_target () = default;
action_target (const void* t): target (t) {}
- const target_type&
- as_target () const {return *static_cast<const target_type*> (target);}
+ template <typename T>
+ const T&
+ as () const {return *static_cast<const T*> (target);}
};
class action_targets: public vector<action_target>
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index ca4f8d5..7b76d18 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -213,9 +213,14 @@ namespace build2
void parser::
parse_buildfile (istream& is, const path& p, scope& root, scope& base)
{
- path_ = &p;
+ lexer l (is, p);
+ parse_buildfile (l, root, base);
+ }
- lexer l (is, *path_);
+ void parser::
+ parse_buildfile (lexer& l, scope& root, scope& base)
+ {
+ path_ = &l.name ();
lexer_ = &l;
root_ = &root;
scope_ = &base;
@@ -224,7 +229,7 @@ namespace build2
prerequisite_ = nullptr;
default_target_ = nullptr;
- enter_buildfile (p); // Needs scope_.
+ enter_buildfile (*path_); // Needs scope_.
token t;
type tt;
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 8b6179c..04a8e5c 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -36,6 +36,9 @@ namespace build2
void
parse_buildfile (istream&, const path& name, scope& root, scope& base);
+ void
+ parse_buildfile (lexer&, scope& root, scope& base);
+
buildspec
parse_buildspec (istream&, const path& name);
diff --git a/libbuild2/utility.cxx b/libbuild2/utility.cxx
index 78d2df2..7f40688 100644
--- a/libbuild2/utility.cxx
+++ b/libbuild2/utility.cxx
@@ -8,7 +8,7 @@
#include <cerrno> // ENOENT
#include <cstring> // strlen(), str[n]cmp()
-#include <iostream> // cerr
+#include <iostream> // cin cout cerr
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
@@ -100,6 +100,36 @@ namespace build2
dir_path home;
const dir_path* relative_base = &work;
+ istream&
+ open_file_or_stdin (const path& f, ifdstream& ifs)
+ {
+ if (f.string () != "-")
+ {
+ ifs.open (f);
+ return ifs;
+ }
+ else
+ {
+ cin.exceptions (ifdstream::failbit | ifdstream::badbit);
+ return cin;
+ }
+ }
+
+ ostream&
+ open_file_or_stdout (const path& f, ofdstream& ofs)
+ {
+ if (f.string () != "-")
+ {
+ ofs.open (f);
+ return ofs;
+ }
+ else
+ {
+ cout.exceptions (ofdstream::failbit | ofdstream::badbit);
+ return cout;
+ }
+ }
+
path
relative (const path_target& t)
{
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index 43b556e..415dc8b 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -88,6 +88,19 @@ namespace build2
using butl::eof;
+ // Open a file or, if the file name is `-`, stdin/stdout.
+ //
+ // Note that ofdstream::close() should be called explicitly if not stdout
+ // (but harmless to call even if it is). Also note that our overload of
+ // operator<<(path) always translats `-` to `<stdin>` so care must be taken
+ // when issuing diagnostics.
+ //
+ istream&
+ open_file_or_stdin (const path&, ifdstream&);
+
+ ostream&
+ open_file_or_stdout (const path&, ofdstream&);
+
// Diagnostics state (verbosity level, etc; see <libbuild2/diagnostics.hxx>).
//
// Note on naming of values (here and in the global state below) that come
diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx
index 123dc65..c9d5021 100644
--- a/libbuild2/version/init.cxx
+++ b/libbuild2/version/init.cxx
@@ -221,6 +221,9 @@ namespace build2
// Set all the version.* variables.
//
+ // Note also that we have "gifted" the config.version variable name to
+ // the config module.
+ //
auto& vp (ctx.var_pool.rw (rs));
auto set = [&vp, &rs] (const char* var, auto val)