From 894813b993963de006d0a8aa7480b0403daaa87a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 13 Apr 2023 13:55:00 +0200 Subject: Move cli module to libbuild2-cli library This is a temporary measure (until we unboundle this module) needed for in-process configure support in bpkg. --- build2/b.cxx | 5 +- build2/buildfile | 2 +- build2/cli/init.cxx | 287 --------------------------------------- build2/cli/init.hxx | 29 ---- build2/cli/module.hxx | 30 ----- build2/cli/rule.cxx | 340 ----------------------------------------------- build2/cli/rule.hxx | 43 ------ build2/cli/target.cxx | 75 ----------- build2/cli/target.hxx | 58 -------- libbuild2/buildfile | 2 +- libbuild2/cli/buildfile | 71 ++++++++++ libbuild2/cli/export.hxx | 37 ++++++ libbuild2/cli/init.cxx | 287 +++++++++++++++++++++++++++++++++++++++ libbuild2/cli/init.hxx | 31 +++++ libbuild2/cli/module.hxx | 30 +++++ libbuild2/cli/rule.cxx | 340 +++++++++++++++++++++++++++++++++++++++++++++++ libbuild2/cli/rule.hxx | 46 +++++++ libbuild2/cli/target.cxx | 75 +++++++++++ libbuild2/cli/target.hxx | 61 +++++++++ libbuild2/module.cxx | 1 + 20 files changed, 983 insertions(+), 867 deletions(-) delete mode 100644 build2/cli/init.cxx delete mode 100644 build2/cli/init.hxx delete mode 100644 build2/cli/module.hxx delete mode 100644 build2/cli/rule.cxx delete mode 100644 build2/cli/rule.hxx delete mode 100644 build2/cli/target.cxx delete mode 100644 build2/cli/target.hxx create mode 100644 libbuild2/cli/buildfile create mode 100644 libbuild2/cli/export.hxx create mode 100644 libbuild2/cli/init.cxx create mode 100644 libbuild2/cli/init.hxx create mode 100644 libbuild2/cli/module.hxx create mode 100644 libbuild2/cli/rule.cxx create mode 100644 libbuild2/cli/rule.hxx create mode 100644 libbuild2/cli/target.cxx create mode 100644 libbuild2/cli/target.hxx diff --git a/build2/b.cxx b/build2/b.cxx index 1cfb019..ee0d58c 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -54,8 +54,7 @@ #ifndef BUILD2_BOOTSTRAP # include - -# include +# include #endif using namespace butl; @@ -384,8 +383,8 @@ main (int argc, char* argv[]) load_builtin_module (&in::build2_in_load); #ifndef BUILD2_BOOTSTRAP - load_builtin_module (&cli::build2_cli_load); load_builtin_module (&bash::build2_bash_load); + load_builtin_module (&cli::build2_cli_load); #endif // Start up the scheduler and allocate lock shards. diff --git a/build2/buildfile b/build2/buildfile index 0c21388..5ae8aa0 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -11,7 +11,7 @@ libs = $libbutl include ../libbuild2/ libs += ../libbuild2/lib{build2} -for m: bash bin c cc cxx in version +for m: bash bin c cc cli cxx in version { include ../libbuild2/$m/ libs += ../libbuild2/$m/lib{build2-$m} diff --git a/build2/cli/init.cxx b/build2/cli/init.cxx deleted file mode 100644 index d7d8251..0000000 --- a/build2/cli/init.cxx +++ /dev/null @@ -1,287 +0,0 @@ -// file : build2/cli/init.cxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -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 ("cli.version")); - auto& v_sum (vp.insert ("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 ir ( - import_direct ( - 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 (tgt->vars[v_ver]) : nullptr); - auto* sum (tgt != nullptr ? &cast (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 ("cli.version.number") = v.version; - rs.assign ("cli.version.major") = v.major (); - rs.assign ("cli.version.minor") = v.minor (); - rs.assign ("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> 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 (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 (rs["cxx.loaded"])) - fail (loc) << "cxx module must be loaded before cli"; - - // Load cli.config and get its module instance. - // - if (optional> 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 ()); - - // Register target types. - // - rs.insert_target_type (); - rs.insert_target_type (); - - // 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 (mid, oid, "cli.compile", m); - rs.insert_rule (mid, oid, "cli.compile", m); - rs.insert_rule (mid, oid, "cli.compile", m); - rs.insert_rule (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; - } - } -} diff --git a/build2/cli/init.hxx b/build2/cli/init.hxx deleted file mode 100644 index 1c54316..0000000 --- a/build2/cli/init.hxx +++ /dev/null @@ -1,29 +0,0 @@ -// file : build2/cli/init.hxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_INIT_HXX -#define BUILD2_CLI_INIT_HXX - -#include -#include - -#include - -namespace build2 -{ - namespace cli - { - // Module `cli` does not require bootstrapping. - // - // Submodules: - // - // `cli.guess` -- set variables describing the compiler. - // `cli.config` -- load `cli.guess` and set the rest of the variables. - // `cli` -- load `cli.config` and register targets and rules. - // - extern "C" const module_functions* - build2_cli_load (); - } -} - -#endif // BUILD2_CLI_INIT_HXX diff --git a/build2/cli/module.hxx b/build2/cli/module.hxx deleted file mode 100644 index 70f6ba8..0000000 --- a/build2/cli/module.hxx +++ /dev/null @@ -1,30 +0,0 @@ -// file : build2/cli/module.hxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_MODULE_HXX -#define BUILD2_CLI_MODULE_HXX - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace cli - { - class module: public build2::module, - public virtual data, - public compile_rule - { - public: - explicit - module (data&& d) - : data (move (d)), compile_rule (move (d)) {} - }; - } -} - -#endif // BUILD2_CLI_MODULE_HXX diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx deleted file mode 100644 index 7f610c9..0000000 --- a/build2/cli/rule.cxx +++ /dev/null @@ -1,340 +0,0 @@ -// file : build2/cli/rule.cxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace build2 -{ - namespace cli - { - // Figure out if name contains stem and, optionally, calculate prefix and - // suffix. - // - static bool - match_stem (const string& name, const string& stem, - string* prefix = nullptr, string* suffix = nullptr) - { - size_t p (name.find (stem)); - - if (p != string::npos) - { - if (prefix != nullptr) - prefix->assign (name, 0, p); - - if (suffix != nullptr) - suffix->assign (name, p + stem.size (), string::npos); - - return true; - } - - return false; - } - - bool compile_rule:: - match (action a, target& t) const - { - tracer trace ("cli::compile_rule::match"); - - // Find the .cli source file. - // - auto find = [&trace, a, &t] (auto&& r) -> optional - { - for (prerequisite_member p: r) - { - // If excluded or ad hoc, then don't factor it into our tests. - // - if (include (a, t, p) != include_type::normal) - continue; - - if (p.is_a ()) - { - // Check that the stem match. - // - if (match_stem (t.name, p.name ())) - return p; - - l4 ([&]{trace << ".cli file stem '" << p.name () << "' " - << "doesn't match target " << t;}); - } - } - - return nullopt; - }; - - if (cli_cxx* pt = t.is_a ()) - { - // The cli.cxx{} group. - // - cli_cxx& t (*pt); - - // See if we have a .cli source file. - // - if (!find (group_prerequisite_members (a, t))) - { - l4 ([&]{trace << "no .cli source file for target " << t;}); - return false; - } - - // Figure out the member list. - // - // At this stage, no further changes to cli.options are possible and - // we can determine whether the --suppress-inline option is present. - // - // Passing the group as a "reference target" is a bit iffy, - // conceptually. - // - t.h = &search (t, t.dir, t.out, t.name); - t.c = &search (t, t.dir, t.out, t.name); - t.i = find_option ("--suppress-inline", t, "cli.options") - ? nullptr - : &search (t, t.dir, t.out, t.name); - - return true; - } - else - { - // One of the ?xx{} members. - // - - // Check if there is a corresponding cli.cxx{} group. - // - const cli_cxx* g (t.ctx.targets.find (t.dir, t.out, t.name)); - - // If not or if it has no prerequisites (happens when we use it to - // set cli.options) and this target has a cli{} prerequisite, then - // synthesize the dependency. - // - if (g == nullptr || !g->has_prerequisites ()) - { - if (optional p = find ( - prerequisite_members (a, t))) - { - if (g == nullptr) - g = &t.ctx.targets.insert (t.dir, t.out, t.name, trace); - - prerequisites ps; - ps.push_back (p->as_prerequisite ()); - g->prerequisites (move (ps)); - } - } - - if (g == nullptr) - return false; - - // For ixx{}, verify it is part of the group (i.e., not disabled - // via --suppress-inline). - // - if (t.is_a () && - find_option ("--suppress-inline", *g, "cli.options")) - return false; - - t.group = g; - return true; - } - } - - recipe compile_rule:: - apply (action a, target& xt) const - { - if (cli_cxx* pt = xt.is_a ()) - { - cli_cxx& t (*pt); - - // Derive file names for the members. - // - t.h->derive_path (); - t.c->derive_path (); - if (t.i != nullptr) - t.i->derive_path (); - - // Inject dependency on the output directory. - // - inject_fsdir (a, t); - - // Match prerequisites. - // - match_prerequisite_members (a, t); - - // For update inject dependency on the CLI compiler target. - // - if (a == perform_update_id) - inject (a, t, ctgt); - - switch (a) - { - case perform_update_id: return [this] (action a, const target& t) - { - return perform_update (a, t); - }; - case perform_clean_id: return &perform_clean_group_depdb; - default: return noop_recipe; // Configure/dist update. - } - } - else - { - const cli_cxx& g (xt.group->as ()); - match_sync (a, g); - return group_recipe; // Execute the group's recipe. - } - } - - static void - append_extension (cstrings& args, - const path_target& t, - const char* option, - const char* default_extension) - { - const string* e (t.ext ()); - assert (e != nullptr); // Should have been figured out in apply(). - - if (*e != default_extension) - { - // CLI needs the extension with the leading dot (unless it is empty) - // while we store the extension without. But if there is an extension, - // then we can get it (with the dot) from the file name. - // - args.push_back (option); - args.push_back (e->empty () - ? e->c_str () - : t.path ().extension_cstring () - 1); - } - } - - target_state compile_rule:: - perform_update (action a, const target& xt) const - { - tracer trace ("cli::compile_rule::perform_update"); - - // The rule has been matched which means the members should be resolved - // and paths assigned. We use the header file as our "target path" for - // timestamp, depdb, etc. - // - const cli_cxx& t (xt.as ()); - const path& tp (t.h->path ()); - - context& ctx (t.ctx); - - // Update prerequisites and determine if any relevant ones render us - // out-of-date. Note that currently we treat all the prerequisites as - // potentially affecting the result (think prologues/epilogues, CLI - // compiler target itself, etc). - // - timestamp mt (t.load_mtime (tp)); - auto pr (execute_prerequisites (a, t, mt)); - - bool update (!pr.first); - target_state ts (update ? target_state::changed : *pr.first); - - const cli& s (pr.second); - - // We use depdb to track changes to the .cli file name, options, - // compiler, etc. - // - depdb dd (tp + ".d"); - { - // First should come the rule name/version. - // - if (dd.expect ("cli.compile 1") != nullptr) - l4 ([&]{trace << "rule mismatch forcing update of " << t;}); - - // Then the compiler checksum. - // - if (dd.expect (csum) != nullptr) - l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); - - // Then the options checksum. - // - sha256 cs; - append_options (cs, t, "cli.options"); - - if (dd.expect (cs.string ()) != nullptr) - l4 ([&]{trace << "options mismatch forcing update of " << t;}); - - // Finally the .cli input file. - // - if (dd.expect (s.path ()) != nullptr) - l4 ([&]{trace << "input file mismatch forcing update of " << t;}); - } - - // Update if depdb mismatch. - // - if (dd.writing () || dd.mtime > mt) - update = true; - - dd.close (); - - // If nothing changed, then we are done. - // - if (!update) - return ts; - - // Translate paths to relative (to working directory). This results in - // easier to read diagnostics. - // - path relo (relative (t.dir)); - path rels (relative (s.path ())); - - const process_path& pp (ctgt.process_path ()); - cstrings args {pp.recall_string ()}; - - // See if we need to pass --output-{prefix,suffix} - // - string prefix, suffix; - match_stem (t.name, s.name, &prefix, &suffix); - - if (!prefix.empty ()) - { - args.push_back ("--output-prefix"); - args.push_back (prefix.c_str ()); - } - - if (!suffix.empty ()) - { - args.push_back ("--output-suffix"); - args.push_back (suffix.c_str ()); - } - - // See if we need to pass any --?xx-suffix options. - // - append_extension (args, *t.h, "--hxx-suffix", "hxx"); - append_extension (args, *t.c, "--cxx-suffix", "cxx"); - if (t.i != nullptr) - append_extension (args, *t.i, "--ixx-suffix", "ixx"); - - append_options (args, t, "cli.options"); - - if (!relo.empty ()) - { - args.push_back ("-o"); - args.push_back (relo.string ().c_str ()); - } - - args.push_back (rels.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - else if (verb) - print_diag ("cli", s, t); - - if (!ctx.dry_run) - { - run (ctx, pp, args, 1 /* finish_verbosity */); - dd.check_mtime (tp); - } - - t.mtime (system_clock::now ()); - return target_state::changed; - } - } -} diff --git a/build2/cli/rule.hxx b/build2/cli/rule.hxx deleted file mode 100644 index 0538c57..0000000 --- a/build2/cli/rule.hxx +++ /dev/null @@ -1,43 +0,0 @@ -// file : build2/cli/rule.hxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_RULE_HXX -#define BUILD2_CLI_RULE_HXX - -#include -#include - -#include - -namespace build2 -{ - namespace cli - { - // Cached data shared between rules and the module. - // - struct data - { - const exe& ctgt; // CLI compiler target. - const string& csum; // CLI compiler checksum. - }; - - // @@ Redo as two separate rules? - // - class compile_rule: public simple_rule, virtual data - { - public: - compile_rule (data&& d): data (move (d)) {} - - virtual bool - match (action, target&) const override; - - virtual recipe - apply (action, target&) const override; - - target_state - perform_update (action, const target&) const; - }; - } -} - -#endif // BUILD2_CLI_RULE_HXX diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx deleted file mode 100644 index 37eee97..0000000 --- a/build2/cli/target.cxx +++ /dev/null @@ -1,75 +0,0 @@ -// file : build2/cli/target.cxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#include - -#include - -namespace build2 -{ - namespace cli - { - // cli - // - extern const char cli_ext_def[] = "cli"; - - const target_type cli::static_type - { - "cli", - &file::static_type, - &target_factory, - nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, - nullptr, - &file_search, - target_type::flag::none - }; - - // cli.cxx - // - group_view cli_cxx:: - group_members (action) const - { - static_assert (sizeof (cli_cxx_members) == sizeof (const target*) * 3, - "member layout incompatible with array"); - - return h != nullptr - ? group_view {reinterpret_cast (&h), - (i != nullptr ? 3U : 2U)} - : group_view {nullptr, 0}; - } - - static target* - cli_cxx_factory (context& ctx, - const target_type&, dir_path d, dir_path o, string n) - { - tracer trace ("cli::cli_cxx_factory"); - - // Pre-enter (potential) members as targets. The main purpose of doing - // this is to avoid searching for existing files in src_base if the - // buildfile mentions some of them explicitly as prerequisites. - // - // Also required for the src-out remapping logic. - // - ctx.targets.insert (d, o, n, trace); - ctx.targets.insert (d, o, n, trace); - ctx.targets.insert (d, o, n, trace); - - return new cli_cxx (ctx, move (d), move (o), move (n)); - } - - const target_type cli_cxx::static_type - { - "cli.cxx", - &mtime_target::static_type, - &cli_cxx_factory, - nullptr, - nullptr, - nullptr, - nullptr, - &target_search, - target_type::flag::see_through // Group with "see through" iteration. - }; - } -} diff --git a/build2/cli/target.hxx b/build2/cli/target.hxx deleted file mode 100644 index f27ee89..0000000 --- a/build2/cli/target.hxx +++ /dev/null @@ -1,58 +0,0 @@ -// file : build2/cli/target.hxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_TARGET_HXX -#define BUILD2_CLI_TARGET_HXX - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace cli - { - class cli: public file - { - public: - cli (context& c, dir_path d, dir_path o, string n) - : file (c, move (d), move (o), move (n)) - { - dynamic_type = &static_type; - } - - public: - static const target_type static_type; - }; - - // Standard layout type compatible with group_view's const target*[3]. - // - struct cli_cxx_members - { - const cxx::hxx* h = nullptr; - const cxx::cxx* c = nullptr; - const cxx::ixx* i = nullptr; - }; - - class cli_cxx: public mtime_target, public cli_cxx_members - { - public: - cli_cxx (context& c, dir_path d, dir_path o, string n) - : mtime_target (c, move (d), move (o), move (n)) - { - dynamic_type = &static_type; - } - - virtual group_view - group_members (action) const override; - - public: - static const target_type static_type; - }; - } -} - -#endif // BUILD2_CLI_TARGET_HXX diff --git a/libbuild2/buildfile b/libbuild2/buildfile index 6707b05..b3a9c52 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -4,7 +4,7 @@ # NOTE: remember to update bundled_modules in libbuild2/module.cxx if adding a # new module. # -bundled_modules = bash/ bin/ c/ cc/ cxx/ in/ version/ +bundled_modules = bash/ bin/ c/ cc/ cli/ cxx/ in/ version/ ./: lib{build2} $bundled_modules diff --git a/libbuild2/cli/buildfile b/libbuild2/cli/buildfile new file mode 100644 index 0000000..9b6e4eb --- /dev/null +++ b/libbuild2/cli/buildfile @@ -0,0 +1,71 @@ +# file : libbuild2/cli/buildfile +# license : MIT; see accompanying LICENSE file + +# NOTE: shared imports should go into root.build. +# +include ../ +impl_libs = ../lib{build2} # Implied interface dependency. + +include ../cxx/ +intf_libs = ../cxx/lib{build2-cxx} + +./: lib{build2-cli}: libul{build2-cli}: {hxx ixx txx cxx}{** -**.test...} \ + $intf_libs $impl_libs + +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} + $d/exe{$n}: libul{build2-cli}: bin.whole = false +} + +# Build options. +# +obja{*}: cxx.poptions += -DLIBBUILD2_CLI_STATIC_BUILD +objs{*}: cxx.poptions += -DLIBBUILD2_CLI_SHARED_BUILD + +# Export options. +# +lib{build2-cli}: +{ + cxx.export.poptions = "-I$out_root" "-I$src_root" + cxx.export.libs = $intf_libs +} + +liba{build2-cli}: cxx.export.poptions += -DLIBBUILD2_CLI_STATIC +libs{build2-cli}: cxx.export.poptions += -DLIBBUILD2_CLI_SHARED + +# For pre-releases use the complete version to make sure they cannot be used +# in place of another pre-release or the final version. See the version module +# for details on the version.* variable values. +# +# And because this is a build system module, we also embed the same value as +# the interface version (note that we cannot use build.version.interface for +# bundled modules because we could be built with a different version of the +# build system). +# +ver = ($version.pre_release \ + ? "$version.project_id" \ + : "$version.major.$version.minor") + +lib{build2-cli}: bin.lib.version = @"-$ver" +libs{build2-cli}: bin.lib.load_suffix = "-$ver" + +# Install into the libbuild2/cli/ subdirectory of, say, /usr/include/ +# recreating subdirectories. +# +{hxx ixx txx}{*}: +{ + install = include/libbuild2/cli/ + install.subdirs = true +} diff --git a/libbuild2/cli/export.hxx b/libbuild2/cli/export.hxx new file mode 100644 index 0000000..67c1eb9 --- /dev/null +++ b/libbuild2/cli/export.hxx @@ -0,0 +1,37 @@ +// file : libbuild2/cli/export.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#pragma once + +// Normally we don't export class templates (but do complete specializations), +// inline functions, and classes with only inline member functions. Exporting +// classes that inherit from non-exported/imported bases (e.g., std::string) +// will end up badly. The only known workarounds are to not inherit or to not +// export. Also, MinGW GCC doesn't like seeing non-exported functions being +// used before their inline definition. The workaround is to reorder code. In +// the end it's all trial and error. + +#if defined(LIBBUILD2_CLI_STATIC) // Using static. +# define LIBBUILD2_CLI_SYMEXPORT +#elif defined(LIBBUILD2_CLI_STATIC_BUILD) // Building static. +# define LIBBUILD2_CLI_SYMEXPORT +#elif defined(LIBBUILD2_CLI_SHARED) // Using shared. +# ifdef _WIN32 +# define LIBBUILD2_CLI_SYMEXPORT __declspec(dllimport) +# else +# define LIBBUILD2_CLI_SYMEXPORT +# endif +#elif defined(LIBBUILD2_CLI_SHARED_BUILD) // Building shared. +# ifdef _WIN32 +# define LIBBUILD2_CLI_SYMEXPORT __declspec(dllexport) +# else +# define LIBBUILD2_CLI_SYMEXPORT +# endif +#else +// If none of the above macros are defined, then we assume we are being used +// by some third-party build system that cannot/doesn't signal the library +// type. Note that this fallback works for both static and shared but in case +// of shared will be sub-optimal compared to having dllimport. +// +# define LIBBUILD2_CLI_SYMEXPORT // Using static or shared. +#endif 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 + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +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 ("cli.version")); + auto& v_sum (vp.insert ("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 ir ( + import_direct ( + 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 (tgt->vars[v_ver]) : nullptr); + auto* sum (tgt != nullptr ? &cast (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 ("cli.version.number") = v.version; + rs.assign ("cli.version.major") = v.major (); + rs.assign ("cli.version.minor") = v.minor (); + rs.assign ("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> 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 (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 (rs["cxx.loaded"])) + fail (loc) << "cxx module must be loaded before cli"; + + // Load cli.config and get its module instance. + // + if (optional> 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 ()); + + // Register target types. + // + rs.insert_target_type (); + rs.insert_target_type (); + + // 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 (mid, oid, "cli.compile", m); + rs.insert_rule (mid, oid, "cli.compile", m); + rs.insert_rule (mid, oid, "cli.compile", m); + rs.insert_rule (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; + } + } +} diff --git a/libbuild2/cli/init.hxx b/libbuild2/cli/init.hxx new file mode 100644 index 0000000..6d23795 --- /dev/null +++ b/libbuild2/cli/init.hxx @@ -0,0 +1,31 @@ +// file : libbuild2/cli/init.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CLI_INIT_HXX +#define LIBBUILD2_CLI_INIT_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace cli + { + // Module `cli` does not require bootstrapping. + // + // Submodules: + // + // `cli.guess` -- set variables describing the compiler. + // `cli.config` -- load `cli.guess` and set the rest of the variables. + // `cli` -- load `cli.config` and register targets and rules. + // + extern "C" LIBBUILD2_CLI_SYMEXPORT const module_functions* + build2_cli_load (); + } +} + +#endif // LIBBUILD2_CLI_INIT_HXX diff --git a/libbuild2/cli/module.hxx b/libbuild2/cli/module.hxx new file mode 100644 index 0000000..ba10540 --- /dev/null +++ b/libbuild2/cli/module.hxx @@ -0,0 +1,30 @@ +// file : libbuild2/cli/module.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CLI_MODULE_HXX +#define LIBBUILD2_CLI_MODULE_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace cli + { + class module: public build2::module, + public virtual data, + public compile_rule + { + public: + explicit + module (data&& d) + : data (move (d)), compile_rule (move (d)) {} + }; + } +} + +#endif // LIBBUILD2_CLI_MODULE_HXX diff --git a/libbuild2/cli/rule.cxx b/libbuild2/cli/rule.cxx new file mode 100644 index 0000000..996ca51 --- /dev/null +++ b/libbuild2/cli/rule.cxx @@ -0,0 +1,340 @@ +// file : libbuild2/cli/rule.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace build2 +{ + namespace cli + { + // Figure out if name contains stem and, optionally, calculate prefix and + // suffix. + // + static bool + match_stem (const string& name, const string& stem, + string* prefix = nullptr, string* suffix = nullptr) + { + size_t p (name.find (stem)); + + if (p != string::npos) + { + if (prefix != nullptr) + prefix->assign (name, 0, p); + + if (suffix != nullptr) + suffix->assign (name, p + stem.size (), string::npos); + + return true; + } + + return false; + } + + bool compile_rule:: + match (action a, target& t) const + { + tracer trace ("cli::compile_rule::match"); + + // Find the .cli source file. + // + auto find = [&trace, a, &t] (auto&& r) -> optional + { + for (prerequisite_member p: r) + { + // If excluded or ad hoc, then don't factor it into our tests. + // + if (include (a, t, p) != include_type::normal) + continue; + + if (p.is_a ()) + { + // Check that the stem match. + // + if (match_stem (t.name, p.name ())) + return p; + + l4 ([&]{trace << ".cli file stem '" << p.name () << "' " + << "doesn't match target " << t;}); + } + } + + return nullopt; + }; + + if (cli_cxx* pt = t.is_a ()) + { + // The cli.cxx{} group. + // + cli_cxx& t (*pt); + + // See if we have a .cli source file. + // + if (!find (group_prerequisite_members (a, t))) + { + l4 ([&]{trace << "no .cli source file for target " << t;}); + return false; + } + + // Figure out the member list. + // + // At this stage, no further changes to cli.options are possible and + // we can determine whether the --suppress-inline option is present. + // + // Passing the group as a "reference target" is a bit iffy, + // conceptually. + // + t.h = &search (t, t.dir, t.out, t.name); + t.c = &search (t, t.dir, t.out, t.name); + t.i = find_option ("--suppress-inline", t, "cli.options") + ? nullptr + : &search (t, t.dir, t.out, t.name); + + return true; + } + else + { + // One of the ?xx{} members. + // + + // Check if there is a corresponding cli.cxx{} group. + // + const cli_cxx* g (t.ctx.targets.find (t.dir, t.out, t.name)); + + // If not or if it has no prerequisites (happens when we use it to + // set cli.options) and this target has a cli{} prerequisite, then + // synthesize the dependency. + // + if (g == nullptr || !g->has_prerequisites ()) + { + if (optional p = find ( + prerequisite_members (a, t))) + { + if (g == nullptr) + g = &t.ctx.targets.insert (t.dir, t.out, t.name, trace); + + prerequisites ps; + ps.push_back (p->as_prerequisite ()); + g->prerequisites (move (ps)); + } + } + + if (g == nullptr) + return false; + + // For ixx{}, verify it is part of the group (i.e., not disabled + // via --suppress-inline). + // + if (t.is_a () && + find_option ("--suppress-inline", *g, "cli.options")) + return false; + + t.group = g; + return true; + } + } + + recipe compile_rule:: + apply (action a, target& xt) const + { + if (cli_cxx* pt = xt.is_a ()) + { + cli_cxx& t (*pt); + + // Derive file names for the members. + // + t.h->derive_path (); + t.c->derive_path (); + if (t.i != nullptr) + t.i->derive_path (); + + // Inject dependency on the output directory. + // + inject_fsdir (a, t); + + // Match prerequisites. + // + match_prerequisite_members (a, t); + + // For update inject dependency on the CLI compiler target. + // + if (a == perform_update_id) + inject (a, t, ctgt); + + switch (a) + { + case perform_update_id: return [this] (action a, const target& t) + { + return perform_update (a, t); + }; + case perform_clean_id: return &perform_clean_group_depdb; + default: return noop_recipe; // Configure/dist update. + } + } + else + { + const cli_cxx& g (xt.group->as ()); + match_sync (a, g); + return group_recipe; // Execute the group's recipe. + } + } + + static void + append_extension (cstrings& args, + const path_target& t, + const char* option, + const char* default_extension) + { + const string* e (t.ext ()); + assert (e != nullptr); // Should have been figured out in apply(). + + if (*e != default_extension) + { + // CLI needs the extension with the leading dot (unless it is empty) + // while we store the extension without. But if there is an extension, + // then we can get it (with the dot) from the file name. + // + args.push_back (option); + args.push_back (e->empty () + ? e->c_str () + : t.path ().extension_cstring () - 1); + } + } + + target_state compile_rule:: + perform_update (action a, const target& xt) const + { + tracer trace ("cli::compile_rule::perform_update"); + + // The rule has been matched which means the members should be resolved + // and paths assigned. We use the header file as our "target path" for + // timestamp, depdb, etc. + // + const cli_cxx& t (xt.as ()); + const path& tp (t.h->path ()); + + context& ctx (t.ctx); + + // Update prerequisites and determine if any relevant ones render us + // out-of-date. Note that currently we treat all the prerequisites as + // potentially affecting the result (think prologues/epilogues, CLI + // compiler target itself, etc). + // + timestamp mt (t.load_mtime (tp)); + auto pr (execute_prerequisites (a, t, mt)); + + bool update (!pr.first); + target_state ts (update ? target_state::changed : *pr.first); + + const cli& s (pr.second); + + // We use depdb to track changes to the .cli file name, options, + // compiler, etc. + // + depdb dd (tp + ".d"); + { + // First should come the rule name/version. + // + if (dd.expect ("cli.compile 1") != nullptr) + l4 ([&]{trace << "rule mismatch forcing update of " << t;}); + + // Then the compiler checksum. + // + if (dd.expect (csum) != nullptr) + l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); + + // Then the options checksum. + // + sha256 cs; + append_options (cs, t, "cli.options"); + + if (dd.expect (cs.string ()) != nullptr) + l4 ([&]{trace << "options mismatch forcing update of " << t;}); + + // Finally the .cli input file. + // + if (dd.expect (s.path ()) != nullptr) + l4 ([&]{trace << "input file mismatch forcing update of " << t;}); + } + + // Update if depdb mismatch. + // + if (dd.writing () || dd.mtime > mt) + update = true; + + dd.close (); + + // If nothing changed, then we are done. + // + if (!update) + return ts; + + // Translate paths to relative (to working directory). This results in + // easier to read diagnostics. + // + path relo (relative (t.dir)); + path rels (relative (s.path ())); + + const process_path& pp (ctgt.process_path ()); + cstrings args {pp.recall_string ()}; + + // See if we need to pass --output-{prefix,suffix} + // + string prefix, suffix; + match_stem (t.name, s.name, &prefix, &suffix); + + if (!prefix.empty ()) + { + args.push_back ("--output-prefix"); + args.push_back (prefix.c_str ()); + } + + if (!suffix.empty ()) + { + args.push_back ("--output-suffix"); + args.push_back (suffix.c_str ()); + } + + // See if we need to pass any --?xx-suffix options. + // + append_extension (args, *t.h, "--hxx-suffix", "hxx"); + append_extension (args, *t.c, "--cxx-suffix", "cxx"); + if (t.i != nullptr) + append_extension (args, *t.i, "--ixx-suffix", "ixx"); + + append_options (args, t, "cli.options"); + + if (!relo.empty ()) + { + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + } + + args.push_back (rels.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + else if (verb) + print_diag ("cli", s, t); + + if (!ctx.dry_run) + { + run (ctx, pp, args, 1 /* finish_verbosity */); + dd.check_mtime (tp); + } + + t.mtime (system_clock::now ()); + return target_state::changed; + } + } +} diff --git a/libbuild2/cli/rule.hxx b/libbuild2/cli/rule.hxx new file mode 100644 index 0000000..0132b44 --- /dev/null +++ b/libbuild2/cli/rule.hxx @@ -0,0 +1,46 @@ +// file : libbuild2/cli/rule.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CLI_RULE_HXX +#define LIBBUILD2_CLI_RULE_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace cli + { + // Cached data shared between rules and the module. + // + struct data + { + const exe& ctgt; // CLI compiler target. + const string& csum; // CLI compiler checksum. + }; + + // @@ Redo as two separate rules? + // + class LIBBUILD2_CLI_SYMEXPORT compile_rule: public simple_rule, + private virtual data + { + public: + compile_rule (data&& d): data (move (d)) {} + + virtual bool + match (action, target&) const override; + + virtual recipe + apply (action, target&) const override; + + target_state + perform_update (action, const target&) const; + }; + } +} + +#endif // LIBBUILD2_CLI_RULE_HXX diff --git a/libbuild2/cli/target.cxx b/libbuild2/cli/target.cxx new file mode 100644 index 0000000..22ae75c --- /dev/null +++ b/libbuild2/cli/target.cxx @@ -0,0 +1,75 @@ +// file : libbuild2/cli/target.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +namespace build2 +{ + namespace cli + { + // cli + // + extern const char cli_ext_def[] = "cli"; + + const target_type cli::static_type + { + "cli", + &file::static_type, + &target_factory, + nullptr, /* fixed_extension */ + &target_extension_var, + &target_pattern_var, + nullptr, + &file_search, + target_type::flag::none + }; + + // cli.cxx + // + group_view cli_cxx:: + group_members (action) const + { + static_assert (sizeof (cli_cxx_members) == sizeof (const target*) * 3, + "member layout incompatible with array"); + + return h != nullptr + ? group_view {reinterpret_cast (&h), + (i != nullptr ? 3U : 2U)} + : group_view {nullptr, 0}; + } + + static target* + cli_cxx_factory (context& ctx, + const target_type&, dir_path d, dir_path o, string n) + { + tracer trace ("cli::cli_cxx_factory"); + + // Pre-enter (potential) members as targets. The main purpose of doing + // this is to avoid searching for existing files in src_base if the + // buildfile mentions some of them explicitly as prerequisites. + // + // Also required for the src-out remapping logic. + // + ctx.targets.insert (d, o, n, trace); + ctx.targets.insert (d, o, n, trace); + ctx.targets.insert (d, o, n, trace); + + return new cli_cxx (ctx, move (d), move (o), move (n)); + } + + const target_type cli_cxx::static_type + { + "cli.cxx", + &mtime_target::static_type, + &cli_cxx_factory, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + target_type::flag::see_through // Group with "see through" iteration. + }; + } +} diff --git a/libbuild2/cli/target.hxx b/libbuild2/cli/target.hxx new file mode 100644 index 0000000..8efb837 --- /dev/null +++ b/libbuild2/cli/target.hxx @@ -0,0 +1,61 @@ +// file : libbuild2/cli/target.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CLI_TARGET_HXX +#define LIBBUILD2_CLI_TARGET_HXX + +#include +#include + +#include + +#include + +#include + +namespace build2 +{ + namespace cli + { + class LIBBUILD2_CLI_SYMEXPORT cli: public file + { + public: + cli (context& c, dir_path d, dir_path o, string n) + : file (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // Standard layout type compatible with group_view's const target*[3]. + // + struct cli_cxx_members + { + const cxx::hxx* h = nullptr; + const cxx::cxx* c = nullptr; + const cxx::ixx* i = nullptr; + }; + + class LIBBUILD2_CLI_SYMEXPORT cli_cxx: public mtime_target, + public cli_cxx_members + { + public: + cli_cxx (context& c, dir_path d, dir_path o, string n) + : mtime_target (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + virtual group_view + group_members (action) const override; + + public: + static const target_type static_type; + }; + } +} + +#endif // LIBBUILD2_CLI_TARGET_HXX diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx index 0928307..4c1271c 100644 --- a/libbuild2/module.cxx +++ b/libbuild2/module.cxx @@ -50,6 +50,7 @@ namespace build2 "bin", "c", "cc", + "cli", "cxx", "in", "version" -- cgit v1.1