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. --- libbuild2/cli/rule.cxx | 340 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 libbuild2/cli/rule.cxx (limited to 'libbuild2/cli/rule.cxx') 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; + } + } +} -- cgit v1.1