aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-11-29 17:44:48 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-11-29 17:44:48 +0200
commit074a8c04a384a9752466bd2af69b695333b2955c (patch)
tree707454105647db7886268551ffc661a359e4637c
parentf588f2e5a62d7621d20b2a567b8835bb87ff31f4 (diff)
Reimplement module sidebuilding using an ad hoc subproject
-rw-r--r--build2/b.cxx19
-rw-r--r--build2/cc/compile.cxx112
-rw-r--r--build2/config/operation.cxx179
-rw-r--r--build2/config/utility.cxx150
-rw-r--r--build2/config/utility.hxx16
-rw-r--r--build2/cxx/init.cxx3
-rw-r--r--build2/file.cxx54
-rw-r--r--build2/file.hxx19
8 files changed, 348 insertions, 204 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index 94c6279..9ad21bc 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -702,17 +702,18 @@ main (int argc, char* argv[])
src_root = out_root;
}
- // Now we know out_root and, if it was explicitly specified
- // or the same as out_root, src_root. The next step is to
- // create the root scope and load the out_root bootstrap
- // files, if any. Note that we might already have done this
- // as a result of one of the preceding target processing.
+ // Now we know out_root and, if it was explicitly specified or the
+ // same as out_root, src_root. The next step is to create the root
+ // scope and load the out_root bootstrap files, if any. Note that we
+ // might already have done this as a result of one of the preceding
+ // target processing.
//
- // If we know src_root, set that variable as well. This could
- // be of use to the bootstrap files (other than src-root.build,
- // which, BTW, doesn't need to exist if src_root == out_root).
+ // If we know src_root, set that variable as well. This could be of
+ // use to the bootstrap files (other than src-root.build, which,
+ // BTW, doesn't need to exist if src_root == out_root).
//
- scope& rs (create_root (*scope::global_, out_root, src_root));
+ scope& rs (
+ create_root (*scope::global_, out_root, src_root)->second);
bool bootstrapped (build2::bootstrapped (rs));
diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx
index fef7eb6..0ab9bfc 100644
--- a/build2/cc/compile.cxx
+++ b/build2/cc/compile.cxx
@@ -7,6 +7,7 @@
#include <cstdlib> // exit()
#include <cstring> // strlen()
+#include <build2/file.hxx>
#include <build2/depdb.hxx>
#include <build2/scope.hxx>
#include <build2/context.hxx>
@@ -15,6 +16,7 @@
#include <build2/diagnostics.hxx>
#include <build2/bin/target.hxx>
+#include <build2/config/utility.hxx> // create_project()
#include <build2/cc/parser.hxx>
#include <build2/cc/target.hxx> // h
@@ -3692,15 +3694,12 @@ namespace build2
tracer trace (x, "compile::make_module_sidebuild");
// First figure out where we are going to build. We want to avoid
- // multiple sidebuilds so the outermost scope that has loaded the module
- // capable of compiling things and that is within our amalgmantion seems
- // like a good place.
+ // multiple sidebuilds so the outermost scope that has loaded the
+ // cc.config module and that is within our amalgmantion seems like a
+ // good place.
//
- // @@ TODO: this is actually pretty restrictive: we need cxx and with
- // modules enabled! Which means things like bpkg configurations won't
- // work (only loads cc.config).
- //
- const scope* as (bs.root_scope ());
+ const scope& rs (*bs.root_scope ());
+ const scope* as (&rs);
{
const scope* ws (as->weak_scope ());
if (as != ws)
@@ -3710,14 +3709,82 @@ namespace build2
{
s = s->parent_scope ()->root_scope ();
- const module* m (s->modules.lookup<module> ("cxx"));
- if (m != nullptr && m->modules)
+ // Use cc.core.vars as a proxy for {c,cxx}.config (a bit smelly).
+ //
+ if (cast_false<bool> ((*s)["cc.core.vars.loaded"]))
as = s;
} while (s != ws);
}
}
+ // We build modules in a subproject (since there might be no full
+ // language support module loaded in the amalgamation, only *.config).
+ // So the first step is to check if the project has already been created
+ // and/or loaded and if not, then to go ahead and do so.
+ //
+ dir_path pd (as->out_path ());
+ pd /= "build";
+ pd /= "cc";
+ pd /= "modules";
+ pd /= x;
+ {
+ const scope* ps (&scopes.find (pd));
+
+ if (ps->out_path () != pd)
+ {
+ // Switch the phase to load then create and load the subproject.
+ //
+ phase_switch phs (run_phase::load);
+
+ // Re-test again now that we are in exclusive phase (another thread
+ // could have already created and loaded the subproject).
+ //
+ ps = &scopes.find (pd);
+
+ if (ps->out_path () != pd)
+ {
+ // The project might already be created in which case we just need
+ // to load it.
+ //
+ if (!is_src_root (pd))
+ {
+ // Copy our standard and force modules.
+ //
+ string extra;
+
+ if (const string* std = cast_null<string> (rs[x_std]))
+ extra += string (x) + ".std = " + *std + '\n';
+
+ extra += string (x) + ".features.modules = true";
+
+ dir_path ad (((dir_path ("..") /= "..") /= "..") /= "..");
+
+ config::create_project (pd,
+ ad, /* amalgamation */
+ {}, /* boot_modules */
+ extra, /* root_pre */
+ {string (x) + '.'}, /* root_modules */
+ "", /* root_post */
+ false, /* config */
+ false, /* buildfile */
+ "the cc module",
+ 2); /* verbosity */
+ }
+
+ ps = &load_project (as->rw () /* lock */, pd, pd);
+ }
+ }
+
+ // Some sanity checks.
+ //
+#ifndef NDEBUG
+ assert (ps->root ());
+ const module* m (ps->modules.lookup<module> (x));
+ assert (m != nullptr && m->modules);
+#endif
+ }
+
// Next we need to come up with a file/target name that will be unique
// enough not to conflict with other modules. If we assume that within
// an amalgamation there is only one "version" of each module, then the
@@ -3729,13 +3796,6 @@ namespace build2
back_inserter (mf),
[] (char c) {return c == '.' ? '-' : c;});
- // Store the BMI target in the build/<mod>/modules/ subdirectory.
- //
- dir_path md (as->out_path ());
- md /= "build";
- md /= x;
- md /= "modules";
-
// It seems natural to build a BMI type that corresponds to the library
// type. After all, this is where the object file part of the BMI is
// going to come from (though things will probably be different for
@@ -3749,27 +3809,19 @@ namespace build2
case otype::e: assert (false);
}
- // If the target already exists then we assume all this is already done
- // (otherwise why would someone have created such a target).
+ // Store the BMI target in the subproject root. If the target already
+ // exists then we assume all this is already done (otherwise why would
+ // someone have created such a target).
//
if (const target* bt = targets.find (
*tt,
- md,
+ pd,
dir_path (), // Always in the out tree.
mf,
nullopt, // Use default extension.
trace))
return *bt;
- // Make sure the output directory exists. This is not strictly necessary
- // if out != src since inject_fsdir() will take care of it. For out ==
- // src we initially tried to add an explicit fsdir{} preprequisite but
- // that didn't work out since this is a nested directory. So now we keep
- // it simple and just create it. The proper way to handle this as well
- // as cleanup is probably at the cxx module level, which is @@ TODO.
- //
- mkdir_p (md, 3);
-
prerequisites ps;
ps.push_back (prerequisite (mt));
@@ -3794,7 +3846,7 @@ namespace build2
}
auto p (targets.insert_locked (*tt,
- move (md),
+ move (pd),
dir_path (), // Always in the out tree.
move (mf),
nullopt, // Use default extension.
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index c7cfefd..2fad6b0 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -415,7 +415,7 @@ namespace build2
// disfigure
//
static void
- load_project (scope& root)
+ bootstrap_project (scope& root)
{
if (auto l = root.vars[var_subprojects])
{
@@ -432,7 +432,7 @@ namespace build2
// The same logic for src_root as in create_bootstrap_inner().
//
- scope& nroot (create_root (root, out_nroot, dir_path ()));
+ scope& nroot (create_root (root, out_nroot, dir_path ())->second);
if (!bootstrapped (nroot))
{
@@ -447,7 +447,7 @@ namespace build2
bootstrap_src (nroot);
}
- load_project (nroot);
+ bootstrap_project (nroot);
}
}
}
@@ -476,7 +476,7 @@ namespace build2
// disfigure all the subprojects (see disfigure_project() below), we
// bootstrap all known subprojects.
//
- load_project (root);
+ bootstrap_project (root);
}
static void
@@ -644,151 +644,18 @@ namespace build2
// create
//
static void
- create_project (const dir_path& d,
- const strings& bmod, // Bootstrap modules.
- const strings& rmod, // Root modules.
- const variable_overrides& var_ovs)
+ save_config (const dir_path& d, const variable_overrides& var_ovs)
{
- // If the directory exists, verify it's empty. Otherwise, create it.
+ // 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.
//
- if (exists (d))
- {
- if (!empty (d))
- fail << "directory " << d << " exists and is not empty";
- }
- else
- mkdir_p (d);
-
- // Create the build/ subdirectory.
- //
- mkdir (d / build_dir);
-
- // Write build/bootstrap.build.
- //
- {
- path f (d / bootstrap_file);
-
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << f;
-
- try
- {
- ofdstream ofs (f);
-
- // For now we disable amalgamating this project. Sooner or later
- // someone will probably want to do this, though (i.e., nested
- // configurations).
- //
- ofs << "# Generated by the create meta-operation. Edit if you " <<
- "know what you are doing." << endl
- << "#" << endl
- << "project =" << endl
- << "amalgamation =" << endl
- << endl
- << "using config" << endl;
-
- for (const string& m: bmod)
- {
- if (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 / root_file);
-
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << f;
-
- try
- {
- ofdstream ofs (f);
-
- ofs << "# Generated by the create meta-operation. Edit if you " <<
- "know what you are doing." << 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;
- }
-
- ofs.close ();
- }
- catch (const io_error& e)
- {
- fail << "unable to write " << f << ": " << e;
- }
- }
-
- // Write root buildfile.
- //
- {
- path f (d / "buildfile");
-
- if (verb)
- text << (verb >= 2 ? "cat >" : "save ") << f;
-
- try
- {
- ofdstream ofs (f);
-
- ofs << "# Generated by the create meta-operation. Edit if you " <<
- "know what you are doing." << endl
- << "#" << endl
- << "./: {*/ -build/}" << endl;
-
- ofs.close ();
- }
- catch (const io_error& e)
- {
- fail << "unable to write " << f << ": " << e;
- }
- }
-
- // Well, this wasn't too bad. There is just one little snag: 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 (except here we can omit all the guessing/sanity checks).
- //
- current_oname = &empty_string; // Make sure valid.
-
scope& gs (*scope::global_);
- scope& rs (create_root (gs, d, d));
-
- if (!bootstrapped (rs))
- {
- bootstrap_out (rs);
- setup_root (rs);
- bootstrap_src (rs);
- }
-
+ scope& rs (load_project (gs, d, d, false /* load */));
module& m (*rs.modules.lookup<module> (module::name));
// Save all the global config.import.* variables.
@@ -834,7 +701,7 @@ namespace build2
bool lifted,
const location& l)
{
- tracer trace ("create_project");
+ 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
@@ -865,6 +732,8 @@ namespace build2
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)
@@ -919,7 +788,21 @@ namespace build2
l5 ([&]{trace << "creating project in " << d;});
- create_project (d, bmod, rmod, var_ovs);
+ // 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);
}
}
diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx
index c195e0b..1c80d13 100644
--- a/build2/config/utility.cxx
+++ b/build2/config/utility.cxx
@@ -4,7 +4,10 @@
#include <build2/config/utility.hxx>
+#include <build2/file.hxx>
#include <build2/context.hxx>
+#include <build2/filesystem.hxx>
+#include <build2/diagnostics.hxx>
#include <build2/config/module.hxx>
@@ -151,5 +154,152 @@ namespace build2
if (module* m = r.modules.lookup<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.
+ //
+ mkdir (d / build_dir, verbosity);
+
+ // Write build/bootstrap.build.
+ //
+ {
+ path f (d / 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 / 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 / "buildfile");
+
+ 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/build2/config/utility.hxx b/build2/config/utility.hxx
index 33a3985..c09dc07 100644
--- a/build2/config/utility.hxx
+++ b/build2/config/utility.hxx
@@ -92,6 +92,8 @@ namespace build2
//
// Return the value (as always defined lookup), which can be NULL.
//
+ // @@ Rename since clashes with the optional class template.
+ //
lookup
optional (scope& root, const variable&);
@@ -144,6 +146,20 @@ namespace build2
//
void
save_module (scope& root, const char* name, int prio = 0);
+
+ // Create a project in the specified directory.
+ //
+ 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.
}
}
diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx
index 5b568fc..9f2d912 100644
--- a/build2/cxx/init.cxx
+++ b/build2/cxx/init.cxx
@@ -57,6 +57,9 @@ namespace build2
return var_pool.rw (rs).insert<bool> (v, variable_visibility::project);
};
+ // NOTE: see also module sidebuild subproject if changing anything about
+ // modules here.
+
//bool concepts (false); auto& v_c (enter ("cxx.features.concepts"));
bool modules (false); auto& v_m (enter ("cxx.features.modules"));
diff --git a/build2/file.cxx b/build2/file.cxx
index 8a0f2dc..1ad3500 100644
--- a/build2/file.cxx
+++ b/build2/file.cxx
@@ -127,7 +127,7 @@ namespace build2
return true;
}
- scope&
+ scope_map::iterator
create_root (scope& l, const dir_path& out_root, const dir_path& src_root)
{
auto i (scopes.rw (l).insert (out_root, true));
@@ -193,7 +193,7 @@ namespace build2
}
}
- return rs;
+ return i;
}
void
@@ -291,6 +291,30 @@ namespace build2
return pair<scope&, scope*> (base, rs);
}
+ scope&
+ load_project (scope& lock,
+ const dir_path& out_root, const dir_path& src_root,
+ bool load)
+ {
+ auto i (create_root (lock, out_root, src_root));
+ scope& rs (i->second);
+
+ if (!bootstrapped (rs))
+ {
+ bootstrap_out (rs);
+ setup_root (rs);
+ bootstrap_src (rs);
+ }
+
+ if (load)
+ {
+ load_root_pre (rs);
+ setup_base (i, out_root, src_root); // Setup as base.
+ }
+
+ return rs;
+ }
+
void
bootstrap_out (scope& root)
{
@@ -515,16 +539,16 @@ namespace build2
r = true;
}
- // See if we are a part of an amalgamation. There are two key
- // players: the outer root scope which may already be present
- // (i.e., we were loaded as part of an amalgamation) and the
- // amalgamation variable that may or may not be set by the
- // user (in bootstrap.build) or by an earlier call to this
- // function for the same scope. When set by the user, the
- // empty special value means that the project shall not be
- // amalgamated (and which we convert to NULL below). When
- // calculated, the NULL value indicates that we are not
- // amalgamated.
+ // See if we are a part of an amalgamation. There are two key players: the
+ // outer root scope which may already be present (i.e., we were loaded as
+ // part of an amalgamation) and the amalgamation variable that may or may
+ // not be set by the user (in bootstrap.build) or by an earlier call to
+ // this function for the same scope. When set by the user, the empty
+ // special value means that the project shall not be amalgamated (and
+ // which we convert to NULL below). When calculated, the NULL value
+ // indicates that we are not amalgamated.
+ //
+ // Note: the amalgamation variable value is always a relative directory.
//
{
auto rp (root.vars.insert (*var_amalgamation)); // Set NULL by default.
@@ -746,7 +770,7 @@ namespace build2
// explicitly configured by the user. After that, #2 followed
// by #1 seems reasonable.
//
- scope& rs (create_root (root, out_root, dir_path ()));
+ scope& rs (create_root (root, out_root, dir_path ())->second);
if (!bootstrapped (rs))
{
@@ -792,7 +816,7 @@ namespace build2
// The same logic to src_root as in create_bootstrap_outer().
//
- scope& rs (create_root (root, out_root, dir_path ()));
+ scope& rs (create_root (root, out_root, dir_path ())->second);
if (!bootstrapped (rs))
{
@@ -1033,7 +1057,7 @@ namespace build2
for (;;)
{
src_root = is_src_root (out_root) ? out_root : dir_path ();
- root = &create_root (iroot, out_root, src_root);
+ root = &create_root (iroot, out_root, src_root)->second;
if (!bootstrapped (*root))
{
diff --git a/build2/file.hxx b/build2/file.hxx
index c394818..d946c88 100644
--- a/build2/file.hxx
+++ b/build2/file.hxx
@@ -72,7 +72,7 @@ namespace build2
// passed src_root value is not empty. The scope argument is only used
// as proof of lock.
//
- scope&
+ scope_map::iterator
create_root (scope&, const dir_path& out_root, const dir_path& src_root);
// Setup root scope. Note that it assume the src_root variable
@@ -90,12 +90,27 @@ namespace build2
// Return a scope for the specified directory (first). Note that switching
// to this scope might also involve switch to a new root scope (second) if
- // the new scope is in another project. In the new scope is not in any
+ // the new scope is in another project. If the new scope is not in any
// project, then NULL is returned in second.
//
pair<scope&, scope*>
switch_scope (scope& root, const dir_path&);
+ // Bootstrap and optionally load an ad hoc (sub)project (i.e., the kind that
+ // is not discovered and loaded automatically by bootstrap/load functions
+ // above).
+ //
+ // Note that we expect the outer project (if any) to be bootstrapped and
+ // loaded and currently we do not add the newly loaded subproject to the
+ // outer project's subprojects map.
+ //
+ // The scope argument is only used as proof of lock.
+ //
+ scope&
+ load_project (scope&,
+ const dir_path& out_root, const dir_path& src_root,
+ bool load = true);
+
// Bootstrap the project's root scope, the out part.
//
void