From 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 11:55:15 +0200 Subject: Rename build directory/namespace to build2 --- build2/cli/module | 23 ++++ build2/cli/module.cxx | 244 ++++++++++++++++++++++++++++++++++++++++ build2/cli/rule | 32 ++++++ build2/cli/rule.cxx | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++ build2/cli/target | 62 ++++++++++ build2/cli/target.cxx | 77 +++++++++++++ 6 files changed, 743 insertions(+) create mode 100644 build2/cli/module create mode 100644 build2/cli/module.cxx create mode 100644 build2/cli/rule create mode 100644 build2/cli/rule.cxx create mode 100644 build2/cli/target create mode 100644 build2/cli/target.cxx (limited to 'build2/cli') diff --git a/build2/cli/module b/build2/cli/module new file mode 100644 index 0000000..f2890c9 --- /dev/null +++ b/build2/cli/module @@ -0,0 +1,23 @@ +// file : build2/cli/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_MODULE +#define BUILD2_CLI_MODULE + +#include +#include + +#include + +namespace build2 +{ + namespace cli + { + extern "C" bool + cli_init ( + scope&, scope&, const location&, unique_ptr&, bool, bool); + } +} + +#endif // BUILD2_CLI_MODULE diff --git a/build2/cli/module.cxx b/build2/cli/module.cxx new file mode 100644 index 0000000..eafa4a0 --- /dev/null +++ b/build2/cli/module.cxx @@ -0,0 +1,244 @@ +// file : build2/cli/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + static compile compile_; + + extern "C" bool + cli_init (scope& root, + scope& base, + const location& loc, + std::unique_ptr&, + bool first, + bool optional) + { + tracer trace ("cli::init"); + level5 ([&]{trace << "for " << base.out_path ();}); + + // 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. + // + { + auto l (base["cxx.loaded"]); + + if (!l || !as (*l)) + fail (loc) << "cxx module must be loaded before cli"; + } + + // Enter module variables. + // + if (first) + { + auto& v (var_pool); + + v.find ("config.cli.configured", bool_type); + + v.find ("config.cli", string_type); //@@ VAR type + + v.find ("config.cli.options", strings_type); + v.find ("cli.options", strings_type); + } + + // Register target types. + // + { + auto& t (base.target_types); + + t.insert (); + t.insert (); + } + + // Configure. + // + // The plan is as follows: try to configure the module. If this fails, + // we are using default values, and the module is optional, leave it + // unconfigured. + // + + // We will only honor optional if the user didn't specify any cli + // configuration explicitly. + // + optional = optional && !config::specified (root, "config.cli"); + + // Don't re-run tests if the configuration says we are unconfigured. + // + if (optional) + { + auto l (root["config.cli.configured"]); + + if (l && !as (*l)) + return false; + } + + // config.cli + // + if (first) + { + // Return version or empty string if unable to execute (e.g., + // the cli executable is not found). + // + auto test = [optional] (const char* cli) -> string + { + const char* args[] = {cli, "--version", nullptr}; + + if (verb >= 2) + print_process (args); + else if (verb) + text << "test " << cli; + + string ver; + try + { + process pr (args, 0, -1); // Open pipe to stdout. + ifdstream is (pr.in_ofd); + + // The version should be the last word on the first line. + // + getline (is, ver); + auto p (ver.rfind (' ')); + if (p != string::npos) + ver = string (ver, p + 1); + + is.close (); // Don't block the other end. + + if (!pr.wait ()) + return string (); // Not found. + + if (ver.empty ()) + fail << "unexpected output from " << cli; + + return ver; + } + catch (const process_error& e) + { + // In some cases this is not enough (e.g., the runtime linker + // will print scary errors if some shared libraries are not + // found. So it would be good to redirect child's STDERR. + // + if (!optional) + error << "unable to execute " << cli << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + }; + + string ver; + const char* cli ("cli"); // Default. + + if (optional) + { + // Test the default value before setting any config.cli.* values + // so that if we fail to configure, nothing will be written to + // config.build. + // + ver = test (cli); + + if (ver.empty ()) + { + // Note that we are unconfigured so that we don't keep re-testing + // this on each run. + // + root.assign ("config.cli.configured") = false; + + if (verb >= 2) + text << cli << " not found, leaving cli module unconfigured"; + + return false; + } + else + { + auto p (config::required (root, "config.cli", cli)); + assert (p.second && as (p.first) == cli); + } + } + else + { + auto p (config::required (root, "config.cli", cli)); + + // If we actually set a new value, test it by trying to execute. + // + if (p.second) + { + cli = as (p.first).c_str (); + ver = test (cli); + + if (ver.empty ()) + throw failed (); + } + } + + // Clear the unconfigured flag, if any. + // + root.assign ("config.cli.configured") = true; + + if (!ver.empty () && verb >= 2) + text << cli << " " << ver; + } + + // config.cli.options + // + // This one is optional. We also merge it into the corresponding + // cli.* variables. See the cxx module for more information on + // this merging semantics and some of its tricky aspects. + // + if (const value& v = config::optional (root, "config.cli.options")) + base.assign ("cli.options") += as (v); + + // Register our rules. + // + { + auto& r (base.rules); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + r.insert (perform_update_id, "cli.compile", compile_); + r.insert (perform_clean_id, "cli.compile", compile_); + + // Other rules (e.g., cxx::compile) may need to have the group + // members resolved. Looks like a general pattern: groups should + // resolve on configure(update). + // + r.insert (configure_update_id, "cli.compile", compile_); + } + + return true; + } + } +} diff --git a/build2/cli/rule b/build2/cli/rule new file mode 100644 index 0000000..6c154e2 --- /dev/null +++ b/build2/cli/rule @@ -0,0 +1,32 @@ +// file : build2/cli/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_RULE +#define BUILD2_CLI_RULE + +#include + +namespace build2 +{ + namespace cli + { + class compile: public rule + { + public: + virtual match_result + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, const match_result&) const; + + static target_state + perform_update (action, target&); + + static target_state + perform_clean (action, target&); + }; + } +} + +#endif // BUILD2_CLI_RULE diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx new file mode 100644 index 0000000..ae1f48a --- /dev/null +++ b/build2/cli/rule.cxx @@ -0,0 +1,305 @@ +// file : build2/cli/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + match_result compile:: + match (action a, target& xt, const std::string&) const + { + tracer trace ("cli::compile::match"); + + if (cli_cxx* pt = xt.is_a ()) + { + // The cli.cxx{} group. + // + cli_cxx& t (*pt); + + // See if we have a .cli source file. + // + match_result r; + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a ()) + { + // Check that the stems match. + // + if (t.name != p.name ()) + { + level4 ([&]{trace << ".cli file stem '" << p.name () << "' " + << "doesn't match target " << t;}); + return r; + } + + r = p; + break; + } + } + + if (!r) + { + level4 ([&]{trace << "no .cli source file for target " << t;}); + return r; + } + + // If we still haven't figured out the member list, we can do + // that now. Specifically, at this stage, no further changes to + // cli.options are possible and we can determine whether the + // --suppress-inline option is present. + // + if (t.h == nullptr) + { + t.h = &search (t.dir, t.name, nullptr, nullptr); + t.h->group = &t; + + t.c = &search (t.dir, t.name, nullptr, nullptr); + t.c->group = &t; + + if (!config::find_option ("--suppress-inline", t, "cli.options")) + { + t.i = &search (t.dir, t.name, nullptr, nullptr); + t.i->group = &t; + } + } + + return r; + } + else + { + // One of the ?xx{} members. + // + target& t (xt); + + // First see if we are already linked-up to the cli.cxx{} group. + // If it is some other group, then we are definitely not a match. + // + if (t.group != nullptr) + return t.group->is_a (); + + // Then check if there is a corresponding cli.cxx{} group. + // + cli_cxx* g (targets.find (t.dir, 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 group. + // + if (g == nullptr || !g->has_prerequisites ()) + { + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (p.is_a ()) + { + // Check that the stems match. + // + if (t.name == p.name ()) + { + if (g == nullptr) + g = &targets.insert (t.dir, t.name, trace); + + g->prerequisites.emplace_back (p.as_prerequisite (trace)); + } + else + level4 ([&]{trace << ".cli file stem '" << p.name () << "' " + << "doesn't match target " << t;}); + break; + } + } + } + + if (g != nullptr) + { + // Resolve the group's members. This should link us up to + // the group. + // + resolve_group_members (a, *g); + + // For ixx{}, verify it is part of the group. + // + if (t.is_a () && g->i == nullptr) + { + level4 ([&]{trace << "generation of inline file " << t + << " is disabled with --suppress-inline";}); + g = nullptr; + } + } + + assert (t.group == g); + return g; + } + } + + recipe compile:: + apply (action a, target& xt, const match_result& mr) 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_parent_fsdir (a, t); + + // Search and match prerequisite members. + // + search_and_match_prerequisite_members (a, t); + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return noop_recipe; // Configure update. + } + } + else + { + cli_cxx& g (*static_cast (mr.target)); + build2::match (a, g); + return group_recipe; // Execute the group's recipe. + } + } + + static void + append_extension (cstrings& args, + path_target& t, + const char* option, + const char* default_extension) + { + assert (t.ext != nullptr); // Should have been figured out in apply(). + + if (*t.ext != 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 (t.ext->empty () + ? t.ext->c_str () + : t.path ().extension () - 1); + } + } + + target_state compile:: + perform_update (action a, target& xt) + { + cli_cxx& t (static_cast (xt)); + + // Execute our prerequsites and check if we are out of date. + // + cli* s (execute_prerequisites (a, t, t.mtime ())); + + if (s == nullptr) + return target_state::unchanged; + + // Translate paths to relative (to working directory). This + // results in easier to read diagnostics. + // + path relo (relative (t.dir)); + path rels (relative (s->path ())); + + scope& rs (t.root_scope ()); + const string& cli (as (*rs["config.cli"])); + + cstrings args {cli.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"); + + config::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) + text << "cli " << *s; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + + t.mtime (system_clock::now ()); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + return target_state::changed; + } + + target_state compile:: + perform_clean (action a, target& xt) + { + cli_cxx& t (static_cast (xt)); + + // The reverse order of update: first delete the files, then clean + // prerequisites. Also update timestamp in case there are operations + // after us that could use the information. + // + bool r (false); + + if (t.i != nullptr) + r = rmfile (t.i->path (), *t.i) || r; + r = rmfile (t.c->path (), *t.c) || r; + r = rmfile (t.h->path (), *t.h) || r; + + t.mtime (timestamp_nonexistent); + + target_state ts (r ? target_state::changed : target_state::unchanged); + + // Clean prerequisites. + // + ts |= reverse_execute_prerequisites (a, t); + + return ts; + } + } +} diff --git a/build2/cli/target b/build2/cli/target new file mode 100644 index 0000000..dde20be --- /dev/null +++ b/build2/cli/target @@ -0,0 +1,62 @@ +// file : build2/cli/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_TARGET +#define BUILD2_CLI_TARGET + +#include + +#include + +namespace build2 +{ + namespace cli + { + class cli: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class cli_cxx: public mtime_target + { + public: + using mtime_target::mtime_target; + + union + { + // It is theoretically possible that the compiler will add + // padding between the members of this struct. This would + // mean that the optimal alignment for a pointer is greater + // than its size (and that an array of pointers is sub- + // optimally aligned). We will deal with such a beast of + // an architecture when we see it. + // + struct + { + cxx::hxx* h; + cxx::cxx* c; + cxx::ixx* i; + }; + target* m[3] = {nullptr, nullptr, nullptr}; + }; + + virtual group_view + group_members (action_type) const; + + virtual timestamp + load_mtime () const; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_CLI_TARGET diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx new file mode 100644 index 0000000..ef69e88 --- /dev/null +++ b/build2/cli/target.cxx @@ -0,0 +1,77 @@ +// file : build2/cli/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cli + { + // cli + // + constexpr const char cli_ext_var[] = "extension"; + constexpr const char cli_ext_def[] = "cli"; + + const target_type cli::static_type + { + "cli", + &file::static_type, + &target_factory, + &target_extension_var, + &search_file, + false + }; + + // cli.cxx + // + group_view cli_cxx:: + group_members (action_type) const + { + return h != nullptr + ? group_view {m, (i != nullptr ? 3U : 2U)} + : group_view {nullptr, 0}; + } + + timestamp cli_cxx:: + load_mtime () const + { + // The rule has been matched which means the members should + // be resolved and paths assigned. + // + return file_mtime (h->path ()); + } + + static target* + cli_cxx_factory (const target_type&, dir_path d, string n, const string* e) + { + 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. + // + targets.insert (d, n, trace); + targets.insert (d, n, trace); + targets.insert (d, n, trace); + + return new cli_cxx (move (d), move (n), e); + } + + const target_type cli_cxx::static_type + { + "cli.cxx", + &mtime_target::static_type, + &cli_cxx_factory, + nullptr, + &search_target, + true // "See through" default iteration mode. + }; + } +} -- cgit v1.1