aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/algorithm9
-rw-r--r--build2/algorithm.cxx34
-rw-r--r--build2/algorithm.ixx12
-rw-r--r--build2/cc/common.cxx5
-rw-r--r--build2/config/operation.cxx3
-rw-r--r--build2/context44
-rw-r--r--build2/context.cxx2
-rw-r--r--build2/dist/operation.cxx44
-rw-r--r--build2/file.cxx9
-rw-r--r--build2/operation.cxx36
-rw-r--r--build2/search.cxx27
-rw-r--r--build2/target.cxx45
-rw-r--r--build2/test/common2
-rw-r--r--build2/test/common.cxx22
-rw-r--r--build2/test/rule.cxx6
-rw-r--r--build2/test/script/script.cxx5
-rw-r--r--build2/variable8
-rw-r--r--build2/variable.cxx47
-rw-r--r--tests/test/config-test/testscript6
-rw-r--r--unit-tests/function/buildfile1
-rw-r--r--unit-tests/lexer/buildfile8
-rw-r--r--unit-tests/scheduler/buildfile7
-rw-r--r--unit-tests/test/script/lexer/buildfile7
23 files changed, 292 insertions, 97 deletions
diff --git a/build2/algorithm b/build2/algorithm
index 552ea7f..5cd31fe 100644
--- a/build2/algorithm
+++ b/build2/algorithm
@@ -65,6 +65,15 @@ namespace build2
target&
search (name, scope&);
+ // As above but only search for an already existing target. Unlike the
+ // above version, this one can be called during the execute phase.
+ //
+ // Note that currently we return NULL for project-qualified names and
+ // unknown target types.
+ //
+ target*
+ search_existing (const name&, scope&, const dir_path& out = dir_path ());
+
// Match and apply a rule to the action/target with ambiguity detection.
// Increment the target's dependents count, which means that you should call
// this function with the intent to also call execute(). In case of
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 6c812f3..1e7a1b8 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -22,6 +22,8 @@ namespace build2
target&
search (const prerequisite_key& pk)
{
+ assert (phase == run_phase::search_match);
+
// If this is a project-qualified prerequisite, then this is import's
// business.
//
@@ -37,6 +39,8 @@ namespace build2
target&
search (name n, scope& s)
{
+ assert (phase == run_phase::search_match);
+
optional<string> ext;
const target_type* tt (s.find_target_type (n, ext));
@@ -52,6 +56,36 @@ namespace build2
return search (*tt, n.dir, dir_path (), n.value, ext, &s, n.proj);
}
+ target*
+ search_existing (const name& cn, scope& s, const dir_path& out)
+ {
+ assert (phase == run_phase::search_match || phase == run_phase::execute);
+
+ // We don't handle this for now.
+ //
+ if (cn.qualified ())
+ return nullptr;
+
+ name n (cn);
+ optional<string> ext;
+ const target_type* tt (s.find_target_type (n, ext));
+
+ // For now we treat an unknown target type as an unknown target. Seems
+ // logical.
+ //
+ if (tt == nullptr)
+ return nullptr;
+
+ if (!n.dir.empty ())
+ n.dir.normalize (false, true); // Current dir collapses to an empty one.
+
+ // @@ OUT: for now we assume the prerequisite's out is undetermined.
+ // Would need to pass a pair of names.
+ //
+ return search_existing_target (
+ prerequisite_key {n.proj, {tt, &n.dir, &out, &n.value, ext}, &s});
+ }
+
pair<const rule*, match_result>
match_impl (slock& ml, action a, target& t, bool apply, const rule* skip)
{
diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx
index 7da189d..ec06ddf 100644
--- a/build2/algorithm.ixx
+++ b/build2/algorithm.ixx
@@ -11,6 +11,8 @@ namespace build2
inline target&
search (prerequisite& p)
{
+ assert (phase == run_phase::search_match);
+
if (p.target == nullptr)
p.target = &search (p.key ());
@@ -56,6 +58,8 @@ namespace build2
inline void
match (slock& ml, action a, target& t)
{
+ assert (phase == run_phase::search_match);
+
if (!t.recipe (a))
match_impl (ml, a, t, true);
@@ -72,6 +76,8 @@ namespace build2
{
// text << "U " << t << ": " << t.dependents << " " << dependency_count;
+ assert (phase == run_phase::search_match);
+
//@@ MT
//
assert (t.dependents != 0 && dependency_count != 0);
@@ -82,6 +88,8 @@ namespace build2
inline void
match_only (slock& ml, action a, target& t)
{
+ assert (phase == run_phase::search_match);
+
if (!t.recipe (a))
match_impl (ml, a, t, false);
}
@@ -89,6 +97,8 @@ namespace build2
inline pair<recipe, action>
match_delegate (slock& ml, action a, target& t, const rule& r)
{
+ assert (phase == run_phase::search_match);
+
auto rp (match_impl (ml, a, t, false, &r));
const match_result& mr (rp.second);
return make_pair (rp.first->apply (ml, mr.recipe_action, t),
@@ -101,6 +111,8 @@ namespace build2
inline group_view
resolve_group_members (slock& ml, action a, target& g)
{
+ assert (phase == run_phase::search_match);
+
group_view r (g.group_members (a));
return r.members != nullptr ? r : resolve_group_members_impl (ml, a, g);
}
diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx
index b941e6e..aa0a45b 100644
--- a/build2/cc/common.cxx
+++ b/build2/cc/common.cxx
@@ -402,7 +402,10 @@ namespace build2
// Search for an existing target with this name "as if" it was a
// prerequisite.
//
- xt = &search (move (n), s);
+ xt = search_existing (n, s);
+
+ if (xt == nullptr)
+ fail << "unable to find library " << n;
}
else
{
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index 5339ef9..63e6d9b 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -387,6 +387,7 @@ namespace build2
set_current_oif (*oif);
dependency_count = 0;
+ phase_guard pg (run_phase::search_match);
match (ml, action (configure_id, id), t);
}
@@ -566,6 +567,8 @@ namespace build2
set<scope*> projects;
+ // Note: doing everything in the load phase.
+ //
for (void* v: ts)
{
scope& root (*static_cast<scope*> (v));
diff --git a/build2/context b/build2/context
index 7f533ca..cf5483e 100644
--- a/build2/context
+++ b/build2/context
@@ -14,14 +14,43 @@
namespace build2
{
- // Top-level model (internal state) mutex and its per-thread shared lock.
+ // In order to perform each operation the build system goes through the
+ // following phases:
//
- // - model_lock is NULL during serial execution, all changes are a go
- // - model_lock is not NULL during parallel execution, append-only changes
- // - parallel execution starts with a shared lock by creating model_slock
+ // load - load the buildfiles
+ // search & match - search prerequisites and match rules
+ // execute - execute the matched rule
+ //
+ // The phase can only be changed during serial or exclusive execution
+ // (see below).
+ //
+ extern enum class run_phase {load, search_match, execute} phase;
+
+ // The build system model (internal state) is protected at the top level by
+ // the model mutex. During serial execution the model mutex is unlocked.
//
extern shared_mutex model;
+ // Parallel execution always starts with acquiring a shared model lock (by
+ // creating model_slock; see below). Pointers to these locks are cached in
+ // the model_lock TLS variable (which is NULL during serial execution).
+ //
+ // The build system starts with a "serial load" phase and then continues
+ // with parallel search & match and execute. Search & match, however, can be
+ // interrupted with an "exclusive load" by re-locking the shared lock as
+ // exclusive, changing the phase, and loading additional buildfiles.
+ //
+ // Serial load can perform arbitrary changes to the model. Exclusive load,
+ // however, can only perform "pure appends". That is, it can create new
+ // "nodes" (variables, scopes, etc) but not change already existing nodes
+ // or invalidate any references to such (the idea here is that one should
+ // be able to load additional buildfiles as long as they don't interfere
+ // with the existing build state).
+ //
+ // @@ MT: do we really have to hold shared lock during execute?
+ // @@ MT: we can also interrupt load s&m with execute -- neither handled
+ // nor documented.
+ //
extern
#ifdef __cpp_thread_local
thread_local
@@ -30,6 +59,13 @@ namespace build2
#endif
slock* model_lock;
+ struct phase_guard
+ {
+ explicit phase_guard (run_phase p): o (phase) {phase = p;}
+ ~phase_guard () {phase = o;}
+ run_phase o;
+ };
+
// A shared model lock. If there is already an instance of model_slock in
// this thread, then the new instance simply references it (asserting that
// it is locked).
diff --git a/build2/context.cxx b/build2/context.cxx
index e3f728e..0f13f27 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -23,6 +23,8 @@ using namespace butl;
namespace build2
{
+ run_phase phase = run_phase::load;
+
shared_mutex model;
#ifdef __cpp_thread_local
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
index 06df0be..c7fe83b 100644
--- a/build2/dist/operation.cxx
+++ b/build2/dist/operation.cxx
@@ -115,31 +115,35 @@ namespace build2
if (verb >= 6)
dump (a);
- scheduler::atomic_count task_count (0);
{
- model_slock ml;
+ phase_guard pg (run_phase::search_match);
- for (void* v: ts)
+ scheduler::atomic_count task_count (0);
{
- target& t (*static_cast<target*> (v));
-
- if (rs != t.base_scope ().root_scope ())
- fail << "target " << t << " is from a different project" <<
- info << "one dist() meta-operation can handle one project" <<
- info << "consider using several dist() meta-operations";
-
- l5 ([&]{trace << diag_doing (a, t);});
-
- sched.async (task_count,
- [a] (target& t)
- {
- model_slock ml;
- build2::match (ml, a, t); // @@ MT exception.
- },
- ref (t));
+ model_slock ml;
+
+ for (void* v: ts)
+ {
+ target& t (*static_cast<target*> (v));
+
+ if (rs != t.base_scope ().root_scope ())
+ fail << "target " << t << " is from a different project" <<
+ info << "one dist() meta-operation can handle one project" <<
+ info << "consider using several dist() meta-operations";
+
+ l5 ([&]{trace << diag_doing (a, t);});
+
+ sched.async (task_count,
+ [a] (target& t)
+ {
+ model_slock ml;
+ build2::match (ml, a, t); // @@ MT exception.
+ },
+ ref (t));
+ }
}
+ sched.wait (task_count);
}
- sched.wait (task_count);
if (verb >= 6)
dump (a);
diff --git a/build2/file.cxx b/build2/file.cxx
index 9440738..72d2807 100644
--- a/build2/file.cxx
+++ b/build2/file.cxx
@@ -892,6 +892,8 @@ namespace build2
// over anything that we may discover. In particular, we will prefer it
// over any bundled subprojects.
//
+ auto& vp (var_pool.rw (iroot));
+
for (;;) // Break-out loop.
{
string n ("config.import." + proj);
@@ -901,8 +903,7 @@ namespace build2
// Note: overridable variable with path auto-completion.
//
{
- const variable& var (
- var_pool.rw (ibase).insert<abs_dir_path> (n, true));
+ const variable& var (vp.insert<abs_dir_path> (n, true));
if (auto l = iroot[var])
{
@@ -931,9 +932,9 @@ namespace build2
//
if (!target.value.empty ())
{
- auto lookup = [&iroot, &loc] (string name) -> path
+ auto lookup = [&iroot, &vp, &loc] (string name) -> path
{
- const variable& var (var_pool.rw (iroot).insert<path> (name, true));
+ const variable& var (vp.insert<path> (name, true));
path r;
if (auto l = iroot[var])
diff --git a/build2/operation.cxx b/build2/operation.cxx
index 0163e04..0ade4bb 100644
--- a/build2/operation.cxx
+++ b/build2/operation.cxx
@@ -74,6 +74,8 @@ namespace build2
{
tracer trace ("search");
+ phase_guard pg (run_phase::search_match);
+
auto i (targets.find (tk, trace));
if (i == targets.end ())
fail (l) << "unknown target " << tk;
@@ -89,25 +91,29 @@ namespace build2
if (verb >= 6)
dump (a);
- scheduler::atomic_count task_count (0);
{
- model_slock ml;
+ phase_guard pg (run_phase::search_match);
- for (void* vt: ts)
+ scheduler::atomic_count task_count (0);
{
- target& t (*static_cast<target*> (vt));
- l5 ([&]{trace << "matching " << t;});
-
- sched.async (task_count,
- [a] (target& t)
- {
- model_slock ml;
- match (ml, a, t); // @@ MT exception handling.
- },
- ref (t));
+ model_slock ml;
+
+ for (void* vt: ts)
+ {
+ target& t (*static_cast<target*> (vt));
+ l5 ([&]{trace << "matching " << t;});
+
+ sched.async (task_count,
+ [a] (target& t)
+ {
+ model_slock ml;
+ match (ml, a, t); // @@ MT exception handling.
+ },
+ ref (t));
+ }
}
+ sched.wait (task_count);
}
- sched.wait (task_count);
if (verb >= 6)
dump (a);
@@ -118,6 +124,8 @@ namespace build2
{
tracer trace ("execute");
+ phase_guard pg (run_phase::execute);
+
// Execute collecting postponed targets (to be re-examined later).
// Do it in reverse order if the execution mode is 'last'.
//
diff --git a/build2/search.cxx b/build2/search.cxx
index ef4814d..65eb2fd 100644
--- a/build2/search.cxx
+++ b/build2/search.cxx
@@ -31,7 +31,7 @@ namespace build2
d = *tk.dir; // Already normalized.
else
{
- d = pk.scope->out_path ();
+ d = tk.out->empty () ? pk.scope->out_path () : pk.scope->src_path ();
if (!tk.dir->empty ())
{
@@ -51,12 +51,27 @@ namespace build2
//
// relative The out directory was specified using @-syntax as relative (to
// the prerequisite's scope) and we need to complete it similar
- // to how we complete the relative dir above. This is @@ OUT TODO.
- //
- // What if we have user-supplied out but it is in-src build. Shouldn't we
- // drop it?
+ // to how we complete the relative dir above.
//
- auto i (targets.find (*tk.type, d, *tk.out, *tk.name, tk.ext, trace));
+ dir_path o;
+ if (!tk.out->empty ())
+ {
+ if (tk.out->absolute ())
+ o = *tk.out; // Already normalized.
+ else
+ {
+ o = pk.scope->out_path ();
+ o /= *tk.out;
+ o.normalize ();
+ }
+
+ // Drop out if it is the same as src (in-src build).
+ //
+ if (o == d)
+ o.clear ();
+ }
+
+ auto i (targets.find (*tk.type, d, o, *tk.name, tk.ext, trace));
if (i == targets.end ())
return 0;
diff --git a/build2/target.cxx b/build2/target.cxx
index 81d613f..24bd246 100644
--- a/build2/target.cxx
+++ b/build2/target.cxx
@@ -625,36 +625,41 @@ namespace build2
// target_set). Also, a buildfile could load from a directory that is
// not a subdirectory of out_base. So for now we just assume that this
// is so. And so it is.
-
- // Relock for exclusive access.
//
- rlock rl (model_lock);
-
- pair<scope&, scope*> sp (switch_scope (*s.root_scope (), out_base));
-
- if (sp.second != nullptr) // Ignore scopes out of any project.
+ bool retest (false);
{
- scope& base (sp.first);
- scope& root (*sp.second);
+ // Relock for exclusive access and change to the load phase.
+ //
+ rlock rl (model_lock);
+ phase_guard pg (run_phase::load);
- path bf (base.src_path () / "buildfile");
+ pair<scope&, scope*> sp (switch_scope (*s.root_scope (), out_base));
- if (exists (bf))
+ if (sp.second != nullptr) // Ignore scopes out of any project.
{
- l5 ([&]{trace << "loading buildfile " << bf << " for " << pk;});
+ scope& base (sp.first);
+ scope& root (*sp.second);
- if (source_once (root, base, bf, root))
- {
- // If we loaded the buildfile, examine the target again.
- //
- if (t == nullptr)
- t = search_existing_target (pk);
+ path bf (base.src_path () / "buildfile");
- if (t != nullptr && !t->implied)
- return t;
+ if (exists (bf))
+ {
+ l5 ([&]{trace << "loading buildfile " << bf << " for " << pk;});
+ retest = source_once (root, base, bf, root);
}
}
}
+
+ // If we loaded the buildfile, examine the target again.
+ //
+ if (retest)
+ {
+ if (t == nullptr)
+ t = search_existing_target (pk);
+
+ if (t != nullptr && !t->implied)
+ return t;
+ }
}
fail << "no explicit target for prerequisite " << pk <<
diff --git a/build2/test/common b/build2/test/common
index e9213b6..d3678c8 100644
--- a/build2/test/common
+++ b/build2/test/common
@@ -21,7 +21,7 @@ namespace build2
const names* test_ = nullptr; // The config.test value if any.
scope* root_ = nullptr; // The root scope for target resolution.
- // Return true if the specified alias target should pass-through to it
+ // Return true if the specified alias target should pass-through to its
// prerequisites.
//
bool
diff --git a/build2/test/common.cxx b/build2/test/common.cxx
index 66be9d8..1b4c194 100644
--- a/build2/test/common.cxx
+++ b/build2/test/common.cxx
@@ -136,14 +136,19 @@ namespace build2
r = d.sub (n->dir);
else
// First quickly and cheaply weed out names that cannot possibly
- // match. Only then search for a target as if it was a prerequisite,
- // which can be expensive.
+ // match. Only then search for a target (as if it was a
+ // prerequisite), which can be expensive.
+ //
+ // We cannot specify an src target in config.test since we used
+ // the pair separator for ids. As a result, we search for both
+ // out and src targets.
//
r =
- t.name == n->value && // Name matches.
- tt.name == n->type && // Target type matches.
- d == n->dir && // Directory matches.
- search (*n, *root_) == t;
+ t.name == n->value && // Name matches.
+ tt.name == n->type && // Target type matches.
+ d == n->dir && // Directory matches.
+ (search_existing (*n, *root_) == &t ||
+ search_existing (*n, *root_, d) == &t);
if (r)
break;
@@ -189,8 +194,9 @@ namespace build2
r =
t.name == n->value &&
tt.name == n->type &&
- d == n->dir &&
- search (*n, *root_) == t;
+ d == n->dir &&
+ (search_existing (*n, *root_) == &t ||
+ search_existing (*n, *root_, d) == &t);
if (!r)
continue; // Not our target.
diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx
index 17821a4..6b06dca 100644
--- a/build2/test/rule.cxx
+++ b/build2/test/rule.cxx
@@ -589,7 +589,11 @@ namespace build2
//
// @@ OUT: what if this is a @-qualified pair or names?
//
- t = &search (*n, tt.base_scope ());
+ t = search_existing (*n, tt.base_scope ());
+
+ if (t == nullptr)
+ fail << "invalid test executable override: unknown target: '"
+ << n << "'";
}
}
else
diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx
index 342cae9..bd13845 100644
--- a/build2/test/script/script.cxx
+++ b/build2/test/script/script.cxx
@@ -549,7 +549,10 @@ namespace build2
//
// @@ OUT: what if this is a @-qualified pair or names?
//
- t = &search (*n, tt.base_scope ());
+ t = search_existing (*n, tt.base_scope ());
+
+ if (t == nullptr)
+ fail << "unknown target '" << n << "' in test variable";
}
}
else
diff --git a/build2/variable b/build2/variable
index e296e37..d31d20f 100644
--- a/build2/variable
+++ b/build2/variable
@@ -781,7 +781,7 @@ namespace build2
// Variable pool.
//
- // Protected by the model mutex.
+ // The global version is protected by the model mutex.
//
class variable_pool
{
@@ -856,6 +856,8 @@ namespace build2
void
clear () {map_.clear ();}
+ variable_pool (): variable_pool (false) {}
+
// Proof of lock for RW access.
//
variable_pool&
@@ -902,6 +904,10 @@ namespace build2
return r;
}
+ explicit
+ variable_pool (bool global): global_ (global) {}
+
+ bool global_;
map map_;
};
diff --git a/build2/variable.cxx b/build2/variable.cxx
index ec72fdc..bbe08d0 100644
--- a/build2/variable.cxx
+++ b/build2/variable.cxx
@@ -6,6 +6,7 @@
#include <cstring> // memcmp()
+#include <build2/context>
#include <build2/diagnostics>
using namespace std;
@@ -908,6 +909,8 @@ namespace build2
const variable& variable_pool::
insert (string n)
{
+ assert (!global_ || phase == run_phase::load);
+
// We are not overriding anything so skip the insert_() checks.
//
auto p (
@@ -928,6 +931,8 @@ namespace build2
const variable_visibility* v,
const bool* o)
{
+ assert (!global_ || phase == run_phase::load);
+
auto p (
insert (
variable {
@@ -940,9 +945,39 @@ namespace build2
if (!p.second)
{
+ // Check overridability (all overrides, if any, should already have
+ // been entered (see context.cxx:reset()).
+ //
+ if (r.override != nullptr && (o == nullptr || !*o))
+ fail << "variable " << r.name << " cannot be overridden";
+
+ bool ut (t != nullptr && r.type != t);
+ bool uv (v != nullptr && r.visibility != *v);
+
+ // In the global pool existing variables can only be updated during
+ // serial load.
+ //
+ /*
+ @@ MT
+
+ if (global_)
+ {
+ //assert (!(ut || uv) || model_lock == nullptr);
+
+ if (model_lock != nullptr)
+ {
+ if (ut)
+ text << r.name << " type update during exclusive load";
+
+ if (uv)
+ text << r.name << " visibility update during exclusive load";
+ }
+ }
+ */
+
// Update type?
//
- if (t != nullptr && r.type != t)
+ if (ut)
{
assert (r.type == nullptr);
const_cast<variable&> (r).type = t; // Not changing the key.
@@ -953,23 +988,17 @@ namespace build2
// were set, in which case the variable will be entered with the
// default visibility.
//
- if (v != nullptr && r.visibility != *v)
+ if (uv)
{
assert (r.visibility == variable_visibility::normal); // Default.
const_cast<variable&> (r).visibility = *v; // Not changing the key.
}
-
- // Check overridability (all overrides, if any, should already have
- // been entered (see context.cxx:reset()).
- //
- if (r.override != nullptr && (o == nullptr || !*o))
- fail << "variable " << r.name << " cannot be overridden";
}
return r;
}
- variable_pool variable_pool::instance;
+ variable_pool variable_pool::instance (true);
const variable_pool& variable_pool::cinstance = variable_pool::instance;
const variable_pool& var_pool = variable_pool::cinstance;
diff --git a/tests/test/config-test/testscript b/tests/test/config-test/testscript
index 2c262b7..833a33a 100644
--- a/tests/test/config-test/testscript
+++ b/tests/test/config-test/testscript
@@ -21,7 +21,7 @@ EOI
+cat <<EOI >=proj/buildfile
d = tests/ units/
./: $d
-include $d
+#include $d
EOI
# tests/ - as a subproject
@@ -37,7 +37,7 @@ EOI
+cat <<EOI >=proj/tests/buildfile
d = script/
./: $d
-include $d
+#include $d
EOI
# tests/script - scripted test
@@ -66,7 +66,7 @@ EOI
+cat <<EOI >=proj/units/buildfile
d = simple/ script/
./: $d test{testscript}
-include $d
+#include $d
EOI
+cat <<EOI >=proj/units/testscript
echo 'units' >|
diff --git a/unit-tests/function/buildfile b/unit-tests/function/buildfile
index 3631958..4d2862c 100644
--- a/unit-tests/function/buildfile
+++ b/unit-tests/function/buildfile
@@ -13,7 +13,6 @@ functions-builtin functions-path functions-process-path functions-string \
functions-target-triplet algorithm search dump filesystem scheduler \
config/{utility init operation}
-
exe{driver}: cxx{driver} ../../build2/cxx{$src} $libs test{call syntax}
include ../../build2/
diff --git a/unit-tests/lexer/buildfile b/unit-tests/lexer/buildfile
index 3ad0ac6..f37c7cf 100644
--- a/unit-tests/lexer/buildfile
+++ b/unit-tests/lexer/buildfile
@@ -4,8 +4,14 @@
#@@ Temporary until we get utility library support.
#
+if ($cxx.target.class != "windows")
+ cxx.libs += -lpthread
import libs = libbutl%lib{butl}
-src = token lexer diagnostics utility variable name b-options types-parsers
+src = token lexer diagnostics utility variable name b-options types-parsers \
+context scope parser target operation rule prerequisite file module function \
+functions-builtin functions-path functions-process-path functions-string \
+functions-target-triplet algorithm search dump filesystem scheduler \
+config/{utility init operation}
exe{driver}: cxx{driver} ../../build2/cxx{$src} $libs \
test{comment eval quoting}
diff --git a/unit-tests/scheduler/buildfile b/unit-tests/scheduler/buildfile
index 2e79c16..056930e 100644
--- a/unit-tests/scheduler/buildfile
+++ b/unit-tests/scheduler/buildfile
@@ -7,7 +7,12 @@
if ($cxx.target.class != "windows")
cxx.libs += -lpthread
import libs = libbutl%lib{butl}
-src = scheduler diagnostics utility variable name b-options types-parsers
+src = token lexer diagnostics utility variable name b-options types-parsers \
+context scope parser target operation rule prerequisite file module function \
+functions-builtin functions-path functions-process-path functions-string \
+functions-target-triplet algorithm search dump filesystem scheduler \
+config/{utility init operation}
+
exe{driver}: cxx{driver} ../../build2/cxx{$src} $libs
diff --git a/unit-tests/test/script/lexer/buildfile b/unit-tests/test/script/lexer/buildfile
index 43df2e3..aaeadc9 100644
--- a/unit-tests/test/script/lexer/buildfile
+++ b/unit-tests/test/script/lexer/buildfile
@@ -4,9 +4,14 @@
#@@ Temporary until we get utility library support.
#
+if ($cxx.target.class != "windows")
+ cxx.libs += -lpthread
import libs = libbutl%lib{butl}
src = token lexer diagnostics utility variable name b-options types-parsers \
-test/script/{token lexer}
+context scope parser target operation rule prerequisite file module function \
+functions-builtin functions-path functions-process-path functions-string \
+functions-target-triplet algorithm search dump filesystem scheduler \
+config/{utility init operation} test/script/{token lexer}
exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs \
test{command-line first-token second-token command-expansion variable-line \