aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)