diff options
Diffstat (limited to 'libbuild2/context.cxx')
-rw-r--r-- | libbuild2/context.cxx | 869 |
1 files changed, 598 insertions, 271 deletions
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index b78cd27..6e4fd6f 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -29,13 +29,13 @@ using namespace butl; namespace build2 { // Create global scope. Note that the empty path is a prefix for any other - // path. See the comment in <libbutl/prefix-map.mxx> for details. + // path. See the comment in <libbutl/prefix-map.hxx> for details. // static inline scope& create_global_scope (scope_map& m) { - auto i (m.insert (dir_path ())); - scope& r (i->second); + auto i (m.insert_out (dir_path ())); + scope& r (*i->second.front ()); r.out_path_ = &i->first; return r; }; @@ -45,6 +45,7 @@ namespace build2 scope_map scopes; target_set targets; variable_pool var_pool; + variable_patterns var_patterns; variable_overrides var_overrides; function_map functions; @@ -52,30 +53,267 @@ namespace build2 variable_override_cache global_override_cache; strings global_var_overrides; - data (context& c): scopes (c), targets (c), var_pool (&c /* global */) {} + data (context& c) + : scopes (c), + targets (c), + var_pool (&c /* shared */, nullptr /* outer */, &var_patterns), + var_patterns (&c /* shared */, &var_pool) {} }; + void context:: + reserve (reserves res) + { + assert (phase == run_phase::load); + + if (res.targets != 0) + data_->targets.map_.reserve (res.targets); + + if (res.variables != 0) + data_->var_pool.map_.reserve (res.variables); + } + + pair<char, variable_override> context:: + parse_variable_override (const string& s, size_t i, bool buildspec) + { + istringstream is (s); + is.exceptions (istringstream::failbit | istringstream::badbit); + + // Similar to buildspec we do "effective escaping" of the special `'"\$(` + // characters (basically what's escapable inside a double-quoted literal + // plus the single quote; note, however, that we exclude line + // continuations and `)` since they would make directory paths on Windows + // unusable). + // + path_name in ("<cmdline>"); + lexer l (is, in, 1 /* line */, "\'\"\\$("); + + // At the buildfile level the scope-specific variable should be separated + // from the directory with a whitespace, for example: + // + // ./ foo=$bar + // + // However, requiring this for command line variables would be too + // inconvinient so we support both. + // + // We also have the optional visibility modifier as a first character of + // the variable name: + // + // ! - global + // % - project + // / - scope + // + // The last one clashes a bit with the directory prefix: + // + // ./ /foo=bar + // .//foo=bar + // + // But that's probably ok (the need for a scope-qualified override with + // scope visibility should be pretty rare). Note also that to set the + // value on the global scope we use !. + // + // And so the first token should be a word which can be either a variable + // name (potentially with the directory qualification) or just the + // directory, in which case it should be followed by another word + // (unqualified variable name). To avoid treating any of the visibility + // modifiers as special we use the cmdvar mode. + // + l.mode (lexer_mode::cmdvar); + token t (l.next ()); + + optional<dir_path> dir; + if (t.type == token_type::word) + { + string& v (t.value); + size_t p (path::traits_type::rfind_separator (v)); + + if (p != string::npos && p != 0) // If first then visibility. + { + if (p == v.size () - 1) + { + // Separate directory. + // + dir = dir_path (move (v)); + t = l.next (); + + // Target-specific overrides are not yet supported (and probably + // never will be; the beast is already complex enough). + // + if (t.type == token_type::colon) + { + diag_record dr (fail); + + dr << "'" << s << "' is a target-specific override"; + + if (buildspec) + dr << info << "use double '--' to treat this argument as " + << "buildspec"; + } + } + else + { + // Combined directory. + // + // If double separator (visibility marker), then keep the first in + // name. + // + if (p != 0 && path::traits_type::is_separator (v[p - 1])) + --p; + + dir = dir_path (t.value, 0, p + 1); // Include the separator. + t.value.erase (0, p + 1); // Erase the separator. + } + + if (dir->relative ()) + { + // Handle the special relative to base scope case (.../). + // + auto i (dir->begin ()); + + if (*i == "...") + dir = dir_path (++i, dir->end ()); // Note: can become empty. + else + dir->complete (); // Relative to CWD. + } + + if (dir->absolute ()) + dir->normalize (); + } + } + + token_type tt (l.next ().type); + + // The token should be the variable name followed by =, +=, or =+. + // + if (t.type != token_type::word || t.value.empty () || + (tt != token_type::assign && + tt != token_type::prepend && + tt != token_type::append)) + { + diag_record dr (fail); + + dr << "expected variable assignment instead of '" << s << "'"; + + if (buildspec) + dr << 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]); + + if (path::traits_type::is_separator (c)) + c = '/'; // Normalize. + + string n (t.value, c == '!' || c == '%' || c == '/' ? 1 : 0); + + // Make sure it is qualified. + // + // We can support overridable public unqualified variables (which must + // all be pre-entered by the end of this constructor) but we will need + // to detect their names here in an ad hoc manner (we cannot enter them + // before this logic because of the "untyped override" requirement). + // + // Note: issue the same diagnostics as in variable_pool::update(). + // + if (n.find ('.') == string::npos) + fail << "variable " << n << " cannot be overridden"; + + if (c == '!' && dir) + fail << "scope-qualified global override of variable " << n; + + // Pre-enter the main variable. Note that we rely on all the overridable + // variables with global visibility to be known (either entered or + // handled via a pattern) at this stage. + // + variable_pool& vp (data_->var_pool); + variable& var ( + const_cast<variable&> (vp.insert (n, true /* overridable */))); + + const variable* o; + { + variable_visibility v (c == '/' ? variable_visibility::scope : + c == '%' ? variable_visibility::project : + variable_visibility::global); + + const char* k (tt == token_type::assign ? "__override" : + tt == token_type::append ? "__suffix" : "__prefix"); + + unique_ptr<variable> p ( + new variable { + n + '.' + to_string (i + 1) + '.' + k, + &vp /* owner */, + nullptr /* aliases */, + nullptr /* type */, + nullptr /* overrides */, + v}); + + // Back link. + // + p->aliases = p.get (); + if (var.overrides != nullptr) + swap (p->aliases, + const_cast<variable*> (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. + // + scope& gs (global_scope.rw ()); + + parser p (*this); + pair<value, token> 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; + + return make_pair ( + c, + variable_override {var, *o, move (dir), move (r.first)}); + } + context:: context (scheduler& s, global_mutexes& ms, - bool mo, + file_cache& fc, + optional<match_only_level> mo, bool nem, bool dr, + bool ndb, bool kg, const strings& cmd_vars, + reserves res, optional<context*> mc, - const loaded_modules_lock* ml) + const module_libraries_lock* ml, + const function<var_override_function>& var_ovr_func) : data_ (new data (*this)), - sched (s), - mutexes (ms), + sched (&s), + mutexes (&ms), + fcache (&fc), match_only (mo), no_external_modules (nem), dry_run_option (dr), + no_diag_buffer (ndb), keep_going (kg), phase_mutex (*this), scopes (data_->scopes), targets (data_->targets), var_pool (data_->var_pool), + var_patterns (data_->var_patterns), var_overrides (data_->var_overrides), functions (data_->functions), global_scope (create_global_scope (data_->scopes)), @@ -88,12 +326,17 @@ namespace build2 ? optional<unique_ptr<context>> (nullptr) : nullopt) { + // NOTE: see also the bare minimum version below if adding anything here. + tracer trace ("context"); l6 ([&]{trace << "initializing build state";}); + reserve (res); + scope_map& sm (data_->scopes); variable_pool& vp (data_->var_pool); + variable_patterns& vpats (data_->var_patterns); insert_builtin_functions (functions); @@ -102,7 +345,7 @@ namespace build2 // meta_operation_table.insert ("noop"); meta_operation_table.insert ("perform"); - meta_operation_table.insert ("configure"); + meta_operation_table.insert ("configure"); // bpkg assumes no process. meta_operation_table.insert ("disfigure"); if (config_preprocess_create != nullptr) @@ -132,13 +375,26 @@ namespace build2 // Any variable assigned on the global scope should natually have the // global visibility. // - auto set = [&gs, &vp] (const char* var, auto val) + auto set = [&gs, &vp] (const char* var, auto val) -> const value& { using T = decltype (val); value& v (gs.assign (vp.insert<T> (var, variable_visibility::global))); v = move (val); + return v; }; + // Build system mode. + // + // This value signals any special mode the build system may be running + // in. The two core modes are `no-external-modules` (bootstrapping of + // external modules is disabled) and `normal` (normal build system + // execution). Build system drivers may invent additional modes (for + // example, the bpkg `skeleton` mode that is used to evaluate depends + // clauses). + // + set ("build.mode", + no_external_modules ? "no-external-modules" : "normal"); + set ("build.work", work); set ("build.home", home); @@ -165,6 +421,29 @@ namespace build2 // set ("build.verbosity", uint64_t (verb)); + // Build system diagnostics progress and color. + // + // Note that these can be true, false, or NULL if neither requested nor + // suppressed explicitly. + // + { + value& v (gs.assign (vp.insert<bool> ("build.progress", v_g))); + if (diag_progress_option) + v = *diag_progress_option; + } + + { + value& v (gs.assign (vp.insert<bool> ("build.diag_color", v_g))); + if (diag_color_option) + v = *diag_color_option; + } + + // These are the "effective" values that incorporate a suitable default + // if neither requested nor suppressed explicitly. + // + set ("build.show_progress", show_progress (verb_never)); + set ("build.show_diag_color", show_diag_color ()); + // Build system version (similar to what we do in the version module // except here we don't include package epoch/revision). // @@ -220,7 +499,8 @@ namespace build2 // Did the user ask us to use config.guess? // string orig (config_guess - ? run<string> (3, + ? run<string> (*this, + 3, *config_guess, [](string& l, bool) {return move (l);}) : BUILD2_HOST_TRIPLET); @@ -243,7 +523,7 @@ namespace build2 set ("build.host.version", t.version); set ("build.host.class", t.class_); - set ("build.host", move (t)); + build_host = &set ("build.host", move (t)).as<target_triplet> (); } catch (const invalid_argument& e) { @@ -260,15 +540,22 @@ namespace build2 { target_type_map& t (data_->global_target_types); - t.insert<file> (); - t.insert<alias> (); - t.insert<dir> (); - t.insert<fsdir> (); - t.insert<exe> (); - t.insert<doc> (); - t.insert<legal> (); - t.insert<man> (); - t.insert<man1> (); + // These are abstract. + // + t.insert<target> (); + t.insert<mtime_target> (); + t.insert<path_target> (); + + t.insert<file> (); + t.insert<group> (); + t.insert<alias> (); + t.insert<dir> (); + t.insert<fsdir> (); + t.insert<exe> (); + t.insert<doc> (); + t.insert<legal> (); + t.insert<man> (); + t.insert<man1> (); { auto& tt (t.insert<manifest> ()); @@ -295,215 +582,51 @@ namespace build2 // Note that some config.config.* variables have project visibility thus // the match argument is false. // - vp.insert_pattern ("config.**", nullopt, true, v_g, true, false); + vpats.insert ("config.**", nullopt, true, v_g, true, false); // Parse and enter the command line variables. We do it before entering // any other variables so that all the variables that are overriden are // marked as such first. Then, as we enter variables, we can verify that // the override is alowed. // - for (size_t i (0); i != cmd_vars.size (); ++i) { - const string& s (cmd_vars[i]); - - istringstream is (s); - is.exceptions (istringstream::failbit | istringstream::badbit); + size_t i (0); + for (; i != cmd_vars.size (); ++i) + { + const string& s (cmd_vars[i]); - // Similar to buildspec we do "effective escaping" and only for ['"\$(] - // (basically what's necessary inside a double-quoted literal plus the - // single quote). - // - path_name in ("<cmdline>"); - lexer l (is, in, 1 /* line */, "\'\"\\$("); + pair<char, variable_override> p ( + parse_variable_override (s, i, true /* buildspec */)); - // At the buildfile level the scope-specific variable should be - // separated from the directory with a whitespace, for example: - // - // ./ foo=$bar - // - // However, requiring this for command line variables would be too - // inconvinient so we support both. - // - // We also have the optional visibility modifier as a first character of - // the variable name: - // - // ! - global - // % - project - // / - scope - // - // The last one clashes a bit with the directory prefix: - // - // ./ /foo=bar - // .//foo=bar - // - // But that's probably ok (the need for a scope-qualified override with - // scope visibility should be pretty rare). Note also that to set the - // value on the global scope we use !. - // - // And so the first token should be a word which can be either a - // variable name (potentially with the directory qualification) or just - // the directory, in which case it should be followed by another word - // (unqualified variable name). To avoid treating any of the visibility - // modifiers as special we use the cmdvar mode. - // - l.mode (lexer_mode::cmdvar); - token t (l.next ()); - - optional<dir_path> dir; - if (t.type == token_type::word) - { - string& v (t.value); - size_t p (path::traits_type::rfind_separator (v)); + char c (p.first); + variable_override& vo (p.second); - if (p != string::npos && p != 0) // If first then visibility. + // Global and absolute scope overrides we can enter directly. Project + // and relative scope ones will be entered later for each project. + // + if (c == '!' || (vo.dir && vo.dir->absolute ())) { - if (p == v.size () - 1) - { - // Separate directory. - // - dir = dir_path (move (v)); - t = l.next (); - - // Target-specific overrides are not yet supported (and probably - // never will be; the beast is already complex enough). - // - if (t.type == token_type::colon) - fail << "'" << s << "' is a target-specific override" << - info << "use double '--' to treat this argument as buildspec"; - } - else - { - // Combined directory. - // - // If double separator (visibility marker), then keep the first in - // name. - // - if (p != 0 && path::traits_type::is_separator (v[p - 1])) - --p; - - dir = dir_path (t.value, 0, p + 1); // Include the separator. - t.value.erase (0, p + 1); // Erase the separator. - } + scope& s (c == '!' ? gs : *sm.insert_out (*vo.dir)->second.front ()); - if (dir->relative ()) - { - // Handle the special relative to base scope case (.../). - // - auto i (dir->begin ()); - - if (*i == "...") - dir = dir_path (++i, dir->end ()); // Note: can become empty. - else - dir->complete (); // Relative to CWD. - } + auto p (s.vars.insert (vo.ovr)); + assert (p.second); // Variable name is unique. - if (dir->absolute ()) - dir->normalize (); + value& v (p.first); + v = move (vo.val); } - } - - token_type tt (l.next ().type); - - // The token should be the variable name followed by =, +=, or =+. - // - if (t.type != token_type::word || t.value.empty () || - (tt != token_type::assign && - tt != token_type::prepend && - tt != token_type::append)) - { - fail << "expected variable assignment instead of '" << s << "'" << - info << "use double '--' to treat this argument as buildspec"; - } + else + data_->var_overrides.push_back (move (vo)); - // 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; - - // Pre-enter the main variable. Note that we rely on all the overridable - // variables with global visibility to be known (either entered or - // handled via a pettern) at this stage. - // - variable& var ( - const_cast<variable&> (vp.insert (n, true /* overridable */))); - - const variable* o; - { - variable_visibility v (c == '/' ? variable_visibility::scope : - c == '%' ? variable_visibility::project : - variable_visibility::global); - - const char* k (tt == token_type::assign ? "__override" : - tt == token_type::append ? "__suffix" : "__prefix"); - - unique_ptr<variable> p ( - new variable { - n + '.' + to_string (i + 1) + '.' + k, - nullptr /* aliases */, - nullptr /* type */, - nullptr /* overrides */, - v}); - - // Back link. + // Save global overrides for nested contexts. // - p->aliases = p.get (); - if (var.overrides != nullptr) - swap (p->aliases, - const_cast<variable*> (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<value, token> 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); + if (c == '!') + data_->global_var_overrides.push_back (s); } - else - data_->var_overrides.push_back ( - variable_override {var, *o, move (dir), move (r.first)}); - // Save global overrides for nested contexts. + // Parse any ad hoc project-wide overrides. // - if (c == '!') - data_->global_var_overrides.push_back (s); + if (var_ovr_func != nullptr) + var_ovr_func (*this, i); } // Enter remaining variable patterns and builtin variables. @@ -512,24 +635,26 @@ namespace build2 const auto v_t (variable_visibility::target); const auto v_q (variable_visibility::prereq); - vp.insert_pattern<bool> ("config.**.configured", false, v_p); + vpats.insert<bool> ("config.**.configured", false, v_p); - // file.cxx:import() (note: order is important; see insert_pattern()). + // file.cxx:import() + // + // Note: the order is important (see variable_patterns::insert()). // // Note that if any are overriden, they are "pre-typed" by the config.** // pattern above and we just "add" the types. // - vp.insert_pattern<abs_dir_path> ("config.import.*", true, v_g, true); - vp.insert_pattern<path> ("config.import.**", true, v_g, true); + vpats.insert<abs_dir_path> ("config.import.*", true, v_g, true); + vpats.insert<path> ("config.import.**", true, v_g, true); // module.cxx:boot/init_module(). // // Note that we also have the config.<module>.configured variable (see // above). // - vp.insert_pattern<bool> ("**.booted", false /* overridable */, v_p); - vp.insert_pattern<bool> ("**.loaded", false, v_p); - vp.insert_pattern<bool> ("**.configured", false, v_p); + vpats.insert<bool> ("**.booted", false /* overridable */, v_p); + vpats.insert<bool> ("**.loaded", false, v_p); + vpats.insert<bool> ("**.configured", false, v_p); var_src_root = &vp.insert<dir_path> ("src_root"); var_out_root = &vp.insert<dir_path> ("out_root"); @@ -555,29 +680,71 @@ namespace build2 var_export_metadata = &vp.insert ("export.metadata", v_t); // Untyped. var_extension = &vp.insert<string> ("extension", v_t); - var_clean = &vp.insert<bool> ("clean", v_t); - var_backlink = &vp.insert<string> ("backlink", v_t); - var_include = &vp.insert<string> ("include", v_q); + var_update = &vp.insert<string> ("update", v_q); + var_clean = &vp.insert<bool> ("clean", v_t); + var_backlink = &vp.insert ("backlink", v_t); // Untyped. + var_include = &vp.insert<string> ("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"; + gs.target_vars[exe::static_type]["*"].assign (var_backlink) = + names {name ("true")}; + gs.target_vars[doc::static_type]["*"].assign (var_backlink) = + names {name ("true")}; // Register builtin rules. // { rule_map& r (gs.rules); // Note: global scope! - //@@ outer - r.insert<alias> (perform_id, 0, "alias", alias_rule::instance); + r.insert<alias> (perform_id, 0, "build.alias", alias_rule::instance); - r.insert<fsdir> (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert<fsdir> (perform_clean_id, "fsdir", fsdir_rule::instance); + r.insert<fsdir> (perform_update_id, "build.fsdir", fsdir_rule::instance); + r.insert<fsdir> (perform_clean_id, "build.fsdir", fsdir_rule::instance); - r.insert<mtime_target> (perform_update_id, "file", file_rule::instance); - r.insert<mtime_target> (perform_clean_id, "file", file_rule::instance); + r.insert<mtime_target> (perform_update_id, "build.file", file_rule::instance); + r.insert<mtime_target> (perform_clean_id, "build.file", file_rule::instance); } + + // End of initialization. + // + load_generation = 1; + } + + context:: + context () + : data_ (new data (*this)), + sched (nullptr), + mutexes (nullptr), + fcache (nullptr), + match_only (nullopt), + no_external_modules (true), + dry_run_option (false), + no_diag_buffer (false), + keep_going (false), + phase_mutex (*this), + scopes (data_->scopes), + targets (data_->targets), + var_pool (data_->var_pool), + var_patterns (data_->var_patterns), + var_overrides (data_->var_overrides), + functions (data_->functions), + global_scope (create_global_scope (data_->scopes)), + global_target_types (data_->global_target_types), + global_override_cache (data_->global_override_cache), + global_var_overrides (data_->global_var_overrides), + modules_lock (nullptr), + module_context (nullptr) + { + variable_pool& vp (data_->var_pool); + + var_src_root = &vp.insert<dir_path> ("src_root"); + var_out_root = &vp.insert<dir_path> ("out_root"); + + var_project = &vp.insert<project_name> ("project"); + var_amalgamation = &vp.insert<dir_path> ("amalgamation"); + + load_generation = 1; } context:: @@ -587,6 +754,68 @@ namespace build2 } void context:: + enter_project_overrides (scope& rs, + const dir_path& out_base, + const variable_overrides& ovrs, + scope* as) + { + // The mildly tricky part here is to distinguish the situation where we + // are bootstrapping the same project multiple times. The first override + // that we set cannot already exist (because the override variable names + // are unique) so if it is already set, then it can only mean this project + // is already bootstrapped. + // + // This is further complicated by the project vs amalgamation logic (we + // may have already done the amalgamation but not the project). So we + // split it into two passes. + // + auto& sm (scopes.rw ()); + + for (const variable_override& o: ovrs) + { + if (o.ovr.visibility != variable_visibility::global) + continue; + + // If we have a directory, enter the scope, similar to how we do + // it in the context ctor. + // + scope& s ( + o.dir + ? *sm.insert_out ((out_base / *o.dir).normalize ())->second.front () + : *(as != nullptr ? as : (as = rs.weak_scope ()))); + + auto p (s.vars.insert (o.ovr)); + + if (!p.second) + break; + + value& v (p.first); + v = o.val; + } + + for (const variable_override& o: ovrs) + { + // Ours is either project (%foo) or scope (/foo). + // + if (o.ovr.visibility == variable_visibility::global) + continue; + + scope& s ( + o.dir + ? *sm.insert_out ((out_base / *o.dir).normalize ())->second.front () + : rs); + + auto p (s.vars.insert (o.ovr)); + + if (!p.second) + break; + + value& v (p.first); + v = o.val; + } + } + + void context:: current_meta_operation (const meta_operation_info& mif) { if (current_mname != mif.name) @@ -596,6 +825,7 @@ namespace build2 } current_mif = &mif; + current_mdata = current_data_ptr (nullptr, null_current_data_deleter); current_on = 0; // Reset. } @@ -604,9 +834,13 @@ namespace build2 const operation_info* outer_oif, bool diag_noise) { - current_oname = (outer_oif == nullptr ? inner_oif : *outer_oif).name; + const auto& oif (outer_oif == nullptr ? inner_oif : *outer_oif); + + current_oname = oif.name; current_inner_oif = &inner_oif; current_outer_oif = outer_oif; + current_inner_odata = current_data_ptr (nullptr, null_current_data_deleter); + current_outer_odata = current_data_ptr (nullptr, null_current_data_deleter); current_on++; current_mode = inner_oif.mode; current_diag_noise = diag_noise; @@ -616,10 +850,15 @@ namespace build2 dependency_count.store (0, memory_order_relaxed); target_count.store (0, memory_order_relaxed); skip_count.store (0, memory_order_relaxed); + resolve_count.store (0, memory_order_relaxed); + + // Clear accumulated targets with post hoc prerequisites. + // + current_posthoc_targets.clear (); } bool run_phase_mutex:: - lock (run_phase p) + lock (run_phase n) { bool r; @@ -630,7 +869,7 @@ namespace build2 // Increment the counter. // condition_variable* v (nullptr); - switch (p) + switch (n) { case run_phase::load: lc_++; v = &lv_; break; case run_phase::match: mc_++; v = &mv_; break; @@ -643,16 +882,18 @@ namespace build2 // if (u) { - ctx_.phase = p; + ctx_.phase = n; r = !fail_; } - else if (ctx_.phase != p) + else if (ctx_.phase != n) { - ctx_.sched.deactivate (false /* external */); - for (; ctx_.phase != p; v->wait (l)) ; + ++contention; // Protected by m_. + + 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 */); + ctx_.sched->activate (false /* external */); } else r = !fail_; @@ -660,9 +901,16 @@ namespace build2 // In case of load, acquire the exclusive access mutex. // - if (p == run_phase::load) + if (n == run_phase::load) { - lm_.lock (); + if (!lm_.try_lock ()) + { + ctx_.sched->deactivate (false /* external */); + lm_.lock (); + ctx_.sched->activate (false /* external */); + + ++contention_load; // Protected by lm_. + } r = !fail_; // Re-query. } @@ -670,11 +918,11 @@ namespace build2 } void run_phase_mutex:: - unlock (run_phase p) + unlock (run_phase o) { // In case of load, release the exclusive access mutex. // - if (p == run_phase::load) + if (o == run_phase::load) lm_.unlock (); { @@ -683,25 +931,35 @@ namespace build2 // Decrement the counter and see if this phase has become unlocked. // bool u (false); - switch (p) + 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; } - // 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 the phase became 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) { + run_phase n; condition_variable* v; + if (lc_ != 0) {n = run_phase::load; v = &lv_;} + else if (mc_ != 0) {n = run_phase::match; v = &mv_;} + else if (ec_ != 0) {n = run_phase::execute; v = &ev_;} + else {n = run_phase::load; v = nullptr;} - 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;} + ctx_.phase = n; + + // Enter/leave scheduler sub-phase. See also the other half in + // relock(). + // + if (o == run_phase::match && n == run_phase::execute) + ctx_.sched->push_phase (); + else if (o == run_phase::execute && n == run_phase::match) + ctx_.sched->pop_phase (); if (v != nullptr) { @@ -712,7 +970,7 @@ namespace build2 } } - bool run_phase_mutex:: + optional<bool> run_phase_mutex:: relock (run_phase o, run_phase n) { // Pretty much a fused unlock/lock implementation except that we always @@ -721,6 +979,7 @@ namespace build2 assert (o != n); bool r; + bool s (true); // True switch. if (o == run_phase::load) lm_.unlock (); @@ -751,6 +1010,14 @@ namespace build2 ctx_.phase = n; r = !fail_; + // Enter/leave scheduler sub-phase. See also the other half in + // unlock(). + // + if (o == run_phase::match && n == run_phase::execute) + ctx_.sched->push_phase (); + else if (o == run_phase::execute && n == run_phase::match) + ctx_.sched->pop_phase (); + // Notify others that could be waiting for this phase. // if (v != nullptr) @@ -761,21 +1028,37 @@ namespace build2 } else // phase != n { - ctx_.sched.deactivate (false /* external */); + ++contention; // Protected by m_. + + 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 */); + ctx_.sched->activate (false /* external */); } } if (n == run_phase::load) { - lm_.lock (); + if (!lm_.try_lock ()) + { + // If we failed to acquire the load mutex, then we know there is (or + // was) someone before us in the load phase. And it's impossible to + // switch to a different phase between our calls to try_lock() above + // and lock() below because of our +1 in lc_. + // + s = false; + + ctx_.sched->deactivate (false /* external */); + lm_.lock (); + ctx_.sched->activate (false /* external */); + + ++contention_load; // Protected by lm_. + } r = !fail_; // Re-query. } - return r; + return r ? optional<bool> (s) : nullopt; } // C++17 deprecated uncaught_exception() so use uncaught_exceptions() if @@ -834,34 +1117,42 @@ namespace build2 phase_lock_instance = prev; ctx.phase_mutex.unlock (phase); - //text << this_thread::get_id () << " phase release " << p; + //text << this_thread::get_id () << " phase release " << phase; } } // phase_unlock // phase_unlock:: - phase_unlock (context& ctx, bool u) - : l (u ? phase_lock_instance : nullptr) + phase_unlock (context* c, bool d) + : ctx (c), lock_ (nullptr) { - if (u) + if (ctx != nullptr && !d) + unlock (); + } + + void phase_unlock:: + unlock () + { + if (ctx != nullptr && lock_ == nullptr) { - assert (&l->ctx == &ctx); + lock_ = phase_lock_instance; + assert (&lock_->ctx == ctx); - phase_lock_instance = nullptr; // Note: not l->prev. - ctx.phase_mutex.unlock (l->phase); + phase_lock_instance = nullptr; // Note: not lock->prev. + ctx->phase_mutex.unlock (lock_->phase); - //text << this_thread::get_id () << " phase unlock " << l->p; + //text << this_thread::get_id () << " phase unlock " << lock_->phase; } } - phase_unlock:: - ~phase_unlock () noexcept (false) + void phase_unlock:: + lock () { - if (l != nullptr) + if (lock_ != nullptr) { - bool r (l->ctx.phase_mutex.lock (l->phase)); - phase_lock_instance = l; + bool r (ctx->phase_mutex.lock (lock_->phase)); + phase_lock_instance = lock_; // 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. @@ -869,10 +1160,16 @@ namespace build2 if (!r && !uncaught_exception ()) throw failed (); - //text << this_thread::get_id () << " phase lock " << l->p; + //text << this_thread::get_id () << " phase lock " << lock_->phase; } } + phase_unlock:: + ~phase_unlock () noexcept (false) + { + lock (); + } + // phase_switch // phase_switch:: @@ -882,7 +1179,8 @@ namespace build2 phase_lock* pl (phase_lock_instance); assert (&pl->ctx == &ctx); - if (!ctx.phase_mutex.relock (old_phase, new_phase)) + optional<bool> r (ctx.phase_mutex.relock (old_phase, new_phase)); + if (!r) { ctx.phase_mutex.relock (new_phase, old_phase); throw failed (); @@ -891,11 +1189,38 @@ namespace build2 pl->phase = new_phase; if (new_phase == run_phase::load) // Note: load lock is exclusive. + { ctx.load_generation++; - //text << this_thread::get_id () << " phase switch " << o << " " << n; + // Invalidate cached target base_scope values if we are switching from a + // non-load phase (we don't cache during load which means load->load + // switch doesn't have anything to invalidate). + // + // @@ This is still quite expensive on project like Boost with a large + // number of files (targets) and a large number of load phase + // switches (due to directory buildfiles). + // + // Thinking some more on this, we shouldn't need to do this since such + // loads can (or at least should) only perform "island appends" see + // comment on context::phase for details. + // +#if 0 + if (*r) + { + for (const unique_ptr<target>& t: ctx.targets) + t->base_scope_.store (nullptr, memory_order_relaxed); + } +#endif + } + + //text << this_thread::get_id () << " phase switch " + // << old_phase << " " << new_phase; } +#if 0 + // NOTE: see push/pop_phase() logic if trying to enable this. Also + // the load stuff above. + // phase_switch:: phase_switch (phase_unlock&& u, phase_lock&& l) : old_phase (u.l->phase), new_phase (l.phase) @@ -903,6 +1228,7 @@ namespace build2 phase_lock_instance = u.l; // Disarms phase_lock u.l = nullptr; // Disarms phase_unlock } +#endif phase_switch:: ~phase_switch () noexcept (false) @@ -928,6 +1254,7 @@ namespace build2 if (!r && !uncaught_exception ()) throw failed (); - //text << this_thread::get_id () << " phase restore " << n << " " << o; + //text << this_thread::get_id () << " phase restore " + // << new_phase << " " << old_phase; } } |