aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/b.cxx24
-rw-r--r--build2/config/operation.cxx31
-rw-r--r--build2/context88
-rw-r--r--build2/context.cxx4
-rw-r--r--build2/dist/operation.cxx102
-rw-r--r--build2/operation24
-rw-r--r--build2/operation.cxx54
-rw-r--r--build2/scheduler42
-rw-r--r--build2/scheduler.cxx26
-rw-r--r--build2/scheduler.txx8
-rw-r--r--build2/variable45
-rw-r--r--unit-tests/function/buildfile2
-rw-r--r--unit-tests/function/driver.cxx4
-rw-r--r--unit-tests/test/script/parser/driver.cxx11
14 files changed, 278 insertions, 187 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index 54defb3..6f52a0e 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -96,8 +96,6 @@ main (int argc, char* argv[])
<< system_error (errno, system_category ()); // Sanitize.
#endif
- ulock ml (model);
-
// Parse the command line. We want to be able to specify options, vars,
// and buildspecs in any order (it is really handy to just add -v at the
// end of the command line).
@@ -364,7 +362,7 @@ main (int argc, char* argv[])
//
if (dirty)
{
- var_ovs = reset (ml, cmd_vars);
+ var_ovs = reset (cmd_vars);
dirty = false;
}
@@ -975,7 +973,7 @@ main (int argc, char* argv[])
// Load the buildfile.
//
- mif->load (ml, rs, ts.buildfile, ts.out_base, ts.src_base, l);
+ mif->load (rs, ts.buildfile, ts.out_base, ts.src_base, l);
// Next search and match the targets. We don't want to start
// building before we know how to for all the targets in this
@@ -1009,11 +1007,7 @@ main (int argc, char* argv[])
? out_src (d, rs)
: dir_path ());
- mif->search (ml,
- rs,
- target_key {ti, &d, &out, &tn.value, e},
- l,
- tgs);
+ mif->search (rs, target_key {ti, &d, &out, &tn.value, e}, l, tgs);
}
} // target
@@ -1032,8 +1026,8 @@ main (int argc, char* argv[])
action a (mid, pre_oid, oid);
- mif->match (ml, a, tgs);
- mif->execute (ml, a, tgs, true); // Run quiet.
+ mif->match (a, tgs);
+ mif->execute (a, tgs, true); // Run quiet.
if (mif->operation_post != nullptr)
mif->operation_post (pre_oid);
@@ -1047,8 +1041,8 @@ main (int argc, char* argv[])
action a (mid, oid, 0);
- if (mif->match != nullptr) mif->match (ml, a, tgs);
- if (mif->execute != nullptr) mif->execute (ml, a, tgs, verb == 0);
+ if (mif->match != nullptr) mif->match (a, tgs);
+ if (mif->execute != nullptr) mif->execute (a, tgs, verb == 0);
if (post_oid != 0)
{
@@ -1063,8 +1057,8 @@ main (int argc, char* argv[])
action a (mid, post_oid, oid);
- mif->match (ml, a, tgs);
- mif->execute (ml, a, tgs, true); // Run quiet.
+ mif->match (a, tgs);
+ mif->execute (a, tgs, true); // Run quiet.
if (mif->operation_post != nullptr)
mif->operation_post (post_oid);
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index 175338a..5339ef9 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -345,13 +345,13 @@ namespace build2
}
static void
- configure_match (ulock&, action, action_targets&)
+ configure_match (action, action_targets&)
{
// Don't match anything -- see execute ().
}
static void
- configure_execute (ulock& ml, action a, const action_targets& ts, bool)
+ configure_execute (action a, const action_targets& ts, bool)
{
// Match rules to configure every operation supported by each
// project. Note that we are not calling operation_pre/post()
@@ -360,9 +360,13 @@ namespace build2
//
set<scope*> projects;
- // Relock for shared access.
+ // Note that we cannot do this in parallel. We cannot parallelize the
+ // outer loop because we should match for a single action at a time.
+ // And we cannot swap the loops because the list of operations is
+ // target-specific. However, inside match(), things can proceed in
+ // parallel.
//
- ml.unlock ();
+ model_slock ml;
for (void* v: ts)
{
@@ -372,9 +376,6 @@ namespace build2
if (rs == nullptr)
fail << "out of project target " << t;
- slock sl (*ml.mutex ());
- model_lock = &sl; // @@ Guard?
-
for (operations::size_type id (default_id + 1); // Skip default_id
id < rs->operations.size ();
++id)
@@ -386,15 +387,11 @@ namespace build2
set_current_oif (*oif);
dependency_count = 0;
- match (sl, action (configure_id, id), t);
+ match (ml, action (configure_id, id), t);
}
- model_lock = nullptr;
-
configure_project (a, *rs, projects);
}
-
- ml.lock ();
}
const meta_operation_info configure {
@@ -425,8 +422,7 @@ namespace build2
}
static void
- disfigure_load (ulock&,
- scope&,
+ disfigure_load (scope&,
const path& bf,
const dir_path&,
const dir_path&,
@@ -437,8 +433,7 @@ namespace build2
}
static void
- disfigure_search (ulock&,
- scope& root,
+ disfigure_search (scope& root,
const target_key&,
const location&,
action_targets& ts)
@@ -449,7 +444,7 @@ namespace build2
}
static void
- disfigure_match (ulock&, action, action_targets&)
+ disfigure_match (action, action_targets&)
{
}
@@ -565,7 +560,7 @@ namespace build2
}
static void
- disfigure_execute (ulock&, action a, const action_targets& ts, bool quiet)
+ disfigure_execute (action a, const action_targets& ts, bool quiet)
{
tracer trace ("disfigure_execute");
diff --git a/build2/context b/build2/context
index 33b99a0..9f1e956 100644
--- a/build2/context
+++ b/build2/context
@@ -28,6 +28,80 @@ namespace build2
#endif
slock* model_lock;
+ // 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).
+ //
+ // The reason for this semantics is to support the following scheduling
+ // pattern:
+ //
+ // scheduler::atomic_count task_count (0);
+ //
+ // {
+ // model_slock ml; // (1)
+ //
+ // for (...)
+ // {
+ // sched.async (task_count,
+ // [] (...)
+ // {
+ // model_slock ml; // (2)
+ // ...
+ // },
+ // ...);
+ // }
+ // }
+ //
+ // sched.wait (); // (3)
+ //
+ // Here is what's going on here:
+ //
+ // 1. We first get a shared lock "for ourselves" since after the first
+ // iteration of the loop, things may become asynchronous (including
+ // attempts to relock for exclusive access and change the structure we
+ // are iteration upon).
+ //
+ // 2. The task can be queued or it can be executed synchronously inside
+ // async() (refer to the scheduler class for details on this semantics).
+ //
+ // If this is an async()-synchronous execution, then the task will create
+ // a referencing model_slock. If, however, this is a queued execution
+ // (including wait()-synchronous), then the task will create a top-level
+ // model_slock.
+ //
+ // Note that we only acquire the lock once the task starts executing
+ // (there is no reason to hold the lock while the task is sitting in the
+ // queue). This optimization assumes that whatever else we pass to the
+ // task (for example, a reference to a target) is immutable (so such a
+ // reference cannot become invalid).
+ //
+ // 3. Before calling wait(), we release our shared lock to allow re-locking
+ // for exclusive access. And once wait() returns we are again running
+ // serially.
+ //
+ struct model_slock
+ {
+ model_slock ()
+ {
+ if (slock* l = model_lock)
+ assert (l->owns_lock ());
+ else
+ model_lock = &(l_ = slock (model));
+ }
+
+ ~model_slock ()
+ {
+ if (&l_ == model_lock)
+ model_lock = nullptr;
+ }
+
+ operator slock& () {return *model_lock;}
+ operator const slock& () const {return *model_lock;}
+
+ private:
+ slock l_;
+ };
+
// Cached variables.
//
extern const variable* var_src_root;
@@ -80,18 +154,6 @@ namespace build2
//
extern uint64_t dependency_count;
- // Project-wide (as opposed to global) variable overrides. Returned by
- // reset().
- //
- struct variable_override
- {
- const variable& var; // Original variable.
- const variable& ovr; // Override variable.
- value val;
- };
-
- using variable_overrides = vector<variable_override>;
-
// Variable override value cache.
//
extern variable_override_cache var_override_cache;
@@ -100,7 +162,7 @@ namespace build2
// scopes, and variables.
//
variable_overrides
- reset (const ulock&, const strings& cmd_vars);
+ reset (const strings& cmd_vars);
// Return the project name or empty string if unnamed.
//
diff --git a/build2/context.cxx b/build2/context.cxx
index 1cf3dd9..e3f728e 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -57,7 +57,7 @@ namespace build2
variable_override_cache var_override_cache;
variable_overrides
- reset (const ulock& ml, const strings& cmd_vars)
+ reset (const strings& cmd_vars)
{
tracer trace ("reset");
@@ -66,7 +66,7 @@ namespace build2
l6 ([&]{trace << "resetting build state";});
- auto& vp (var_pool.rw (ml));
+ auto& vp (variable_pool::instance);
variable_overrides vos;
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
index 83a5b01..06df0be 100644
--- a/build2/dist/operation.cxx
+++ b/build2/dist/operation.cxx
@@ -10,6 +10,7 @@
#include <build2/target>
#include <build2/context>
#include <build2/algorithm>
+#include <build2/scheduler>
#include <build2/filesystem>
#include <build2/diagnostics>
@@ -20,21 +21,6 @@ namespace build2
{
namespace dist
{
- static operation_id
- dist_operation_pre (operation_id o)
- {
- if (o != default_id)
- fail << "explicit operation specified for dist meta-operation";
-
- return o;
- }
-
- static void
- dist_match (ulock&, action, action_targets&)
- {
- // Don't match anything -- see execute ().
- }
-
// install -d <dir>
//
static void
@@ -53,8 +39,23 @@ namespace build2
const dir_path& dir,
const string& ext);
+ static operation_id
+ dist_operation_pre (operation_id o)
+ {
+ if (o != default_id)
+ fail << "explicit operation specified for dist meta-operation";
+
+ return o;
+ }
+
+ static void
+ dist_match (action, action_targets&)
+ {
+ // Don't match anything -- see execute ().
+ }
+
static void
- dist_execute (ulock& ml, action, const action_targets& ts, bool)
+ dist_execute (action, const action_targets& ts, bool)
{
tracer trace ("dist_execute");
@@ -97,57 +98,60 @@ namespace build2
const string& dist_package (cast<string> (l));
const process_path& dist_cmd (cast<process_path> (rs->vars["dist.cmd"]));
- // Relock for shared access. @@ BOGUS
- //
- ml.unlock ();
-
- // Get the list of operations supported by this project. Skip
+ // Match a rule for every operation supported by this project. Skip
// default_id.
//
- for (operations::size_type id (default_id + 1);
- id < rs->operations.size ();
- ++id)
+ auto match = [&trace, &rs, &ts] (const operation_info& o)
{
- const operation_info* oif (rs->operations[id]);
- if (oif == nullptr)
- continue;
-
// Note that we are not calling operation_pre/post() callbacks
// here since the meta operation is dist and we know what we
// are doing.
//
- set_current_oif (*oif);
+ set_current_oif (o);
dependency_count = 0;
- action a (dist_id, id);
+ action a (dist_id, o.id);
if (verb >= 6)
dump (a);
- slock sl (*ml.mutex ());
- model_lock = &sl; // @@ Guard?
-
- for (void* v: ts)
+ scheduler::atomic_count task_count (0);
{
- target& t (*static_cast<target*> (v));
+ model_slock ml;
- 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);});
-
- match (sl, a, t);
+ 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));
+ }
}
-
- model_lock = nullptr;
+ sched.wait (task_count);
if (verb >= 6)
dump (a);
- }
+ };
- ml.lock ();
+ for (operations::size_type id (default_id + 1);
+ id < rs->operations.size ();
+ ++id)
+ {
+ if (const operation_info* oif = rs->operations[id])
+ match (*oif);
+ }
// Add buildfiles that are not normally loaded as part of the
// project, for example, the export stub. They will still be
@@ -258,8 +262,8 @@ namespace build2
action a (perform_id, update_id);
- perform.match (ml, a, files);
- perform.execute (ml, a, files, true); // Run quiet.
+ perform.match (a, files);
+ perform.execute (a, files, true); // Run quiet.
if (perform.operation_post != nullptr)
perform.operation_post (update_id);
diff --git a/build2/operation b/build2/operation
index d52c748..22bb8a7 100644
--- a/build2/operation
+++ b/build2/operation
@@ -110,7 +110,7 @@ namespace build2
// something here remember to update the man page.
//
const operation_id default_id = 1; // Shall be first.
- const operation_id update_id = 2;
+ const operation_id update_id = 2; // Shall be second.
const operation_id clean_id = 3;
const operation_id test_id = 4;
const operation_id install_id = 5;
@@ -198,25 +198,20 @@ namespace build2
// Meta-operation-specific logic to load the buildfile, search and match
// the targets, and execute the action on the targets.
//
- // Note that the model lock is passed locked and is expected to also be
- // locked on return (but it can be released and re-acquired inside).
- //
- void (*load) (ulock&,
- scope& root,
+ void (*load) (scope& root,
const path& buildfile,
const dir_path& out_base,
const dir_path& src_base,
const location&);
- void (*search) (ulock&,
- scope& root,
+ void (*search) (scope& root,
const target_key&,
const location&,
action_targets&);
- void (*match) (ulock&, action, action_targets&);
+ void (*match) (action, action_targets&);
- void (*execute) (ulock&, action, const action_targets&, bool quiet);
+ void (*execute) (action, const action_targets&, bool quiet);
void (*operation_post) (operation_id); // End of operation batch.
void (*meta_operation_post) (); // End of meta-operation batch.
@@ -234,8 +229,7 @@ namespace build2
// scope.
//
void
- load (ulock&,
- scope& root,
+ load (scope& root,
const path& buildfile,
const dir_path& out_base,
const dir_path& src_base,
@@ -245,17 +239,17 @@ namespace build2
// that does just that and adds a pointer to the target to the list.
//
void
- search (ulock&, scope&, const target_key&, const location&, action_targets&);
+ search (scope&, const target_key&, const location&, action_targets&);
void
- match (ulock&, action, action_targets&);
+ match (action, action_targets&);
// Execute the action on the list of targets. This is the default
// implementation that does just that while issuing appropriate
// diagnostics (unless quiet).
//
void
- execute (ulock&, action, const action_targets&, bool quiet);
+ execute (action, const action_targets&, bool quiet);
extern const meta_operation_info noop;
extern const meta_operation_info perform;
diff --git a/build2/operation.cxx b/build2/operation.cxx
index 3871970..0163e04 100644
--- a/build2/operation.cxx
+++ b/build2/operation.cxx
@@ -4,12 +4,13 @@
#include <build2/operation>
+#include <build2/file>
+#include <build2/dump>
#include <build2/scope>
#include <build2/target>
-#include <build2/file>
#include <build2/algorithm>
+#include <build2/scheduler>
#include <build2/diagnostics>
-#include <build2/dump>
using namespace std;
using namespace butl;
@@ -44,8 +45,7 @@ namespace build2
// perform
//
void
- load (ulock&,
- scope& root,
+ load (scope& root,
const path& bf,
const dir_path& out_base,
const dir_path& src_base,
@@ -67,8 +67,7 @@ namespace build2
}
void
- search (ulock&,
- scope&,
+ search (scope&,
const target_key& tk,
const location& l,
action_targets& ts)
@@ -83,38 +82,39 @@ namespace build2
}
void
- match (ulock& ml, action a, action_targets& ts)
+ match (action a, action_targets& ts)
{
tracer trace ("match");
if (verb >= 6)
dump (a);
- // Relock for shared access.
- //
- ml.unlock ();
-
+ scheduler::atomic_count task_count (0);
{
+ model_slock ml;
+
for (void* vt: ts)
{
target& t (*static_cast<target*> (vt));
l5 ([&]{trace << "matching " << t;});
- slock sl (*ml.mutex ());
- model_lock = &sl; // @@ Guard?
- match (sl, a, t);
- model_lock = nullptr;
+ sched.async (task_count,
+ [a] (target& t)
+ {
+ model_slock ml;
+ match (ml, a, t); // @@ MT exception handling.
+ },
+ ref (t));
}
}
-
- ml.lock ();
+ sched.wait (task_count);
if (verb >= 6)
dump (a);
}
void
- execute (ulock& ml, action a, const action_targets& ts, bool quiet)
+ execute (action a, const action_targets& ts, bool quiet)
{
tracer trace ("execute");
@@ -123,21 +123,13 @@ namespace build2
//
vector<reference_wrapper<target>> psp;
- auto body = [&ml, a, quiet, &psp, &trace] (void* v)
+ auto body = [a, quiet, &psp, &trace] (void* v)
{
target& t (*static_cast<target*> (v));
l5 ([&]{trace << diag_doing (a, t);});
- target_state ts;
- {
- slock sl (*ml.mutex ());
- model_lock = &sl; // @@ Guard?
- ts = execute (a, t);
- model_lock = nullptr;
- }
-
- switch (ts)
+ switch (execute (a, t))
{
case target_state::unchanged:
{
@@ -157,17 +149,11 @@ namespace build2
}
};
- // Relock for shared access.
- //
- ml.unlock ();
-
if (current_mode == execution_mode::first)
for (void* v: ts) body (v);
else
for (void* v: reverse_iterate (ts)) body (v);
- ml.lock ();
-
// We should have executed every target that we matched.
//
assert (dependency_count == 0);
diff --git a/build2/scheduler b/build2/scheduler
index 8416940..563ec31 100644
--- a/build2/scheduler
+++ b/build2/scheduler
@@ -25,15 +25,16 @@ namespace build2
// tests). To acomplish this, the master, via a call to async(), can ask the
// scheduler to run a task in another thread (called "helper"). If a helper
// is available, then the task is executed asynchronously by such helper.
- // Otherwise, the task is executed synchronously as part of the async()
- // call. Once the master thread has scheduled all the tasks, it calls wait()
- // to await for their completion.
+ // Otherwise, the task is (normally) executed synchronously as part of the
+ // wait() call below. However, in certain cases (serial execution or full
+ // queue), the task may be executed synchronously as part of the async()
+ // call itself. Once the master thread has scheduled all the tasks, it calls
+ // wait() to await for their completion.
//
// The scheduler makes sure that only a certain number of threads (for
// example, the number of available hardware threads) are "active" at any
- // given time (thus the reason why async() may choose to perform the task
- // synchronously). When a master thread calls wait(), it is "suspended"
- // until all its asynchronous tasks are completed (at which point it becomes
+ // given time. When a master thread calls wait(), it is "suspended" until
+ // all its asynchronous tasks are completed (at which point it becomes
// "ready"). A suspension of a master results in either another ready master
// being "resumed" or another helper thread becoming available.
//
@@ -46,7 +47,7 @@ namespace build2
// helper thread is always created if none is available. This is done to
// allow a ready master to continue as soon as possible. If it were reused
// as a helper, then it could be blocked on a nested wait() further down the
- // stack. This means that the number of threads created by the scheduler
+ // stack. All this means that the number of threads created by the scheduler
// will normally exceed the maximum active allowed.
//
class scheduler
@@ -61,7 +62,10 @@ namespace build2
//
// The argument passing semantics is the same as for std::thread. In
// particular, lvalue-references are passed as copies (use ref()/cref()
- // for the by-reference semantics).
+ // for the by-reference semantics), except the case where the task is
+ // executed synchronously and as part of the async() call itself (this
+ // subtlety can become important when passing shared locks; you would
+ // only want it to be copied if the task is queued).
//
// If the scheduler is shutdown, throw system_error(ECANCELED).
//
@@ -111,6 +115,19 @@ namespace build2
size_t max_threads = 0,
size_t queue_depth = 0);
+ // Tune a started up scheduler.
+ //
+ // Currently one cannot increase the number of max_active. Pass 0 to
+ // restore the initial value.
+ //
+ // Note that tuning can only be done while the scheduler is inactive, that
+ // is, no threads are executing a task or are suspended. For example, in a
+ // setup with a single initial active thread that would be after a return
+ // from the top-level wait() call.
+ //
+ void
+ tune (size_t max_active);
+
// Wait for all the helper threads to terminate. Throw system_error on
// failure. Note that the initially active threads are not waited for.
// Return scheduling statistics.
@@ -221,8 +238,8 @@ namespace build2
private:
std::mutex mutex_;
- bool shutdown_ = true; // Shutdown flag.
- bool task_ = false; // Task queued flag (see below).
+ bool shutdown_ = true; // Shutdown flag.
+ bool task_ = false; // Task queued flag (see below).
// The constraints that we must maintain:
//
@@ -247,6 +264,11 @@ namespace build2
size_t ready_ = 0; // Ready master thread waiting to become active.
size_t starting_ = 0; // Helper threads starting up.
+ // Original values (as specified during startup) that can be altered via
+ // tuning.
+ //
+ size_t orig_max_active_ = 0;
+
std::condition_variable idle_condv_; // Idle helpers queue.
std::condition_variable ready_condv_; // Ready masters queue.
diff --git a/build2/scheduler.cxx b/build2/scheduler.cxx
index be50b28..9abafbb 100644
--- a/build2/scheduler.cxx
+++ b/build2/scheduler.cxx
@@ -146,7 +146,7 @@ namespace build2
max_active <= max_threads);
active_ = init_active_ = init_active;
- max_active_ = max_active;
+ max_active_ = orig_max_active_ = max_active;
max_threads_ = max_threads;
// This value should be proportional to the amount of hardware concurrency
@@ -212,12 +212,32 @@ namespace build2
wait_queue_[i].shutdown = false;
}
+ void scheduler::
+ tune (size_t max_active)
+ {
+ lock l (mutex_);
+
+ if (max_active)
+ max_active = orig_max_active_;
+
+ assert (max_active >= init_active_ &&
+ max_active <= orig_max_active_);
+
+ // The schduler must not be active.
+ //
+ assert (active_ == init_active_);
+ assert (waiting_ == 0);
+ assert (ready_ == 0);
+
+ max_active_ = max_active;
+ }
+
auto scheduler::
shutdown () -> stat
{
// Our overall approach to shutdown is not to try and stop everything as
// quickly as possible but rather to avoid performing any tasks. This
- // avoids having code littered with if(shutdown) on every second line.
+ // avoids having code littered with if(shutdown) on every other line.
stat r;
lock l (mutex_);
@@ -274,7 +294,7 @@ namespace build2
wait_queue_.reset ();
task_queues_.clear ();
- r.thread_max_active = max_active_;
+ r.thread_max_active = orig_max_active_;
r.thread_max_total = max_threads_;
r.thread_max_waiting = stat_max_waiters_;
diff --git a/build2/scheduler.txx b/build2/scheduler.txx
index fd1106b..127ce48 100644
--- a/build2/scheduler.txx
+++ b/build2/scheduler.txx
@@ -40,8 +40,8 @@ namespace build2
//
new (&td->data) task {
&task_count,
- decay_copy (forward <F> (f)),
- typename task::args_type (decay_copy (forward <A> (a))...)};
+ decay_copy (forward<F> (f)),
+ typename task::args_type (decay_copy (forward<A> (a))...)};
td->thunk = &task_thunk<F, A...>;
}
@@ -49,8 +49,8 @@ namespace build2
tq->stat_full++;
}
- // If serial/full, then run the task synchronously. In this case
- // there is no need to mess with task count.
+ // If serial/full, then run the task synchronously. In this case there is
+ // no need to mess with task count.
//
if (td == nullptr)
{
diff --git a/build2/variable b/build2/variable
index 4377d7b..e296e37 100644
--- a/build2/variable
+++ b/build2/variable
@@ -755,6 +755,30 @@ namespace build2
static const value_type_ex value_type;
};
+ // Variable override cache.
+ //
+ struct variable_override_value
+ {
+ build2::value value;
+ const variable_map* stem_vars = nullptr; // NULL means there is no stem.
+ };
+
+ using variable_override_cache = std::map<pair<const variable_map*,
+ const variable*>,
+ variable_override_value>;
+
+ // Project-wide (as opposed to global) variable overrides. Returned by
+ // context.cxx:reset().
+ //
+ struct variable_override
+ {
+ const variable& var; // Original variable.
+ const variable& ovr; // Override variable.
+ value val;
+ };
+
+ using variable_overrides = vector<variable_override>;
+
// Variable pool.
//
// Protected by the model mutex.
@@ -841,10 +865,6 @@ namespace build2
rw (scope&) const {return const_cast<variable_pool&> (*this);}
private:
- // Classes that can access bypassing the lock.
- //
- friend class scope;
-
static variable_pool instance;
const variable&
@@ -853,6 +873,11 @@ namespace build2
const variable_visibility* = nullptr,
const bool* overridable = nullptr);
+ // Entities that can access bypassing the lock.
+ //
+ friend class scope;
+ friend variable_overrides reset (const strings&);
+
public:
static const variable_pool& cinstance; // For var_pool initialization.
@@ -1034,18 +1059,6 @@ namespace build2
mutable
std::map<tuple<const value*, const target_type*, string>, value> cache;
};
-
- // Override cache.
- //
- struct variable_override_value
- {
- build2::value value;
- const variable_map* stem_vars = nullptr; // NULL means there is no stem.
- };
-
- using variable_override_cache = std::map<pair<const variable_map*,
- const variable*>,
- variable_override_value>;
}
#include <build2/variable.ixx>
diff --git a/unit-tests/function/buildfile b/unit-tests/function/buildfile
index 27c73d1..3631958 100644
--- a/unit-tests/function/buildfile
+++ b/unit-tests/function/buildfile
@@ -10,7 +10,7 @@ import libs = libbutl%lib{butl}
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 \
+functions-target-triplet algorithm search dump filesystem scheduler \
config/{utility init operation}
diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx
index 4fbbdb6..2f605b0 100644
--- a/unit-tests/function/driver.cxx
+++ b/unit-tests/function/driver.cxx
@@ -26,9 +26,7 @@ namespace build2
main (int, char* argv[])
{
init (argv[0], 1); // Fake build system driver, default verbosity.
-
- ulock ml (model);
- reset (ml, strings ()); // No command line variables.
+ reset (strings ()); // No command line variables.
function_family f ("dummy");
diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx
index 1e959c1..f1e7483 100644
--- a/unit-tests/test/script/parser/driver.cxx
+++ b/unit-tests/test/script/parser/driver.cxx
@@ -142,10 +142,9 @@ namespace build2
{
tracer trace ("main");
- init (argv[0], 1); // Fake build system driver, default verbosity.
- ulock ml (model);
- sched.startup (1); // Serial execution.
- reset (ml, strings ()); // No command line variables.
+ init (argv[0], 1); // Fake build system driver, default verbosity.
+ sched.startup (1); // Serial execution.
+ reset (strings ()); // No command line variables.
bool scope (false);
bool id (false);
@@ -183,6 +182,8 @@ namespace build2
// be absolute. However, the testscript implementation doesn't
// really care.
//
+ ulock ml (model);
+
file& tt (
targets.insert<file> (work,
dir_path (),
@@ -204,6 +205,8 @@ namespace build2
name.leaf ().extension (),
trace));
+ ml.unlock ();
+
tt.path (path ("driver"));
st.path (name);