aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/context.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/context.cxx')
-rw-r--r--libbuild2/context.cxx777
1 files changed, 527 insertions, 250 deletions
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 924c370..6e4fd6f 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -29,7 +29,7 @@ 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)
@@ -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,32 +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,
file_cache& fc,
- bool mo,
+ 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),
- fcache (fc),
+ 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)),
@@ -90,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);
@@ -104,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)
@@ -134,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);
@@ -167,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).
//
@@ -222,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);
@@ -245,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)
{
@@ -269,6 +547,7 @@ namespace build2
t.insert<path_target> ();
t.insert<file> ();
+ t.insert<group> ();
t.insert<alias> ();
t.insert<dir> ();
t.insert<fsdir> ();
@@ -303,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]);
+ size_t i (0);
+ for (; i != cmd_vars.size (); ++i)
+ {
+ const string& s (cmd_vars[i]);
- istringstream is (s);
- is.exceptions (istringstream::failbit | istringstream::badbit);
+ pair<char, variable_override> p (
+ parse_variable_override (s, i, true /* buildspec */));
- // 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 */, "\'\"\\$(");
+ char c (p.first);
+ variable_override& vo (p.second);
- // 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.
+ // 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";
- }
-
- // 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.
+ else
+ data_->var_overrides.push_back (move (vo));
- 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.
- //
- p->aliases = p.get ();
- if (var.overrides != nullptr)
- swap (p->aliases,
- const_cast<variable*> (var.overrides.get ())->aliases);
-
- // Forward link.
+ // Save global overrides for nested contexts.
//
- 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_out (*dir)->second.front ());
-
- 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.
@@ -520,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");
@@ -563,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::
@@ -595,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)
@@ -604,6 +825,7 @@ namespace build2
}
current_mif = &mif;
+ current_mdata = current_data_ptr (nullptr, null_current_data_deleter);
current_on = 0; // Reset.
}
@@ -612,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;
@@ -624,6 +850,11 @@ 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::
@@ -656,11 +887,13 @@ namespace build2
}
else if (ctx_.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 */);
}
else
r = !fail_;
@@ -672,9 +905,11 @@ namespace build2
{
if (!lm_.try_lock ())
{
- ctx_.sched.deactivate (false /* external */);
+ ctx_.sched->deactivate (false /* external */);
lm_.lock ();
- ctx_.sched.activate (false /* external */);
+ ctx_.sched->activate (false /* external */);
+
+ ++contention_load; // Protected by lm_.
}
r = !fail_; // Re-query.
}
@@ -722,9 +957,9 @@ namespace build2
// relock().
//
if (o == run_phase::match && n == run_phase::execute)
- ctx_.sched.push_phase ();
+ ctx_.sched->push_phase ();
else if (o == run_phase::execute && n == run_phase::match)
- ctx_.sched.pop_phase ();
+ ctx_.sched->pop_phase ();
if (v != nullptr)
{
@@ -735,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
@@ -744,6 +979,7 @@ namespace build2
assert (o != n);
bool r;
+ bool s (true); // True switch.
if (o == run_phase::load)
lm_.unlock ();
@@ -778,9 +1014,9 @@ namespace build2
// unlock().
//
if (o == run_phase::match && n == run_phase::execute)
- ctx_.sched.push_phase ();
+ ctx_.sched->push_phase ();
else if (o == run_phase::execute && n == run_phase::match)
- ctx_.sched.pop_phase ();
+ ctx_.sched->pop_phase ();
// Notify others that could be waiting for this phase.
//
@@ -792,11 +1028,13 @@ 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 */);
}
}
@@ -804,14 +1042,23 @@ namespace build2
{
if (!lm_.try_lock ())
{
- ctx_.sched.deactivate (false /* external */);
+ // 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 */);
+ 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
@@ -877,35 +1124,35 @@ namespace build2
// phase_unlock
//
phase_unlock::
- phase_unlock (context& c, bool u, bool d)
- : ctx (u ? &c : nullptr), lock (nullptr)
+ phase_unlock (context* c, bool d)
+ : ctx (c), lock_ (nullptr)
{
- if (u && !d)
+ if (ctx != nullptr && !d)
unlock ();
}
void phase_unlock::
unlock ()
{
- if (ctx != nullptr && lock == nullptr)
+ if (ctx != nullptr && lock_ == nullptr)
{
- lock = phase_lock_instance;
- assert (&lock->ctx == ctx);
+ lock_ = phase_lock_instance;
+ assert (&lock_->ctx == ctx);
phase_lock_instance = nullptr; // Note: not lock->prev.
- ctx->phase_mutex.unlock (lock->phase);
+ ctx->phase_mutex.unlock (lock_->phase);
- //text << this_thread::get_id () << " phase unlock " << lock->phase;
+ //text << this_thread::get_id () << " phase unlock " << lock_->phase;
}
}
- phase_unlock::
- ~phase_unlock () noexcept (false)
+ void phase_unlock::
+ lock ()
{
- if (lock != nullptr)
+ if (lock_ != nullptr)
{
- bool r (ctx->phase_mutex.lock (lock->phase));
- phase_lock_instance = lock;
+ 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.
@@ -913,10 +1160,16 @@ namespace build2
if (!r && !uncaught_exception ())
throw failed ();
- //text << this_thread::get_id () << " phase lock " << lock->phase;
+ //text << this_thread::get_id () << " phase lock " << lock_->phase;
}
}
+ phase_unlock::
+ ~phase_unlock () noexcept (false)
+ {
+ lock ();
+ }
+
// phase_switch
//
phase_switch::
@@ -926,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 ();
@@ -935,14 +1189,37 @@ namespace build2
pl->phase = new_phase;
if (new_phase == run_phase::load) // Note: load lock is exclusive.
+ {
ctx.load_generation++;
+ // 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.
+ // 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)