From 83f8b6a45fc041586819537ca86be2eb534f79b0 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 16 Mar 2017 18:14:16 +0200 Subject: Implement create meta-operation --- build2/config/init.cxx | 47 ++++---- build2/config/module | 10 +- build2/config/module.cxx | 66 +++++++++++ build2/config/operation | 7 ++ build2/config/operation.cxx | 283 +++++++++++++++++++++++++++++++++++++++++++- build2/config/utility.cxx | 44 +------ 6 files changed, 392 insertions(+), 65 deletions(-) create mode 100644 build2/config/module.cxx (limited to 'build2/config') diff --git a/build2/config/init.cxx b/build2/config/init.cxx index 682d817..65aa7bb 100644 --- a/build2/config/init.cxx +++ b/build2/config/init.cxx @@ -22,18 +22,36 @@ namespace build2 { namespace config { - const string module::name ("config"); - const uint64_t module::version (1); - void - boot (scope& rs, const location& loc, unique_ptr&) + boot (scope& rs, const location& loc, unique_ptr& mod) { tracer trace ("config::boot"); const dir_path& out_root (rs.out_path ()); l5 ([&]{trace << "for " << out_root;}); - // Register meta-operations. + const string& mname (*current_mname); + const string& oname (*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. + // + if (( mname == "configure" || mname == "create") || + (mname.empty () && (oname == "configure" || oname == "create"))) + { + unique_ptr m (new module); + + // Adjust priority for the import pseudo-module so that + // config.import.* values come first in config.build. + // + m->save_module ("import", INT32_MIN); + + mod = move (m); + } + + // Register meta-operations. Note that we don't register create_id + // since it will be pre-processed into configure. // rs.meta_operations.insert (configure_id, configure); rs.meta_operations.insert (disfigure_id, disfigure); @@ -54,11 +72,10 @@ namespace build2 // const variable& c_v (vp.insert ("config.version", false)); - // Don't load it if we are disfiguring. This is a bit tricky since the - // build2 core may not yet know it is disfiguring. But we know. + // Don't load it if we are disfiguring. The same situation as with + // module loading above. // - if (*current_mname != disfigure.name && - (!current_mname->empty () || *current_oname != disfigure.name)) + if (mname != "disfigure" && (!mname.empty () || oname != "disfigure")) { path f (out_root / config_file); @@ -96,7 +113,7 @@ namespace build2 init (scope& rs, scope&, const location& l, - unique_ptr& mod, + unique_ptr&, bool first, bool, const variable_map& config_hints) @@ -113,16 +130,6 @@ namespace build2 assert (config_hints.empty ()); // We don't known any hints. - // Only create the module if we are configuring. - // - if (current_mif->id == configure_id) - mod.reset (new module); - - // Adjust priority for the import pseudo-module so that config.import.* - // values come first in config.build. - // - config::save_module (rs, "import", INT32_MIN); - // Register alias and fallback rule for the configure meta-operation. // { diff --git a/build2/config/module b/build2/config/module index ddd54d6..3b58aa0 100644 --- a/build2/config/module +++ b/build2/config/module @@ -79,8 +79,14 @@ namespace build2 { config::saved_modules saved_modules; - static const string name; // init.cxx - static const uint64_t version; // init.cxx + void + save_variable (const variable&, uint64_t flags = 0); + + void + save_module (const char* name, int prio = 0); + + static const string name; + static const uint64_t version; }; } } diff --git a/build2/config/module.cxx b/build2/config/module.cxx new file mode 100644 index 0000000..6d54e46 --- /dev/null +++ b/build2/config/module.cxx @@ -0,0 +1,66 @@ +// file : build2/config/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build2 +{ + namespace config + { + void module:: + save_variable (const variable& var, uint64_t flags) + { + const string& n (var.name); + + // First try to find the module with the name that is the longest + // prefix of this variable name. + // + auto& sm (saved_modules); + auto i (sm.end ()); + + if (!sm.empty ()) + { + i = sm.upper_bound (n); + + // Get the greatest less than, if any. We might still not be a + // suffix. And we still have to check the last element if + // upper_bound() returned end(). + // + if (i == sm.begin () || !sm.key_comp ().prefix ((--i)->first, n)) + i = sm.end (); + } + + // If no module matched, then create one based on the variable name. + // + if (i == sm.end ()) + { + // @@ For now with 'config.' prefix. + // + i = sm.insert (string (n, 0, n.find ('.', 7))); + } + + // Don't insert duplicates. The config.import vars are particularly + // susceptible to duplication. + // + saved_variables& sv (i->second); + auto j (sv.find (var)); + + if (j == sv.end ()) + sv.push_back (saved_variable {var, flags}); + else + assert (j->flags == flags); + } + + void module:: + save_module (const char* name, int prio) + { + saved_modules.insert (string ("config.") += name, prio); + } + + const string module::name ("config"); + const uint64_t module::version (1); + } +} diff --git a/build2/config/operation b/build2/config/operation index f2f0538..425869c 100644 --- a/build2/config/operation +++ b/build2/config/operation @@ -16,6 +16,13 @@ namespace build2 { extern const meta_operation_info configure; extern const meta_operation_info disfigure; + + const string& + preprocess_create (const variable_overrides&, + values&, + vector_view&, + bool, + const location&); } } diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index a8874fd..2285dd9 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -413,7 +414,6 @@ namespace build2 // disfigure // - static void load_project (scope& root) { @@ -640,5 +640,286 @@ namespace build2 nullptr, // operation post nullptr, // meta-operation post }; + + // create + // + static void + create_project (const dir_path& d, + const strings& bmod, // Bootstrap modules. + const strings& rmod, // Root modules. + const variable_overrides& var_ovs) + { + // 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); + + // 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); + + ofs << "# Generated by the create meta-operation. Edit if you " << + "know what you are doing." << endl + << "#" << endl + << "project =" << 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); + } + + module& m (*rs.modules.lookup (module::name)); + + // Save all the global config.import.* variables. + // + variable_pool& vp (var_pool.rw (rs)); + for (auto p (gs.vars.find_namespace (vp.insert ("config.import"))); + p.first != p.second; + ++p.first) + { + const variable& var (p.first->first); + + // Annoyingly, this is one of the __override/__prefix/__suffix + // values. So we strip the last component. + // + size_t n (var.name.size ()); + + if (var.name.compare (n - 11, 11, ".__override") == 0) + n -= 11; + else if (var.name.compare (n - 9, 9, ".__prefix") == 0) + n -= 9; + else if (var.name.compare (n - 9, 9, ".__suffix") == 0) + n -= 9; + + m.save_variable (*vp.find (string (var.name, 0, n))); + } + + // Now project-specific. For now we just save all of them and let + // save_config() above weed out the ones that don't apply. + // + for (const variable_override& vo: var_ovs) + { + const variable& var (vo.var); + + if (var.name.compare (0, 14, "config.import.") == 0) + m.save_variable (var); + } + } + + const string& + preprocess_create (const variable_overrides& var_ovs, + values& params, + vector_view& spec, + bool lifted, + const location& l) + { + tracer trace ("create_project"); + + // The overall plan is to create the project(s), update the buildspec, + // clear the parameters, and then continue as if we were the configure + // meta-operation. + + // Start with process parameters. The first parameter, if any, is a list + // of root.build modules. The second parameter, if any, is a list of + // bootstrap.build modules. If the second is not specified, then the + // default is test and install (config is mandatory). + // + strings bmod {"test", "install"}; + strings rmod; + try + { + size_t n (params.size ()); + + if (n > 0) + rmod = convert (move (params[0])); + + if (n > 1) + bmod = convert (move (params[1])); + + if (n > 2) + fail (l) << "unexpected parameters for meta-operation create"; + } + catch (const invalid_argument& e) + { + fail (l) << "invalid module name: " << e.what (); + } + + // Now handle each target in each operation spec. + // + for (const opspec& os: spec) + { + // First do some sanity checks: there should be no explicit operation + // and our targets should all be directories. + // + if (!lifted && !os.name.empty ()) + fail (l) << "explicit operation specified for meta-operation create"; + + for (const targetspec& ts: os) + { + const name& tn (ts.name); + + // Figure out the project directory. This code must be consistent + // with find_target_type() and other places. + // + dir_path d; + + if (tn.simple () && + (tn.empty () || tn.value == "." || tn.value == "..")) + d = dir_path (tn.value); + else if (tn.directory ()) + d = tn.dir; + else if (tn.typed () && tn.type == "dir") + d = tn.dir / dir_path (tn.value); + else + fail(l) << "non-directory target '" << ts << "' in " + << "meta-operation create"; + + if (d.relative ()) + d = work / d; + + d.normalize (true); + + // If src_base was explicitly specified, make sure it is the same as + // the project directory. + // + if (!ts.src_base.empty ()) + { + dir_path s (ts.src_base); + + if (s.relative ()) + s = work / s; + + s.normalize (true); + + if (s != d) + fail(l) << "different src/out directories for target '" << ts + << "' in meta-operation create"; + } + + l5 ([&]{trace << "creating project in " << d;}); + + create_project (d, bmod, rmod, var_ovs); + } + } + + params.clear (); + return configure.name; + } } } diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx index 08faeeb..ea66e92 100644 --- a/build2/config/utility.cxx +++ b/build2/config/utility.cxx @@ -139,47 +139,7 @@ namespace build2 // could we be configuring it? Good question. // if (module* m = r.modules.lookup (module::name)) - { - const string& n (var.name); - - // First try to find the module with the name that is the longest - // prefix of this variable name. - // - saved_modules& sm (m->saved_modules); - auto i (sm.end ()); - - if (!sm.empty ()) - { - i = sm.upper_bound (n); - - // Get the greatest less than, if any. We might still not be a - // suffix. And we still have to check the last element if - // upper_bound() returned end(). - // - if (i == sm.begin () || !sm.key_comp ().prefix ((--i)->first, n)) - i = sm.end (); - } - - // If no module matched, then create one based on the variable name. - // - if (i == sm.end ()) - { - // @@ For now with 'config.' prefix. - // - i = sm.insert (string (n, 0, n.find ('.', 7))); - } - - // Don't insert duplicates. The config.import vars are particularly - // susceptible to duplication. - // - saved_variables& sv (i->second); - auto j (sv.find (var)); - - if (j == sv.end ()) - sv.push_back (saved_variable {var, flags}); - else - assert (j->flags == flags); - } + m->save_variable (var, flags); } void @@ -189,7 +149,7 @@ namespace build2 return; if (module* m = r.modules.lookup (module::name)) - m->saved_modules.insert (string ("config.") += name, prio); + m->save_module (name, prio); } } } -- cgit v1.1