diff options
Diffstat (limited to 'libbuild2/cli/init.cxx')
-rw-r--r-- | libbuild2/cli/init.cxx | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/libbuild2/cli/init.cxx b/libbuild2/cli/init.cxx new file mode 100644 index 0000000..581fdaf --- /dev/null +++ b/libbuild2/cli/init.cxx @@ -0,0 +1,287 @@ +// file : libbuild2/cli/init.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/cli/init.hxx> + +#include <libbuild2/file.hxx> +#include <libbuild2/scope.hxx> +#include <libbuild2/target.hxx> +#include <libbuild2/variable.hxx> +#include <libbuild2/diagnostics.hxx> + +#include <libbuild2/config/utility.hxx> + +#include <libbuild2/cxx/target.hxx> + +#include <libbuild2/cli/rule.hxx> +#include <libbuild2/cli/module.hxx> +#include <libbuild2/cli/target.hxx> + +namespace build2 +{ + namespace cli + { + // Remaining issues/semantics change: + // + // @@ Unconfigured caching. + // + // @@ Default-found cli used to result in config.cli=cli and now it's just + // omitted (and default-not-found -- in config.cli.configured=false). + // + // - Writing any default will take precedence over config.import.cli. + // In fact, this duality is a bigger problem: if we have a config + // that uses config.cli there is no way to reconfigure it to use + // config.import.cli. + // + // - We could have saved it commented. + // + // - We could do this at the module level only since we also have + // config.cli.options? + // + // - Note that in the CLI compiler itself we now rely on default cli + // being NULL/undefined. So if faving, should probably be commented + // out. BUT: it will still be defined, so will need to be defined + // NULL. Note also that long term the CLI compiler will not use the + // module relying on an ad hoc recipe instead. + // + // ! Maybe reserving NULL (instead of making it the same as NULL) for + // this "configured to default" state and saving commented is not a + // bad idea. Feels right to have some marker in config.build that + // things are in effect. And I believe if config.import.cli is + // specified, it will just be dropped. + + bool + guess_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool opt, + module_init_extra& extra) + { + tracer trace ("cli::guess_init"); + l5 ([&]{trace << "for " << rs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cli.guess module must be loaded in project root"; + + // Adjust module config.build save priority (code generator). + // + config::save_module (rs, "cli", 150); + + // Enter metadata variables. + // + // They are all qualified so go straight for the public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); + + auto& v_ver (vp.insert<string> ("cli.version")); + auto& v_sum (vp.insert<string> ("cli.checksum")); + + // Import the CLI compiler target. + // + // Note that the special config.cli=false value (recognized by the + // import machinery) is treated as an explicit request to leave the + // module unconfigured. + // + bool new_cfg (false); + import_result<exe> ir ( + import_direct<exe> ( + new_cfg, + rs, + name ("cli", dir_path (), "exe", "cli"), // cli%exe{cli} + true /* phase2 */, + opt, + true /* metadata */, + loc, + "module load")); + + const exe* tgt (ir.target); + + // Extract metadata. + // + auto* ver (tgt != nullptr ? &cast<string> (tgt->vars[v_ver]) : nullptr); + auto* sum (tgt != nullptr ? &cast<string> (tgt->vars[v_sum]) : nullptr); + + // Print the report. + // + // If this is a configuration with new values, then print the report + // at verbosity level 2 and up (-v). + // + if (verb >= (new_cfg ? 2 : 3)) + { + diag_record dr (text); + dr << "cli " << project (rs) << '@' << rs << '\n'; + + if (tgt != nullptr) + dr << " cli " << ir << '\n' + << " version " << *ver << '\n' + << " checksum " << *sum; + else + dr << " cli " << "not found, leaving unconfigured"; + } + + if (tgt == nullptr) + return false; + + // The cli variable (untyped) is an imported compiler target name. + // + rs.assign ("cli") = move (ir.name); + rs.assign (v_sum) = *sum; + rs.assign (v_ver) = *ver; + + { + standard_version v (*ver); + + rs.assign<uint64_t> ("cli.version.number") = v.version; + rs.assign<uint64_t> ("cli.version.major") = v.major (); + rs.assign<uint64_t> ("cli.version.minor") = v.minor (); + rs.assign<uint64_t> ("cli.version.patch") = v.patch (); + } + + // Cache some values in the module for easier access in the rule. + // + extra.set_module (new module (data {*tgt, *sum})); + + return true; + } + + bool + config_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool opt, + module_init_extra& extra) + { + tracer trace ("cli::config_init"); + l5 ([&]{trace << "for " << rs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cli.config module must be loaded in project root"; + + // Load cli.guess and share its module instance as ours. + // + if (optional<shared_ptr<build2::module>> r = load_module ( + rs, rs, "cli.guess", loc, opt, extra.hints)) + { + extra.module = *r; + } + else + { + // This can happen if someone already optionally loaded cli.guess + // and it has failed to configure. + // + if (!opt) + fail (loc) << "cli could not be configured" << + info << "re-run with -V for more information"; + + return false; + } + + // Configuration. + // + using config::append_config; + + // config.cli.options + // + // Note that we merge it into the corresponding cli.* variable. + // + append_config<strings> (rs, rs, "cli.options", nullptr); + + return true; + } + + bool + init (scope& rs, + scope& bs, + const location& loc, + bool, + bool opt, + module_init_extra& extra) + { + tracer trace ("cli::init"); + l5 ([&]{trace << "for " << rs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cli module must be loaded in project root"; + + // Make sure the cxx module has been loaded since we need its targets + // types (?xx{}). Note that we don't try to load it ourselves because of + // the non-trivial variable merging semantics. So it is better to let + // the user load cxx explicitly. @@ Not sure the reason still holds + // though it might still make sense to expect the user to load cxx. + // + if (!cast_false<bool> (rs["cxx.loaded"])) + fail (loc) << "cxx module must be loaded before cli"; + + // Load cli.config and get its module instance. + // + if (optional<shared_ptr<build2::module>> r = load_module ( + rs, rs, "cli.config", loc, opt, extra.hints)) + { + extra.module = *r; + } + else + { + // This can happen if someone already optionally loaded cli.config + // and it has failed to configure. + // + if (!opt) + fail (loc) << "cli could not be configured" << + info << "re-run with -V for more information"; + + return false; + } + + auto& m (extra.module_as<module> ()); + + // Register target types. + // + rs.insert_target_type<cli> (); + rs.insert_target_type<cli_cxx> (); + + // Register our rules. + // + // Other rules (e.g., cc::compile) may need to have the group members + // resolved/linked up. Looks like a general pattern: groups should + // resolve on *(update). + { + auto reg = [&rs, &m] (meta_operation_id mid, operation_id oid) + { + rs.insert_rule<cli_cxx> (mid, oid, "cli.compile", m); + rs.insert_rule<cxx::hxx> (mid, oid, "cli.compile", m); + rs.insert_rule<cxx::cxx> (mid, oid, "cli.compile", m); + rs.insert_rule<cxx::ixx> (mid, oid, "cli.compile", m); + }; + + reg (0 /* wildcard */, update_id); + reg (perform_id, clean_id); + } + + return true; + } + + static const module_functions mod_functions[] = + { + // NOTE: don't forget to also update the documentation in init.hxx if + // changing anything here. + + {"cli.guess", nullptr, guess_init}, + {"cli.config", nullptr, config_init}, + {"cli", nullptr, init}, + {nullptr, nullptr, nullptr} + }; + + const module_functions* + build2_cli_load () + { + return mod_functions; + } + } +} |