aboutsummaryrefslogtreecommitdiff
path: root/build/cli
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-06-24 13:53:28 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-06-24 13:53:28 +0200
commite815af118562c68794efbd310c887acd8eae800c (patch)
treecedd8745cce259693c038c309d663a682c982e98 /build/cli
parent4f52c4ed65883dacef32587cf066fbb1182c6628 (diff)
First take on the cli module plus necessary infrastructure
Diffstat (limited to 'build/cli')
-rw-r--r--build/cli/module20
-rw-r--r--build/cli/module.cxx147
-rw-r--r--build/cli/rule35
-rw-r--r--build/cli/rule.cxx312
-rw-r--r--build/cli/target51
-rw-r--r--build/cli/target.cxx46
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
+ };
+ }
+}