From 074a8c04a384a9752466bd2af69b695333b2955c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 29 Nov 2017 17:44:48 +0200 Subject: Reimplement module sidebuilding using an ad hoc subproject --- build2/b.cxx | 19 ++--- build2/cc/compile.cxx | 112 +++++++++++++++++++-------- build2/config/operation.cxx | 179 ++++++++------------------------------------ build2/config/utility.cxx | 150 +++++++++++++++++++++++++++++++++++++ build2/config/utility.hxx | 16 ++++ build2/cxx/init.cxx | 3 + build2/file.cxx | 54 +++++++++---- build2/file.hxx | 19 ++++- 8 files changed, 348 insertions(+), 204 deletions(-) (limited to 'build2') 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 // exit() #include // strlen() +#include #include #include #include @@ -15,6 +16,7 @@ #include #include +#include // create_project() #include #include // 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 ("cxx")); - if (m != nullptr && m->modules) + // Use cc.core.vars as a proxy for {c,cxx}.config (a bit smelly). + // + if (cast_false ((*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 (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 (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//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::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 +#include #include +#include +#include #include @@ -151,5 +154,152 @@ namespace build2 if (module* m = r.modules.lookup (module::name)) m->save_module (name, prio); } + + void + create_project (const dir_path& d, + const build2::optional& 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& 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 (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 (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 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 -- cgit v1.1