From 57b10c06925d0bdf6ffb38488ee908f085109e95 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 4 Jul 2019 19:12:15 +0300 Subject: Move config, dist, test, and install modules into library --- libbuild2/config/init.cxx | 159 +++++++ libbuild2/config/init.hxx | 36 ++ libbuild2/config/module.cxx | 54 +++ libbuild2/config/module.hxx | 93 ++++ libbuild2/config/operation.cxx | 997 +++++++++++++++++++++++++++++++++++++++++ libbuild2/config/operation.hxx | 29 ++ libbuild2/config/utility.cxx | 307 +++++++++++++ libbuild2/config/utility.hxx | 179 ++++++++ libbuild2/config/utility.txx | 66 +++ 9 files changed, 1920 insertions(+) create mode 100644 libbuild2/config/init.cxx create mode 100644 libbuild2/config/init.hxx create mode 100644 libbuild2/config/module.cxx create mode 100644 libbuild2/config/module.hxx create mode 100644 libbuild2/config/operation.cxx create mode 100644 libbuild2/config/operation.hxx create mode 100644 libbuild2/config/utility.cxx create mode 100644 libbuild2/config/utility.hxx create mode 100644 libbuild2/config/utility.txx (limited to 'libbuild2/config') diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx new file mode 100644 index 0000000..73275c6 --- /dev/null +++ b/libbuild2/config/init.cxx @@ -0,0 +1,159 @@ +// file : libbuild2/config/init.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include +#include // exists() +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace config + { + bool + boot (scope& rs, const location&, unique_ptr& mod) + { + tracer trace ("config::boot"); + + l5 ([&]{trace << "for " << rs;}); + + 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.insert_meta_operation (configure_id, mo_configure); + rs.insert_meta_operation (disfigure_id, mo_disfigure); + + return true; // Initialize first (load config.build). + } + + bool + init (scope& rs, + scope&, + const location& l, + unique_ptr&, + bool first, + bool, + const variable_map& config_hints) + { + tracer trace ("config::init"); + + if (!first) + { + warn (l) << "multiple config module initializations"; + return true; + } + + const dir_path& out_root (rs.out_path ()); + l5 ([&]{trace << "for " << out_root;}); + + assert (config_hints.empty ()); // We don't known any hints. + + auto& vp (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). + // + const variable& c_v (vp.insert ("config.version", false)); + + { + path f (config_file (rs)); + + if (exists (f)) + { + // 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 (f, c_v)); + uint64_t v (p.second ? cast (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); + } + } + + // Register alias and fallback rule for the configure meta-operation. + // + // We need this rule for out-of-any-project dependencies (e.g., + // libraries imported from /usr/lib). We are registring it on the + // global scope similar to builtin rules. + // + { + auto& r (rs.global ().rules); + r.insert ( + configure_id, 0, "config.file", file_rule::instance); + } + { + auto& r (rs.rules); + + //@@ outer + r.insert (configure_id, 0, "config.alias", alias_rule::instance); + + // This allows a custom configure rule while doing nothing by default. + // + r.insert (configure_id, 0, "config", noop_rule::instance); + r.insert (configure_id, 0, "config.file", noop_rule::instance); + } + + return true; + } + + module_functions + build2_config_load () + { + // Initialize the config entry points in the build system core. + // + config_save_variable = &config::save_variable; + config_preprocess_create = &config::preprocess_create; + + return module_functions {&boot, &init}; + } + } +} diff --git a/libbuild2/config/init.hxx b/libbuild2/config/init.hxx new file mode 100644 index 0000000..ff5e923 --- /dev/null +++ b/libbuild2/config/init.hxx @@ -0,0 +1,36 @@ +// file : libbuild2/config/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CONFIG_INIT_HXX +#define LIBBUILD2_CONFIG_INIT_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace config + { + bool + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + extern "C" LIBBUILD2_SYMEXPORT module_functions + build2_config_load (); + } +} + +#endif // LIBBUILD2_CONFIG_INIT_HXX diff --git a/libbuild2/config/module.cxx b/libbuild2/config/module.cxx new file mode 100644 index 0000000..7e9b765 --- /dev/null +++ b/libbuild2/config/module.cxx @@ -0,0 +1,54 @@ +// file : libbuild2/config/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 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.find_sup (n)); + + // 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/libbuild2/config/module.hxx b/libbuild2/config/module.hxx new file mode 100644 index 0000000..6222319 --- /dev/null +++ b/libbuild2/config/module.hxx @@ -0,0 +1,93 @@ +// file : libbuild2/config/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CONFIG_MODULE_HXX +#define LIBBUILD2_CONFIG_MODULE_HXX + +#include + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + namespace config + { + // An ordered list of modules each with an ordered list of list of + // config.* variables and their "save flags" (see save_variable()) that + // are used (as opposed to just being specified) in this configuration. + // Populated by the config utility functions (required(), optional()) + // and saved in the order populated. + // + struct saved_variable + { + reference_wrapper var; + uint64_t flags; + }; + + struct saved_variables: vector + { + // Normally each module only have a handful of config variables and we + // only do this during configuration so for now we do linear search + // instead of adding a map. + // + const_iterator + find (const variable& var) const + { + return std::find_if ( + begin (), + end (), + [&var] (const saved_variable& v) {return var == v.var;}); + } + }; + + struct saved_modules: butl::prefix_map + { + // Priority order with INT32_MIN being the highest. Modules with the + // same priority are saved in the order inserted. + // + // Generally, the idea is that we want higher-level modules at the top + // of the file since that's the configuration that we usualy want to + // change. So we have the following priority bands/defaults: + // + // 101-200/150 - code generators (e.g., yacc, bison) + // 201-300/250 - compilers (e.g., C, C++), + // 301-400/350 - binutils (ar, ld) + // + std::multimap order; + + iterator + insert (string name, int prio = 0) + { + auto p (emplace (move (name), saved_variables ())); + + if (p.second) + order.emplace (prio, p.first); + + return p.first; + } + }; + + struct module: module_base + { + config::saved_modules saved_modules; + + 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; + }; + } +} + +#endif // LIBBUILD2_CONFIG_MODULE_HXX diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx new file mode 100644 index 0000000..c3ce4b7 --- /dev/null +++ b/libbuild2/config/operation.cxx @@ -0,0 +1,997 @@ +// file : libbuild2/config/operation.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace config + { + // configure + // + static void + save_src_root (const scope& root) + { + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path f (out_root / root.root_extra->src_root_file); + + if (verb >= 2) + text << "cat >" << f; + + try + { + ofdstream ofs (f); + + ofs << "# Created automatically by the config module." << endl + << "#" << endl + << "src_root = "; + to_stream (ofs, name (src_root), true, '@'); // Quote. + ofs << endl; + + ofs.close (); + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + } + + static void + save_out_root (const scope& root) + { + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + path f (src_root / root.root_extra->out_root_file); + + if (verb) + text << (verb >= 2 ? "cat >" : "save ") << f; + + try + { + ofdstream ofs (f); + + ofs << "# Created automatically by the config module." << endl + << "#" << endl + << "out_root = "; + to_stream (ofs, name (out_root), true, '@'); // Quote. + ofs << endl; + + ofs.close (); + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + } + + using project_set = set; // Use pointers to get comparison. + + static void + save_config (const scope& root, const project_set& projects) + { + path f (config_file (root)); + + if (verb) + text << (verb >= 2 ? "cat >" : "save ") << f; + + const module& mod (*root.lookup_module (module::name)); + + try + { + ofdstream ofs (f); + + ofs << "# Created automatically by the config module, but feel " << + "free to edit." << endl + << "#" << endl; + + ofs << "config.version = " << module::version << endl; + + if (auto l = root.vars[var_amalgamation]) + { + const dir_path& d (cast (l)); + + ofs << endl + << "# Base configuration inherited from " << d << endl + << "#" << endl; + } + + // Save config variables. + // + names storage; + + for (auto p: mod.saved_modules.order) + { + const string& sname (p.second->first); + const saved_variables& svars (p.second->second); + + bool first (true); // Separate modules with a blank line. + for (const saved_variable& sv: svars) + { + const variable& var (sv.var); + + pair org (root.find_original (var)); + pair ovr (var.overrides == nullptr + ? org + : root.find_override (var, org)); + const lookup& l (ovr.first); + + // We definitely write values that are set on our root scope or + // are global overrides. Anything in-between is presumably + // inherited. We might also not have any value at all (see + // unconfigured()). + // + if (!l.defined ()) + continue; + + if (!(l.belongs (root) || l.belongs (*global_scope))) + { + // This is presumably an inherited value. But it could also be + // some left-over garbage. For example, an amalgamation could + // have used a module but then dropped it while its config + // values are still lingering in config.build. They are probably + // still valid and we should probably continue using them but we + // definitely want to move them to our config.build since they + // will be dropped from the amalgamation's config.build. Let's + // also warn the user just in case. + // + // There is also another case that falls under this now that + // overrides are by default amalgamation-wide rather than just + // "project and subprojects": we may be (re-)configuring a + // subproject but the override is now set on the outer project's + // root. + // + bool found (false); + const scope* r (&root); + while ((r = r->parent_scope ()->root_scope ()) != nullptr) + { + if (l.belongs (*r)) + { + // Find the config module. + // + if (auto* m = r->lookup_module (module::name)) + { + // Find the corresponding saved module. + // + auto i (m->saved_modules.find (sname)); + + if (i != m->saved_modules.end ()) + { + // Find the variable. + // + const saved_variables& sv (i->second); + found = sv.find (var) != sv.end (); + + // Handle that other case: if this is an override but + // the outer project itself is not being configured, + // then we need to save this override. + // + // One problem with using the already configured project + // set is that the outer project may be configured only + // after us in which case both projects will save the + // value. But perhaps this is a feature, not a bug since + // this is how project-local (%) override behaves. + // + if (found && + org.first != ovr.first && + projects.find (r) == projects.end ()) + found = false; + } + } + + break; + } + } + + 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) + { + 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) + { + dr << info (loc) << "variable value: "; + + if (*l) + { + storage.clear (); + dr << "'" << reverse (*l, storage) << "'"; + } + else + dr << "[null]"; + } + } + } + + const string& n (var.name); + const value& v (*l); + + // We will only write config.*.configured if it is false (true is + // implied by its absence). We will also ignore false values if + // there is any other value for this module (see unconfigured()). + // + if (n.size () > 11 && + n.compare (n.size () - 11, 11, ".configured") == 0) + { + if (cast (v) || svars.size () != 1) + continue; + } + + // If we got here then we are saving this variable. Handle the + // blank line. + // + if (first) + { + ofs << endl; + first = false; + } + + // Handle the save_commented flag. + // + if ((org.first.defined () && org.first->extra) && // Default value. + org.first == ovr.first && // Not overriden. + (sv.flags & save_commented) == save_commented) + { + ofs << '#' << n << " =" << endl; + continue; + } + + if (v) + { + storage.clear (); + names_view ns (reverse (v, storage)); + + ofs << n; + + if (ns.empty ()) + ofs << " ="; + else + { + ofs << " = "; + to_stream (ofs, ns, true, '@'); // Quote. + } + + ofs << endl; + } + else + ofs << n << " = [null]" << endl; + } + } + + ofs.close (); + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + } + + static void + configure_project (action a, const scope& root, project_set& projects) + { + tracer trace ("configure_project"); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + if (!projects.insert (&root).second) + { + l5 ([&]{trace << "skipping already configured " << out_root;}); + return; + } + + // Make sure the directories exist. + // + if (out_root != src_root) + { + mkdir_p (out_root / root.root_extra->build_dir); + mkdir (out_root / root.root_extra->bootstrap_dir, 2); + } + + // We distinguish between a complete configure and operation- + // specific. + // + if (a.operation () == default_id) + { + l5 ([&]{trace << "completely configuring " << out_root;}); + + // Save src-root.build unless out_root is the same as src. + // + if (out_root != src_root) + save_src_root (root); + + // Save config.build. + // + save_config (root, projects); + } + else + { + } + + // Configure subprojects that have been loaded. + // + if (auto l = root.vars[var_subprojects]) + { + for (auto p: cast (l)) + { + const dir_path& pd (p.second); + dir_path out_nroot (out_root / pd); + const scope& nroot (scopes.find (out_nroot)); + + // @@ Strictly speaking we need to check whether the config + // module was loaded for this subproject. + // + if (nroot.out_path () != out_nroot) // This subproject not loaded. + continue; + + configure_project (a, nroot, projects); + } + } + } + + static void + configure_forward (const scope& root, project_set& projects) + { + tracer trace ("configure_forward"); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + if (!projects.insert (&root).second) + { + l5 ([&]{trace << "skipping already configured " << src_root;}); + return; + } + + mkdir (src_root / root.root_extra->bootstrap_dir, 2); // Make sure exists. + save_out_root (root); + + // Configure subprojects. Since we don't load buildfiles if configuring + // a forward, we do it for all known subprojects. + // + if (auto l = root.vars[var_subprojects]) + { + for (auto p: cast (l)) + { + dir_path out_nroot (out_root / p.second); + const scope& nroot (scopes.find (out_nroot)); + assert (nroot.out_path () == out_nroot); + + configure_forward (nroot, projects); + } + } + } + + operation_id (*pre) (const values&, meta_operation_id, const location&); + + static operation_id + configure_operation_pre (const values&, operation_id o) + { + // Don't translate default to update. In our case unspecified + // means configure everything. + // + return o; + } + + // The (vague) idea is that in the future we may turn this into to some + // sort of key-value sequence (similar to the config initializer idea), + // for example: + // + // configure(out/@src/, forward foo bar@123) + // + // Though using commas instead spaces and '=' instead of '@' would have + // been nicer. + // + static bool + forward (const values& params, + const char* mo = nullptr, + const location& l = location ()) + { + if (params.size () == 1) + { + const names& ns (cast (params[0])); + + if (ns.size () == 1 && ns[0].simple () && ns[0].value == "forward") + return true; + else if (!ns.empty ()) + fail (l) << "unexpected parameter '" << ns << "' for " + << "meta-operation " << mo; + } + else if (!params.empty ()) + fail (l) << "unexpected parameters for meta-operation " << mo; + + return false; + } + + static void + configure_pre (const values& params, const location& l) + { + forward (params, "configure", l); // Validate. + } + + static void + configure_load (const values& params, + scope& root, + const path& buildfile, + const dir_path& out_base, + const dir_path& src_base, + const location& l) + { + if (forward (params)) + { + // We don't need to load the buildfiles in order to configure + // forwarding but in order to configure subprojects we have to + // bootstrap them (similar to disfigure). + // + create_bootstrap_inner (root); + + if (root.out_path () == root.src_path ()) + fail (l) << "forwarding to source directory " << root.src_path (); + } + else + load (params, root, buildfile, out_base, src_base, l); // Normal load. + } + + static void + configure_search (const values& params, + const scope& root, + const scope& base, + const path& bf, + const target_key& tk, + const location& l, + action_targets& ts) + { + if (forward (params)) + { + // For forwarding we only collect the projects (again, similar to + // disfigure). + // + ts.push_back (&root); + } + else + search (params, root, base, bf, tk, l, ts); // Normal search. + } + + static void + configure_match (const values&, action, action_targets&, uint16_t, bool) + { + // Don't match anything -- see execute (). + } + + static void + configure_execute (const values& params, + action a, + action_targets& ts, + uint16_t, + bool) + { + bool fwd (forward (params)); + + project_set projects; + + for (const action_target& at: ts) + { + if (fwd) + { + // Forward configuration. + // + const scope& root (*static_cast (at.target)); + configure_forward (root, projects); + continue; + } + + // 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; + + 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]) + { + // Skip aliases (e.g., update-for-install). + // + if (oif->id != id) + continue; + + set_current_oif (*oif); + + phase_lock pl (run_phase::match); + match (action (configure_id, id), t); + } + } + + configure_project (a, *rs, projects); + } + } + + const meta_operation_info mo_configure { + configure_id, + "configure", + "configure", + "configuring", + "configured", + "is configured", + true, // bootstrap_outer + &configure_pre, // meta-operation pre + &configure_operation_pre, + &configure_load, // normal load unless configuring forward + &configure_search, // normal search unless configuring forward + &configure_match, + &configure_execute, + nullptr, // operation post + nullptr, // meta-operation post + nullptr // include + }; + + // disfigure + // + + static bool + disfigure_project (action a, const scope& root, project_set& projects) + { + tracer trace ("disfigure_project"); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + if (!projects.insert (&root).second) + { + l5 ([&]{trace << "skipping already disfigured " << out_root;}); + return false; + } + + bool r (false); // Keep track of whether we actually did anything. + + // Disfigure subprojects. Since we don't load buildfiles during + // disfigure, we do it for all known subprojects. + // + if (auto l = root.vars[var_subprojects]) + { + for (auto p: cast (l)) + { + const dir_path& pd (p.second); + dir_path out_nroot (out_root / pd); + const scope& nroot (scopes.find (out_nroot)); + assert (nroot.out_path () == out_nroot); // See disfigure_load(). + + r = disfigure_project (a, nroot, projects) || r; + + // We use mkdir_p() to create the out_root of a subproject + // which means there could be empty parent directories left + // behind. Clean them up. + // + if (!pd.simple () && out_root != src_root) + { + for (dir_path d (pd.directory ()); + !d.empty (); + d = d.directory ()) + { + rmdir_status s (rmdir (out_root / d, 2)); + + if (s == rmdir_status::not_empty) + break; // No use trying do remove parent ones. + + r = (s == rmdir_status::success) || r; + } + } + } + } + + // We distinguish between a complete disfigure and operation- + // specific. + // + if (a.operation () == default_id) + { + l5 ([&]{trace << "completely disfiguring " << out_root;}); + + r = rmfile (config_file (root)) || r; + + if (out_root != src_root) + { + r = rmfile (out_root / root.root_extra->src_root_file, 2) || r; + + // Clean up the directories. + // + // Note: try to remove the root/ hooks directory if it is empty. + // + r = rmdir (out_root / root.root_extra->root_dir, 2) || r; + r = rmdir (out_root / root.root_extra->bootstrap_dir, 2) || r; + r = rmdir (out_root / root.root_extra->build_dir, 2) || r; + + switch (rmdir (out_root)) + { + case rmdir_status::not_empty: + { + // We used to issue a warning but it is actually a valid usecase + // to leave the build output around in case, for example, of a + // reconfigure. + // + if (verb) + info << "directory " << out_root << " is " + << (out_root == work + ? "current working directory" + : "not empty") << ", not removing"; + break; + } + case rmdir_status::success: + r = true; + default: + break; + } + } + } + else + { + } + + return r; + } + + static bool + disfigure_forward (const scope& root, project_set& projects) + { + // Pretty similar logic to disfigure_project(). + // + tracer trace ("disfigure_forward"); + + const dir_path& out_root (root.out_path ()); + const dir_path& src_root (root.src_path ()); + + if (!projects.insert (&root).second) + { + l5 ([&]{trace << "skipping already disfigured " << src_root;}); + return false; + } + + bool r (false); + + if (auto l = root.vars[var_subprojects]) + { + for (auto p: cast (l)) + { + dir_path out_nroot (out_root / p.second); + const scope& nroot (scopes.find (out_nroot)); + assert (nroot.out_path () == out_nroot); + + r = disfigure_forward (nroot, projects) || r; + } + } + + // Remove the out-root.build file and try to remove the bootstrap/ + // directory if it is empty. + // + r = rmfile (src_root / root.root_extra->out_root_file) || r; + r = rmdir (src_root / root.root_extra->bootstrap_dir, 2) || r; + + return r; + } + + static void + disfigure_pre (const values& params, const location& l) + { + forward (params, "disfigure", l); // Validate. + } + + static operation_id + disfigure_operation_pre (const values&, operation_id o) + { + // Don't translate default to update. In our case unspecified + // means disfigure everything. + // + return o; + } + + static void + disfigure_load (const values&, + scope& root, + const path&, + const dir_path&, + const dir_path&, + const location&) + { + // Since we don't load buildfiles during disfigure but still want to + // disfigure all the subprojects (see disfigure_project() below), we + // bootstrap all the known subprojects. + // + create_bootstrap_inner (root); + } + + static void + disfigure_search (const values&, + const scope& root, + const scope&, + const path&, + const target_key&, + const location&, + action_targets& ts) + { + ts.push_back (&root); + } + + static void + disfigure_match (const values&, action, action_targets&, uint16_t, bool) + { + } + + static void + disfigure_execute (const values& params, + action a, + action_targets& ts, + uint16_t diag, + bool) + { + tracer trace ("disfigure_execute"); + + bool fwd (forward (params)); + + project_set projects; + + // Note: doing everything in the load phase (disfigure_project () does + // modify the build state). + // + for (const action_target& at: ts) + { + const scope& root (*static_cast (at.target)); + + if (!(fwd + ? disfigure_forward ( root, projects) + : disfigure_project (a, root, projects))) + { + // Create a dir{$out_root/} target to signify the project's root in + // diagnostics. Not very clean but seems harmless. + // + target& t ( + targets.insert (dir::static_type, + fwd ? root.src_path () : root.out_path (), + dir_path (), // Out tree. + "", + nullopt, + true, // Implied. + trace).first); + + if (verb != 0 && diag >= 2) + info << diag_done (a, t); + } + } + } + + const meta_operation_info mo_disfigure { + disfigure_id, + "disfigure", + "disfigure", + "disfiguring", + "disfigured", + "is disfigured", + false, // bootstrap_outer + disfigure_pre, // meta-operation pre + &disfigure_operation_pre, + &disfigure_load, + &disfigure_search, + &disfigure_match, + &disfigure_execute, + nullptr, // operation post + nullptr, // meta-operation post + nullptr // include + }; + + // create + // + static void + save_config (const dir_path& d, const variable_overrides& var_ovs) + { + // 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. + // + scope& gs (*scope::global_); + scope& rs (load_project (gs, d, d, false /* fwd */, false /* load */)); + module& m (*rs.lookup_module (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 can be (always is?) one of the overrides + // (__override, __prefix, etc). + // + size_t n (var.override ()); + m.save_variable (n != 0 ? *vp.find (string (var.name, 0, n)) : var); + } + + // 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 ("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 + // 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, dist, and install (config is mandatory). + // + strings bmod {"test", "dist", "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 (); + } + + current_oname = empty_string; // Make sure valid. + + // 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 logic must be consistent + // with find_target_type() and other places (grep for ".."). + // + 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;}); + + // 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); + } + } + + params.clear (); + return mo_configure.name; + } + } +} diff --git a/libbuild2/config/operation.hxx b/libbuild2/config/operation.hxx new file mode 100644 index 0000000..0a88f96 --- /dev/null +++ b/libbuild2/config/operation.hxx @@ -0,0 +1,29 @@ +// file : libbuild2/config/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CONFIG_OPERATION_HXX +#define LIBBUILD2_CONFIG_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace config + { + extern const meta_operation_info mo_configure; + extern const meta_operation_info mo_disfigure; + + const string& + preprocess_create (const variable_overrides&, + values&, + vector_view&, + bool, + const location&); + } +} + +#endif // LIBBUILD2_CONFIG_OPERATION_HXX diff --git a/libbuild2/config/utility.cxx b/libbuild2/config/utility.cxx new file mode 100644 index 0000000..746639d --- /dev/null +++ b/libbuild2/config/utility.cxx @@ -0,0 +1,307 @@ +// file : libbuild2/config/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace config + { + pair + omitted (scope& r, const variable& var) + { + // This is a stripped-down version of the required() twisted + // implementation. + + pair org (r.find_original (var)); + + bool n (false); // New flag. + lookup l (org.first); + + // Treat an inherited value that was set to default as new. + // + if (l.defined () && l->extra) + n = true; + + if (var.overrides != nullptr) + { + pair ovr (r.find_override (var, move (org))); + + if (l != ovr.first) // Overriden? + { + // Override is always treated as new. + // + n = true; + l = move (ovr.first); + } + } + + if (l.defined () && current_mif->id == configure_id) + save_variable (r, var); + + return pair (l, n); + } + + lookup + optional (scope& r, const variable& var) + { + if (current_mif->id == configure_id) + save_variable (r, var); + + auto l (r[var]); + return l.defined () + ? l + : lookup (r.assign (var), var, r); // NULL. + } + + bool + specified (scope& r, const string& n) + { + // Search all outer scopes for any value in this namespace. + // + // What about "pure" overrides, i.e., those without any original values? + // Well, they will also be found since their names have the original + // variable as a prefix. But do they apply? Yes, since we haven't found + // any original values, they will be "visible"; see find_override() for + // details. + // + const variable& vns (var_pool.rw (r).insert ("config." + n)); + for (scope* s (&r); s != nullptr; s = s->parent_scope ()) + { + for (auto p (s->vars.find_namespace (vns)); + p.first != p.second; + ++p.first) + { + const variable& var (p.first->first); + + // Ignore config.*.configured. + // + if (var.name.size () < 11 || + var.name.compare (var.name.size () - 11, 11, ".configured") != 0) + return true; + } + } + + return false; + } + + bool + unconfigured (scope& rs, const string& n) + { + // Pattern-typed in boot() as bool. + // + const variable& var ( + var_pool.rw (rs).insert ("config." + n + ".configured")); + + if (current_mif->id == configure_id) + save_variable (rs, var); + + auto l (rs[var]); // Include inherited values. + return l && !cast (l); + } + + bool + unconfigured (scope& rs, const string& n, bool v) + { + // Pattern-typed in boot() as bool. + // + const variable& var ( + var_pool.rw (rs).insert ("config." + n + ".configured")); + + if (current_mif->id == configure_id) + save_variable (rs, var); + + value& x (rs.assign (var)); + + if (x.null || cast (x) != !v) + { + x = !v; + return true; + } + else + return false; + } + + void + save_variable (scope& r, const variable& var, uint64_t flags) + { + if (current_mif->id != configure_id) + return; + + // The project might not be using the config module. But then how + // could we be configuring it? Good question. + // + if (module* m = r.lookup_module (module::name)) + m->save_variable (var, flags); + } + + void + save_module (scope& r, const char* name, int prio) + { + if (current_mif->id != configure_id) + return; + + if (module* m = r.lookup_module (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. + // + // Note that for now we use the standard build file/directory scheme. + // + mkdir (d / std_build_dir, verbosity); + + // Write build/bootstrap.build. + // + { + path f (d / std_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 / std_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 / std_buildfile_file); + + 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/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx new file mode 100644 index 0000000..e41aaa7 --- /dev/null +++ b/libbuild2/config/utility.hxx @@ -0,0 +1,179 @@ +// file : libbuild2/config/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CONFIG_UTILITY_HXX +#define LIBBUILD2_CONFIG_UTILITY_HXX + +#include +#include + +#include +#include +#include + +#include + +namespace build2 +{ + class scope; + + namespace config + { + // Set, if necessary, a required config.* variable. + // + // If override is true and the variable doesn't come from this root scope + // or from the command line (i.e., it is inherited from the amalgamtion), + // then its value is "overridden" to the default value on this root scope. + // See save_variable() for more information on save_flags. + // + // Return the reference to the value as well as the indication of whether + // the value is "new", that is, it was set to the default value (inherited + // or not, including overrides). We also treat command line overrides + // (inherited or not) as new. This flag is usually used to test that the + // new value is valid, print report, etc. We return the value as lookup + // (always defined) to pass alone its location (could be used to detect + // inheritance, etc). + // + // Note also that if save_flags has save_commented, then a default value + // is never considered "new" since for such variables absence of a value + // means the default value. + // + template + pair + required (scope& root, + const variable&, + const T& default_value, + bool override = false, + uint64_t save_flags = 0); + + // Note that the variable is expected to have already been registered. + // + template + inline pair + required (scope& root, + const string& name, + const T& default_value, + bool override = false, + uint64_t save_flags = 0) + { + return required ( + root, var_pool[name], default_value, override, save_flags); + } + + inline pair + required (scope& root, + const string& name, + const char* default_value, + bool override = false, + uint64_t save_flags = 0) + { + return required ( + root, name, string (default_value), override, save_flags); + } + + // As above, but leave the unspecified value as undefined rather than + // setting it to the default value. + // + // This can be useful when we don't have a default value but may figure + // out some fallback. See config.bin.target for an example. + // + LIBBUILD2_SYMEXPORT pair + omitted (scope& root, const variable&); + + // Note that the variable is expected to have already been registered. + // + inline pair + omitted (scope& root, const string& name) + { + return omitted (root, var_pool[name]); + } + + // Set, if necessary, an optional config.* variable. In particular, an + // unspecified variable is set to NULL which is used to distinguish + // between the "configured as unspecified" and "not yet configured" cases. + // + // Return the value (as always defined lookup), which can be NULL. + // + // @@ Rename since clashes with the optional class template. + // + LIBBUILD2_SYMEXPORT lookup + optional (scope& root, const variable&); + + // Note that the variable is expected to have already been registered. + // + inline lookup + optional (scope& root, const string& name) + { + return optional (root, var_pool[name]); + } + + // Check whether there are any variables specified from the config + // namespace. The idea is that we can check if there are any, say, + // config.install.* values. If there are none, then we can assume + // this functionality is not (yet) used and omit writing a whole + // bunch of NULL config.install.* values to the config.build file. + // We call it omitted/delayed configuration. + // + // Note that this function detects and ignores the special + // config.*.configured variable which may be used by a module to + // "remember" that it is unconfigured (e.g., in order to avoid re- + // running the tests, etc). + // + LIBBUILD2_SYMEXPORT bool + specified (scope& root, const string& name); + + // Check if there is a false config.*.configured value. This mechanism can + // be used to "remember" that the module is left unconfigured in order to + // avoid re-running the tests, etc. + // + LIBBUILD2_SYMEXPORT bool + unconfigured (scope& root, const string& name); + + // Set the config.*.configured value. Note that you only need to set it to + // false. It will be automatically ignored if there are any other config.* + // values for this module. Return true if this sets a new value. + // + LIBBUILD2_SYMEXPORT bool + unconfigured (scope& root, const string& name, bool); + + // Enter the variable so that it is saved during configuration. See + // config::module for details. + // + const uint64_t save_commented = 0x01; // Save default value as commented. + + LIBBUILD2_SYMEXPORT void + save_variable (scope& root, const variable&, uint64_t flags = 0); + + // Establish module order/priority. See config::module for details. + // + LIBBUILD2_SYMEXPORT void + save_module (scope& root, const char* name, int prio = 0); + + // Create a project in the specified directory. + // + LIBBUILD2_SYMEXPORT 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. + + inline path + config_file (const scope& root) + { + return (root.out_path () / + root.root_extra->build_dir / + "config." + root.root_extra->build_ext); + } + } +} + +#include + +#endif // LIBBUILD2_CONFIG_UTILITY_HXX diff --git a/libbuild2/config/utility.txx b/libbuild2/config/utility.txx new file mode 100644 index 0000000..d2ffa69 --- /dev/null +++ b/libbuild2/config/utility.txx @@ -0,0 +1,66 @@ +// file : libbuild2/config/utility.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include + +namespace build2 +{ + namespace config + { + template + pair + required (scope& root, + const variable& var, + const T& def_val, + bool def_ovr, + uint64_t save_flags) + { + // Note: see also omitted() if changing anything here. + + if (current_mif->id == configure_id) + save_variable (root, var, save_flags); + + pair org (root.find_original (var)); + + bool n (false); // New flag. + lookup l (org.first); + + // The interaction with command line overrides can get tricky. For + // example, the override to defaul value could make (non-recursive) + // command line override in the outer scope no longer apply. So what we + // are going to do is first ignore overrides and perform the normal + // logic on the original. Then we apply the overrides on the result. + // + if (!l.defined () || (def_ovr && !l.belongs (root))) + { + value& v (root.assign (var) = def_val); + v.extra = true; // Default value flag. + + n = (save_flags & save_commented) == 0; // Absence means default. + l = lookup (v, var, root); + org = make_pair (l, 1); // Lookup depth is 1 since it's in root.vars. + } + // Treat an inherited value that was set to default as new. + // + else if (l->extra) + n = (save_flags & save_commented) == 0; // Absence means default. + + if (var.overrides != nullptr) + { + pair ovr (root.find_override (var, move (org))); + + if (l != ovr.first) // Overriden? + { + // Override is always treated as new. + // + n = true; + l = move (ovr.first); + } + } + + return pair (l, n); + } + } +} -- cgit v1.1