diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-06-24 13:53:28 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-06-24 13:53:28 +0200 |
commit | e815af118562c68794efbd310c887acd8eae800c (patch) | |
tree | cedd8745cce259693c038c309d663a682c982e98 /build/cli | |
parent | 4f52c4ed65883dacef32587cf066fbb1182c6628 (diff) |
First take on the cli module plus necessary infrastructure
Diffstat (limited to 'build/cli')
-rw-r--r-- | build/cli/module | 20 | ||||
-rw-r--r-- | build/cli/module.cxx | 147 | ||||
-rw-r--r-- | build/cli/rule | 35 | ||||
-rw-r--r-- | build/cli/rule.cxx | 312 | ||||
-rw-r--r-- | build/cli/target | 51 | ||||
-rw-r--r-- | build/cli/target.cxx | 46 |
6 files changed, 611 insertions, 0 deletions
diff --git a/build/cli/module b/build/cli/module new file mode 100644 index 0000000..c2bc0ab --- /dev/null +++ b/build/cli/module @@ -0,0 +1,20 @@ +// file : build/cli/module -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CLI_MODULE +#define BUILD_CLI_MODULE + +#include <build/types> +#include <build/module> + +namespace build +{ + namespace cli + { + void + init (scope&, scope&, const location&); + } +} + +#endif // BUILD_CLI_MODULE diff --git a/build/cli/module.cxx b/build/cli/module.cxx new file mode 100644 index 0000000..96da40d --- /dev/null +++ b/build/cli/module.cxx @@ -0,0 +1,147 @@ +// file : build/cli/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build/cli/module> + +#include <butl/process> +#include <butl/fdstream> + +#include <build/scope> +#include <build/target> +#include <build/variable> +#include <build/diagnostics> + +#include <build/cxx/target> +#include <build/cxx/module> + +#include <build/config/utility> + +#include <build/cli/target> +#include <build/cli/rule> + +using namespace std; +using namespace butl; + +namespace build +{ + namespace cli + { + static compile compile_; + + void + init (scope& root, scope& base, const location& l) + { + //@@ TODO: avoid multiple inits (generally, for modules). + // + tracer trace ("cli::init"); + + //@@ Should it be this way? + // + if (&root != &base) + fail (l) << "cli module must be initialized in project root scope"; + + // Initialize the cxx module. We need its targets types (?xx{}). + // + cxx::init (root, base, l); + + const dir_path& out_root (root.path ()); + level4 ([&]{trace << "for " << out_root;}); + + // Register our target types. + // + target_types.insert (cli::static_type); + target_types.insert (cli_cxx::static_type); + + // Register our rules. + // + rules[default_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); + rules[update_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); + rules[clean_id][typeid (cli_cxx)].emplace ("cli.compile", compile_); + + rules[default_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); + rules[update_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); + rules[clean_id][typeid (cxx::cxx)].emplace ("cli.compile", compile_); + + rules[default_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); + rules[update_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); + rules[clean_id][typeid (cxx::hxx)].emplace ("cli.compile", compile_); + + rules[default_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); + rules[update_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); + rules[clean_id][typeid (cxx::ixx)].emplace ("cli.compile", compile_); + + // Configure. + // + + // config.cli + // + { + auto r (config::required (root, "config.cli", "cli")); + + // If we actually set a new value, test it by trying to execute. + // + if (r.second) + { + const string& cli (r.first); + const char* args[] = {cli.c_str (), "--version", nullptr}; + + if (verb) + print_process (args); + else + text << "test " << cli; + + string ver; + try + { + process pr (args, false, false, true); + ifdstream is (pr.in_ofd); + + for (bool first (true); !is.eof (); ) + { + string l; + getline (is, l); + + if (first) + { + // The version is the last word on the first line. + // + auto p (l.rfind (' ')); + if (p != string::npos) + ver = string (l, p + 1); + + first = false; + } + } + + if (!pr.wait ()) + throw failed (); + + if (ver.empty ()) + fail << "unexpected output from " << cli; + } + catch (const process_error& e) + { + error << "unable to execute " << cli << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + + if (verb) + text << cli << " " << ver; + } + } + + // config.cli.options + // + // This one is optional. We also merge it into the corresponding + // cli.* variables. + // + if (auto* v = config::optional<list_value> (root, "config.cli.options")) + root.append ("cli.options") += *v; + } + } +} diff --git a/build/cli/rule b/build/cli/rule new file mode 100644 index 0000000..0f38381 --- /dev/null +++ b/build/cli/rule @@ -0,0 +1,35 @@ +// file : build/cli/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CLI_RULE +#define BUILD_CLI_RULE + +#include <build/rule> + +namespace build +{ + namespace cli + { + class compile: public rule + { + public: + virtual void* + match (action, target&, const std::string& hint) const; + + virtual recipe + apply (action, target&, void*) const; + + static target_state + perform_update (action, target&); + + static target_state + perform_clean (action, target&); + + static target_state + delegate (action, target&); + }; + } +} + +#endif // BUILD_CLI_RULE diff --git a/build/cli/rule.cxx b/build/cli/rule.cxx new file mode 100644 index 0000000..abcbb70 --- /dev/null +++ b/build/cli/rule.cxx @@ -0,0 +1,312 @@ +// file : build/cli/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build/cli/rule> + +#include <butl/process> + +#include <build/scope> +#include <build/target> +#include <build/algorithm> +#include <build/diagnostics> + +#include <build/cli/target> + +#include <build/config/utility> + +using namespace std; +using namespace butl; + +namespace build +{ + namespace cli + { + using config::append_options; + + void* compile:: + match (action a, target& xt, const std::string&) const + { + tracer trace ("cli::compile::match"); + + if (cli_cxx* pt = xt.is_a<cli_cxx> ()) + { + cli_cxx& t (*pt); + + // See if we have a .cli source file. + // + prerequisite* r (nullptr); + for (prerequisite& p: group_prerequisites (t)) + { + if (p.is_a<cli> ()) + { + //@@ Need to verify input and output stems match. + r = &p; + break; + } + } + + if (r == nullptr) + { + level3 ([&]{trace << "no .cli source file for target " << t;}); + return nullptr; + } + + // 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) + { + cxx::hxx& h (search<cxx::hxx> (t.dir, t.name, nullptr, nullptr)); + h.group = &t; + t.h (h); + + cxx::cxx& c (search<cxx::cxx> (t.dir, t.name, nullptr, nullptr)); + c.group = &t; + t.c (c); + + bool inl (true); + if (auto val = t["cli.options"]) + { + for (const name& n: val.template as<const list_value&> ()) + { + if (n.value == "--suppress-inline") + { + inl = false; + break; + } + } + } + + if (inl) + { + cxx::ixx& i (search<cxx::ixx> (t.dir, t.name, nullptr, nullptr)); + i.group = &t; + t.i (i); + } + } + + 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<cli_cxx> (); + + // Then see if there is a corresponding cli.cxx{} group. + // + cli_cxx* g (targets.find<cli_cxx> (t.dir, t.name)); + + // Finally, if this target has a cli{} prerequisite, synthesize + // the group. + // + if (g == nullptr) + { + for (prerequisite& p: group_prerequisites (t)) + { + if (p.is_a<cli> ()) // @@ Need to check that stems match. + { + g = &targets.insert<cli_cxx> (t.dir, t.name, trace); + g->prerequisites.emplace_back (p); + 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<cxx::ixx> () && g->i () == nullptr) + { + level3 ([&]{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, void* vp) const + { + if (cli_cxx* pt = xt.is_a<cli_cxx> ()) + { + 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 prerequisites. + // + switch (a.operation ()) + { + case default_id: + case update_id: search_and_match (a, t); break; + case clean_id: search_and_match (a, t, t.dir); break; + default: assert (false); // We didn't register for this. + } + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return default_recipe; // Forward to prerequisites. + } + } + else + { + cli_cxx& g (*static_cast<cli_cxx*> (vp)); + build::match (a, g); + return &delegate; + } + } + + static void + append_extension (vector<const char*>& args, + path_target& t, + const char* opt, + const char* def) + { + assert (t.ext != nullptr); // Should have been figured out in apply(). + + if (*t.ext != def) + { + // 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 (opt); + 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<cli_cxx&> (xt)); + + // Execute our prerequsites and check if we are out of date. + // + cli* s (execute_prerequisites<cli> (a, t, t.h ()->mtime ())); + + if (s == nullptr) + return target_state::unchanged; + + // Translate source path 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 (rs["config.cli"].as<const string&> ()); + + vector<const char*> 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"); + + 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) + print_process (args); + else + text << "cli " << *s; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + + timestamp ts (system_clock::now ()); + + t.h ()->mtime (ts); + t.c ()->mtime (ts); + if (t.i () != nullptr) + t.i ()->mtime (ts); + + return target_state::changed; + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + target_state compile:: + perform_clean (action a, target& xt) + { + cli_cxx& t (static_cast<cli_cxx&> (xt)); + + target_state ts (target_state::unchanged); + + if (t.i () != nullptr && + build::perform_clean (a, *t.i ()) == target_state::changed) + ts = target_state::changed; + + if (build::perform_clean (a, *t.c ()) == target_state::changed) + ts = target_state::changed; + + if (build::perform_clean (a, *t.h ()) == target_state::changed) + ts = target_state::changed; + + return ts; + } + + target_state compile:: + delegate (action a, target& t) + { + // Delegate to our group. + // + return execute (a, *t.group); + } + } +} diff --git a/build/cli/target b/build/cli/target new file mode 100644 index 0000000..e5bf16d --- /dev/null +++ b/build/cli/target @@ -0,0 +1,51 @@ +// file : build/cli/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CLI_TARGET +#define BUILD_CLI_TARGET + +#include <build/target> + +#include <build/cxx/target> + +namespace build +{ + namespace cli + { + class cli: public file + { + public: + using file::file; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + + class cli_cxx: public target_group + { + public: + using target_group::target_group; + + target* m[3] {nullptr, nullptr, nullptr}; + + cxx::hxx* h () const {return static_cast<cxx::hxx*> (m[0]);} + cxx::cxx* c () const {return static_cast<cxx::cxx*> (m[1]);} + cxx::ixx* i () const {return static_cast<cxx::ixx*> (m[2]);} + + void h (cxx::hxx& t) {m[0] = &t;} + void c (cxx::cxx& t) {m[1] = &t;} + void i (cxx::ixx& t) {m[2] = &t;} + + virtual group_view + members (action) const; + + public: + virtual const target_type& type () const {return static_type;} + static const target_type static_type; + }; + } +} + +#endif // BUILD_CLI_TARGET diff --git a/build/cli/target.cxx b/build/cli/target.cxx new file mode 100644 index 0000000..2854818 --- /dev/null +++ b/build/cli/target.cxx @@ -0,0 +1,46 @@ +// file : build/cli/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <build/cli/target> + +using namespace std; + +namespace build +{ + namespace cli + { + // cli + // + constexpr const char cli_ext[] = "cli"; + const target_type cli::static_type + { + typeid (cli), + "cli", + &file::static_type, + &target_factory<cli>, + &target_extension_fix<cli_ext>, + &search_file + }; + + // cli.cxx + // + group_view cli_cxx:: + members (action) const + { + return m[0] != nullptr + ? group_view {m, (m[2] != nullptr ? 3U : 2U)} + : group_view {nullptr, 0}; + } + + const target_type cli_cxx::static_type + { + typeid (cli_cxx), + "cli.cxx", + &target_group::static_type, + &target_factory<cli_cxx>, + nullptr, + &search_target + }; + } +} |