aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/algorithm.cxx203
-rw-r--r--build/b.cxx103
-rw-r--r--build/bin/module4
-rw-r--r--build/bin/module.cxx59
-rw-r--r--build/cli/module4
-rw-r--r--build/cli/module.cxx72
-rw-r--r--build/config/module5
-rw-r--r--build/config/module.cxx19
-rw-r--r--build/config/operation.cxx3
-rw-r--r--build/config/utility6
-rw-r--r--build/context1
-rw-r--r--build/context.cxx38
-rw-r--r--build/cxx/module4
-rw-r--r--build/cxx/module.cxx91
-rw-r--r--build/module37
-rw-r--r--build/module.cxx32
-rw-r--r--build/parser.cxx14
-rw-r--r--build/rule13
-rw-r--r--build/rule-map46
-rw-r--r--build/rule.cxx3
-rw-r--r--build/scope41
-rw-r--r--build/scope.cxx100
-rw-r--r--build/target30
-rw-r--r--build/target-key38
-rw-r--r--build/target-type70
-rw-r--r--build/target.cxx77
-rw-r--r--tests/cli/lib/libtest/build/root.build1
-rw-r--r--tests/cli/lib/libtest/test/buildfile2
28 files changed, 673 insertions, 443 deletions
diff --git a/build/algorithm.cxx b/build/algorithm.cxx
index bec51aa..488bbb7 100644
--- a/build/algorithm.cxx
+++ b/build/algorithm.cxx
@@ -45,131 +45,148 @@ namespace build
//
t.prerequisite_targets.clear ();
+ size_t oi (a.operation () - 1); // Operation index in rule_map.
+ scope& bs (t.base_scope ());
+
for (auto tt (&t.type ());
tt != nullptr && !t.recipe (a);
tt = tt->base)
{
- auto i (current_rules->find (tt->id));
+ // Search scopes outwards, stopping at the project root.
+ //
+ for (const scope* s (&bs);
+ s != nullptr;
+ s = s->root () ? global_scope : s->parent_scope ())
+ {
+ const rule_map& om (s->rules);
- if (i == current_rules->end () || i->second.empty ())
- continue; // No rules registered for this target type, try base.
+ if (om.size () <= oi)
+ continue; // No entry for this operation id.
- const auto& rules (i->second); // Hint map.
+ const target_type_rule_map& ttm (om[oi]);
- // @@ TODO
- //
- // Different rules can be used for different operations (update
- // vs test is a good example). So, at some point, we will probably
- // have to support a list of hints or even an operation-hint map
- // (e.g., 'hint=cxx test=foo' if cxx supports the test operation
- // but we want the foo rule instead). This is also the place where
- // the '{build clean}=cxx' construct (which we currently do not
- // support) can come handy.
- //
- // Also, ignore the hint (that is most likely ment for a different
- // operation) if this is a unique match.
- //
- string hint;
- auto rs (rules.size () == 1
- ? make_pair (rules.begin (), rules.end ())
- : rules.find_prefix (hint));
+ if (ttm.empty ())
+ continue; // Empty map for this operation id.
- for (auto i (rs.first); i != rs.second; ++i)
- {
- const string& n (i->first);
- const rule& ru (i->second);
+ auto i (ttm.find (tt->id));
- match_result m;
- {
- auto g (
- make_exception_guard (
- [](action a, target& t, const string& n)
- {
- info << "while matching rule " << n << " to "
- << diag_do (a, t);
- },
- a, t, n));
+ if (i == ttm.end () || i->second.empty ())
+ continue; // No rules registered for this target type.
- m = ru.match (a, t, hint);
- }
+ const auto& rules (i->second); // Hint map.
+
+ // @@ TODO
+ //
+ // Different rules can be used for different operations (update
+ // vs test is a good example). So, at some point, we will probably
+ // have to support a list of hints or even an operation-hint map
+ // (e.g., 'hint=cxx test=foo' if cxx supports the test operation
+ // but we want the foo rule instead). This is also the place where
+ // the '{build clean}=cxx' construct (which we currently do not
+ // support) can come handy.
+ //
+ // Also, ignore the hint (that is most likely ment for a different
+ // operation) if this is a unique match.
+ //
+ string hint;
+ auto rs (rules.size () == 1
+ ? make_pair (rules.begin (), rules.end ())
+ : rules.find_prefix (hint));
- if (m)
+ for (auto i (rs.first); i != rs.second; ++i)
{
- // Do the ambiguity test.
- //
- bool ambig (false);
+ const string& n (i->first);
+ const rule& ru (i->second);
- diag_record dr;
+ match_result m;
+ {
+ auto g (
+ make_exception_guard (
+ [](action a, target& t, const string& n)
+ {
+ info << "while matching rule " << n << " to "
+ << diag_do (a, t);
+ },
+ a, t, n));
+
+ m = ru.match (a, t, hint);
+ }
- for (++i; i != rs.second; ++i)
+ if (m)
{
- const string& n1 (i->first);
- const rule& ru1 (i->second);
+ // Do the ambiguity test.
+ //
+ bool ambig (false);
- match_result m1;
- {
- auto g (
- make_exception_guard (
- [](action a, target& t, const string& n1)
- {
- info << "while matching rule " << n1 << " to "
- << diag_do (a, t);
- },
- a, t, n1));
-
- m1 = ru1.match (a, t, hint);
- }
+ diag_record dr;
- if (m1)
+ for (++i; i != rs.second; ++i)
{
- if (!ambig)
+ const string& n1 (i->first);
+ const rule& ru1 (i->second);
+
+ match_result m1;
{
- dr << fail << "multiple rules matching " << diag_doing (a, t)
- << info << "rule " << n << " matches";
- ambig = true;
+ auto g (
+ make_exception_guard (
+ [](action a, target& t, const string& n1)
+ {
+ info << "while matching rule " << n1 << " to "
+ << diag_do (a, t);
+ },
+ a, t, n1));
+
+ m1 = ru1.match (a, t, hint);
}
- dr << info << "rule " << n1 << " also matches";
+ if (m1)
+ {
+ if (!ambig)
+ {
+ dr << fail << "multiple rules matching " << diag_doing (a, t)
+ << info << "rule " << n << " matches";
+ ambig = true;
+ }
+
+ dr << info << "rule " << n1 << " also matches";
+ }
}
- }
- if (!ambig)
- {
- 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
+ if (!ambig)
{
- r.first = &ru;
- r.second = m;
+ 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));
+ }
+ else
+ {
+ r.first = &ru;
+ r.second = m;
+ }
+
return r;
}
+ else
+ dr << info << "use rule hint to disambiguate this match";
}
- else
- dr << info << "use rule hint to disambiguate this match";
}
}
}
- if (!t.recipe (a))
- {
- diag_record dr;
- dr << 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";
- }
+ if (verb < 3)
+ dr << info << "re-run with --verbose 3 for more information";
return r;
}
diff --git a/build/b.cxx b/build/b.cxx
index d7fb9d3..0291729 100644
--- a/build/b.cxx
+++ b/build/b.cxx
@@ -77,15 +77,8 @@ namespace build
}
#include <build/config/module>
-
-#include <build/bin/target> //@@ tmp
-#include <build/bin/rule> //@@ tmp
#include <build/bin/module>
-
-#include <build/cxx/target> //@@ tmp
-#include <build/cxx/rule> //@@ tmp
#include <build/cxx/module>
-
#include <build/cli/module>
using namespace build;
@@ -131,91 +124,12 @@ main (int argc, char* argv[])
//
tzset ();
- // Register modules.
- //
- modules["config"] = &config::init;
- modules["bin"] = &bin::init;
- modules["cxx"] = &cxx::init;
- modules["cli"] = &cli::init;
-
- // Register target types.
- //
- target_types.insert (file::static_type);
- target_types.insert (dir::static_type);
- target_types.insert (fsdir::static_type);
-
- target_types.insert (bin::obja::static_type);
- target_types.insert (bin::objso::static_type);
- target_types.insert (bin::obj::static_type);
- target_types.insert (bin::exe::static_type);
- target_types.insert (bin::liba::static_type);
- target_types.insert (bin::libso::static_type);
- target_types.insert (bin::lib::static_type);
-
- target_types.insert (cxx::h::static_type);
- target_types.insert (cxx::c::static_type);
-
- target_types.insert (cxx::cxx::static_type);
- target_types.insert (cxx::hxx::static_type);
- target_types.insert (cxx::ixx::static_type);
- target_types.insert (cxx::txx::static_type);
-
- // Register rules.
+ // Register builtin modules.
//
- bin::obj_rule obj_rule;
- bin::lib_rule lib_rule;
- {
- using namespace bin;
-
- rules[default_id][typeid (obj)].emplace ("bin.obj", obj_rule);
- rules[update_id][typeid (obj)].emplace ("bin.obj", obj_rule);
- rules[clean_id][typeid (obj)].emplace ("bin.obj", obj_rule);
-
- rules[default_id][typeid (lib)].emplace ("bin.lib", lib_rule);
- rules[update_id][typeid (lib)].emplace ("bin.lib", lib_rule);
- rules[clean_id][typeid (lib)].emplace ("bin.lib", lib_rule);
- }
-
- cxx::compile cxx_compile;
- cxx::link cxx_link;
- {
- using namespace bin;
-
- rules[default_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile);
- rules[update_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile);
- rules[clean_id][typeid (obja)].emplace ("cxx.gnu.compile", cxx_compile);
-
- rules[default_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile);
- rules[update_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile);
- rules[clean_id][typeid (objso)].emplace ("cxx.gnu.compile", cxx_compile);
-
- rules[default_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link);
- rules[update_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link);
- rules[clean_id][typeid (exe)].emplace ("cxx.gnu.link", cxx_link);
-
- rules[default_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link);
- rules[update_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link);
- rules[clean_id][typeid (liba)].emplace ("cxx.gnu.link", cxx_link);
-
- rules[default_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link);
- rules[update_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link);
- rules[clean_id][typeid (libso)].emplace ("cxx.gnu.link", cxx_link);
- }
-
- dir_rule dir_r;
- rules[default_id][typeid (dir)].emplace ("dir", dir_r);
- rules[update_id][typeid (dir)].emplace ("dir", dir_r);
- rules[clean_id][typeid (dir)].emplace ("dir", dir_r);
-
- fsdir_rule fsdir_r;
- rules[default_id][typeid (fsdir)].emplace ("fsdir", fsdir_r);
- rules[update_id][typeid (fsdir)].emplace ("fsdir", fsdir_r);
- rules[clean_id][typeid (fsdir)].emplace ("fsdir", fsdir_r);
-
- file_rule file_r;
- rules[default_id][typeid (file)].emplace ("file", file_r);
- rules[update_id][typeid (file)].emplace ("file", file_r);
- rules[clean_id][typeid (file)].emplace ("file", file_r);
+ builtin_modules["config"] = &config::config_init;
+ builtin_modules["bin"] = &bin::bin_init;
+ builtin_modules["cxx"] = &cxx::cxx_init;
+ builtin_modules["cli"] = &cli::cli_init;
// Figure out work and home directories.
//
@@ -379,7 +293,7 @@ main (int argc, char* argv[])
// Handle a few common cases as special: empty name, '.',
// '..', as well as dir{foo/bar} (without trailing '/').
- // This code must be consistent with target_type_map::find().
+ // This code must be consistent with find_target_type().
//
if (v.empty () || v == "." || v == ".." || tn.type == "dir")
out_base = dir_path (v);
@@ -744,7 +658,6 @@ main (int argc, char* argv[])
current_mif = mif;
current_oif = oif;
current_mode = oif->mode;
- current_rules = &rules[oid];
}
//
// Similar to meta-operations, check that all the targets in
@@ -787,8 +700,10 @@ main (int argc, char* argv[])
// operation batch.
//
{
+ scope& bs (scopes.find (out_base));
+
const string* e;
- const target_type* ti (target_types.find (tn, e));
+ const target_type* ti (bs.find_target_type (tn, e));
if (ti == nullptr)
fail (l) << "unknown target type " << tn.type;
diff --git a/build/bin/module b/build/bin/module
index e09e742..b87a0b0 100644
--- a/build/bin/module
+++ b/build/bin/module
@@ -12,8 +12,8 @@ namespace build
{
namespace bin
{
- void
- init (scope&, scope&, const location&);
+ extern "C" void
+ bin_init (scope&, scope&, const location&, std::unique_ptr<module>&, bool);
}
}
diff --git a/build/bin/module.cxx b/build/bin/module.cxx
index 3775302..b2374ff 100644
--- a/build/bin/module.cxx
+++ b/build/bin/module.cxx
@@ -10,39 +10,70 @@
#include <build/config/utility>
+#include <build/bin/rule>
+#include <build/bin/target>
+
using namespace std;
namespace build
{
namespace bin
{
+ static obj_rule obj_;
+ static lib_rule lib_;
+
// Default config.bin.*.lib values.
//
static const list_value exe_lib (names {name ("shared"), name ("static")});
static const list_value liba_lib ("static");
static const list_value libso_lib ("shared");
- void
- init (scope& root, scope& base, const location& l)
+ extern "C" void
+ bin_init (scope& root,
+ scope& base,
+ const location& l,
+ std::unique_ptr<module>&,
+ bool)
{
- //@@ TODO: avoid multiple inits (generally, for modules).
- //
tracer trace ("bin::init");
+ level4 ([&]{trace << "for " << base.path ();});
+
+ // Register target types.
+ //
+ {
+ auto& tts (base.target_types);
+ tts.insert<obja> ();
+ tts.insert<objso> ();
+ tts.insert<obj> ();
+ tts.insert<exe> ();
+ tts.insert<liba> ();
+ tts.insert<libso> ();
+ tts.insert<lib> ();
+ }
- //@@ Should it be this way?
+ // Register rules.
//
- if (&root != &base)
- fail (l) << "bin module must be initialized in project root scope";
+ {
+ auto& rs (base.rules);
- //@@ TODO: need to register target types, rules here instead of main().
+ rs.insert<obj> (default_id, "bin.obj", obj_);
+ rs.insert<obj> (update_id, "bin.obj", obj_);
+ rs.insert<obj> (clean_id, "bin.obj", obj_);
- const dir_path& out_root (root.path ());
- level4 ([&]{trace << "for " << out_root;});
+ rs.insert<lib> (default_id, "bin.lib", lib_);
+ rs.insert<lib> (update_id, "bin.lib", lib_);
+ rs.insert<lib> (clean_id, "bin.lib", lib_);
+ }
// Configure.
//
using config::required;
+ // The idea here is as follows: if we already have one of
+ // the bin.* variables set, then we assume this is static
+ // project configuration and don't bother setting the
+ // corresponding config.bin.* variable.
+ //
//@@ Need to validate the values. Would be more efficient
// to do it once on assignment than every time on query.
// Custom var type?
@@ -51,7 +82,7 @@ namespace build
// config.bin.lib
//
{
- auto v (root.vars.assign ("bin.lib"));
+ auto v (base.vars.assign ("bin.lib"));
if (!v)
v = required (root, "config.bin.lib", "shared").first;
}
@@ -59,7 +90,7 @@ namespace build
// config.bin.exe.lib
//
{
- auto v (root.vars.assign ("bin.exe.lib"));
+ auto v (base.vars.assign ("bin.exe.lib"));
if (!v)
v = required (root, "config.bin.exe.lib", exe_lib).first;
}
@@ -67,7 +98,7 @@ namespace build
// config.bin.liba.lib
//
{
- auto v (root.vars.assign ("bin.liba.lib"));
+ auto v (base.vars.assign ("bin.liba.lib"));
if (!v)
v = required (root, "config.bin.liba.lib", liba_lib).first;
}
@@ -75,7 +106,7 @@ namespace build
// config.bin.libso.lib
//
{
- auto v (root.vars.assign ("bin.libso.lib"));
+ auto v (base.vars.assign ("bin.libso.lib"));
if (!v)
v = required (root, "config.bin.libso.lib", libso_lib).first;
}
diff --git a/build/cli/module b/build/cli/module
index c2bc0ab..cf6258f 100644
--- a/build/cli/module
+++ b/build/cli/module
@@ -12,8 +12,8 @@ namespace build
{
namespace cli
{
- void
- init (scope&, scope&, const location&);
+ extern "C" void
+ cli_init (scope&, scope&, const location&, std::unique_ptr<module>&, bool);
}
}
diff --git a/build/cli/module.cxx b/build/cli/module.cxx
index d3fa87d..c1d0aab 100644
--- a/build/cli/module.cxx
+++ b/build/cli/module.cxx
@@ -13,7 +13,6 @@
#include <build/diagnostics>
#include <build/cxx/target>
-#include <build/cxx/module>
#include <build/config/utility>
@@ -29,54 +28,62 @@ namespace build
{
static compile compile_;
- void
- init (scope& root, scope& base, const location& l)
+ extern "C" void
+ cli_init (scope& root,
+ scope& base,
+ const location& l,
+ std::unique_ptr<module>&,
+ bool first)
{
- //@@ TODO: avoid multiple inits (generally, for modules).
- //
tracer trace ("cli::init");
+ level4 ([&]{trace << "for " << base.path ();});
- //@@ Should it be this way?
+ // 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.
//
- if (&root != &base)
- fail (l) << "cli module must be initialized in project root scope";
+ if (base.find_target_type ("cxx") == nullptr)
+ fail (l) << "cxx module must be initialized before cli";
- // Initialize the cxx module. We need its targets types (?xx{}).
- // @@ Or better require it?
+ // Register target types.
//
- cxx::init (root, base, l);
-
- const dir_path& out_root (root.path ());
- level4 ([&]{trace << "for " << out_root;});
+ {
+ auto& tts (base.target_types);
- // Register our target types.
- //
- target_types.insert (cli::static_type);
- target_types.insert (cli_cxx::static_type);
+ tts.insert<cli> ();
+ tts.insert<cli_cxx> ();
+ }
// 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_);
+ {
+ auto& rs (base.rules);
- 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_);
+ rs.insert<cli_cxx> (default_id, "cli.compile", compile_);
+ rs.insert<cli_cxx> (update_id, "cli.compile", compile_);
+ rs.insert<cli_cxx> (clean_id, "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_);
+ rs.insert<cxx::hxx> (default_id, "cli.compile", compile_);
+ rs.insert<cxx::hxx> (update_id, "cli.compile", compile_);
+ rs.insert<cxx::hxx> (clean_id, "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_);
+ rs.insert<cxx::cxx> (default_id, "cli.compile", compile_);
+ rs.insert<cxx::cxx> (update_id, "cli.compile", compile_);
+ rs.insert<cxx::cxx> (clean_id, "cli.compile", compile_);
+
+ rs.insert<cxx::ixx> (default_id, "cli.compile", compile_);
+ rs.insert<cxx::ixx> (update_id, "cli.compile", compile_);
+ rs.insert<cxx::ixx> (clean_id, "cli.compile", compile_);
+ }
// Configure.
//
// config.cli
//
+ if (first)
{
auto r (config::required (root, "config.cli", "cli"));
@@ -132,10 +139,11 @@ namespace build
// config.cli.options
//
// This one is optional. We also merge it into the corresponding
- // cli.* variables.
+ // cli.* variables. See the cxx module for more information on
+ // this merging semantics and some of its tricky aspects.
//
if (auto* v = config::optional<list_value> (root, "config.cli.options"))
- root.append ("cli.options") += *v;
+ base.assign ("cli.options") += *v;
}
}
}
diff --git a/build/config/module b/build/config/module
index 4ab3e7e..58d9814 100644
--- a/build/config/module
+++ b/build/config/module
@@ -12,8 +12,9 @@ namespace build
{
namespace config
{
- void
- init (scope&, scope&, const location&);
+ extern "C" void
+ config_init (
+ scope&, scope&, const location&, std::unique_ptr<module>&, bool);
}
}
diff --git a/build/config/module.cxx b/build/config/module.cxx
index 7ac9071..237547c 100644
--- a/build/config/module.cxx
+++ b/build/config/module.cxx
@@ -23,16 +23,23 @@ namespace build
//
static const path config_file ("build/config.build");
- void
- init (scope& root, scope& base, const location& l)
+ extern "C" void
+ config_init (scope& root,
+ scope& base,
+ const location& l,
+ std::unique_ptr<module>&,
+ bool first)
{
- //@@ TODO: avoid multiple inits (generally, for modules).
- //
-
tracer trace ("config::init");
if (&root != &base)
- fail (l) << "config module must be initialized in project root scope";
+ fail (l) << "config module must be initialized in bootstrap.build";
+
+ if (!first)
+ {
+ warn (l) << "multiple config module initializations";
+ return;
+ }
const dir_path& out_root (root.path ());
level4 ([&]{trace << "for " << out_root;});
diff --git a/build/config/operation.cxx b/build/config/operation.cxx
index ecd805f..78ad9e8 100644
--- a/build/config/operation.cxx
+++ b/build/config/operation.cxx
@@ -8,8 +8,9 @@
#include <butl/filesystem>
-#include <build/scope>
#include <build/file>
+#include <build/scope>
+#include <build/target>
#include <build/context>
#include <build/diagnostics>
diff --git a/build/config/utility b/build/config/utility
index 8ab6afe..e1d81a7 100644
--- a/build/config/utility
+++ b/build/config/utility
@@ -31,9 +31,9 @@ namespace build
required (scope& root, const char* name, const char* default_value);
// Set, if necessary, an optional config.* variable. In particular,
- // an unspecified variable is set to NULL which is used to to
- // distinguish between the "configured as unspecified" and "not
- // yet configured" cases.
+ // an unspecified variable is set to NULL which is used to distinguish
+ // between the "configured as unspecified" and "not yet configured"
+ // cases.
//
// Return the pointer to the value, which can be NULL.
//
diff --git a/build/context b/build/context
index 44948ec..a52232a 100644
--- a/build/context
+++ b/build/context
@@ -28,7 +28,6 @@ namespace build
extern const operation_info* current_oif;
extern execution_mode current_mode;
- extern const target_rule_map* current_rules;
// Reset the dependency state. In particular, this removes all the
// targets, scopes, and variable names.
diff --git a/build/context.cxx b/build/context.cxx
index 22b6f53..d62be61 100644
--- a/build/context.cxx
+++ b/build/context.cxx
@@ -9,6 +9,8 @@
#include <system_error>
#include <build/scope>
+#include <build/target>
+#include <build/rule>
#include <build/diagnostics>
using namespace std;
@@ -22,7 +24,12 @@ namespace build
const meta_operation_info* current_mif;
const operation_info* current_oif;
execution_mode current_mode;
- const target_rule_map* current_rules;
+
+ // Builtin rules.
+ //
+ static dir_rule dir_;
+ static fsdir_rule fsdir_;
+ static file_rule file_;
void
reset ()
@@ -43,6 +50,35 @@ namespace build
global_scope->assign ("work") = work;
global_scope->assign ("home") = home;
+
+ // Register builtin target types.
+ //
+ {
+ target_type_map& tts (global_scope->target_types);
+
+ tts.insert<file> ();
+ tts.insert<dir> ();
+ tts.insert<fsdir> ();
+ }
+
+ // Register builtin rules.
+ //
+ {
+ rule_map& rs (global_scope->rules);
+
+ rs.insert<dir> (default_id, "dir", dir_);
+ rs.insert<dir> (update_id, "dir", dir_);
+ rs.insert<dir> (clean_id, "dir", dir_);
+
+ rs.insert<fsdir> (default_id, "fsdir", fsdir_);
+ rs.insert<fsdir> (update_id, "fsdir", fsdir_);
+ rs.insert<fsdir> (clean_id, "fsdir", fsdir_);
+
+ rs.insert<file> (default_id, "file", file_);
+ rs.insert<file> (update_id, "file", file_);
+ rs.insert<file> (clean_id, "file", file_);
+ }
+
}
fs_status<mkdir_status>
diff --git a/build/cxx/module b/build/cxx/module
index 2d50f59..dcadcdd 100644
--- a/build/cxx/module
+++ b/build/cxx/module
@@ -12,8 +12,8 @@ namespace build
{
namespace cxx
{
- void
- init (scope&, scope&, const location&);
+ extern "C" void
+ cxx_init (scope&, scope&, const location&, std::unique_ptr<module>&, bool);
}
}
diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx
index 7e6a211..ce7c968 100644
--- a/build/cxx/module.cxx
+++ b/build/cxx/module.cxx
@@ -10,10 +10,13 @@
#include <build/scope>
#include <build/diagnostics>
-#include <build/bin/module>
-
#include <build/config/utility>
+#include <build/bin/target>
+
+#include <build/cxx/rule>
+#include <build/cxx/target>
+
using namespace std;
using namespace butl;
@@ -21,33 +24,73 @@ namespace build
{
namespace cxx
{
- void
- init (scope& root, scope& base, const location& l)
+ compile compile_;
+ link link_;
+
+ extern "C" void
+ cxx_init (scope& root,
+ scope& base,
+ const location& l,
+ std::unique_ptr<module>&,
+ bool first)
{
- //@@ TODO: avoid multiple inits (generally, for modules).
- //
-
tracer trace ("cxx::init");
+ level4 ([&]{trace << "for " << base.path ();});
- //@@ Should it be this way?
+ // Initialize the bin module. Only do this if it hasn't already
+ // been loaded so that we don't overwrite user's bin.* settings.
//
- if (&root != &base)
- fail (l) << "cxx module must be initialized in project root scope";
+ if (base.find_target_type ("obj") == nullptr)
+ load_module ("bin", root, base, l);
- // Initialize the bin module.
+ // Register target types.
//
- bin::init (root, base, l);
+ {
+ auto& tts (base.target_types);
+
+ tts.insert<h> ();
+ tts.insert<c> ();
- //@@ TODO: need to register target types, rules here instead of main().
+ tts.insert<cxx> ();
+ tts.insert<hxx> ();
+ tts.insert<ixx> ();
+ tts.insert<txx> ();
+ }
- const dir_path& out_root (root.path ());
- level4 ([&]{trace << "for " << out_root;});
+ // Register rules.
+ //
+ {
+ using namespace bin;
+
+ auto& rs (base.rules);
+
+ rs.insert<obja> (default_id, "cxx.compile", compile_);
+ rs.insert<obja> (update_id, "cxx.compile", compile_);
+ rs.insert<obja> (clean_id, "cxx.compile", compile_);
+
+ rs.insert<objso> (default_id, "cxx.compile", compile_);
+ rs.insert<objso> (update_id, "cxx.compile", compile_);
+ rs.insert<objso> (clean_id, "cxx.compile", compile_);
+
+ rs.insert<exe> (default_id, "cxx.link", link_);
+ rs.insert<exe> (update_id, "cxx.link", link_);
+ rs.insert<exe> (clean_id, "cxx.link", link_);
+
+ rs.insert<liba> (default_id, "cxx.link", link_);
+ rs.insert<liba> (update_id, "cxx.link", link_);
+ rs.insert<liba> (clean_id, "cxx.link", link_);
+
+ rs.insert<libso> (default_id, "cxx.link", link_);
+ rs.insert<libso> (update_id, "cxx.link", link_);
+ rs.insert<libso> (clean_id, "cxx.link", link_);
+ }
// Configure.
//
// config.cxx
//
+ if (first)
{
auto r (config::required (root, "config.cxx", "g++"));
@@ -98,17 +141,27 @@ namespace build
// These are optional. We also merge them into the corresponding
// cxx.* variables.
//
+ // The merging part gets a bit tricky if this module has already
+ // been loaded in one of the outer scopes. By doing the straight
+ // append we would just be repeating the same options over and
+ // over. So what we are going to do is only append to a value if
+ // it came from this scope. Then the usage for merging becomes:
+ //
+ // cxx.coptions = <overridable options> # Note: '='.
+ // using cxx
+ // cxx.coptions += <overriding options> # Note: '+='.
+ //
if (auto* v = config::optional<list_value> (root, "config.cxx.poptions"))
- root.append ("cxx.poptions") += *v;
+ base.assign ("cxx.poptions") += *v;
if (auto* v = config::optional<list_value> (root, "config.cxx.coptions"))
- root.append ("cxx.coptions") += *v;
+ base.assign ("cxx.coptions") += *v;
if (auto* v = config::optional<list_value> (root, "config.cxx.loptions"))
- root.append ("cxx.loptions") += *v;
+ base.assign ("cxx.loptions") += *v;
if (auto* v = config::optional<list_value> (root, "config.cxx.libs"))
- root.append ("cxx.libs") += *v;
+ base.assign ("cxx.libs") += *v;
}
}
}
diff --git a/build/module b/build/module
index 54a202c..43ab1e0 100644
--- a/build/module
+++ b/build/module
@@ -5,18 +5,47 @@
#ifndef BUILD_MODULE
#define BUILD_MODULE
+#include <map>
#include <string>
-#include <unordered_map>
+#include <memory> // unique_ptr
namespace build
{
class scope;
class location;
- using module_init_function = void (scope& root, scope& base, const location&);
- using module_map = std::unordered_map<std::string, module_init_function*>;
+ class module
+ {
+ public:
+ virtual
+ ~module () = default;
+ };
- extern module_map modules;
+ extern "C"
+ using module_init_function =
+ void (scope& root,
+ scope& base,
+ const location&,
+ std::unique_ptr<module>&,
+ bool first); // First time for this project.
+
+ using loaded_module_map =
+ std::map<std::string,
+ std::pair<module_init_function*, std::unique_ptr<module>>>;
+
+ // Load the specified module. Used by the parser but also by some
+ // modules to load prerequisite modules.
+ //
+ void
+ load_module (const std::string& name,
+ scope& root,
+ scope& base,
+ const location&);
+
+ // Builtin modules.
+ //
+ using available_module_map = std::map<std::string, module_init_function*>;
+ extern available_module_map builtin_modules;
}
#endif // BUILD_MODULE
diff --git a/build/module.cxx b/build/module.cxx
index 6b410e6..0f2b1b2 100644
--- a/build/module.cxx
+++ b/build/module.cxx
@@ -4,9 +4,39 @@
#include <build/module>
+#include <utility> // make_pair()
+
+#include <build/scope>
+#include <build/diagnostics>
+
using namespace std;
namespace build
{
- module_map modules;
+ available_module_map builtin_modules;
+
+ void
+ load_module (const string& name, scope& root, scope& base, const location& l)
+ {
+ // First see if this modules has already been loaded for this
+ // project.
+ //
+ loaded_module_map& lm (root.modules);
+ auto i (lm.find (name));
+ bool f (i == lm.end ());
+
+ if (f)
+ {
+ // Otherwise search for this module.
+ //
+ auto j (builtin_modules.find (name));
+
+ if (j == builtin_modules.end ())
+ fail (l) << "unknown module " << name;
+
+ i = lm.emplace (name, make_pair (j->second, nullptr)).first;
+ }
+
+ i->second.first (root, base, l, i->second.second, f);
+ }
}
diff --git a/build/parser.cxx b/build/parser.cxx
index c8bc241..59e3783 100644
--- a/build/parser.cxx
+++ b/build/parser.cxx
@@ -261,7 +261,7 @@ namespace build
auto enter_target = [this, &nloc, &trace] (name&& tn) -> target&
{
const string* e;
- const target_type* ti (target_types.find (tn, e));
+ const target_type* ti (scope_->find_target_type (tn, e));
if (ti == nullptr)
fail (nloc) << "unknown target type " << tn.type;
@@ -335,7 +335,7 @@ namespace build
for (auto& pn: pns)
{
const string* e;
- const target_type* ti (target_types.find (pn, e));
+ const target_type* ti (scope_->find_target_type (pn, e));
if (ti == nullptr)
fail (ploc) << "unknown target type " << pn.type;
@@ -720,16 +720,10 @@ namespace build
{
// For now it should be a simple name.
//
- if (!n.type.empty () || !n.dir.empty ())
+ if (!n.simple ())
fail (l) << "module name expected instead of " << n;
- const string& name (n.value);
- auto i (modules.find (name));
-
- if (i == modules.end ())
- fail (l) << "unknown module " << name;
-
- i->second (*root_, *scope_, l);
+ load_module (n.value, *root_, *scope_, l);
}
if (tt == type::newline)
diff --git a/build/rule b/build/rule
index eda6ed3..fbcf06b 100644
--- a/build/rule
+++ b/build/rule
@@ -7,11 +7,6 @@
#include <string>
#include <cstddef> // nullptr_t
-#include <typeindex>
-#include <functional> // reference_wrapper
-#include <unordered_map>
-
-#include <butl/prefix-map>
#include <build/types>
#include <build/target>
@@ -56,14 +51,6 @@ namespace build
apply (action, target&, const match_result&) const = 0;
};
- using target_rule_map = std::unordered_map<
- std::type_index,
- butl::prefix_map<std::string, std::reference_wrapper<rule>, '.'>>;
-
- using operation_rule_map = std::unordered_map<operation_id, target_rule_map>;
-
- extern operation_rule_map rules;
-
// Fallback rule that on update verifies that the file exists and is
// not older than any of its prerequisites.
//
diff --git a/build/rule-map b/build/rule-map
new file mode 100644
index 0000000..883800b
--- /dev/null
+++ b/build/rule-map
@@ -0,0 +1,46 @@
+// file : build/rule-map -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_RULE_MAP
+#define BUILD_RULE_MAP
+
+#include <map>
+#include <vector>
+#include <string>
+#include <typeindex>
+#include <functional> // reference_wrapper
+
+#include <butl/prefix-map>
+
+#include <build/types>
+#include <build/operation>
+
+namespace build
+{
+ class rule;
+
+ using target_type_rule_map = std::map<
+ std::type_index, // Target type.
+ butl::prefix_map<std::string, // Rule hint.
+ std::reference_wrapper<rule>, '.'>>;
+
+ // This is an "indexed map" with (operation_id - 1) being the
+ // index.
+ //
+ class rule_map: public std::vector<target_type_rule_map>
+ {
+ public:
+ template <typename T>
+ void
+ insert (operation_id oid, const char* hint, rule& r)
+ {
+ if (oid > size ())
+ resize (oid < 3 ? 3 : oid); // 3 is the number of builtin operations.
+
+ (*this)[oid - 1][typeid (T)].emplace (hint, r);
+ }
+ };
+}
+
+#endif // BUILD_RULE_MAP
diff --git a/build/rule.cxx b/build/rule.cxx
index a8c77a8..eadbecb 100644
--- a/build/rule.cxx
+++ b/build/rule.cxx
@@ -10,6 +10,7 @@
#include <butl/filesystem>
#include <build/scope>
+#include <build/target>
#include <build/algorithm>
#include <build/diagnostics>
#include <build/context>
@@ -19,8 +20,6 @@ using namespace butl;
namespace build
{
- operation_rule_map rules;
-
// file_rule
//
// Note that this rule is special. It is the last, fallback rule. If
diff --git a/build/scope b/build/scope
index 5266370..d1cd941 100644
--- a/build/scope
+++ b/build/scope
@@ -12,8 +12,11 @@
#include <butl/path-map>
#include <build/types>
+#include <build/module>
#include <build/variable>
#include <build/prerequisite>
+#include <build/target-type>
+#include <build/rule-map>
#include <build/operation>
namespace build
@@ -60,10 +63,11 @@ namespace build
return operator[] (variable_pool.find (name));
}
- // Return a value_proxy suitable for assignment. If the variable
- // does not exist in this scope's map, then a new one with the
- // NULL value is added and returned. Otherwise the existing value
- // if returned.
+ // Return a value_proxy suitable for assignment (or append if you
+ // only want to append to the value from this scope). If the variable
+ // does not exist in this scope's map, then a new one with the NULL
+ // value is added and returned. Otherwise the existing value is
+ // returned.
//
value_proxy
assign (const variable& var)
@@ -92,6 +96,8 @@ namespace build
return append (variable_pool.find (name));
}
+ // Prerequisite cache.
+ //
public:
prerequisite_set prerequisites;
@@ -109,6 +115,33 @@ namespace build
//
std::unordered_set<path_type> buildfiles;
+ // Target types.
+ //
+ public:
+ target_type_map target_types;
+
+ const target_type*
+ find_target_type (const char*, const scope** = nullptr) const;
+
+ // Given a name, figure out its type, taking into account extensions,
+ // special names (e.g., '.' and '..'), or anything else that might be
+ // relevant. Also process the name (in place) by extracting the
+ // extension, adjusting dir/value, etc., (note that the dir is not
+ // necessarily normalized). Return NULL if not found.
+ //
+ const target_type*
+ find_target_type (name&, const std::string*& ext) const;
+
+ // Rules.
+ //
+ public:
+ rule_map rules;
+
+ // Modules.
+ //
+ public:
+ loaded_module_map modules; // Only on root scope.
+
private:
friend class scope_map;
friend class temp_scope;
diff --git a/build/scope.cxx b/build/scope.cxx
index 5bd2b62..4645979 100644
--- a/build/scope.cxx
+++ b/build/scope.cxx
@@ -4,6 +4,8 @@
#include <build/scope>
+#include <build/target>
+
using namespace std;
namespace build
@@ -40,6 +42,104 @@ namespace build
return r;
}
+ const target_type* scope::
+ find_target_type (const char* tt, const scope** rs) const
+ {
+ // Search scopes outwards, stopping at the project root.
+ //
+ for (const scope* s (this);
+ s != nullptr;
+ s = s->root () ? global_scope : s->parent_scope ())
+ {
+ if (s->target_types.empty ())
+ continue;
+
+ auto i (s->target_types.find (tt));
+
+ if (i != s->target_types.end ())
+ {
+ if (rs != nullptr)
+ *rs = s;
+
+ return &i->second.get ();
+ }
+ }
+
+ return nullptr;
+ }
+
+ const target_type* scope::
+ find_target_type (name& n, const string*& ext) const
+ {
+ ext = nullptr;
+
+ string& v (n.value);
+
+ // First determine the target type.
+ //
+ const char* tt;
+ if (n.type.empty ())
+ {
+ // Empty name or '.' and '..' signify a directory.
+ //
+ if (v.empty () || v == "." || v == "..")
+ tt = "dir";
+ else
+ //@@ TODO: derive type from extension.
+ //
+ tt = "file";
+ }
+ else
+ tt = n.type.c_str ();
+
+ const target_type* r (find_target_type (tt));
+
+ if (r == nullptr)
+ return r;
+
+ // Directories require special name processing. If we find that more
+ // targets deviate, then we should make this target-type-specific.
+ //
+ if (r->is_a<dir> () || r->is_a<fsdir> ())
+ {
+ // The canonical representation of a directory name is with empty
+ // value.
+ //
+ if (!v.empty ())
+ {
+ n.dir /= dir_path (v); // Move name value to dir.
+ v.clear ();
+ }
+ }
+ else
+ {
+ // Split the path into its directory part (if any) the name part,
+ // and the extension (if any). We cannot assume the name part is
+ // a valid filesystem name so we will have to do the splitting
+ // manually.
+ //
+ path::size_type i (path::traits::rfind_separator (v));
+
+ if (i != string::npos)
+ {
+ n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/".
+ v = string (v, i + 1, string::npos);
+ }
+
+ // Extract the extension.
+ //
+ string::size_type j (path::traits::find_extension (v));
+
+ if (j != string::npos)
+ {
+ ext = &extension_pool.find (v.c_str () + j + 1);
+ v.resize (j);
+ }
+ }
+
+ return r;
+ }
+
// scope_map
//
scope_map scopes;
diff --git a/build/target b/build/target
index 4131338..954604c 100644
--- a/build/target
+++ b/build/target
@@ -10,19 +10,20 @@
#include <vector>
#include <memory> // unique_ptr
#include <cstddef> // size_t
-#include <functional> // function, reference_wrapper
+#include <functional> // reference_wrapper
#include <ostream>
#include <cassert>
#include <utility> // move(), forward(), declval()
#include <iterator>
#include <type_traits>
-#include <butl/utility> // compare_c_string, reverse_iterate()
+#include <butl/utility> // reverse_iterate()
#include <butl/multi-index> // map_iterator_adapter
#include <build/types>
#include <build/variable>
#include <build/operation>
+#include <build/target-type>
#include <build/target-key>
#include <build/prerequisite>
@@ -756,31 +757,6 @@ namespace build
extern target_set targets;
- using target_type_map_base = std::map<
- const char*,
- std::reference_wrapper<const target_type>,
- butl::compare_c_string>;
-
- class target_type_map: public target_type_map_base
- {
- public:
- void
- insert (const target_type& tt) {emplace (tt.name, tt);}
-
- using target_type_map_base::find;
-
- // Given a name, figure out its type, taking into account extensions,
- // special names (e.g., '.' and '..'), or anything else that might be
- // relevant. Also process the name (in place) by extracting the
- // extension, adjusting dir/value, etc (note that the dir is not
- // necessarily normalized). Return NULL if not found.
- //
- const target_type*
- find (name&, const std::string*& ext) const;
- };
-
- extern target_type_map target_types;
-
// Modification time-based target.
//
class mtime_target: public target
diff --git a/build/target-key b/build/target-key
index 54813b0..37fa6ab 100644
--- a/build/target-key
+++ b/build/target-key
@@ -5,45 +5,19 @@
#ifndef BUILD_TARGET_KEY
#define BUILD_TARGET_KEY
+#include <map>
#include <string>
-#include <typeindex>
#include <ostream>
+#include <typeindex>
+#include <functional> // reference_wrapper
+
+#include <butl/utility> // compare_c_string
#include <build/types>
+#include <build/target-type>
namespace build
{
- class scope;
- class target;
- class target_key;
- class prerequisite_key;
-
- // Target type.
- //
- struct target_type
- {
- std::type_index id;
- 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&);
- bool see_through; // A group with the default "see through" semantics.
-
- bool
- is_a (const std::type_index&) const; // Defined in target.cxx
-
- template <typename T>
- bool
- is_a () const {return is_a (typeid (T));}
- };
-
- inline std::ostream&
- operator<< (std::ostream& os, const target_type& tt)
- {
- return os << tt.name;
- }
-
// Light-weight (by being shallow-pointing) target key.
//
class target_key
diff --git a/build/target-type b/build/target-type
new file mode 100644
index 0000000..d5e7971
--- /dev/null
+++ b/build/target-type
@@ -0,0 +1,70 @@
+// file : build/target-type -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_TARGET_TYPE
+#define BUILD_TARGET_TYPE
+
+#include <map>
+#include <string>
+#include <ostream>
+#include <typeindex>
+#include <functional> // reference_wrapper
+
+#include <butl/utility> // compare_c_string
+
+#include <build/types>
+
+namespace build
+{
+ class scope;
+ class target;
+ class target_key;
+ class prerequisite_key;
+
+ // Target type.
+ //
+ struct target_type
+ {
+ std::type_index id;
+ 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&);
+ bool see_through; // A group with the default "see through" semantics.
+
+ bool
+ is_a (const std::type_index&) const; // Defined in target.cxx
+
+ template <typename T>
+ bool
+ is_a () const {return is_a (typeid (T));}
+ };
+
+ inline std::ostream&
+ operator<< (std::ostream& os, const target_type& tt)
+ {
+ return os << tt.name;
+ }
+
+ // Target type map.
+ //
+ using target_type_map_base = std::map<
+ const char*,
+ std::reference_wrapper<const target_type>,
+ butl::compare_c_string>;
+
+ class target_type_map: public target_type_map_base
+ {
+ public:
+ void
+ insert (const target_type& tt) {emplace (tt.name, tt);}
+
+ template <typename T>
+ void
+ insert () {insert (T::static_type);}
+ };
+}
+
+#endif // BUILD_TARGET_TYPE
diff --git a/build/target.cxx b/build/target.cxx
index f692a5e..63bb2fe 100644
--- a/build/target.cxx
+++ b/build/target.cxx
@@ -197,83 +197,6 @@ namespace build
return os;
}
- //
- //
- target_type_map target_types;
-
- const target_type* target_type_map::
- find (name& n, const string*& ext) const
- {
- ext = nullptr;
-
- string& v (n.value);
-
- // First determine the target type.
- //
- const char* tt;
- if (n.type.empty ())
- {
- // Empty name or '.' and '..' signify a directory.
- //
- if (v.empty () || v == "." || v == "..")
- tt = "dir";
- else
- //@@ TODO: derive type from extension.
- //
- tt = "file";
- }
- else
- tt = n.type.c_str ();
-
- auto i (find (tt));
- if (i == end ())
- return nullptr;
-
- const target_type& ti (i->second);
-
- // Directories require special name processing. If we find that more
- // targets deviate, then we should make this target-type-specific.
- //
- if (ti.id == dir::static_type.id || ti.id == fsdir::static_type.id)
- {
- // The canonical representation of a directory name is with empty
- // value.
- //
- if (!v.empty ())
- {
- n.dir /= dir_path (v); // Move name value to dir.
- v.clear ();
- }
- }
- else
- {
- // Split the path into its directory part (if any) the name part,
- // and the extension (if any). We cannot assume the name part is
- // a valid filesystem name so we will have to do the splitting
- // manually.
- //
- path::size_type i (path::traits::rfind_separator (v));
-
- if (i != string::npos)
- {
- n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/".
- v = string (v, i + 1, string::npos);
- }
-
- // Extract the extension.
- //
- string::size_type j (path::traits::find_extension (v));
-
- if (j != string::npos)
- {
- ext = &extension_pool.find (v.c_str () + j + 1);
- v.resize (j);
- }
- }
-
- return &ti;
- }
-
// path_target
//
void path_target::
diff --git a/tests/cli/lib/libtest/build/root.build b/tests/cli/lib/libtest/build/root.build
index f2f5ca6..4dce48b 100644
--- a/tests/cli/lib/libtest/build/root.build
+++ b/tests/cli/lib/libtest/build/root.build
@@ -4,4 +4,3 @@ hxx.ext =
ixx.ext = ipp
cxx.ext = cpp
-using cli
diff --git a/tests/cli/lib/libtest/test/buildfile b/tests/cli/lib/libtest/test/buildfile
index 226e43d..6b0d9bd 100644
--- a/tests/cli/lib/libtest/test/buildfile
+++ b/tests/cli/lib/libtest/test/buildfile
@@ -1,3 +1,5 @@
+using cli
+
lib{test}: cxx{utility} cli.cxx{test base} extra/cxx{test}
cli.cxx{test}: cli{test}
cli.cxx{base}: cli{base}