aboutsummaryrefslogtreecommitdiff
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
parent4f52c4ed65883dacef32587cf066fbb1182c6628 (diff)
First take on the cli module plus necessary infrastructure
-rw-r--r--build/algorithm9
-rw-r--r--build/algorithm.cxx82
-rw-r--r--build/algorithm.ixx18
-rw-r--r--build/b.cxx13
-rw-r--r--build/bin/target.cxx7
-rw-r--r--build/buildfile6
-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
-rw-r--r--build/config/operation.cxx2
-rw-r--r--build/config/utility24
-rw-r--r--build/context3
-rw-r--r--build/cxx/module.cxx3
-rw-r--r--build/cxx/rule.cxx58
-rw-r--r--build/cxx/target.cxx12
-rw-r--r--build/options32
-rw-r--r--build/options.cxx66
-rw-r--r--build/options.ixx2
-rw-r--r--build/rule.cxx13
-rw-r--r--build/search.cxx45
-rw-r--r--build/target80
-rw-r--r--build/target-key1
-rw-r--r--build/target.cxx74
-rw-r--r--build/target.txx44
-rw-r--r--build/utility3
-rw-r--r--tests/cli/build/bootstrap.build2
-rw-r--r--tests/cli/build/root.build3
-rw-r--r--tests/cli/buildfile6
-rw-r--r--tests/cli/driver.cpp4
-rw-r--r--tests/cli/test.cli5
33 files changed, 1065 insertions, 163 deletions
diff --git a/build/algorithm b/build/algorithm
index bc756c2..59021f5 100644
--- a/build/algorithm
+++ b/build/algorithm
@@ -68,6 +68,13 @@ namespace build
void
search_and_match (action, target&, const dir_path& dir);
+ // Unless already available, match, and, if necessary, execute
+ // (not yet implemented) the group in order to obtain its members
+ // list.
+ //
+ group_view
+ resolve_group_members (action, target_group&);
+
// Inject dependency on the parent directory's fsdir{}, unless it is
// the project's out_root (or is outside of any project; say, for
// example, an install directory). Normally this function is called
@@ -114,7 +121,7 @@ namespace build
//
template <typename T>
T*
- execute_find_prerequisites (action, target&, const timestamp&);
+ execute_prerequisites (action, target&, const timestamp&);
// Return noop_recipe instead of using this function directly.
//
diff --git a/build/algorithm.cxx b/build/algorithm.cxx
index abae023..c438fb8 100644
--- a/build/algorithm.cxx
+++ b/build/algorithm.cxx
@@ -34,9 +34,11 @@ namespace build
return create_new_target (pk);
}
- void
- match_impl (action a, target& t)
+ pair<const rule*, void*>
+ match_impl (action a, target& t, bool apply)
{
+ pair<const rule*, void*> r (nullptr, nullptr);
+
// Clear the resolved targets list before calling match(). The rule
// is free to, say, resize() this list in match() (provided that it
// matches) in order to, for example, prepare it for apply().
@@ -133,17 +135,26 @@ namespace build
if (!ambig)
{
- auto g (
- make_exception_guard (
- [](action a, target& t, const string& n)
- {
- info << "while applying rule " << n << " to "
- << diag_do (a, t);
- },
- a, t, n));
-
- t.recipe (a, ru.apply (a, t, m));
- break;
+ if (apply)
+ {
+ auto g (
+ make_exception_guard (
+ [](action a, target& t, const string& n)
+ {
+ info << "while applying rule " << n << " to "
+ << diag_do (a, t);
+ },
+ a, t, n));
+
+ t.recipe (a, ru.apply (a, t, m));
+ break;
+ }
+ else
+ {
+ r.first = &ru;
+ r.second = m;
+ return r;
+ }
}
else
dr << info << "use rule hint to disambiguate this match";
@@ -152,7 +163,50 @@ namespace build
}
if (!t.recipe (a))
- fail << "no rule to " << diag_do (a, t);
+ {
+ diag_record dr;
+ dr << fail << "no rule to " << diag_do (a, t);
+
+ if (verb < 3)
+ dr << info << "re-run with --verbose 3 for more information";
+ }
+
+ return r;
+ }
+
+ group_view
+ resolve_group_members_impl (action a, target_group& g)
+ {
+ group_view r;
+
+ // Unless we already have a recipe, try matching the target to
+ // the rule.
+ //
+ if (!g.recipe (a))
+ {
+ auto p (match_impl (a, g, false));
+
+ r = g.members (a);
+ if (r.members != nullptr)
+ return r;
+
+ // That didn't help, so apply the rule and go to the building
+ // phase.
+ //
+ g.recipe (a, p.first->apply (a, g, p.second));
+ }
+
+ // Note the we use execute_impl() rather than execute() here
+ // to sidestep the dependents count logic. In this context,
+ // this is by definition the first attempt to execute this
+ // rule (otherwise we would have already known the members
+ // list) and we really do need to execute it now.
+ //
+ execute_impl (a, g);
+
+ r = g.members (a);
+ assert (r.members != nullptr); // What "next step" did the group expect?
+ return r;
}
void
diff --git a/build/algorithm.ixx b/build/algorithm.ixx
index 3907209..3a6f183 100644
--- a/build/algorithm.ixx
+++ b/build/algorithm.ixx
@@ -2,6 +2,8 @@
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
+#include <utility> // pair
+
#include <build/prerequisite>
#include <build/context>
@@ -36,18 +38,28 @@ namespace build
return static_cast<T&> (search (T::static_type, dir, name, ext, scope));
}
- void
- match_impl (action, target&);
+ std::pair<const rule*, void*>
+ match_impl (action, target&, bool apply);
inline void
match (action a, target& t)
{
if (!t.recipe (a))
- match_impl (a, t);
+ match_impl (a, t, true);
t.dependents++;
}
+ group_view
+ resolve_group_members_impl (action, target_group&);
+
+ inline group_view
+ resolve_group_members (action a, target_group& g)
+ {
+ group_view r (g.members (a));
+ return r.members != nullptr ? r : resolve_group_members_impl (a, g);
+ }
+
target_state
execute_impl (action, target&);
diff --git a/build/b.cxx b/build/b.cxx
index 8e75c14..ef39446 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -78,14 +78,16 @@ namespace build
#include <build/config/module>
-#include <build/bin/target>
-#include <build/bin/rule>
+#include <build/bin/target> //@@ tmp
+#include <build/bin/rule> //@@ tmp
#include <build/bin/module>
-#include <build/cxx/target>
-#include <build/cxx/rule>
+#include <build/cxx/target> //@@ tmp
+#include <build/cxx/rule> //@@ tmp
#include <build/cxx/module>
+#include <build/cli/module>
+
using namespace build;
int
@@ -95,7 +97,7 @@ main (int argc, char* argv[])
{
tracer trace ("main");
- cli::argv_scanner scan (argc, argv, true);
+ cl::argv_scanner scan (argc, argv, true);
options ops (scan);
// Version.
@@ -134,6 +136,7 @@ main (int argc, char* argv[])
modules["config"] = &config::init;
modules["bin"] = &bin::init;
modules["cxx"] = &cxx::init;
+ modules["cli"] = &cli::init;
// Register target types.
//
diff --git a/build/bin/target.cxx b/build/bin/target.cxx
index 1d69063..66eadb7 100644
--- a/build/bin/target.cxx
+++ b/build/bin/target.cxx
@@ -28,6 +28,7 @@ namespace build
"obja",
&file::static_type,
&obja_factory,
+ nullptr,
&search_file
};
@@ -49,6 +50,7 @@ namespace build
"objso",
&file::static_type,
&objso_factory,
+ nullptr,
&search_file
};
@@ -74,6 +76,7 @@ namespace build
"obj",
&target::static_type,
&obj_factory,
+ nullptr,
&search_target
};
@@ -83,6 +86,7 @@ namespace build
"exe",
&file::static_type,
&target_factory<exe>,
+ nullptr,
&search_file
};
@@ -104,6 +108,7 @@ namespace build
"liba",
&file::static_type,
&liba_factory,
+ nullptr,
&search_file
};
@@ -125,6 +130,7 @@ namespace build
"libso",
&file::static_type,
&libso_factory,
+ nullptr,
&search_file
};
@@ -150,6 +156,7 @@ namespace build
"lib",
&target::static_type,
&lib_factory,
+ nullptr,
&search_target
};
}
diff --git a/build/buildfile b/build/buildfile
index b9c1ca2..7885c1d 100644
--- a/build/buildfile
+++ b/build/buildfile
@@ -7,11 +7,13 @@ import libs += libbutl
config = config/{operation module utility}
bin = bin/{target rule module}
cxx = cxx/{target rule module}
+cli = cli/{target rule module}
exe{b}: cxx{b algorithm name operation spec scope variable target \
prerequisite rule file module context search diagnostics token \
- lexer parser path-io utility dump options $config $bin $cxx} $libs
+ lexer parser path-io utility dump options $config $bin $cxx $cli} \
+ $libs
#@@ TODO
#
-# cli --include-with-brackets --include-prefix build --guard-prefix BUILD --hxx-suffix "" options.cli
+# cli --cli-namespace cl --include-with-brackets --include-prefix build --guard-prefix BUILD --hxx-suffix "" options.cli
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
+ };
+ }
+}
diff --git a/build/config/operation.cxx b/build/config/operation.cxx
index 89bef61..ecd805f 100644
--- a/build/config/operation.cxx
+++ b/build/config/operation.cxx
@@ -198,7 +198,7 @@ namespace build
for (void* v: ts)
{
target& t (*static_cast<target*> (v));
- scope* rs (t.root_scope ());
+ scope* rs (t.base_scope ().root_scope ());
if (rs == nullptr)
fail << "out of project target " << t;
diff --git a/build/config/utility b/build/config/utility
index 83fcd75..3da990f 100644
--- a/build/config/utility
+++ b/build/config/utility
@@ -5,9 +5,13 @@
#ifndef BUILD_CONFIG_UTILITY
#define BUILD_CONFIG_UTILITY
+#include <vector>
#include <string>
#include <utility> // pair
+#include <build/types>
+#include <build/diagnostics>
+
namespace build
{
class scope;
@@ -37,6 +41,26 @@ namespace build
template <typename T>
const T*
optional (scope& root, const char* name);
+
+ // Add all the values from a variable to the C-string list. T is
+ // either target or scope.
+ //
+ template <typename T>
+ void
+ append_options (std::vector<const char*>& args, T& s, const char* var)
+ {
+ if (auto val = s[var])
+ {
+ for (const name& n: val.template as<const list_value&> ())
+ {
+ if (!n.type.empty () || !n.dir.empty ())
+ fail << "expected option instead of " << n <<
+ info << "in variable " << var;
+
+ args.push_back (n.value.c_str ());
+ }
+ }
+ }
}
}
diff --git a/build/context b/build/context
index dc865d2..44948ec 100644
--- a/build/context
+++ b/build/context
@@ -97,7 +97,8 @@ namespace build
// If possible and beneficial, translate an absolute, normalized path
// into relative to the relative_base directory, which is normally
- // work.
+ // work. Note that if the passed path is the same as relative_base,
+ // then this function returns empty path.
//
template <typename K>
basic_path<char, K>
diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx
index 21f1b85..495819d 100644
--- a/build/cxx/module.cxx
+++ b/build/cxx/module.cxx
@@ -87,7 +87,8 @@ namespace build
throw failed ();
}
- //text << "toolchain version " << ver;
+ if (verb)
+ text << cxx << " " << ver;
}
}
diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx
index e4c0dd7..cba1a1b 100644
--- a/build/cxx/rule.cxx
+++ b/build/cxx/rule.cxx
@@ -24,6 +24,8 @@
#include <build/bin/target>
#include <build/cxx/target>
+#include <build/config/utility>
+
using namespace std;
using namespace butl;
@@ -33,24 +35,7 @@ namespace build
{
using namespace bin;
- // T is either target or scope.
- //
- template <typename T>
- static void
- append_options (vector<const char*>& args, T& s, const char* var)
- {
- if (auto val = s[var])
- {
- for (const name& n: val.template as<const list_value&> ())
- {
- if (!n.type.empty () || !n.dir.empty ())
- fail << "expected option instead of " << n <<
- info << "in variable " << var;
-
- args.push_back (n.value.c_str ());
- }
- }
- }
+ using config::append_options;
static void
append_std (vector<const char*>& args, target& t, string& opt)
@@ -127,12 +112,7 @@ namespace build
// Derive file name from target name.
//
if (t.path ().empty ())
- {
- if (t.is_a <obja> ())
- t.path (t.derived_path ("o"));
- else
- t.path (t.derived_path ("o", nullptr, "-so"));
- }
+ t.derive_path ("o", nullptr, (t.is_a<objso> () ? "-so" : nullptr));
// Inject dependency on the output directory.
//
@@ -236,7 +216,7 @@ namespace build
{
tracer trace ("cxx::compile::inject_prerequisites");
- scope& rs (*t.root_scope ()); // Shouldn't have matched if nullptr.
+ scope& rs (t.root_scope ());
const string& cxx (rs["config.cxx"].as<const string&> ());
vector<const char*> args {cxx.c_str ()};
@@ -346,7 +326,20 @@ namespace build
// then assume it is a header. Otherwise, let the standard
// mechanism derive the type from the extension. @@ TODO.
//
- path_target& pt (search<hxx> (d, n, e, &ds));
+ const target_type* tt (&hxx::static_type);
+
+ //@@ TMP
+ //
+ if (e != nullptr)
+ {
+ if (*e == "ixx")
+ tt = &ixx::static_type;
+ else if (*e == "txx")
+ tt = &txx::static_type;
+ }
+
+ path_target& pt (
+ static_cast<path_target&> (search (*tt, d, n, e, &ds)));
// Assign path.
//
@@ -398,7 +391,7 @@ namespace build
path relo (relative (t.path ()));
path rels (relative (s->path ()));
- scope& rs (*t.root_scope ()); // Shouldn't have matched if nullptr.
+ scope& rs (t.root_scope ());
const string& cxx (rs["config.cxx"].as<const string&> ());
vector<const char*> args {cxx.c_str ()};
@@ -580,9 +573,9 @@ namespace build
{
switch (lt)
{
- case type::e: t.path (t.derived_path ( )); break;
- case type::a: t.path (t.derived_path ("a", "lib")); break;
- case type::so: t.path (t.derived_path ("so", "lib")); break;
+ case type::e: t.derive_path ("" ); break;
+ case type::a: t.derive_path ("a", "lib"); break;
+ case type::so: t.derive_path ("so", "lib"); break;
}
}
@@ -674,8 +667,7 @@ namespace build
// but possible, the prerequisite is from a different project
// altogether. So we are going to use the target's project.
//
- root = t.root_scope ();
- assert (root != nullptr); // Otherwise shouldn't have matched.
+ root = &t.root_scope ();
out_root = &root->path ();
src_root = &root->src_path ();
}
@@ -837,7 +829,7 @@ namespace build
//
path relt (relative (t.path ()));
- scope& rs (*t.root_scope ()); // Shouldn't have matched if nullptr.
+ scope& rs (t.root_scope ());
vector<const char*> args;
string storage1;
diff --git a/build/cxx/target.cxx b/build/cxx/target.cxx
index e02801b..9fd5487 100644
--- a/build/cxx/target.cxx
+++ b/build/cxx/target.cxx
@@ -10,57 +10,69 @@ namespace build
{
namespace cxx
{
+ constexpr const char hxx_ext_var[] = "hxx.ext";
const target_type hxx::static_type
{
typeid (hxx),
"hxx",
&file::static_type,
&target_factory<hxx>,
+ &target_extension_var<hxx_ext_var>,
&search_file
};
+ constexpr const char ixx_ext_var[] = "ixx.ext";
const target_type ixx::static_type
{
typeid (ixx),
"ixx",
&file::static_type,
&target_factory<ixx>,
+ &target_extension_var<ixx_ext_var>,
&search_file
};
+ constexpr const char txx_ext_var[] = "txx.ext";
const target_type txx::static_type
{
typeid (txx),
"txx",
&file::static_type,
&target_factory<txx>,
+ &target_extension_var<txx_ext_var>,
&search_file
};
+ constexpr const char cxx_ext_var[] = "cxx.ext";
const target_type cxx::static_type
{
typeid (cxx),
"cxx",
&file::static_type,
&target_factory<cxx>,
+ &target_extension_var<cxx_ext_var>,
&search_file
};
+ constexpr const char h_ext_var[] = "h.ext";
const target_type h::static_type
{
typeid (h),
"h",
&file::static_type,
&target_factory<h>,
+ &target_extension_var<h_ext_var>,
&search_file
};
+ constexpr const char c_ext_var[] = "c.ext";
const target_type c::static_type
{
typeid (c),
"c",
&file::static_type,
&target_factory<c>,
+ &target_extension_var<c_ext_var>,
&search_file
};
}
diff --git a/build/options b/build/options
index 053c066..c0b9930 100644
--- a/build/options
+++ b/build/options
@@ -17,7 +17,7 @@
#include <cstddef>
#include <exception>
-namespace cli
+namespace cl
{
class unknown_mode
{
@@ -211,34 +211,34 @@ class options
options (int& argc,
char** argv,
bool erase = false,
- ::cli::unknown_mode option = ::cli::unknown_mode::fail,
- ::cli::unknown_mode argument = ::cli::unknown_mode::stop);
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
options (int start,
int& argc,
char** argv,
bool erase = false,
- ::cli::unknown_mode option = ::cli::unknown_mode::fail,
- ::cli::unknown_mode argument = ::cli::unknown_mode::stop);
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
options (int& argc,
char** argv,
int& end,
bool erase = false,
- ::cli::unknown_mode option = ::cli::unknown_mode::fail,
- ::cli::unknown_mode argument = ::cli::unknown_mode::stop);
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
options (int start,
int& argc,
char** argv,
int& end,
bool erase = false,
- ::cli::unknown_mode option = ::cli::unknown_mode::fail,
- ::cli::unknown_mode argument = ::cli::unknown_mode::stop);
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
- options (::cli::scanner&,
- ::cli::unknown_mode option = ::cli::unknown_mode::fail,
- ::cli::unknown_mode argument = ::cli::unknown_mode::stop);
+ options (::cl::scanner&,
+ ::cl::unknown_mode option = ::cl::unknown_mode::fail,
+ ::cl::unknown_mode argument = ::cl::unknown_mode::stop);
options ();
@@ -265,13 +265,13 @@ class options
//
protected:
bool
- _parse (const char*, ::cli::scanner&);
+ _parse (const char*, ::cl::scanner&);
private:
void
- _parse (::cli::scanner&,
- ::cli::unknown_mode option,
- ::cli::unknown_mode argument);
+ _parse (::cl::scanner&,
+ ::cl::unknown_mode option,
+ ::cl::unknown_mode argument);
public:
bool help_;
diff --git a/build/options.cxx b/build/options.cxx
index 0d4248d..3c1e194 100644
--- a/build/options.cxx
+++ b/build/options.cxx
@@ -18,7 +18,7 @@
#include <ostream>
#include <sstream>
-namespace cli
+namespace cl
{
// unknown_option
//
@@ -323,14 +323,14 @@ options::
options (int& argc,
char** argv,
bool erase,
- ::cli::unknown_mode opt,
- ::cli::unknown_mode arg)
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
: help_ (),
version_ (),
v_ (),
verbose_ (0)
{
- ::cli::argv_scanner s (argc, argv, erase);
+ ::cl::argv_scanner s (argc, argv, erase);
_parse (s, opt, arg);
}
@@ -339,14 +339,14 @@ options (int start,
int& argc,
char** argv,
bool erase,
- ::cli::unknown_mode opt,
- ::cli::unknown_mode arg)
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
: help_ (),
version_ (),
v_ (),
verbose_ (0)
{
- ::cli::argv_scanner s (start, argc, argv, erase);
+ ::cl::argv_scanner s (start, argc, argv, erase);
_parse (s, opt, arg);
}
@@ -355,14 +355,14 @@ options (int& argc,
char** argv,
int& end,
bool erase,
- ::cli::unknown_mode opt,
- ::cli::unknown_mode arg)
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
: help_ (),
version_ (),
v_ (),
verbose_ (0)
{
- ::cli::argv_scanner s (argc, argv, erase);
+ ::cl::argv_scanner s (argc, argv, erase);
_parse (s, opt, arg);
end = s.end ();
}
@@ -373,22 +373,22 @@ options (int start,
char** argv,
int& end,
bool erase,
- ::cli::unknown_mode opt,
- ::cli::unknown_mode arg)
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
: help_ (),
version_ (),
v_ (),
verbose_ (0)
{
- ::cli::argv_scanner s (start, argc, argv, erase);
+ ::cl::argv_scanner s (start, argc, argv, erase);
_parse (s, opt, arg);
end = s.end ();
}
options::
-options (::cli::scanner& s,
- ::cli::unknown_mode opt,
- ::cli::unknown_mode arg)
+options (::cl::scanner& s,
+ ::cl::unknown_mode opt,
+ ::cl::unknown_mode arg)
: help_ (),
version_ (),
v_ (),
@@ -411,7 +411,7 @@ print_usage (::std::ostream& os)
}
typedef
-std::map<std::string, void (*) (options&, ::cli::scanner&)>
+std::map<std::string, void (*) (options&, ::cl::scanner&)>
_cli_options_map;
static _cli_options_map _cli_options_map_;
@@ -421,20 +421,20 @@ struct _cli_options_map_init
_cli_options_map_init ()
{
_cli_options_map_["--help"] =
- &::cli::thunk< options, bool, &options::help_ >;
+ &::cl::thunk< options, bool, &options::help_ >;
_cli_options_map_["--version"] =
- &::cli::thunk< options, bool, &options::version_ >;
+ &::cl::thunk< options, bool, &options::version_ >;
_cli_options_map_["-v"] =
- &::cli::thunk< options, bool, &options::v_ >;
+ &::cl::thunk< options, bool, &options::v_ >;
_cli_options_map_["--verbose"] =
- &::cli::thunk< options, std::uint16_t, &options::verbose_ >;
+ &::cl::thunk< options, std::uint16_t, &options::verbose_ >;
}
};
static _cli_options_map_init _cli_options_map_init_;
bool options::
-_parse (const char* o, ::cli::scanner& s)
+_parse (const char* o, ::cl::scanner& s)
{
_cli_options_map::const_iterator i (_cli_options_map_.find (o));
@@ -448,9 +448,9 @@ _parse (const char* o, ::cli::scanner& s)
}
void options::
-_parse (::cli::scanner& s,
- ::cli::unknown_mode opt_mode,
- ::cli::unknown_mode arg_mode)
+_parse (::cl::scanner& s,
+ ::cl::unknown_mode opt_mode,
+ ::cl::unknown_mode arg_mode)
{
bool opt = true;
@@ -470,18 +470,18 @@ _parse (::cli::scanner& s,
{
switch (opt_mode)
{
- case ::cli::unknown_mode::skip:
+ case ::cl::unknown_mode::skip:
{
s.skip ();
continue;
}
- case ::cli::unknown_mode::stop:
+ case ::cl::unknown_mode::stop:
{
break;
}
- case ::cli::unknown_mode::fail:
+ case ::cl::unknown_mode::fail:
{
- throw ::cli::unknown_option (o);
+ throw ::cl::unknown_option (o);
}
}
@@ -491,18 +491,18 @@ _parse (::cli::scanner& s,
{
switch (arg_mode)
{
- case ::cli::unknown_mode::skip:
+ case ::cl::unknown_mode::skip:
{
s.skip ();
continue;
}
- case ::cli::unknown_mode::stop:
+ case ::cl::unknown_mode::stop:
{
break;
}
- case ::cli::unknown_mode::fail:
+ case ::cl::unknown_mode::fail:
{
- throw ::cli::unknown_argument (o);
+ throw ::cl::unknown_argument (o);
}
}
diff --git a/build/options.ixx b/build/options.ixx
index d242ba5..671f9ec 100644
--- a/build/options.ixx
+++ b/build/options.ixx
@@ -9,7 +9,7 @@
//
// End prologue.
-namespace cli
+namespace cl
{
// unknown_mode
//
diff --git a/build/rule.cxx b/build/rule.cxx
index b3dfaeb..baed3ba 100644
--- a/build/rule.cxx
+++ b/build/rule.cxx
@@ -49,22 +49,13 @@ namespace build
{
case perform_update_id:
{
- // @@ TODO:
- //
- // - need to try all the target-type-specific extensions, just
- // like search_existing_file().
- //
path_target& pt (dynamic_cast<path_target&> (t));
- // Assign the path. While nromally we shouldn't do this in match(),
+ // Assign the path. While normally we shouldn't do this in match(),
// no other rule should ever be ambiguous with the fallback one.
//
if (pt.path ().empty ())
- {
- // @@ TMP: using target name as the default extension.
- //
- pt.path (pt.derived_path (pt.type ().name));
- }
+ pt.derive_path ();
return pt.mtime () != timestamp_nonexistent ? &t : nullptr;
}
diff --git a/build/search.cxx b/build/search.cxx
index feff123..dec86db 100644
--- a/build/search.cxx
+++ b/build/search.cxx
@@ -56,28 +56,48 @@ namespace build
}
target*
- search_existing_file (const prerequisite_key& pk, const dir_paths& sp)
+ search_existing_file (const prerequisite_key& cpk, const dir_paths& sp)
{
tracer trace ("search_existing_file");
- const target_key& tk (pk.tk);
+ prerequisite_key pk (cpk); // Make a copy so we can update extension.
+ target_key& tk (pk.tk);
assert (tk.dir->relative ());
- // Go over paths and extension looking for a file.
+ // Figure out the extension. Pretty similar logic to file::derive_path().
+ //
+ const string* ext (*tk.ext);
+
+ if (ext == nullptr)
+ {
+ if (auto f = tk.type->extension)
+ {
+ ext = &f (tk, *pk.scope); // Already from the pool.
+ tk.ext = &ext;
+ }
+ else
+ // What should we do here, fail or say we didn't find anything?
+ // Current think is that if the target type didn't provide the
+ // default extension, then it doesn't want us to search for an
+ // existing file (of course, if the user specified the extension
+ // explicitly, we will still do so). But let me know what you
+ // think.
+ //
+ //fail << "no default extension for prerequisite " << pk;
+ return nullptr;
+ }
+
+ // Go over paths looking for a file.
//
for (const dir_path& d: sp)
{
path f (d / *tk.dir / path (*tk.name));
f.normalize ();
- // @@ TMP: use target name as an extension.
- //
- const string& e (*tk.ext != nullptr ? **tk.ext : tk.type->name);
-
- if (!e.empty ())
+ if (!ext->empty ())
{
f += '.';
- f += e;
+ f += *ext;
}
timestamp mt (file_mtime (f));
@@ -88,11 +108,9 @@ namespace build
level4 ([&]{trace << "found existing file " << f << " for prerequisite "
<< pk;});
- // Find or insert.
+ // Find or insert. Note: using our updated extension.
//
- auto r (
- targets.insert (
- *tk.type, f.directory (), *tk.name, *tk.ext, trace));
+ auto r (targets.insert (*tk.type, f.directory (), *tk.name, ext, trace));
// Has to be a path_target.
//
@@ -108,6 +126,7 @@ namespace build
return &t;
}
+ level3 ([&]{trace << "no existing file found for prerequisite " << pk;});
return nullptr;
}
diff --git a/build/target b/build/target
index 8eb7813..ddf5a2e 100644
--- a/build/target
+++ b/build/target
@@ -24,12 +24,12 @@
#include <build/operation>
#include <build/target-key>
#include <build/prerequisite>
-#include <build/utility> // extension_pool
namespace build
{
class scope;
class target;
+ class target_group;
// Target state.
//
@@ -111,12 +111,17 @@ namespace build
const std::string* ext; // Extension, NULL means unspecified,
// empty means no extension.
+ //@@ Make target_group.
target* group {nullptr}; // Target group to which this target belongs,
// if any. Note that we assume that the group
// and all its members are in the same scope
// (see, for example, variable lookup).
// We also currently assume that there are
// no multi-level groups.
+
+ target_key
+ key () const {return target_key {&type (), &dir, &name, &ext};}
+
public:
// Most qualified scope that contains this target.
//
@@ -125,9 +130,10 @@ namespace build
// Root scope of a project that contains this target. Note that
// a target can be out of any (known) project root in which case
- // NULL is returned.
+ // this function asserts. If you need to detect this situation,
+ // then use base_scope().root_scope() expression instead.
//
- scope*
+ scope&
root_scope () const;
// Prerequisites.
@@ -429,6 +435,14 @@ namespace build
const std::string* ext,
tracer&);
+ template <typename T>
+ T&
+ insert (const dir_path& dir, const std::string& name, tracer& t)
+ {
+ return static_cast<T&> (
+ insert (T::static_type, dir, name, nullptr, t).first);
+ }
+
void
clear () {map_.clear ();}
@@ -463,6 +477,26 @@ namespace build
extern target_type_map target_types;
+ // Target group.
+ //
+ struct group_view
+ {
+ target* const* members; // NULL means not yet known.
+ std::size_t count;
+ };
+
+ class target_group: public target
+ {
+ public:
+ using target::target;
+
+ virtual group_view
+ members (action) const = 0;
+
+ public:
+ static const target_type static_type;
+ };
+
// Modification time-based target.
//
class mtime_target: public target
@@ -508,16 +542,20 @@ namespace build
void
path (path_type p) {assert (path_.empty ()); path_ = std::move (p);}
- // Return a path derived from target's dir, name, and, if specified,
- // ext. If ext is not specified, then use default_ext. If name_prefix
- // if not NULL, add it before the name part and after the directory.
- // Similarly, if name_suffix if not NULL, add it after the name part
- // and before the extension.
+ // Derive a path from target's dir, name, and, if specified, ext.
+ // If ext is not specified, then use default_ext and also update
+ // the target's extension (this becomes important if later we need
+ // to reliably determine whether this file has an extension; think
+ // hxx{foo.bar.} and hxx.ext is empty).
//
- path_type
- derived_path (const char* default_ext = nullptr,
- const char* name_prefix = nullptr,
- const char* name_suffix = nullptr);
+ // If name_prefix is not NULL, add it before the name part and after
+ // the directory. Similarly, if name_suffix is not NULL, add it after
+ // the name part and before the extension.
+ //
+ void
+ derive_path (const char* default_ext = nullptr,
+ const char* name_prefix = nullptr,
+ const char* name_suffix = nullptr);
public:
static const target_type static_type;
@@ -573,7 +611,8 @@ namespace build
static const target_type static_type;
};
- // Common implementation of the target factory and search functions.
+ // Common implementation of the target factory, extension, and
+ // search functions.
//
template <typename T>
target*
@@ -582,13 +621,25 @@ namespace build
return new T (std::move (d), std::move (n), e);
}
+ // Return fixed target extension.
+ //
+ template <const char* ext>
+ const std::string&
+ target_extension_fix (const target_key&, scope&);
+
+ // Get the extension from the variable.
+ //
+ template <const char* var>
+ const std::string&
+ target_extension_var (const target_key&, scope&);
+
// The default behavior, that is, look for an existing target in the
// prerequisite's directory scope.
//
target*
search_target (const prerequisite_key&);
- // First lookfor an existing target as above. If not found, then look
+ // First look for an existing target as above. If not found, then look
// for an existing file in the target-type-specific list of paths.
//
target*
@@ -597,5 +648,6 @@ namespace build
}
#include <build/target.ixx>
+#include <build/target.txx>
#endif // BUILD_TARGET
diff --git a/build/target-key b/build/target-key
index 04a5b26..3875a6a 100644
--- a/build/target-key
+++ b/build/target-key
@@ -26,6 +26,7 @@ namespace build
const char* name;
const target_type* base;
target* (*const factory) (dir_path, std::string, const std::string*);
+ const std::string& (*const extension) (const target_key&, scope&);
target* (*const search) (const prerequisite_key&);
};
diff --git a/build/target.cxx b/build/target.cxx
index 1040443..87194fb 100644
--- a/build/target.cxx
+++ b/build/target.cxx
@@ -4,6 +4,8 @@
#include <build/target>
+#include <cassert>
+
#include <butl/filesystem>
#include <build/scope>
@@ -29,12 +31,14 @@ namespace build
return scopes.find (dir);
}
- scope* target::
+ scope& target::
root_scope () const
{
// This is tricky to cache so we do the lookup for now.
//
- return scopes.find (dir).root_scope ();
+ scope* r (scopes.find (dir).root_scope ());
+ assert (r != nullptr);
+ return *r;
}
value_proxy target::
@@ -240,8 +244,8 @@ namespace build
// path_target
//
- path path_target::
- derived_path (const char* de, const char* np, const char* ns)
+ void path_target::
+ derive_path (const char* de, const char* np, const char* ns)
{
string n;
@@ -253,21 +257,35 @@ namespace build
if (ns != nullptr)
n += ns;
- if (ext != nullptr)
+ // Update the extension.
+ //
+ // See also search_existing_file() if updating anything here.
+ //
+ if (ext == nullptr)
{
- if (!ext->empty ())
- {
- n += '.';
- n += *ext;
- }
+ // If provided by the caller, then use that.
+ //
+ if (de != nullptr)
+ ext = &extension_pool.find (de);
+ //
+ // Otherwis see if the target type has function that will
+ // give us the default extension.
+ //
+ else if (auto f = type ().extension)
+ ext = &f (key (), base_scope ()); // Already from the pool.
+ else
+ fail << "no default extension for target " << *this;
}
- else if (de != nullptr)
+
+ // Add the extension.
+ //
+ if (!ext->empty ())
{
n += '.';
- n += de;
+ n += *ext;
}
- return dir / path_type (move (n));
+ path (dir / path_type (move (n)));
}
// file_target
@@ -337,6 +355,17 @@ namespace build
"target",
nullptr,
nullptr,
+ nullptr,
+ &search_target,
+ };
+
+ const target_type target_group::static_type
+ {
+ typeid (target_group),
+ "target_group",
+ &target::static_type,
+ nullptr,
+ nullptr,
&search_target
};
@@ -346,6 +375,7 @@ namespace build
"mtime_target",
&target::static_type,
nullptr,
+ nullptr,
&search_target
};
@@ -355,15 +385,29 @@ namespace build
"path_target",
&mtime_target::static_type,
nullptr,
+ nullptr,
&search_target
};
+ static target*
+ file_factory (dir_path d, string n, const string* e)
+ {
+ // The file target type doesn't imply any extension. So if one
+ // wasn't specified, set it to empty rather than unspecified.
+ // In other words, we always treat file{foo} as file{foo.}.
+ //
+ return new file (move (d),
+ move (n),
+ (e != nullptr ? e : &extension_pool.find ("")));
+ }
+
const target_type file::static_type
{
typeid (file),
"file",
&path_target::static_type,
- &target_factory<file>,
+ &file_factory,
+ nullptr, // Factory always assigns an extension.
&search_file
};
@@ -373,6 +417,7 @@ namespace build
"dir",
&target::static_type,
&target_factory<dir>,
+ nullptr, // Should never need.
&search_alias
};
@@ -382,6 +427,7 @@ namespace build
"fsdir",
&target::static_type,
&target_factory<fsdir>,
+ nullptr, // Should never need.
&search_target
};
}
diff --git a/build/target.txx b/build/target.txx
new file mode 100644
index 0000000..6fe3f33
--- /dev/null
+++ b/build/target.txx
@@ -0,0 +1,44 @@
+// file : build/target.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build/scope>
+#include <build/utility> // extension_pool
+#include <build/diagnostics>
+#include <build/prerequisite>
+
+namespace build
+{
+ template <const char* ext>
+ const std::string&
+ target_extension_fix (const target_key&, scope&)
+ {
+ return extension_pool.find (ext);
+ }
+
+ template <const char* var>
+ const std::string&
+ target_extension_var (const target_key& tk, scope& s)
+ {
+ auto val (s[var]);
+
+ if (!val)
+ {
+ diag_record dr;
+ dr << fail << "no default extension in variable " << var
+ << info << "required to derive file name for ";
+
+ // This is a bit hacky: we may be dealing with a target (see
+ // file::derive_path()) or prerequsite (see search_existing_file()).
+ // So we are going to check if dir is absolute. If it is, then
+ // we assume this is a target, otherwise -- prerequsite.
+ //
+ if (tk.dir->absolute ())
+ dr << "target " << tk;
+ else
+ dr << "prerequisite " << prerequisite_key {tk, &s};
+ }
+
+ return extension_pool.find (val.as<const std::string&> ());
+ }
+}
diff --git a/build/utility b/build/utility
index 4135b37..90d1694 100644
--- a/build/utility
+++ b/build/utility
@@ -75,6 +75,9 @@ namespace build
{
const std::string&
find (const char* s) {return *emplace (s).first;}
+
+ const std::string&
+ find (const std::string& s) {return *emplace (s).first;}
};
extern string_pool extension_pool;
diff --git a/tests/cli/build/bootstrap.build b/tests/cli/build/bootstrap.build
new file mode 100644
index 0000000..9e91c9a
--- /dev/null
+++ b/tests/cli/build/bootstrap.build
@@ -0,0 +1,2 @@
+project = cli-test
+using config
diff --git a/tests/cli/build/root.build b/tests/cli/build/root.build
new file mode 100644
index 0000000..8e910cf
--- /dev/null
+++ b/tests/cli/build/root.build
@@ -0,0 +1,3 @@
+using cxx
+using cli
+
diff --git a/tests/cli/buildfile b/tests/cli/buildfile
new file mode 100644
index 0000000..705f302
--- /dev/null
+++ b/tests/cli/buildfile
@@ -0,0 +1,6 @@
+hxx.ext = hpp
+cxx.ext = cpp
+ixx.ext = ipp
+
+exe{test}: cxx{driver test}
+cxx{test}: cli{test}
diff --git a/tests/cli/driver.cpp b/tests/cli/driver.cpp
new file mode 100644
index 0000000..70b4146
--- /dev/null
+++ b/tests/cli/driver.cpp
@@ -0,0 +1,4 @@
+int
+main ()
+{
+}
diff --git a/tests/cli/test.cli b/tests/cli/test.cli
new file mode 100644
index 0000000..db3cfb8
--- /dev/null
+++ b/tests/cli/test.cli
@@ -0,0 +1,5 @@
+class options
+{
+ bool --help;
+ bool --version;
+};