From 5035f4ef68922ac758b1e4734e67d73c9228010b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 22 Aug 2019 14:38:57 +0200 Subject: Introduce notion of build context All non-const global state is now in class context and we can now have multiple independent builds going on at the same time. --- libbuild2/context.cxx | 1032 ++++++++++++++++++++++++------------------------- 1 file changed, 507 insertions(+), 525 deletions(-) (limited to 'libbuild2/context.cxx') diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index 1cc8bbc..687e9aa 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include // uncaught_exceptions @@ -25,379 +27,57 @@ using namespace butl; namespace build2 { - scheduler sched; - - run_phase phase; - run_phase_mutex phase_mutex; - - size_t load_generation; - - bool run_phase_mutex:: - lock (run_phase p) - { - bool r; - - { - mlock l (m_); - bool u (lc_ == 0 && mc_ == 0 && ec_ == 0); // Unlocked. - - // Increment the counter. - // - condition_variable* v (nullptr); - switch (p) - { - case run_phase::load: lc_++; v = &lv_; break; - case run_phase::match: mc_++; v = &mv_; break; - case run_phase::execute: ec_++; v = &ev_; break; - } - - // If unlocked, switch directly to the new phase. Otherwise wait for the - // phase switch. Note that in the unlocked case we don't need to notify - // since there is nobody waiting (all counters are zero). - // - if (u) - { - phase = p; - r = !fail_; - } - else if (phase != p) - { - sched.deactivate (false /* external */); - for (; phase != p; v->wait (l)) ; - r = !fail_; - l.unlock (); // Important: activate() can block. - sched.activate (false /* external */); - } - else - r = !fail_; - } - - // In case of load, acquire the exclusive access mutex. - // - if (p == run_phase::load) - { - lm_.lock (); - r = !fail_; // Re-query. - } - - return r; - } - - void run_phase_mutex:: - unlock (run_phase p) - { - // In case of load, release the exclusive access mutex. - // - if (p == run_phase::load) - lm_.unlock (); - - { - mlock l (m_); - - // Decrement the counter and see if this phase has become unlocked. - // - bool u (false); - switch (p) - { - case run_phase::load: u = (--lc_ == 0); break; - case run_phase::match: u = (--mc_ == 0); break; - case run_phase::execute: u = (--ec_ == 0); break; - } - - // If the phase is unlocked, pick a new phase and notify the waiters. - // Note that we notify all load waiters so that they can all serialize - // behind the second-level mutex. - // - if (u) - { - condition_variable* v; - - if (lc_ != 0) {phase = run_phase::load; v = &lv_;} - else if (mc_ != 0) {phase = run_phase::match; v = &mv_;} - else if (ec_ != 0) {phase = run_phase::execute; v = &ev_;} - else {phase = run_phase::load; v = nullptr;} - - if (v != nullptr) - { - l.unlock (); - v->notify_all (); - } - } - } - } - - bool run_phase_mutex:: - relock (run_phase o, run_phase n) - { - // Pretty much a fused unlock/lock implementation except that we always - // switch into the new phase. - // - assert (o != n); - - bool r; - - if (o == run_phase::load) - lm_.unlock (); - - { - mlock l (m_); - bool u (false); - - switch (o) - { - case run_phase::load: u = (--lc_ == 0); break; - case run_phase::match: u = (--mc_ == 0); break; - case run_phase::execute: u = (--ec_ == 0); break; - } - - // Set if will be waiting or notifying others. - // - condition_variable* v (nullptr); - switch (n) - { - case run_phase::load: v = lc_++ != 0 || !u ? &lv_ : nullptr; break; - case run_phase::match: v = mc_++ != 0 || !u ? &mv_ : nullptr; break; - case run_phase::execute: v = ec_++ != 0 || !u ? &ev_ : nullptr; break; - } - - if (u) - { - phase = n; - r = !fail_; - - // Notify others that could be waiting for this phase. - // - if (v != nullptr) - { - l.unlock (); - v->notify_all (); - } - } - else // phase != n - { - sched.deactivate (false /* external */); - for (; phase != n; v->wait (l)) ; - r = !fail_; - l.unlock (); // Important: activate() can block. - sched.activate (false /* external */); - } - } - - if (n == run_phase::load) - { - lm_.lock (); - r = !fail_; // Re-query. - } - - return r; - } - - // C++17 deprecated uncaught_exception() so use uncaught_exceptions() if - // available. - // - static inline bool - uncaught_exception () - { -#ifdef __cpp_lib_uncaught_exceptions - return std::uncaught_exceptions () != 0; -#else - return std::uncaught_exception (); -#endif - } - - // phase_lock - // - static -#ifdef __cpp_thread_local - thread_local -#else - __thread -#endif - phase_lock* phase_lock_instance; - - phase_lock:: - phase_lock (run_phase p) - : p (p) - { - if (phase_lock* l = phase_lock_instance) - assert (l->p == p); - else - { - if (!phase_mutex.lock (p)) - { - phase_mutex.unlock (p); - throw failed (); - } - - phase_lock_instance = this; - - //text << this_thread::get_id () << " phase acquire " << p; - } - } - - phase_lock:: - ~phase_lock () - { - if (phase_lock_instance == this) - { - phase_lock_instance = nullptr; - phase_mutex.unlock (p); - - //text << this_thread::get_id () << " phase release " << p; - } - } - - // phase_unlock - // - phase_unlock:: - phase_unlock (bool u) - : l (u ? phase_lock_instance : nullptr) - { - if (u) - { - phase_lock_instance = nullptr; - phase_mutex.unlock (l->p); - - //text << this_thread::get_id () << " phase unlock " << l->p; - } - } - - phase_unlock:: - ~phase_unlock () noexcept (false) - { - if (l != nullptr) - { - bool r (phase_mutex.lock (l->p)); - phase_lock_instance = l; - - // Fail unless we are already failing. Note that we keep the phase - // locked since there will be phase_lock down the stack to unlock it. - // - if (!r && !uncaught_exception ()) - throw failed (); - - //text << this_thread::get_id () << " phase lock " << l->p; - } - } - - // phase_switch + // Create global scope. Note that the empty path is a prefix for any other + // path. See the comment in for details. // - phase_switch:: - phase_switch (run_phase n) - : o (phase), n (n) - { - if (!phase_mutex.relock (o, n)) - { - phase_mutex.relock (n, o); - throw failed (); - } - - phase_lock_instance->p = n; - - if (n == run_phase::load) // Note: load lock is exclusive. - load_generation++; - - //text << this_thread::get_id () << " phase switch " << o << " " << n; - } - - phase_switch:: - ~phase_switch () noexcept (false) - { - // If we are coming off a failed load phase, mark the phase_mutex as - // failed to terminate all other threads since the build state may no - // longer be valid. - // - if (n == run_phase::load && uncaught_exception ()) - { - mlock l (phase_mutex.m_); - phase_mutex.fail_ = true; - } - - bool r (phase_mutex.relock (n, o)); - phase_lock_instance->p = o; - - // Similar logic to ~phase_unlock(). - // - if (!r && !uncaught_exception ()) - throw failed (); - - //text << this_thread::get_id () << " phase restore " << n << " " << o; - } - - string current_mname; - string current_oname; - - const meta_operation_info* current_mif; - const operation_info* current_inner_oif; - const operation_info* current_outer_oif; - size_t current_on; - execution_mode current_mode; - bool current_diag_noise; - - atomic_count dependency_count; - atomic_count target_count; - atomic_count skip_count; - - bool keep_going = false; - bool dry_run = false; - - void - set_current_mif (const meta_operation_info& mif) + static inline scope& + create_global_scope (scope_map& m) { - if (current_mname != mif.name) - { - current_mname = mif.name; - global_scope->rw ().assign (var_build_meta_operation) = mif.name; - } - - current_mif = &mif; - current_on = 0; // Reset. - } + auto i (m.insert (dir_path ())); + scope& r (i->second); + r.out_path_ = &i->first; + return r; + }; - void - set_current_oif (const operation_info& inner_oif, - const operation_info* outer_oif, - bool diag_noise) + struct context::data { - current_oname = (outer_oif == nullptr ? inner_oif : *outer_oif).name; - current_inner_oif = &inner_oif; - current_outer_oif = outer_oif; - current_on++; - current_mode = inner_oif.mode; - current_diag_noise = diag_noise; - - // Reset counters (serial execution). - // - dependency_count.store (0, memory_order_relaxed); - target_count.store (0, memory_order_relaxed); - skip_count.store (0, memory_order_relaxed); - } - - variable_overrides - reset (const strings& cmd_vars) + scope_map scopes; + target_set targets; + variable_pool var_pool; + variable_overrides var_overrides; + variable_override_cache global_override_cache; + function_map functions; + + data (context& c): scopes (c), targets (c), var_pool (&c /* global */) {} + }; + + context:: + context (scheduler& s, const strings& cmd_vars, bool dr, bool kg) + : data_ (new data (*this)), + sched (s), + dry_run_option (dr), + keep_going (kg), + phase_mutex (*this), + scopes (data_->scopes), + global_scope (create_global_scope (data_->scopes)), + targets (data_->targets), + var_pool (data_->var_pool), + var_overrides (data_->var_overrides), + global_override_cache (data_->global_override_cache), + functions (data_->functions) { - tracer trace ("reset"); - - // @@ Do we want to unload dynamically loaded modules? Note that this will - // be purely an optimization since a module could be linked-in (i.e., a - // module cannot expect to be unloaded/re-initialized for each meta- - // operation). - - l6 ([&]{trace << "resetting build state";}); + tracer trace ("context"); - auto& vp (variable_pool::instance); - auto& sm (scope_map::instance); + l6 ([&]{trace << "initializing build state";}); - variable_overrides vos; + scope_map& sm (data_->scopes); + variable_pool& vp (data_->var_pool); - targets.clear (); - sm.clear (); - vp.clear (); + register_builtin_functions (functions); - // Reset meta/operation tables. Note that the order should match the id - // constants in . + // Initialize the meta/operation tables. Note that the order should match + // the id constants in . // - meta_operation_table.clear (); meta_operation_table.insert ("noop"); meta_operation_table.insert ("perform"); meta_operation_table.insert ("configure"); @@ -420,23 +100,10 @@ namespace build2 operation_table.insert ("uninstall"); operation_table.insert ("update-for-install"); - // Create global scope. Note that the empty path is a prefix for any other - // path. See the comment in for details. - // - auto make_global_scope = [] () -> scope& - { - auto i (scope_map::instance.insert (dir_path ())); - scope& r (i->second); - r.out_path_ = &i->first; - global_scope = scope::global_ = &r; - return r; - }; - - scope& gs (make_global_scope ()); - // Setup the global scope before parsing any variable overrides since they // may reference these things. // + scope& gs (global_scope.rw ()); gs.assign ("build.work") = work; gs.assign ("build.home") = home; @@ -458,10 +125,10 @@ namespace build2 { const standard_version& v (build_version); - auto set = [&gs] (const char* var, auto val) + auto set = [&gs, &vp] (const char* var, auto val) { using T = decltype (val); - gs.assign (variable_pool::instance.insert (var)) = move (val); + gs.assign (vp.insert (var)) = move (val); }; // Note: here we assume epoch will always be 1 and therefore omit the @@ -509,7 +176,7 @@ namespace build2 // were built with. While it is not as precise (for example, a binary // built for i686 might be running on x86_64), it is good enough of an // approximation/fallback since most of the time we are interested in just - // the target class (e.g., linux, windows, macosx). + // the target class (e.g., linux, windows, macos). // { // Did the user ask us to use config.guess? @@ -687,199 +354,514 @@ namespace build2 info << "use double '--' to treat this argument as buildspec"; } - // Take care of the visibility. Note that here we rely on the fact that - // none of these characters are lexer's name separators. - // - char c (t.value[0]); + // Take care of the visibility. Note that here we rely on the fact that + // none of these characters are lexer's name separators. + // + char c (t.value[0]); + + if (path::traits_type::is_separator (c)) + c = '/'; // Normalize. + + string n (t.value, c == '!' || c == '%' || c == '/' ? 1 : 0); + + if (c == '!' && dir) + fail << "scope-qualified global override of variable " << n; + + variable& var (const_cast ( + vp.insert (n, true /* overridable */))); + + const variable* o; + { + variable_visibility v (c == '/' ? variable_visibility::scope : + c == '%' ? variable_visibility::project : + variable_visibility::normal); + + const char* k (tt == token_type::assign ? "__override" : + tt == token_type::append ? "__suffix" : "__prefix"); + + unique_ptr p ( + new variable { + n + '.' + to_string (i + 1) + '.' + k, + nullptr /* aliases */, + nullptr /* type */, + nullptr /* overrides */, + v}); + + // Back link. + // + p->aliases = p.get (); + if (var.overrides != nullptr) + swap (p->aliases, + const_cast (var.overrides.get ())->aliases); + + // Forward link. + // + p->overrides = move (var.overrides); + var.overrides = move (p); + + o = var.overrides.get (); + } + + // Currently we expand project overrides in the global scope to keep + // things simple. Pass original variable for diagnostics. Use current + // working directory as pattern base. + // + parser p (*this); + pair r (p.parse_variable_value (l, gs, &work, var)); + + if (r.second.type != token_type::eos) + fail << "unexpected " << r.second << " in variable assignment " + << "'" << s << "'"; + + // Make sure the value is not typed. + // + if (r.first.type != nullptr) + fail << "typed override of variable " << n; + + // Global and absolute scope overrides we can enter directly. Project + // and relative scope ones will be entered later for each project. + // + if (c == '!' || (dir && dir->absolute ())) + { + scope& s (c == '!' ? gs : sm.insert (*dir)->second); + + auto p (s.vars.insert (*o)); + assert (p.second); // Variable name is unique. + + value& v (p.first); + v = move (r.first); + } + else + data_->var_overrides.push_back ( + variable_override {var, *o, move (dir), move (r.first)}); + } + + // Enter builtin variables and patterns. + // + + // All config. variables are by default overridable. + // + vp.insert_pattern ("config.**", nullopt, true, nullopt, true, false); + + // file.cxx:import() (note that order is important; see insert_pattern()). + // + vp.insert_pattern ( + "config.import.*", true, variable_visibility::normal, true); + vp.insert_pattern ( + "config.import.**", true, variable_visibility::normal, true); + + // module.cxx:boot/init_module(). + // + { + auto v_p (variable_visibility::project); + + vp.insert_pattern ("**.booted", false, v_p); + vp.insert_pattern ("**.loaded", false, v_p); + vp.insert_pattern ("**.configured", false, v_p); + } + + { + auto v_p (variable_visibility::project); + auto v_t (variable_visibility::target); + auto v_q (variable_visibility::prereq); + + var_src_root = &vp.insert ("src_root"); + var_out_root = &vp.insert ("out_root"); + var_src_base = &vp.insert ("src_base"); + var_out_base = &vp.insert ("out_base"); + + var_forwarded = &vp.insert ("forwarded", v_p); + + // Note that subprojects is not typed since the value requires + // pre-processing (see file.cxx). + // + var_project = &vp.insert ("project", v_p); + var_amalgamation = &vp.insert ("amalgamation", v_p); + var_subprojects = &vp.insert ("subprojects", v_p); + var_version = &vp.insert ("version", v_p); + + var_project_url = &vp.insert ("project.url", v_p); + var_project_summary = &vp.insert ("project.summary", v_p); + + var_import_target = &vp.insert ("import.target"); + + var_extension = &vp.insert ("extension", v_t); + var_clean = &vp.insert ("clean", v_t); + var_backlink = &vp.insert ("backlink", v_t); + var_include = &vp.insert ("include", v_q); + + // Backlink executables and (generated) documentation by default. + // + gs.target_vars[exe::static_type]["*"].assign (var_backlink) = "true"; + gs.target_vars[doc::static_type]["*"].assign (var_backlink) = "true"; + + var_build_meta_operation = &vp.insert ("build.meta_operation"); + } + + // Register builtin rules. + // + { + rule_map& r (gs.rules); // Note: global scope! + + //@@ outer + r.insert (perform_id, 0, "alias", alias_rule::instance); + + r.insert (perform_update_id, "fsdir", fsdir_rule::instance); + r.insert (perform_clean_id, "fsdir", fsdir_rule::instance); + + r.insert (perform_update_id, "file", file_rule::instance); + r.insert (perform_clean_id, "file", file_rule::instance); + } + } + + context:: + ~context () + { + // Cannot be inline since context::data is undefined. + } + + void context:: + current_meta_operation (const meta_operation_info& mif) + { + if (current_mname != mif.name) + { + current_mname = mif.name; + global_scope.rw ().assign (var_build_meta_operation) = mif.name; + } + + current_mif = &mif; + current_on = 0; // Reset. + } + + void context:: + current_operation (const operation_info& inner_oif, + const operation_info* outer_oif, + bool diag_noise) + { + current_oname = (outer_oif == nullptr ? inner_oif : *outer_oif).name; + current_inner_oif = &inner_oif; + current_outer_oif = outer_oif; + current_on++; + current_mode = inner_oif.mode; + current_diag_noise = diag_noise; + + // Reset counters (serial execution). + // + dependency_count.store (0, memory_order_relaxed); + target_count.store (0, memory_order_relaxed); + skip_count.store (0, memory_order_relaxed); + } + + bool run_phase_mutex:: + lock (run_phase p) + { + bool r; + + { + mlock l (m_); + bool u (lc_ == 0 && mc_ == 0 && ec_ == 0); // Unlocked. + + // Increment the counter. + // + condition_variable* v (nullptr); + switch (p) + { + case run_phase::load: lc_++; v = &lv_; break; + case run_phase::match: mc_++; v = &mv_; break; + case run_phase::execute: ec_++; v = &ev_; break; + } + + // If unlocked, switch directly to the new phase. Otherwise wait for the + // phase switch. Note that in the unlocked case we don't need to notify + // since there is nobody waiting (all counters are zero). + // + if (u) + { + ctx_.phase = p; + r = !fail_; + } + else if (ctx_.phase != p) + { + ctx_.sched.deactivate (false /* external */); + for (; ctx_.phase != p; v->wait (l)) ; + r = !fail_; + l.unlock (); // Important: activate() can block. + ctx_.sched.activate (false /* external */); + } + else + r = !fail_; + } + + // In case of load, acquire the exclusive access mutex. + // + if (p == run_phase::load) + { + lm_.lock (); + r = !fail_; // Re-query. + } + + return r; + } + + void run_phase_mutex:: + unlock (run_phase p) + { + // In case of load, release the exclusive access mutex. + // + if (p == run_phase::load) + lm_.unlock (); + + { + mlock l (m_); + + // Decrement the counter and see if this phase has become unlocked. + // + bool u (false); + switch (p) + { + case run_phase::load: u = (--lc_ == 0); break; + case run_phase::match: u = (--mc_ == 0); break; + case run_phase::execute: u = (--ec_ == 0); break; + } + + // If the phase is unlocked, pick a new phase and notify the waiters. + // Note that we notify all load waiters so that they can all serialize + // behind the second-level mutex. + // + if (u) + { + condition_variable* v; + + if (lc_ != 0) {ctx_.phase = run_phase::load; v = &lv_;} + else if (mc_ != 0) {ctx_.phase = run_phase::match; v = &mv_;} + else if (ec_ != 0) {ctx_.phase = run_phase::execute; v = &ev_;} + else {ctx_.phase = run_phase::load; v = nullptr;} + + if (v != nullptr) + { + l.unlock (); + v->notify_all (); + } + } + } + } - if (path::traits_type::is_separator (c)) - c = '/'; // Normalize. + bool run_phase_mutex:: + relock (run_phase o, run_phase n) + { + // Pretty much a fused unlock/lock implementation except that we always + // switch into the new phase. + // + assert (o != n); - string n (t.value, c == '!' || c == '%' || c == '/' ? 1 : 0); + bool r; - if (c == '!' && dir) - fail << "scope-qualified global override of variable " << n; + if (o == run_phase::load) + lm_.unlock (); - variable& var (const_cast ( - vp.insert (n, true /* overridable */))); + { + mlock l (m_); + bool u (false); - const variable* o; + switch (o) { - variable_visibility v (c == '/' ? variable_visibility::scope : - c == '%' ? variable_visibility::project : - variable_visibility::normal); - - const char* k (tt == token_type::assign ? "__override" : - tt == token_type::append ? "__suffix" : "__prefix"); + case run_phase::load: u = (--lc_ == 0); break; + case run_phase::match: u = (--mc_ == 0); break; + case run_phase::execute: u = (--ec_ == 0); break; + } - unique_ptr p ( - new variable { - n + '.' + to_string (i + 1) + '.' + k, - nullptr /* aliases */, - nullptr /* type */, - nullptr /* overrides */, - v}); + // Set if will be waiting or notifying others. + // + condition_variable* v (nullptr); + switch (n) + { + case run_phase::load: v = lc_++ != 0 || !u ? &lv_ : nullptr; break; + case run_phase::match: v = mc_++ != 0 || !u ? &mv_ : nullptr; break; + case run_phase::execute: v = ec_++ != 0 || !u ? &ev_ : nullptr; break; + } - // Back link. - // - p->aliases = p.get (); - if (var.overrides != nullptr) - swap (p->aliases, - const_cast (var.overrides.get ())->aliases); + if (u) + { + ctx_.phase = n; + r = !fail_; - // Forward link. + // Notify others that could be waiting for this phase. // - p->overrides = move (var.overrides); - var.overrides = move (p); - - o = var.overrides.get (); + if (v != nullptr) + { + l.unlock (); + v->notify_all (); + } } + else // phase != n + { + ctx_.sched.deactivate (false /* external */); + for (; ctx_.phase != n; v->wait (l)) ; + r = !fail_; + l.unlock (); // Important: activate() can block. + ctx_.sched.activate (false /* external */); + } + } - // Currently we expand project overrides in the global scope to keep - // things simple. Pass original variable for diagnostics. Use current - // working directory as pattern base. - // - parser p; - pair r (p.parse_variable_value (l, gs, &work, var)); - - if (r.second.type != token_type::eos) - fail << "unexpected " << r.second << " in variable assignment " - << "'" << s << "'"; + if (n == run_phase::load) + { + lm_.lock (); + r = !fail_; // Re-query. + } - // Make sure the value is not typed. - // - if (r.first.type != nullptr) - fail << "typed override of variable " << n; + return r; + } - // Global and absolute scope overrides we can enter directly. Project - // and relative scope ones will be entered by the caller for each - // amalgamation/project. - // - if (c == '!' || (dir && dir->absolute ())) - { - scope& s (c == '!' ? gs : sm.insert (*dir)->second); + // C++17 deprecated uncaught_exception() so use uncaught_exceptions() if + // available. + // + static inline bool + uncaught_exception () + { +#ifdef __cpp_lib_uncaught_exceptions + return std::uncaught_exceptions () != 0; +#else + return std::uncaught_exception (); +#endif + } - auto p (s.vars.insert (*o)); - assert (p.second); // Variable name is unique. + // phase_lock + // + static +#ifdef __cpp_thread_local + thread_local +#else + __thread +#endif + phase_lock* phase_lock_instance; - value& v (p.first); - v = move (r.first); - } - else - vos.push_back ( - variable_override {var, *o, move (dir), move (r.first)}); - } + phase_lock:: + phase_lock (context& c, run_phase p) + : ctx (c), phase (p) + { + phase_lock* pl (phase_lock_instance); - // Enter builtin variables and patterns. + // This is tricky: we might be switching to another context. // + if (pl != nullptr && &pl->ctx == &ctx) + assert (pl->phase == phase); + else + { + if (!ctx.phase_mutex.lock (phase)) + { + ctx.phase_mutex.unlock (phase); + throw failed (); + } - // All config. variables are by default overridable. - // - vp.insert_pattern ("config.**", nullopt, true, nullopt, true, false); + prev = pl; + phase_lock_instance = this; - // file.cxx:import() (note that order is important; see insert_pattern()). - // - vp.insert_pattern ( - "config.import.*", true, variable_visibility::normal, true); - vp.insert_pattern ( - "config.import.**", true, variable_visibility::normal, true); + //text << this_thread::get_id () << " phase acquire " << phase; + } + } - // module.cxx:load_module(). - // + phase_lock:: + ~phase_lock () + { + if (phase_lock_instance == this) { - auto v_p (variable_visibility::project); + phase_lock_instance = prev; + ctx.phase_mutex.unlock (phase); - vp.insert_pattern ("**.booted", false, v_p); - vp.insert_pattern ("**.loaded", false, v_p); - vp.insert_pattern ("**.configured", false, v_p); + //text << this_thread::get_id () << " phase release " << p; } + } + // phase_unlock + // + phase_unlock:: + phase_unlock (context& ctx, bool u) + : l (u ? phase_lock_instance : nullptr) + { + if (u) { - auto v_p (variable_visibility::project); - auto v_t (variable_visibility::target); - auto v_q (variable_visibility::prereq); + assert (&l->ctx == &ctx); - var_src_root = &vp.insert ("src_root"); - var_out_root = &vp.insert ("out_root"); - var_src_base = &vp.insert ("src_base"); - var_out_base = &vp.insert ("out_base"); + phase_lock_instance = nullptr; // Note: not l->prev. + ctx.phase_mutex.unlock (l->phase); - var_forwarded = &vp.insert ("forwarded", v_p); + //text << this_thread::get_id () << " phase unlock " << l->p; + } + } - // Note that subprojects is not typed since the value requires - // pre-processing (see file.cxx). + phase_unlock:: + ~phase_unlock () noexcept (false) + { + if (l != nullptr) + { + bool r (l->ctx.phase_mutex.lock (l->phase)); + phase_lock_instance = l; + + // Fail unless we are already failing. Note that we keep the phase + // locked since there will be phase_lock down the stack to unlock it. // - var_project = &vp.insert ("project", v_p); - var_amalgamation = &vp.insert ("amalgamation", v_p); - var_subprojects = &vp.insert ("subprojects", v_p); - var_version = &vp.insert ("version", v_p); + if (!r && !uncaught_exception ()) + throw failed (); - var_project_url = &vp.insert ("project.url", v_p); - var_project_summary = &vp.insert ("project.summary", v_p); + //text << this_thread::get_id () << " phase lock " << l->p; + } + } - var_import_target = &vp.insert ("import.target"); + // phase_switch + // + phase_switch:: + phase_switch (context& ctx, run_phase n) + : old_phase (ctx.phase), new_phase (n) + { + phase_lock* pl (phase_lock_instance); + assert (&pl->ctx == &ctx); - var_clean = &vp.insert ("clean", v_t); - var_backlink = &vp.insert ("backlink", v_t); - var_include = &vp.insert ("include", v_q); + if (!ctx.phase_mutex.relock (old_phase, new_phase)) + { + ctx.phase_mutex.relock (new_phase, old_phase); + throw failed (); + } - vp.insert (var_extension, v_t); + pl->phase = new_phase; - // Backlink executables and (generated) documentation by default. - // - gs.target_vars[exe::static_type]["*"].assign (var_backlink) = "true"; - gs.target_vars[doc::static_type]["*"].assign (var_backlink) = "true"; + if (new_phase == run_phase::load) // Note: load lock is exclusive. + ctx.load_generation++; - var_build_meta_operation = &vp.insert ("build.meta_operation"); - } + //text << this_thread::get_id () << " phase switch " << o << " " << n; + } - // Register builtin rules. + phase_switch:: + ~phase_switch () noexcept (false) + { + phase_lock* pl (phase_lock_instance); + run_phase_mutex& pm (pl->ctx.phase_mutex); + + // If we are coming off a failed load phase, mark the phase_mutex as + // failed to terminate all other threads since the build state may no + // longer be valid. // + if (new_phase == run_phase::load && uncaught_exception ()) { - rule_map& r (gs.rules); // Note: global scope! - - //@@ outer - r.insert (perform_id, 0, "alias", alias_rule::instance); + mlock l (pm.m_); + pm.fail_ = true; + } - r.insert (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert (perform_clean_id, "fsdir", fsdir_rule::instance); + bool r (pm.relock (new_phase, old_phase)); + pl->phase = old_phase; - r.insert (perform_update_id, "file", file_rule::instance); - r.insert (perform_clean_id, "file", file_rule::instance); - } + // Similar logic to ~phase_unlock(). + // + if (!r && !uncaught_exception ()) + throw failed (); - return vos; + //text << this_thread::get_id () << " phase restore " << n << " " << o; } void (*config_save_variable) (scope&, const variable&, uint64_t); - const string& (*config_preprocess_create) (const variable_overrides&, + const string& (*config_preprocess_create) (context&, values&, vector_view&, bool, const location&); - - const variable* var_src_root; - const variable* var_out_root; - const variable* var_src_base; - const variable* var_out_base; - const variable* var_forwarded; - - const variable* var_project; - const variable* var_amalgamation; - const variable* var_subprojects; - const variable* var_version; - - const variable* var_project_url; - const variable* var_project_summary; - - const variable* var_import_target; - - const variable* var_clean; - const variable* var_backlink; - const variable* var_include; - - const char var_extension[10] = "extension"; - - const variable* var_build_meta_operation; } -- cgit v1.1