aboutsummaryrefslogtreecommitdiff
path: root/build2/cli
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-01-05 11:55:15 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-01-05 11:55:15 +0200
commit9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b (patch)
treed60322d4382ca5f97b676c5abe2e39524f35eab4 /build2/cli
parentf159b1dac68c8714f7ba71ca168e3b695891aad9 (diff)
Rename build directory/namespace to build2
Diffstat (limited to 'build2/cli')
-rw-r--r--build2/cli/module23
-rw-r--r--build2/cli/module.cxx244
-rw-r--r--build2/cli/rule32
-rw-r--r--build2/cli/rule.cxx305
-rw-r--r--build2/cli/target62
-rw-r--r--build2/cli/target.cxx77
6 files changed, 743 insertions, 0 deletions
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 <build2/types>
+#include <build2/utility>
+
+#include <build2/module>
+
+namespace build2
+{
+ namespace cli
+ {
+ extern "C" bool
+ cli_init (
+ scope&, scope&, const location&, unique_ptr<module>&, 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 <build2/cli/module>
+
+#include <butl/process>
+#include <butl/fdstream>
+
+#include <build2/scope>
+#include <build2/target>
+#include <build2/variable>
+#include <build2/diagnostics>
+
+#include <build2/cxx/target>
+
+#include <build2/config/utility>
+
+#include <build2/cli/target>
+#include <build2/cli/rule>
+
+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<module>&,
+ 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<bool> (*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<cli> ();
+ t.insert<cli_cxx> ();
+ }
+
+ // 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<bool> (*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<string> (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<string> (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<strings> (v);
+
+ // Register our rules.
+ //
+ {
+ auto& r (base.rules);
+
+ r.insert<cli_cxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cli_cxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::hxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::hxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::cxx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::cxx> (perform_clean_id, "cli.compile", compile_);
+
+ r.insert<cxx::ixx> (perform_update_id, "cli.compile", compile_);
+ r.insert<cxx::ixx> (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<cli_cxx> (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 <build2/rule>
+
+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 <build2/cli/rule>
+
+#include <butl/process>
+
+#include <build2/types>
+#include <build2/scope>
+#include <build2/target>
+#include <build2/context>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+#include <build2/cli/target>
+
+#include <build2/config/utility>
+
+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<cli_cxx> ())
+ {
+ // 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<cli> ())
+ {
+ // 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<cxx::hxx> (t.dir, t.name, nullptr, nullptr);
+ t.h->group = &t;
+
+ t.c = &search<cxx::cxx> (t.dir, t.name, nullptr, nullptr);
+ t.c->group = &t;
+
+ if (!config::find_option ("--suppress-inline", t, "cli.options"))
+ {
+ t.i = &search<cxx::ixx> (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<cli_cxx> ();
+
+ // Then check if there is a corresponding cli.cxx{} group.
+ //
+ cli_cxx* g (targets.find<cli_cxx> (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<cli> ())
+ {
+ // Check that the stems match.
+ //
+ if (t.name == p.name ())
+ {
+ if (g == nullptr)
+ g = &targets.insert<cli_cxx> (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<cxx::ixx> () && 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> ())
+ {
+ 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<cli_cxx*> (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<cli_cxx&> (xt));
+
+ // Execute our prerequsites and check if we are out of date.
+ //
+ cli* s (execute_prerequisites<cli> (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<string> (*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<cli_cxx&> (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 <build2/target>
+
+#include <build2/cxx/target>
+
+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 <build2/cli/target>
+
+#include <butl/filesystem>
+
+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<cli>,
+ &target_extension_var<cli_ext_var, cli_ext_def>,
+ &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<cxx::hxx> (d, n, trace);
+ targets.insert<cxx::cxx> (d, n, trace);
+ targets.insert<cxx::ixx> (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.
+ };
+ }
+}