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. --- build2/b.cxx | 171 +++--- build2/bin/init.cxx | 12 +- build2/bin/target.cxx | 110 ++-- build2/c/init.cxx | 2 +- build2/cc/common.cxx | 43 +- build2/cc/common.hxx | 4 +- build2/cc/compile-rule.cxx | 82 +-- build2/cc/init.cxx | 13 +- build2/cc/link-rule.cxx | 40 +- build2/cc/module.cxx | 16 +- build2/cc/msvc.cxx | 9 +- build2/cc/pkgconfig.cxx | 19 +- build2/cc/target.cxx | 8 +- build2/cc/utility.cxx | 4 +- build2/cc/windows-manifest.cxx | 2 +- build2/cc/windows-rpath.cxx | 4 +- build2/cli/init.cxx | 2 +- build2/cli/rule.cxx | 6 +- build2/cli/target.cxx | 15 +- build2/cxx/init.cxx | 7 +- build2/cxx/target.cxx | 20 +- libbuild2/action.hxx | 14 +- libbuild2/algorithm.cxx | 318 +++++----- libbuild2/algorithm.hxx | 27 +- libbuild2/algorithm.ixx | 52 +- libbuild2/bash/rule.cxx | 16 +- libbuild2/bash/target.cxx | 4 +- libbuild2/config/init.cxx | 10 +- libbuild2/config/operation.cxx | 202 ++++--- libbuild2/config/operation.hxx | 2 +- libbuild2/config/utility.cxx | 18 +- libbuild2/config/utility.hxx | 6 +- libbuild2/config/utility.txx | 2 +- libbuild2/context.cxx | 1032 ++++++++++++++++---------------- libbuild2/context.hxx | 602 +++++++++++-------- libbuild2/context.ixx | 20 +- libbuild2/diagnostics.cxx | 38 +- libbuild2/diagnostics.hxx | 7 +- libbuild2/dist/init.cxx | 2 +- libbuild2/dist/operation.cxx | 52 +- libbuild2/dump.cxx | 16 +- libbuild2/dump.hxx | 3 +- libbuild2/file.cxx | 193 +++--- libbuild2/file.hxx | 17 +- libbuild2/file.ixx | 14 +- libbuild2/filesystem.cxx | 28 +- libbuild2/filesystem.hxx | 55 +- libbuild2/filesystem.ixx | 40 ++ libbuild2/filesystem.txx | 9 +- libbuild2/function.cxx | 63 +- libbuild2/function.hxx | 18 +- libbuild2/function.test.cxx | 12 +- libbuild2/functions-builtin.cxx | 4 +- libbuild2/functions-filesystem.cxx | 4 +- libbuild2/functions-name.cxx | 6 +- libbuild2/functions-path.cxx | 6 +- libbuild2/functions-process-path.cxx | 4 +- libbuild2/functions-process.cxx | 4 +- libbuild2/functions-project-name.cxx | 6 +- libbuild2/functions-regex.cxx | 4 +- libbuild2/functions-string.cxx | 6 +- libbuild2/functions-target-triplet.cxx | 6 +- libbuild2/in/init.cxx | 2 +- libbuild2/in/rule.cxx | 8 +- libbuild2/install/functions.cxx | 4 +- libbuild2/install/init.cxx | 17 +- libbuild2/install/operation.cxx | 2 + libbuild2/install/rule.cxx | 24 +- libbuild2/install/utility.hxx | 4 +- libbuild2/module.cxx | 4 +- libbuild2/module.hxx | 4 +- libbuild2/operation.cxx | 103 ++-- libbuild2/operation.hxx | 18 +- libbuild2/parser.cxx | 70 +-- libbuild2/parser.hxx | 6 +- libbuild2/prerequisite.cxx | 27 +- libbuild2/prerequisite.hxx | 38 +- libbuild2/prerequisite.ixx | 34 -- libbuild2/rule.cxx | 4 +- libbuild2/scope.cxx | 33 +- libbuild2/scope.hxx | 64 +- libbuild2/scope.ixx | 4 +- libbuild2/search.cxx | 25 +- libbuild2/search.hxx | 10 +- libbuild2/target-key.hxx | 2 +- libbuild2/target-type.hxx | 8 +- libbuild2/target.cxx | 108 ++-- libbuild2/target.hxx | 93 ++- libbuild2/target.ixx | 47 +- libbuild2/target.txx | 25 +- libbuild2/test/init.cxx | 4 +- libbuild2/test/operation.cxx | 2 + libbuild2/test/rule.cxx | 78 +-- libbuild2/test/script/builtin.cxx | 10 +- libbuild2/test/script/parser.cxx | 32 +- libbuild2/test/script/parser.hxx | 4 + libbuild2/test/script/parser.test.cxx | 32 +- libbuild2/test/script/runner.cxx | 55 +- libbuild2/test/script/script.cxx | 46 +- libbuild2/test/script/script.hxx | 19 +- libbuild2/types.hxx | 1 - libbuild2/utility.cxx | 8 +- libbuild2/utility.hxx | 7 +- libbuild2/variable.cxx | 12 +- libbuild2/variable.hxx | 88 +-- libbuild2/variable.ixx | 2 +- libbuild2/version/init.cxx | 19 +- libbuild2/version/rule.cxx | 3 +- libbuild2/version/utility.cxx | 9 +- libbuild2/version/utility.hxx | 6 +- tests/libbuild2/driver.cxx | 6 +- 111 files changed, 2555 insertions(+), 2207 deletions(-) create mode 100644 libbuild2/filesystem.ixx delete mode 100644 libbuild2/prerequisite.ixx diff --git a/build2/b.cxx b/build2/b.cxx index f359dbc..51b024e 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -107,12 +107,15 @@ namespace build2 if (ops.structured_result ()) { + const target& t (at.as_target ()); + context& ctx (t.ctx); + cout << at.state - << ' ' << current_mif->name - << ' ' << current_inner_oif->name; + << ' ' << ctx.current_mif->name + << ' ' << ctx.current_inner_oif->name; - if (current_outer_oif != nullptr) - cout << '(' << current_outer_oif->name << ')'; + if (ctx.current_outer_oif != nullptr) + cout << '(' << ctx.current_outer_oif->name << ')'; // There are two ways one may wish to identify the target of the // operation: as something specific but inherently non-portable (say, @@ -131,7 +134,7 @@ namespace build2 stream_verbosity sv (stream_verb (cout)); stream_verb (cout, stream_verbosity (1, 0)); - cout << ' ' << at.as_target () << endl; + cout << ' ' << t << endl; stream_verb (cout, sv); } @@ -222,6 +225,8 @@ main (int argc, char* argv[]) << system_error (errno, generic_category ()); // Sanitize. #endif + scheduler sched; + // Parse the command line. // try @@ -475,7 +480,6 @@ main (int argc, char* argv[]) // init (&::terminate, argv[0], - !ops.serial_stop (), ops.dry_run (), (ops.mtime_check () ? optional (true) : ops.no_mtime_check () ? optional (false) : nullopt), (ops.config_sub_specified () @@ -490,7 +494,7 @@ main (int argc, char* argv[]) // current and child processes unless we are in the stop mode. Failed that // we may have multiple dialog boxes popping up. // - if (keep_going) + if (!ops.serial_stop ()) SetErrorMode (SetErrorMode (0) | // Returns the current mode. SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); #endif @@ -582,14 +586,16 @@ main (int argc, char* argv[]) fail << "invalid --max-jobs|-J value"; } - sched.startup (jobs, - 1, - max_jobs, - jobs * ops.queue_depth (), - (ops.max_stack_specified () - ? optional (ops.max_stack () * 1024) - : nullopt)); + sched.startup (jobs, + 1, + max_jobs, + jobs * ops.queue_depth (), + (ops.max_stack_specified () + ? optional (ops.max_stack () * 1024) + : nullopt)); + // @@ CTX: should these be per-context? + // variable_cache_mutex_shard_size = sched.shard_size (); variable_cache_mutex_shard.reset ( new shared_mutex[variable_cache_mutex_shard_size]); @@ -606,10 +612,21 @@ main (int argc, char* argv[]) trace << "jobs: " << jobs; } - // Set the build state before parsing the buildspec since it relies on - // global scope being setup. + // Set the build context before parsing the buildspec since it relies on + // the global scope being setup. We reset it for every meta-operation (see + // below). // - variable_overrides var_ovs (reset (cmd_vars)); + unique_ptr ctx; + auto new_context = [&ctx, &sched, &cmd_vars] + { + ctx = nullptr; // Free first. + ctx.reset (new context (sched, + cmd_vars, + ops.dry_run (), + !ops.serial_stop () /* keep_going */)); + }; + + new_context (); // Parse the buildspec. // @@ -619,7 +636,7 @@ main (int argc, char* argv[]) istringstream is (args); is.exceptions (istringstream::failbit | istringstream::badbit); - parser p; + parser p (*ctx); bspec = p.parse_buildspec (is, path ("")); } catch (const io_error&) @@ -745,13 +762,13 @@ main (int argc, char* argv[]) else opspecs.assign (lifted, 1); - // Reset the build state for each meta-operation since there is no + // Reset the build context for each meta-operation since there is no // guarantee their assumptions (e.g., in the load callback) are // compatible. // if (dirty) { - var_ovs = reset (cmd_vars); + new_context (); dirty = false; } @@ -769,24 +786,25 @@ main (int argc, char* argv[]) values& mparams (lifted == nullptr ? mit->params : lifted->params); string mname (lifted == nullptr ? mit->name : lifted->name); - current_mname = mname; // Set early. + ctx->current_mname = mname; // Set early. if (!mname.empty ()) { - if (meta_operation_id m = meta_operation_table.find (mname)) + if (meta_operation_id m = ctx->meta_operation_table.find (mname)) { // Can modify params, opspec, change meta-operation name. // - if (auto f = meta_operation_table[m].process) - mname = current_mname = - f (var_ovs, mparams, opspecs, lifted != nullptr, l); + if (auto f = ctx->meta_operation_table[m].process) + mname = ctx->current_mname = f ( + *ctx, mparams, opspecs, lifted != nullptr, l); } } // Expose early so can be used during bootstrap (with the same // limitations as for pre-processing). // - global_scope->rw ().assign (var_build_meta_operation) = mname; + scope& gs (ctx->global_scope.rw ()); + gs.assign (ctx->var_build_meta_operation) = mname; for (auto oit (opspecs.begin ()); oit != opspecs.end (); ++oit) { @@ -797,7 +815,7 @@ main (int argc, char* argv[]) const values& oparams (lifted == nullptr ? os.params : values ()); const string& oname (lifted == nullptr ? os.name : empty_string); - current_oname = oname; // Set early. + ctx->current_oname = oname; // Set early. if (lifted != nullptr) lifted = nullptr; // Clear for the next iteration. @@ -817,9 +835,11 @@ main (int argc, char* argv[]) // Return true if this operation is lifted. // - auto lift = [&oname, &mname, &os, &mit, &lifted, &skip, &l, &trace] () + auto lift = [&ctx, + &oname, &mname, + &os, &mit, &lifted, &skip, &l, &trace] () { - meta_operation_id m (meta_operation_table.find (oname)); + meta_operation_id m (ctx->meta_operation_table.find (oname)); if (m != 0) { @@ -978,7 +998,7 @@ main (int argc, char* argv[]) // Handle a forwarded configuration. Note that if we've changed // out_root then we also have to remap out_base. // - out_root = bootstrap_fwd (src_root, altn); + out_root = bootstrap_fwd (*ctx, src_root, altn); if (src_root != out_root) { out_base = out_root / out_base.leaf (src_root); @@ -1023,8 +1043,7 @@ main (int argc, char* argv[]) // use to the bootstrap files (other than src-root.build, which, // BTW, doesn't need to exist if src_root == out_root). // - scope& rs ( - create_root (*scope::global_, out_root, src_root)->second); + scope& rs (create_root (gs, out_root, src_root)->second); bool bstrapped (bootstrapped (rs)); @@ -1034,7 +1053,7 @@ main (int argc, char* argv[]) // See if the bootstrap process set/changed src_root. // - value& v (rs.assign (var_src_root)); + value& v (rs.assign (ctx->var_src_root)); if (v) { @@ -1062,8 +1081,8 @@ main (int argc, char* argv[]) << (forwarded ? "forwarded " : "specified ") << src_root; - new_src_root = src_root; - old_src_root = move (p); + ctx->new_src_root = src_root; + ctx->old_src_root = move (p); p = src_root; } } @@ -1096,7 +1115,7 @@ main (int argc, char* argv[]) // command line and import). // if (forwarded) - rs.assign (var_forwarded) = true; + rs.assign (ctx->var_forwarded) = true; // Sync local variable that are used below with actual values. // @@ -1133,7 +1152,7 @@ main (int argc, char* argv[]) // Note that the subprojects variable has already been processed // and converted to a map by the bootstrap_src() call above. // - if (auto l = rs.vars[var_subprojects]) + if (auto l = rs.vars[ctx->var_subprojects]) { for (const auto& p: cast (l)) { @@ -1148,8 +1167,8 @@ main (int argc, char* argv[]) // all be known. We store the combined action id in uint8_t; // see for details. // - assert (operation_table.size () <= 128); - assert (meta_operation_table.size () <= 128); + assert (ctx->operation_table.size () <= 128); + assert (ctx->meta_operation_table.size () <= 128); // Since we now know all the names of meta-operations and // operations, "lift" names that we assumed (from buildspec syntax) @@ -1166,7 +1185,7 @@ main (int argc, char* argv[]) if (!mname.empty ()) { - m = meta_operation_table.find (mname); + m = ctx->meta_operation_table.find (mname); if (m == 0) fail (l) << "unknown meta-operation " << mname; @@ -1174,7 +1193,7 @@ main (int argc, char* argv[]) if (!oname.empty ()) { - o = operation_table.find (oname); + o = ctx->operation_table.find (oname); if (o == 0) fail (l) << "unknown operation " << oname; @@ -1197,7 +1216,7 @@ main (int argc, char* argv[]) if (mif == nullptr) fail (l) << "target " << tn << " does not support meta-" - << "operation " << meta_operation_table[m].name; + << "operation " << ctx->meta_operation_table[m].name; } // // Otherwise, check that all the targets in a meta-operation @@ -1210,7 +1229,7 @@ main (int argc, char* argv[]) if (mi == nullptr) fail (l) << "target " << tn << " does not support meta-" - << "operation " << meta_operation_table[mid].name; + << "operation " << ctx->meta_operation_table[mid].name; if (mi != mif) fail (l) << "different implementations of meta-operation " @@ -1238,7 +1257,7 @@ main (int argc, char* argv[]) fail (l) << "unexpected parameters for meta-operation " << mif->name; - set_current_mif (*mif); + ctx->current_meta_operation (*mif); dirty = true; } @@ -1247,16 +1266,16 @@ main (int argc, char* argv[]) // if (oid == 0) { - auto lookup = - [&rs, &l, &tn] (operation_id o) -> const operation_info* - { - const operation_info* r (rs.root_extra->operations[o]); + auto lookup = [&ctx, &rs, &l, &tn] (operation_id o) -> + const operation_info* + { + const operation_info* r (rs.root_extra->operations[o]); - if (r == nullptr) - fail (l) << "target " << tn << " does not support " - << "operation " << operation_table[o]; - return r; - }; + if (r == nullptr) + fail (l) << "target " << tn << " does not support " + << "operation " << ctx->operation_table[o]; + return r; + }; if (o == 0) o = default_id; @@ -1324,19 +1343,19 @@ main (int argc, char* argv[]) // else { - auto check = - [&rs, &l, &tn] (operation_id o, const operation_info* i) - { - const operation_info* r (rs.root_extra->operations[o]); + auto check = [&ctx, &rs, &l, &tn] (operation_id o, + const operation_info* i) + { + const operation_info* r (rs.root_extra->operations[o]); - if (r == nullptr) - fail (l) << "target " << tn << " does not support " - << "operation " << operation_table[o]; + if (r == nullptr) + fail (l) << "target " << tn << " does not support " + << "operation " << ctx->operation_table[o]; - if (r != i) - fail (l) << "different implementations of operation " - << i->name << " in the same operation batch"; - }; + if (r != i) + fail (l) << "different implementations of operation " + << i->name << " in the same operation batch"; + }; check (orig_oid, oif); @@ -1406,7 +1425,7 @@ main (int argc, char* argv[]) trace << " out_root: " << out_root; trace << " src_root: " << src_root; trace << " forwarded: " << (forwarded ? "true" : "false"); - if (auto l = rs.vars[var_amalgamation]) + if (auto l = rs.vars[ctx->var_amalgamation]) { trace << " amalgamation: " << cast (l); trace << " strong scope: " << *rs.strong_scope (); @@ -1427,9 +1446,9 @@ main (int argc, char* argv[]) // So we split it into two passes. // { - auto& sm (scope_map::instance); + auto& sm (ctx->scopes.rw ()); - for (const variable_override& o: var_ovs) + for (const variable_override& o: ctx->var_overrides) { if (o.ovr.visibility != variable_visibility::normal) continue; @@ -1450,7 +1469,7 @@ main (int argc, char* argv[]) v = o.val; } - for (const variable_override& o: var_ovs) + for (const variable_override& o: ctx->var_overrides) { // Ours is either project (%foo) or scope (/foo). // @@ -1504,7 +1523,7 @@ main (int argc, char* argv[]) // building before we know how to for all the targets in this // operation batch. // - const scope& bs (scopes.find (ts.out_base)); + const scope& bs (ctx->scopes.find (ts.out_base)); // Find the target type and extract the extension. // @@ -1546,7 +1565,7 @@ main (int argc, char* argv[]) } // target if (dump_load) - dump (); + dump (*ctx); // Finally, match the rules and perform the operation. // @@ -1558,7 +1577,7 @@ main (int argc, char* argv[]) if (mif->operation_pre != nullptr) mif->operation_pre (mparams, pre_oid); // Cannot be translated. - set_current_oif (*pre_oif, oif); + ctx->current_operation (*pre_oif, oif); action a (mid, pre_oid, oid); @@ -1570,7 +1589,7 @@ main (int argc, char* argv[]) mif->match (mparams, a, tgs, diag, true /* progress */); if (dump_match) - dump (a); + dump (*ctx, a); if (mif->execute != nullptr && !ops.match_only ()) mif->execute (mparams, a, tgs, diag, true /* progress */); @@ -1585,7 +1604,7 @@ main (int argc, char* argv[]) tgs.reset (); } - set_current_oif (*oif, outer_oif); + ctx->current_operation (*oif, outer_oif); action a (mid, oid, oif->outer_id); @@ -1597,7 +1616,7 @@ main (int argc, char* argv[]) mif->match (mparams, a, tgs, diag, true /* progress */); if (dump_match) - dump (a); + dump (*ctx, a); if (mif->execute != nullptr && !ops.match_only ()) mif->execute (mparams, a, tgs, diag, true /* progress */); @@ -1613,7 +1632,7 @@ main (int argc, char* argv[]) if (mif->operation_pre != nullptr) mif->operation_pre (mparams, post_oid); // Cannot be translated. - set_current_oif (*post_oif, oif); + ctx->current_operation (*post_oif, oif); action a (mid, post_oid, oid); @@ -1625,7 +1644,7 @@ main (int argc, char* argv[]) mif->match (mparams, a, tgs, diag, true /* progress */); if (dump_match) - dump (a); + dump (*ctx, a); if (mif->execute != nullptr && !ops.match_only ()) mif->execute (mparams, a, tgs, diag, true /* progress */); diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index b46d643..4e80834 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -56,7 +56,7 @@ namespace build2 // Target is a string and not target_triplet because it can be // specified by the user. // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); vp.insert ("config.bin.target", true); vp.insert ("config.bin.pattern", true); @@ -266,7 +266,7 @@ namespace build2 // config.bin.target // { - const variable& var (var_pool["config.bin.target"]); + const variable& var (rs.ctx.var_pool["config.bin.target"]); // We first see if the value was specified via the configuration // mechanism. @@ -343,7 +343,7 @@ namespace build2 // config.bin.pattern // { - const variable& var (var_pool["config.bin.pattern"]); + const variable& var (rs.ctx.var_pool["config.bin.pattern"]); // We first see if the value was specified via the configuration // mechanism. @@ -568,7 +568,7 @@ namespace build2 // if (first) { - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); v.insert ("bin.ar.path"); v.insert ("bin.ranlib.path"); @@ -744,7 +744,7 @@ namespace build2 // if (first) { - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); v.insert ("bin.ld.path"); v.insert ("config.bin.ld", true); @@ -857,7 +857,7 @@ namespace build2 // if (first) { - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); v.insert ("bin.rc.path"); v.insert ("config.bin.rc", true); diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index ec4a0ef..9074317 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -88,11 +88,12 @@ namespace build2 // template static target* - m_factory (const target_type&, dir_path dir, dir_path out, string n) + m_factory (context& ctx, + const target_type&, dir_path dir, dir_path out, string n) { - const G* g (targets.find (dir, out, n)); + const G* g (ctx.targets.find (dir, out, n)); - M* m (new M (move (dir), move (out), move (n))); + M* m (new M (ctx, move (dir), move (out), move (n))); m->group = g; return m; @@ -104,8 +105,8 @@ namespace build2 &objx::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -117,8 +118,8 @@ namespace build2 &bmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -130,8 +131,8 @@ namespace build2 &hbmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -143,8 +144,8 @@ namespace build2 &objx::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -156,8 +157,8 @@ namespace build2 &bmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -169,8 +170,8 @@ namespace build2 &hbmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -182,8 +183,8 @@ namespace build2 &objx::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -195,8 +196,8 @@ namespace build2 &bmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -208,8 +209,8 @@ namespace build2 &hbmix::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -221,8 +222,8 @@ namespace build2 &libux::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -234,8 +235,8 @@ namespace build2 &libux::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -247,8 +248,8 @@ namespace build2 &libux::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &target_search, // Note: not _file(); don't look for an existing file. false @@ -258,21 +259,22 @@ namespace build2 // template static target* - g_factory (const target_type&, dir_path dir, dir_path out, string n) + g_factory (context& ctx, + const target_type&, dir_path dir, dir_path out, string n) { // Casts are MT-aware (during serial load). // - E* e (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + E* e (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - A* a (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + A* a (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - S* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + S* s (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - G* g (new G (move (dir), move (out), move (n))); + G* g (new G (ctx, move (dir), move (out), move (n))); if (e != nullptr) e->group = g; if (a != nullptr) a->group = g; @@ -323,16 +325,17 @@ namespace build2 // The same as g_factory() but without E. // static target* - libul_factory (const target_type&, dir_path dir, dir_path out, string n) + libul_factory (context& ctx, + const target_type&, dir_path dir, dir_path out, string n) { - libua* a (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + libua* a (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - libus* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + libus* s (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - libul* g (new libul (move (dir), move (out), move (n))); + libul* g (new libul (ctx, move (dir), move (out), move (n))); if (a != nullptr) a->group = g; if (s != nullptr) s->group = g; @@ -369,8 +372,8 @@ namespace build2 &file::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -382,8 +385,8 @@ namespace build2 &file::static_type, &m_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -403,18 +406,19 @@ namespace build2 } static target* - lib_factory (const target_type&, dir_path dir, dir_path out, string n) + lib_factory (context& ctx, + const target_type&, dir_path dir, dir_path out, string n) { // Casts are MT-aware (during serial load). // - liba* a (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + liba* a (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - libs* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + libs* s (ctx.phase == run_phase::load + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); - lib* l (new lib (move (dir), move (out), move (n))); + lib* l (new lib (ctx, move (dir), move (out), move (n))); if (a != nullptr) a->group = l; if (s != nullptr) s->group = l; @@ -443,8 +447,8 @@ namespace build2 &file::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false diff --git a/build2/c/init.cxx b/build2/c/init.cxx index 5c55281..ff9cc58 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -145,7 +145,7 @@ namespace build2 // Enter all the variables and initialize the module data. // - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); cc::config_data d { cc::lang::c, diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index aa0eb89..4f5db4c 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -87,7 +87,7 @@ namespace build2 bool impl (proc_impl && proc_impl (l, la)); bool cc (false), same (false); - auto& vp (var_pool); + auto& vp (top_bs.ctx.var_pool); lookup c_e_libs; lookup x_e_libs; @@ -221,7 +221,7 @@ namespace build2 : &cast ( bs.root_scope ()->vars[same ? x_sys_lib_dirs - : var_pool[*t + ".sys_lib_dirs"]]); + : bs.ctx.var_pool[*t + ".sys_lib_dirs"]]); }; auto find_linfo = [top_li, t, cc, &bs, &l, &li] () @@ -474,7 +474,7 @@ namespace build2 if (xt == nullptr) { if (n.qualified ()) - xt = import_existing (pk); + xt = import_existing (s.ctx, pk); } if (xt == nullptr) @@ -494,20 +494,21 @@ namespace build2 // template ulock common:: - insert_library (T*& r, + insert_library (context& ctx, + T*& r, const string& name, const dir_path& d, optional ext, bool exist, tracer& trace) { - auto p (targets.insert_locked (T::static_type, - d, - dir_path (), - name, - move (ext), - true, // Implied. - trace)); + auto p (ctx.targets.insert_locked (T::static_type, + d, + dir_path (), + name, + move (ext), + true, // Implied. + trace)); assert (!exist || !p.second.owns_lock ()); r = &p.first.template as (); @@ -626,6 +627,8 @@ namespace build2 &name, ext, &p, &f, exist, &trace, this] (const dir_path& d) -> bool { + context& ctx (p.scope->ctx); + timestamp mt; // libs @@ -648,9 +651,10 @@ namespace build2 if (tclass == "windows") { libi* i (nullptr); - insert_library (i, name, d, se, exist, trace); + insert_library (ctx, i, name, d, se, exist, trace); - ulock l (insert_library (s, name, d, nullopt, exist, trace)); + ulock l ( + insert_library (ctx, s, name, d, nullopt, exist, trace)); if (!exist) { @@ -677,7 +681,7 @@ namespace build2 } else { - insert_library (s, name, d, se, exist, trace); + insert_library (ctx, s, name, d, se, exist, trace); s->mtime (mt); s->path (move (f)); @@ -697,7 +701,7 @@ namespace build2 if (mt != timestamp_nonexistent) { - insert_library (s, name, d, se, exist, trace); + insert_library (ctx, s, name, d, se, exist, trace); s->mtime (mt); s->path (move (f)); @@ -722,7 +726,7 @@ namespace build2 // Note that this target is outside any project which we treat // as out trees. // - insert_library (a, name, d, ae, exist, trace); + insert_library (ctx, a, name, d, ae, exist, trace); a->mtime (mt); a->path (move (f)); } @@ -760,14 +764,14 @@ namespace build2 if (na && !r.first.empty ()) { - insert_library (a, name, d, nullopt, exist, trace); + insert_library (ctx, a, name, d, nullopt, exist, trace); a->mtime (timestamp_unreal); a->path (empty_path); } if (ns && !r.second.empty ()) { - insert_library (s, name, d, nullopt, exist, trace); + insert_library (ctx, s, name, d, nullopt, exist, trace); s->mtime (timestamp_unreal); s->path (empty_path); } @@ -821,7 +825,8 @@ namespace build2 // Enter (or find) the lib{} target group. // lib* lt; - insert_library (lt, name, *pd, l ? p.tk.ext : nullopt, exist, trace); + insert_library ( + p.scope->ctx, lt, name, *pd, l ? p.tk.ext : nullopt, exist, trace); // Result. // diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index c58a7f3..b24eb7d 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -278,7 +279,8 @@ namespace build2 template static ulock - insert_library (T*&, + insert_library (context&, + T*&, const string&, const dir_path&, optional, diff --git a/build2/cc/compile-rule.cxx b/build2/cc/compile-rule.cxx index 833fd44..fa43533 100644 --- a/build2/cc/compile-rule.cxx +++ b/build2/cc/compile-rule.cxx @@ -365,7 +365,9 @@ namespace build2 const variable& var ( com ? c_export_poptions - : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); + : (t == x + ? x_export_poptions + : l.ctx.var_pool[t + ".export.poptions"])); append_options (args, l, var); }; @@ -418,7 +420,9 @@ namespace build2 const variable& var ( com ? c_export_poptions - : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); + : (t == x + ? x_export_poptions + : l.ctx.var_pool[t + ".export.poptions"])); hash_options (cs, l, var); }; @@ -472,7 +476,9 @@ namespace build2 const variable& var ( com ? c_export_poptions - : (t == x ? x_export_poptions : var_pool[t + ".export.poptions"])); + : (t == x + ? x_export_poptions + : l.ctx.var_pool[t + ".export.poptions"])); append_prefixes (m, l, var); }; @@ -553,7 +559,7 @@ namespace build2 // @@ MT perf: so we are going to switch the phase and execute for // any generated header. // - phase_switch ps (run_phase::execute); + phase_switch ps (t.ctx, run_phase::execute); target_state ns (execute_direct (a, t)); if (ns != os && ns != target_state::unchanged) @@ -577,6 +583,8 @@ namespace build2 match_data& md (t.data ()); + context& ctx (t.ctx); + // Note: until refined below, non-BMI-generating translation unit is // assumed non-modular. // @@ -692,7 +700,7 @@ namespace build2 // Start asynchronous matching of prerequisites. Wait with unlocked // phase to allow phase switching. // - wait_guard wg (target::count_busy (), t[a].task_count, true); + wait_guard wg (ctx, ctx.count_busy (), t[a].task_count, true); size_t start (pts.size ()); // Index of the first to be added. for (prerequisite_member p: group_prerequisite_members (a, t)) @@ -754,7 +762,7 @@ namespace build2 continue; } - match_async (a, *pt, target::count_busy (), t[a].task_count); + match_async (a, *pt, ctx.count_busy (), t[a].task_count); pts.push_back (prerequisite_target (pt, pi)); } @@ -1142,7 +1150,7 @@ namespace build2 // to keep re-validating the file on every subsequent dry-run as well // on the real run). // - if (u && dd.reading () && !dry_run) + if (u && dd.reading () && !ctx.dry_run) dd.touch = true; dd.close (); @@ -2161,7 +2169,7 @@ namespace build2 // small_vector tts; - const scope& bs (scopes.find (d)); + const scope& bs (t.ctx.scopes.find (d)); if (const scope* rs = bs.root_scope ()) { tts = map_extension (bs, n, e); @@ -2201,7 +2209,7 @@ namespace build2 // absolute path with a spelled-out extension to multiple targets. // for (const target_type* tt: tts) - if ((r = targets.find (*tt, d, out, n, e, trace)) != nullptr) + if ((r = t.ctx.targets.find (*tt, d, out, n, e, trace)) != nullptr) break; // Note: we can't do this because of the in-source builds where @@ -2887,7 +2895,7 @@ namespace build2 // See if this path is inside a project with an out-of- // tree build and is in the out directory tree. // - const scope& bs (scopes.find (d)); + const scope& bs (t.ctx.scopes.find (d)); if (bs.root_scope () != nullptr) { const dir_path& bp (bs.out_path ()); @@ -5101,18 +5109,18 @@ namespace build2 modules_sidebuild_dir /= x); - const scope* ps (&scopes.find (pd)); + const scope* ps (&rs.ctx.scopes.find (pd)); if (ps->out_path () != pd) { // Switch the phase to load then create and load the subproject. // - phase_switch phs (run_phase::load); + phase_switch phs (rs.ctx, run_phase::load); // Re-test again now that we are in exclusive phase (another thread // could have already created and loaded the subproject). // - ps = &scopes.find (pd); + ps = &rs.ctx.scopes.find (pd); if (ps->out_path () != pd) { @@ -5200,7 +5208,7 @@ namespace build2 // exists then we assume all this is already done (otherwise why would // someone have created such a target). // - if (const file* bt = targets.find ( + if (const file* bt = bs.ctx.targets.find ( tt, pd, dir_path (), // Always in the out tree. @@ -5237,13 +5245,14 @@ namespace build2 } } - auto p (targets.insert_locked (tt, - move (pd), - dir_path (), // Always in the out tree. - move (mf), - nullopt, // Use default extension. - true, // Implied. - trace)); + auto p (bs.ctx.targets.insert_locked ( + tt, + move (pd), + dir_path (), // Always in the out tree. + move (mf), + nullopt, // Use default extension. + true, // Implied. + trace)); file& bt (static_cast (p.first)); // Note that this is racy and someone might have created this target @@ -5295,7 +5304,7 @@ namespace build2 const target_type& tt (compile_types (li.type).hbmi); - if (const file* bt = targets.find ( + if (const file* bt = bs.ctx.targets.find ( tt, pd, dir_path (), // Always in the out tree. @@ -5307,13 +5316,14 @@ namespace build2 prerequisites ps; ps.push_back (prerequisite (ht)); - auto p (targets.insert_locked (tt, - move (pd), - dir_path (), // Always in the out tree. - move (mf), - nullopt, // Use default extension. - true, // Implied. - trace)); + auto p (bs.ctx.targets.insert_locked ( + tt, + move (pd), + dir_path (), // Always in the out tree. + move (mf), + nullopt, // Use default extension. + true, // Implied. + trace)); file& bt (static_cast (p.first)); // Note that this is racy and someone might have created this target @@ -5550,6 +5560,8 @@ namespace build2 match_data md (move (t.data ())); unit_type ut (md.type); + context& ctx (t.ctx); + // While all our prerequisites are already up-to-date, we still have to // execute them to keep the dependency counts straight. Actually, no, we // may also have to update the modules. @@ -5575,9 +5587,9 @@ namespace build2 { if (md.touch) { - touch (tp, false, 2); + touch (ctx, tp, false, 2); t.mtime (system_clock::now ()); - skip_count.fetch_add (1, memory_order_relaxed); + ctx.skip_count.fetch_add (1, memory_order_relaxed); } // Note: else mtime should be cached. @@ -5592,7 +5604,7 @@ namespace build2 ? system_clock::now () : timestamp_unknown); - touch (md.dd, false, verb_never); + touch (ctx, md.dd, false, verb_never); const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -5941,7 +5953,7 @@ namespace build2 // translation unit (i.e., one of the imported module's has BMIs // changed). // - if (!dry_run) + if (!ctx.dry_run) { try { @@ -6020,7 +6032,7 @@ namespace build2 if (verb >= 2) print_process (args); - if (!dry_run) + if (!ctx.dry_run) { // Remove the target file if this fails. If we don't do that, we // will end up with a broken build that is up-to-date. @@ -6053,7 +6065,7 @@ namespace build2 timestamp now (system_clock::now ()); - if (!dry_run) + if (!ctx.dry_run) depdb::check_mtime (start, md.dd, tp, now); // Should we go to the filesystem and get the new mtime? We know the diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index e22ece7..c83d5ed 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -26,27 +26,29 @@ namespace build2 static target_state clean_module_sidebuilds (action, const scope& rs, const dir&) { + context& ctx (rs.ctx); + const dir_path& out_root (rs.out_path ()); dir_path d (out_root / rs.root_extra->build_dir / modules_sidebuild_dir); if (exists (d)) { - if (build2::rmdir_r (d)) + if (rmdir_r (ctx, d)) { // Clean up cc/ if it became empty. // d = out_root / rs.root_extra->build_dir / module_dir; if (empty (d)) { - rmdir (d); + rmdir (ctx, d); // And build/ if it also became empty (e.g., in case of a build // with a transient configuration). // d = out_root / rs.root_extra->build_dir; if (empty (d)) - rmdir (d); + rmdir (ctx, d); } return target_state::changed; @@ -77,7 +79,7 @@ namespace build2 // Enter variables. Note: some overridable, some not. // - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); auto v_t (variable_visibility::target); @@ -276,7 +278,8 @@ namespace build2 // Prepare configuration hints. They are only used on the first load // of bin.config so we only populate them on our first load. // - variable_map h; + variable_map h (rs.ctx); + if (first) { // Note that all these variables have already been registered. diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx index bb91722..bfc31d7 100644 --- a/build2/cc/link-rule.cxx +++ b/build2/cc/link-rule.cxx @@ -165,14 +165,15 @@ namespace build2 // otherwise the above search would have returned the member // target. // - pt = search_existing (p.prerequisite.key (tt)); + pt = search_existing (t.ctx, p.prerequisite.key (tt)); } } else if (!p.is_a ()) { // See if we also/instead have a group. // - pg = search_existing (p.prerequisite.key (libul::static_type)); + pg = search_existing (t.ctx, + p.prerequisite.key (libul::static_type)); if (pt == nullptr) swap (pt, pg); @@ -483,6 +484,7 @@ namespace build2 tracer trace (x, "link_rule::apply"); file& t (xt.as ()); + context& ctx (t.ctx); // Note that for_install is signalled by install_rule and therefore // can only be relied upon during execute. @@ -697,7 +699,7 @@ namespace build2 // Note that ad hoc inputs have to be explicitly marked with the // include=adhoc prerequisite-specific variable. // - if (current_outer_oif != nullptr) + if (ctx.current_outer_oif != nullptr) continue; } @@ -945,7 +947,7 @@ namespace build2 // Windows. // #ifdef _WIN32 - dir.state[a].assign (var_backlink) = "copy"; + dir.state[a].assign (ctx.var_backlink) = "copy"; #endif } } @@ -1163,7 +1165,7 @@ namespace build2 bool u; if ((u = pt->is_a ()) || pt->is_a ()) { - const variable& var (var_pool["bin.whole"]); // @@ Cache. + const variable& var (ctx.var_pool["bin.whole"]); // @@ Cache. // See the bin module for the lookup semantics discussion. Note // that the variable is not overridable so we omit find_override() @@ -1197,7 +1199,7 @@ namespace build2 // Wait with unlocked phase to allow phase switching. // - wait_guard wg (target::count_busy (), t[a].task_count, true); + wait_guard wg (ctx, ctx.count_busy (), t[a].task_count, true); i = start; for (prerequisite_member p: group_prerequisite_members (a, t)) @@ -1229,7 +1231,7 @@ namespace build2 } } - match_async (a, *pt, target::count_busy (), t[a].task_count); + match_async (a, *pt, ctx.count_busy (), t[a].task_count); mark (pt, m); } @@ -1473,7 +1475,7 @@ namespace build2 ? (exp ? c_export_loptions : c_loptions) : (t == x ? (exp ? x_export_loptions : x_loptions) - : var_pool[t + (exp ? ".export.loptions" : ".loptions")])); + : l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")])); append_options (d.args, *g, var); } @@ -1575,7 +1577,7 @@ namespace build2 ? (exp ? c_export_loptions : c_loptions) : (t == x ? (exp ? x_export_loptions : x_loptions) - : var_pool[t + (exp ? ".export.loptions" : ".loptions")])); + : l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")])); hash_options (d.cs, *g, var); } @@ -1747,6 +1749,8 @@ namespace build2 const file& t (xt.as ()); const path& tp (t.path ()); + context& ctx (t.ctx); + const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -1866,7 +1870,7 @@ namespace build2 if (verb >= 3) print_process (args); - if (!dry_run) + if (!ctx.dry_run) { auto_rmfile rm (of); @@ -1966,7 +1970,7 @@ namespace build2 const string& cs ( cast ( rs[tsys == "win32-msvc" - ? var_pool["bin.ld.checksum"] + ? ctx.var_pool["bin.ld.checksum"] : x_checksum])); if (dd.expect (cs) != nullptr) @@ -2726,7 +2730,7 @@ namespace build2 // auto_rmfile rm; - if (!dry_run) + if (!ctx.dry_run) { rm = auto_rmfile (relt); @@ -2823,7 +2827,7 @@ namespace build2 if (verb >= 2) print_process (args); - if (!dry_run) + if (!ctx.dry_run) run (rl, args); } @@ -2843,12 +2847,12 @@ namespace build2 // For shared libraries we may need to create a bunch of symlinks (or // fallback to hardlinks/copies on Windows). // - auto ln = [] (const path& f, const path& l) + auto ln = [&ctx] (const path& f, const path& l) { if (verb >= 3) text << "ln -sf " << f << ' ' << l; - if (dry_run) + if (ctx.dry_run) return; try @@ -2908,12 +2912,12 @@ namespace build2 // if (tsys == "darwin" && cast (rs["bin.ar.id"]) == "generic") { - if (!dry_run) - touch (tp, false /* create */, verb_never); + if (!ctx.dry_run) + touch (ctx, tp, false /* create */, verb_never); } } - if (!dry_run) + if (!ctx.dry_run) { rm.cancel (); dd.check_mtime (tp); diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index 36cdd1a..064d954 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -41,9 +41,13 @@ namespace build2 config::save_module (rs, x, 250); - const variable& config_c_poptions (var_pool["config.cc.poptions"]); - const variable& config_c_coptions (var_pool["config.cc.coptions"]); - const variable& config_c_loptions (var_pool["config.cc.loptions"]); + auto& vp (rs.ctx.var_pool.rw (rs)); + + // Must already exist. + // + const variable& config_c_poptions (vp["config.cc.poptions"]); + const variable& config_c_coptions (vp["config.cc.coptions"]); + const variable& config_c_loptions (vp["config.cc.loptions"]); // config.x // @@ -63,8 +67,6 @@ namespace build2 // if (!cc_loaded) { - auto& vp (var_pool.rw (rs)); - for (const char* const* pm (x_hinters); *pm != nullptr; ++pm) { string m (*pm); @@ -201,7 +203,7 @@ namespace build2 { // Prepare configuration hints. // - variable_map h; + variable_map h (rs.ctx); // Note that all these variables have already been registered. // @@ -545,7 +547,7 @@ namespace build2 // if (!cast_false (rs["cc.core.config.loaded"])) { - variable_map h; + variable_map h (rs.ctx); if (!ci.bin_pattern.empty ()) h.assign ("config.bin.pattern") = ci.bin_pattern; diff --git a/build2/cc/msvc.cxx b/build2/cc/msvc.cxx index 6125fdd..7d8c3f5 100644 --- a/build2/cc/msvc.cxx +++ b/build2/cc/msvc.cxx @@ -365,6 +365,7 @@ namespace build2 { // Pretty similar logic to search_library(). // + assert (p.scope != nullptr); const optional& ext (p.tk.ext); const string& name (*p.tk.name); @@ -403,7 +404,7 @@ namespace build2 // Enter the target. // T* t; - common::insert_library (t, name, d, e, exist, trace); + common::insert_library (p.scope->ctx, t, name, d, e, exist, trace); t->mtime (mt); t->path (move (f)); @@ -453,6 +454,8 @@ namespace build2 { tracer trace (x, "msvc_search_shared"); + assert (pk.scope != nullptr); + libs* s (nullptr); auto search = [&s, &ld, &d, &pk, exist, &trace] ( @@ -461,7 +464,9 @@ namespace build2 if (libi* i = msvc_search_library ( ld, d, pk, otype::s, pf, sf, exist, trace)) { - ulock l (insert_library (s, *pk.tk.name, d, nullopt, exist, trace)); + ulock l ( + insert_library ( + pk.scope->ctx, s, *pk.tk.name, d, nullopt, exist, trace)); if (!exist) { diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index c23b746..3b4c711 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -1027,7 +1027,7 @@ namespace build2 // Parse modules and add them to the prerequisites. // - auto parse_modules = [&trace, &next, this] + auto parse_modules = [&trace, &next, &s, this] (const pkgconf& pc, prerequisites& ps) { string mstr (pc.variable ("cxx_modules")); @@ -1057,7 +1057,7 @@ namespace build2 // For now there are only C++ modules. // auto tl ( - targets.insert_locked ( + s.ctx.targets.insert_locked ( *x_mod, mp.directory (), dir_path (), @@ -1228,6 +1228,8 @@ namespace build2 { tracer trace (x, "pkgconfig_save"); + context& ctx (l.ctx); + const scope& bs (l.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -1246,7 +1248,7 @@ namespace build2 if (verb >= 2) text << "cat >" << p; - if (dry_run) + if (ctx.dry_run) return; auto_rmfile arm (p); @@ -1256,9 +1258,12 @@ namespace build2 ofdstream os (p); { - const project_name& n (cast (rs.vars[var_project])); + const project_name& n (project (rs)); + + if (n.empty ()) + fail << "no project name in " << rs; - lookup vl (rs.vars[var_version]); + lookup vl (rs.vars[ctx.var_version]); if (!vl) fail << "no version variable in project " << n << info << "while generating " << p; @@ -1271,12 +1276,12 @@ namespace build2 // This one is required so make something up if unspecified. // os << "Description: "; - if (const string* s = cast_null (rs[var_project_summary])) + if (const string* s = cast_null (rs[ctx.var_project_summary])) os << *s << endl; else os << n << ' ' << v << endl; - if (const string* u = cast_null (rs[var_project_url])) + if (const string* u = cast_null (rs[ctx.var_project_url])) os << "URL: " << *u << endl; } diff --git a/build2/cc/target.cxx b/build2/cc/target.cxx index c637e60..89b9391 100644 --- a/build2/cc/target.cxx +++ b/build2/cc/target.cxx @@ -33,8 +33,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -48,8 +48,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 3271f7c..e9d4ce3 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -70,9 +70,9 @@ namespace build2 // Called by the compile rule during execute. // - return phase == run_phase::match && !exist + return x.ctx.phase == run_phase::match && !exist ? &search (x, tt, x.dir, x.out, x.name) - : search_existing (tt, x.dir, x.out, x.name); + : search_existing (x.ctx, tt, x.dir, x.out, x.name); } else { diff --git a/build2/cc/windows-manifest.cxx b/build2/cc/windows-manifest.cxx index da12f0f..733cae5 100644 --- a/build2/cc/windows-manifest.cxx +++ b/build2/cc/windows-manifest.cxx @@ -119,7 +119,7 @@ namespace build2 if (verb >= 3) text << "cat >" << mf; - if (!dry_run) + if (!t.ctx.dry_run) { auto_rmfile rm (mf); diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index d81e3b0..4478f7d 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -281,7 +281,7 @@ namespace build2 // simpler. // { - rmdir_status s (build2::rmdir_r (ad, empty, 3)); + rmdir_status s (rmdir_r (t.ctx, ad, empty, 3)); if (empty) return; @@ -362,7 +362,7 @@ namespace build2 if (verb >= 3) text << "cat >" << am; - if (dry_run) + if (t.ctx.dry_run) return; auto_rmfile rm (am); diff --git a/build2/cli/init.cxx b/build2/cli/init.cxx index 6d20aa0..2e553f7 100644 --- a/build2/cli/init.cxx +++ b/build2/cli/init.cxx @@ -43,7 +43,7 @@ namespace build2 // if (first) { - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); // Note: some overridable, some not. // diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx index f6bebee..4ccafc9 100644 --- a/build2/cli/rule.cxx +++ b/build2/cli/rule.cxx @@ -112,7 +112,7 @@ namespace build2 // Check if there is a corresponding cli.cxx{} group. // - const cli_cxx* g (targets.find (t.dir, t.out, t.name)); + const cli_cxx* g (t.ctx.targets.find (t.dir, t.out, t.name)); // If not or if it has no prerequisites (happens when we use it to // set cli.options) and this target has a cli{} prerequisite, then @@ -124,7 +124,7 @@ namespace build2 prerequisite_members (a, t))) { if (g == nullptr) - g = &targets.insert (t.dir, t.out, t.name, trace); + g = &t.ctx.targets.insert (t.dir, t.out, t.name, trace); g->prerequisites (prerequisites {p->as_prerequisite ()}); } @@ -322,7 +322,7 @@ namespace build2 else if (verb) text << "cli " << s; - if (!dry_run) + if (!t.ctx.dry_run) { run (cli, args); dd.check_mtime (tp); diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx index 096295a..2fd70f7 100644 --- a/build2/cli/target.cxx +++ b/build2/cli/target.cxx @@ -23,8 +23,8 @@ namespace build2 &file::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -45,7 +45,8 @@ namespace build2 } static target* - cli_cxx_factory (const target_type&, dir_path d, dir_path o, string n) + cli_cxx_factory (context& ctx, + const target_type&, dir_path d, dir_path o, string n) { tracer trace ("cli::cli_cxx_factory"); @@ -55,11 +56,11 @@ namespace build2 // // Also required for the src-out remapping logic. // - targets.insert (d, o, n, trace); - targets.insert (d, o, n, trace); - targets.insert (d, o, n, trace); + ctx.targets.insert (d, o, n, trace); + ctx.targets.insert (d, o, n, trace); + ctx.targets.insert (d, o, n, trace); - return new cli_cxx (move (d), move (o), move (n)); + return new cli_cxx (ctx, move (d), move (o), move (n)); } const target_type cli_cxx::static_type diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index c355763..1ffa098 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -62,7 +62,8 @@ namespace build2 // auto enter = [&rs] (const char* v) -> const variable& { - return var_pool.rw (rs).insert (v, variable_visibility::project); + return rs.ctx.var_pool.rw (rs).insert ( + v, variable_visibility::project); }; // NOTE: see also module sidebuild subproject if changing anything about @@ -370,7 +371,7 @@ namespace build2 // Enter all the variables and initialize the module data. // - auto& v (var_pool.rw (rs)); + auto& v (rs.ctx.var_pool.rw (rs)); cc::config_data d { cc::lang::cxx, @@ -572,7 +573,7 @@ namespace build2 config_module& cm (*rs.lookup_module ("cxx.guess")); - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); bool modules (cast (rs["cxx.features.modules"])); diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx index 025bf9d..45463f3 100644 --- a/build2/cxx/target.cxx +++ b/build2/cxx/target.cxx @@ -19,8 +19,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -33,8 +33,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -47,8 +47,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -61,8 +61,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false @@ -75,8 +75,8 @@ namespace build2 &cc::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false diff --git a/libbuild2/action.hxx b/libbuild2/action.hxx index 9fa2a16..01c5307 100644 --- a/libbuild2/action.hxx +++ b/libbuild2/action.hxx @@ -116,10 +116,18 @@ namespace build2 template struct action_state { - T data[2]; // [0] -- inner, [1] -- outer. + T inner; + T outer; - T& operator[] (action a) {return data[a.inner () ? 0 : 1];} - const T& operator[] (action a) const {return data[a.inner () ? 0 : 1];} + T& operator[] (action a) {return a.inner () ? inner : outer;} + const T& operator[] (action a) const {return a.inner () ? inner : outer;} + + action_state () = default; + + template + explicit + action_state (A&&... a) + : inner (forward (a)...), outer (forward (a)...) {} }; // Id constants for build-in and pre-defined meta/operations. diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 50db5d3..7a616a5 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -22,7 +22,7 @@ namespace build2 const target& search (const target& t, const prerequisite& p) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); const target* r (p.target.load (memory_order_consume)); @@ -35,13 +35,15 @@ namespace build2 const target* search_existing (const prerequisite& p) { - assert (phase == run_phase::match || phase == run_phase::execute); + context& ctx (p.scope.ctx); + + assert (ctx.phase == run_phase::match || ctx.phase == run_phase::execute); const target* r (p.target.load (memory_order_consume)); if (r == nullptr) { - r = search_existing (p.key ()); + r = search_existing (ctx, p.key ()); if (r != nullptr) search_custom (p, *r); @@ -53,32 +55,34 @@ namespace build2 const target& search (const target& t, const prerequisite_key& pk) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); // If this is a project-qualified prerequisite, then this is import's // business. // if (pk.proj) - return import (pk); + return import (t.ctx, pk); if (const target* pt = pk.tk.type->search (t, pk)) return *pt; - return create_new_target (pk); + return create_new_target (t.ctx, pk); } const target* - search_existing (const prerequisite_key& pk) + search_existing (context& ctx, const prerequisite_key& pk) { - assert (phase == run_phase::match || phase == run_phase::execute); + assert (ctx.phase == run_phase::match || ctx.phase == run_phase::execute); - return pk.proj ? import_existing (pk) : search_existing_target (pk); + return pk.proj + ? import_existing (ctx, pk) + : search_existing_target (ctx, pk); } const target& search (const target& t, name n, const scope& s) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); auto rp (s.find_target_type (n, location ())); const target_type* tt (rp.first); @@ -106,7 +110,8 @@ namespace build2 const target* search_existing (const name& cn, const scope& s, const dir_path& out) { - assert (phase == run_phase::match || phase == run_phase::execute); + assert (s.ctx.phase == run_phase::match || + s.ctx.phase == run_phase::execute); name n (cn); auto rp (s.find_target_type (n, location ())); @@ -130,7 +135,9 @@ namespace build2 prerequisite_key pk { n.proj, {tt, &n.dir, q ? &empty_dir_path : &out, &n.value, ext}, &s}; - return q ? import_existing (pk) : search_existing_target (pk); + return q + ? import_existing (s.ctx, pk) + : search_existing_target (s.ctx, pk); } // target_lock @@ -162,12 +169,14 @@ namespace build2 target_lock lock_impl (action a, const target& ct, optional wq) { - assert (phase == run_phase::match); + context& ctx (ct.ctx); + + assert (ctx.phase == run_phase::match); // Most likely the target's state is (count_touched - 1), that is, 0 or // previously executed, so let's start with that. // - size_t b (target::count_base ()); + size_t b (ctx.count_base ()); size_t e (b + target::offset_touched - 1); size_t appl (b + target::offset_applied); @@ -202,8 +211,8 @@ namespace build2 // to switch the phase to load. Which would result in a deadlock // unless we release the phase. // - phase_unlock ul; - e = sched.wait (busy - 1, task_count, *wq); + phase_unlock ul (ct.ctx); + e = ctx.sched.wait (busy - 1, task_count, *wq); } // We don't lock already applied or executed targets. @@ -241,15 +250,17 @@ namespace build2 void unlock_impl (action a, target& t, size_t offset) { - assert (phase == run_phase::match); + context& ctx (t.ctx); + + assert (ctx.phase == run_phase::match); atomic_count& task_count (t[a].task_count); // Set the task count and wake up any threads that might be waiting for // this target. // - task_count.store (offset + target::count_base (), memory_order_release); - sched.resume (task_count); + task_count.store (offset + ctx.count_base (), memory_order_release); + ctx.sched.resume (task_count); } target& @@ -266,13 +277,13 @@ namespace build2 target& m (*mp != nullptr // Might already be there. ? **mp - : targets.insert (tt, - dir, - out, - move (n), - nullopt /* ext */, - true /* implied */, - trace).first); + : t.ctx.targets.insert (tt, + dir, + out, + move (n), + nullopt /* ext */, + true /* implied */, + trace).first); if (*mp == nullptr) { *mp = &m; @@ -303,7 +314,7 @@ namespace build2 // for (const scope* s (&bs); s != nullptr; - s = s->root () ? global_scope : s->parent_scope ()) + s = s->root () ? &s->global_scope () : s->parent_scope ()) { const operation_rule_map* om (s->rules[mo]); @@ -625,32 +636,33 @@ namespace build2 // Also pass our diagnostics and lock stacks (this is safe since we // expect the caller to wait for completion before unwinding its stack). // - if (sched.async (start_count, - *task_count, - [a, try_match] (const diag_frame* ds, - const target_lock* ls, - target& t, size_t offset) - { - // Switch to caller's diag and lock stacks. - // - diag_frame::stack_guard dsg (ds); - target_lock::stack_guard lsg (ls); - - try - { - phase_lock pl (run_phase::match); // Can throw. - { - target_lock l {a, &t, offset}; // Reassemble. - match_impl (l, false /* step */, try_match); - // Unlock within the match phase. - } - } - catch (const failed&) {} // Phase lock failure. - }, - diag_frame::stack (), - target_lock::stack (), - ref (*ld.target), - ld.offset)) + if (ct.ctx.sched.async ( + start_count, + *task_count, + [a, try_match] (const diag_frame* ds, + const target_lock* ls, + target& t, size_t offset) + { + // Switch to caller's diag and lock stacks. + // + diag_frame::stack_guard dsg (ds); + target_lock::stack_guard lsg (ls); + + try + { + phase_lock pl (t.ctx, run_phase::match); // Throws. + { + target_lock l {a, &t, offset}; // Reassemble. + match_impl (l, false /* step */, try_match); + // Unlock within the match phase. + } + } + catch (const failed&) {} // Phase lock failure. + }, + diag_frame::stack (), + target_lock::stack (), + ref (*ld.target), + ld.offset)) return make_pair (true, target_state::postponed); // Queued. // Matched synchronously, fall through. @@ -728,7 +740,7 @@ namespace build2 // to execute it now. // { - phase_switch ps (run_phase::execute); + phase_switch ps (g.ctx, run_phase::execute); execute_direct (a, g); } @@ -751,7 +763,7 @@ namespace build2 // We can be called during execute though everything should have been // already resolved. // - switch (phase) + switch (g.ctx.phase) { case run_phase::match: { @@ -793,7 +805,7 @@ namespace build2 // Start asynchronous matching of prerequisites. Wait with unlocked phase // to allow phase switching. // - wait_guard wg (target::count_busy (), t[a].task_count, true); + wait_guard wg (t.ctx, t.ctx.count_busy (), t[a].task_count, true); size_t i (pts.size ()); // Index of the first to be added. for (auto&& p: forward (r)) @@ -812,7 +824,7 @@ namespace build2 if (pt.target == nullptr || (s != nullptr && !pt.target->in (*s))) continue; - match_async (a, *pt.target, target::count_busy (), t[a].task_count); + match_async (a, *pt.target, t.ctx.count_busy (), t[a].task_count); pts.push_back (move (pt)); } @@ -850,7 +862,7 @@ namespace build2 // Pretty much identical to match_prerequisite_range() except we don't // search. // - wait_guard wg (target::count_busy (), t[a].task_count, true); + wait_guard wg (t.ctx, t.ctx.count_busy (), t[a].task_count, true); for (size_t i (0); i != n; ++i) { @@ -859,7 +871,7 @@ namespace build2 if (m == nullptr || marked (m)) continue; - match_async (a, *m, target::count_busy (), t[a].task_count); + match_async (a, *m, t.ctx.count_busy (), t[a].task_count); } wg.wait (); @@ -897,7 +909,7 @@ namespace build2 // const dir_path& d (parent && t.name.empty () ? t.dir.directory () : t.dir); - const scope& bs (scopes.find (d)); + const scope& bs (t.ctx.scopes.find (d)); const scope* rs (bs.root_scope ()); // If root scope is NULL, then this can mean that we are out of any @@ -973,7 +985,7 @@ namespace build2 if (op_t != nullptr) { - op_s = &scopes.find (t.dir); + op_s = &t.ctx.scopes.find (t.dir); if (op_s->out_path () == t.dir && !op_s->operation_callbacks.empty ()) { @@ -1088,11 +1100,12 @@ namespace build2 if (!exists (d)) mkdir_p (d, 2 /* verbosity */); - update_backlink (p, l, m); + update_backlink (f.ctx, p, l, m); } void - update_backlink (const path& p, const path& l, bool changed, backlink_mode m) + update_backlink (context& ctx, + const path& p, const path& l, bool changed, backlink_mode m) { // As above but with a slightly different diagnostics. @@ -1126,7 +1139,7 @@ namespace build2 if (!exists (d)) mkdir_p (d, 2 /* verbosity */); - update_backlink (p, l, m); + update_backlink (ctx, p, l, m); } static inline void @@ -1165,7 +1178,8 @@ namespace build2 } void - update_backlink (const path& p, const path& l, backlink_mode om) + update_backlink (context& ctx, + const path& p, const path& l, backlink_mode om) { using mode = backlink_mode; @@ -1196,7 +1210,7 @@ namespace build2 { // Normally will be there. // - if (!dry_run) + if (!ctx.dry_run) try_rmbacklink (l, m); // Skip (ad hoc) targets that don't exist. @@ -1240,7 +1254,7 @@ namespace build2 path f (fr / de.path ()); path t (to / de.path ()); - update_backlink (f, t, mode::link); + update_backlink (ctx, f, t, mode::link); } } else @@ -1281,7 +1295,8 @@ namespace build2 } void - clean_backlink (const path& l, uint16_t v /*verbosity*/, backlink_mode m) + clean_backlink (context& ctx, + const path& l, uint16_t v /*verbosity*/, backlink_mode m) { // Like try_rmbacklink() but with diagnostics and error handling. @@ -1293,9 +1308,9 @@ namespace build2 { case mode::link: case mode::symbolic: - case mode::hard: rmsymlink (l, true /* directory */, v); break; - case mode::copy: rmdir_r (path_cast (l), true, v); break; - case mode::overwrite: break; + case mode::hard: rmsymlink (ctx, l, true /* directory */, v); break; + case mode::copy: rmdir_r (ctx, path_cast (l), true, v); break; + case mode::overwrite: break; } } else @@ -1307,8 +1322,8 @@ namespace build2 case mode::link: case mode::symbolic: case mode::hard: - case mode::copy: rmfile (l, v); break; - case mode::overwrite: break; + case mode::copy: rmfile (ctx, l, v); break; + case mode::overwrite: break; } } } @@ -1370,6 +1385,8 @@ namespace build2 static optional backlink_test (action a, target& t) { + context& ctx (t.ctx); + // Note: the order of these checks is from the least to most expensive. // Only for plain update/clean. @@ -1391,17 +1408,17 @@ namespace build2 // Only for forwarded configurations. // - if (!cast_false (rs->vars[var_forwarded])) + if (!cast_false (rs->vars[ctx.var_forwarded])) return nullopt; - lookup l (t.state[a][var_backlink]); + lookup l (t.state[a][ctx.var_backlink]); // If not found, check for some defaults in the global scope (this does // not happen automatically since target type/pattern-specific lookup // stops at the project boundary). // if (!l.defined ()) - l = global_scope->find (*var_backlink, t.key ()); + l = ctx.global_scope.find (*ctx.var_backlink, t.key ()); return l ? backlink_test (t, l) : nullopt; } @@ -1452,7 +1469,7 @@ namespace build2 // as a target-specific wouldn't be MT-safe). @@ Don't think this // applies to declared ad hoc members. // - lookup l (mt->state[a].vars[var_backlink]); + lookup l (mt->state[a].vars[t.ctx.var_backlink]); optional bm (l ? backlink_test (*mt, l) : m); @@ -1488,7 +1505,7 @@ namespace build2 ts == target_state::changed, bl.mode); else - update_backlink (bl.target, bl.path, bl.mode); + update_backlink (t.ctx, bl.target, bl.path, bl.mode); } // Cancel removal. @@ -1508,16 +1525,18 @@ namespace build2 // backlink& bl (*i); bl.cancel (); - clean_backlink (bl.path, i == b ? 2 : 3 /* verbosity */, bl.mode); + clean_backlink (t.ctx, bl.path, i == b ? 2 : 3 /* verbosity */, bl.mode); } } static target_state execute_impl (action a, target& t) { + context& ctx (t.ctx); + target::opstate& s (t[a]); - assert (s.task_count.load (memory_order_consume) == target::count_busy () + assert (s.task_count.load (memory_order_consume) == t.ctx.count_busy () && s.state == target_state::unknown); target_state ts; @@ -1562,7 +1581,7 @@ namespace build2 { recipe_function** f (s.recipe.target ()); if (f == nullptr || *f != &group_action) - target_count.fetch_sub (1, memory_order_relaxed); + ctx.target_count.fetch_sub (1, memory_order_relaxed); } // Decrement the task count (to count_executed) and wake up any threads @@ -1571,8 +1590,8 @@ namespace build2 size_t tc (s.task_count.fetch_sub ( target::offset_busy - target::offset_executed, memory_order_release)); - assert (tc == target::count_busy ()); - sched.resume (s.task_count); + assert (tc == ctx.count_busy ()); + ctx.sched.resume (s.task_count); return ts; } @@ -1586,9 +1605,11 @@ namespace build2 target& t (const_cast (ct)); // MT-aware. target::opstate& s (t[a]); + context& ctx (t.ctx); + // Update dependency counts and make sure they are not skew. // - size_t gd (dependency_count.fetch_sub (1, memory_order_relaxed)); + size_t gd (ctx.dependency_count.fetch_sub (1, memory_order_relaxed)); size_t td (s.dependents.fetch_sub (1, memory_order_release)); assert (td != 0 && gd != 0); td--; @@ -1614,15 +1635,15 @@ namespace build2 // thread. For other threads the state will still be unknown (until they // try to execute it). // - if (current_mode == execution_mode::last && td != 0) + if (ctx.current_mode == execution_mode::last && td != 0) return target_state::postponed; // Try to atomically change applied to busy. // - size_t tc (target::count_applied ()); + size_t tc (ctx.count_applied ()); - size_t exec (target::count_executed ()); - size_t busy (target::count_busy ()); + size_t exec (ctx.count_executed ()); + size_t busy (ctx.count_busy ()); if (s.task_count.compare_exchange_strong ( tc, @@ -1640,7 +1661,7 @@ namespace build2 execute_recipe (a, t, nullptr /* recipe */); s.task_count.store (exec, memory_order_release); - sched.resume (s.task_count); + ctx.sched.resume (s.task_count); } else { @@ -1650,15 +1671,15 @@ namespace build2 // Pass our diagnostics stack (this is safe since we expect the // caller to wait for completion before unwinding its diag stack). // - if (sched.async (start_count, - *task_count, - [a] (const diag_frame* ds, target& t) - { - diag_frame::stack_guard dsg (ds); - execute_impl (a, t); - }, - diag_frame::stack (), - ref (t))) + if (ctx.sched.async (start_count, + *task_count, + [a] (const diag_frame* ds, target& t) + { + diag_frame::stack_guard dsg (ds); + execute_impl (a, t); + }, + diag_frame::stack (), + ref (t))) return target_state::unknown; // Queued. // Executed synchronously, fall through. @@ -1678,15 +1699,17 @@ namespace build2 target_state execute_direct (action a, const target& ct) { + context& ctx (ct.ctx); + target& t (const_cast (ct)); // MT-aware. target::opstate& s (t[a]); // Similar logic to match() above except we execute synchronously. // - size_t tc (target::count_applied ()); + size_t tc (ctx.count_applied ()); - size_t exec (target::count_executed ()); - size_t busy (target::count_busy ()); + size_t exec (ctx.count_executed ()); + size_t busy (ctx.count_busy ()); if (s.task_count.compare_exchange_strong ( tc, @@ -1708,15 +1731,17 @@ namespace build2 } s.task_count.store (exec, memory_order_release); - sched.resume (s.task_count); + ctx.sched.resume (s.task_count); } } else { // If the target is busy, wait for it. // - if (tc >= busy) sched.wait (exec, s.task_count, scheduler::work_none); - else assert (tc == exec); + if (tc >= busy) + ctx.sched.wait (exec, s.task_count, scheduler::work_none); + else + assert (tc == exec); } return t.executed_state (a); @@ -1736,14 +1761,17 @@ namespace build2 template target_state - straight_execute_members (action a, atomic_count& tc, + straight_execute_members (context& ctx, action a, atomic_count& tc, T ts[], size_t n, size_t p) { target_state r (target_state::unchanged); + size_t busy (ctx.count_busy ()); + size_t exec (ctx.count_executed ()); + // Start asynchronous execution of prerequisites. // - wait_guard wg (target::count_busy (), tc); + wait_guard wg (ctx, busy, tc); n += p; for (size_t i (p); i != n; ++i) @@ -1753,7 +1781,7 @@ namespace build2 if (mt == nullptr) // Skipped. continue; - target_state s (execute_async (a, *mt, target::count_busy (), tc)); + target_state s (execute_async (a, *mt, busy, tc)); if (s == target_state::postponed) { @@ -1778,8 +1806,8 @@ namespace build2 // If the target is still busy, wait for its completion. // const auto& tc (mt[a].task_count); - if (tc.load (memory_order_acquire) >= target::count_busy ()) - sched.wait (target::count_executed (), tc, scheduler::work_none); + if (tc.load (memory_order_acquire) >= busy) + ctx.sched.wait (exec, tc, scheduler::work_none); r |= mt.executed_state (a); @@ -1791,14 +1819,17 @@ namespace build2 template target_state - reverse_execute_members (action a, atomic_count& tc, + reverse_execute_members (context& ctx, action a, atomic_count& tc, T ts[], size_t n, size_t p) { // Pretty much as straight_execute_members() but in reverse order. // target_state r (target_state::unchanged); - wait_guard wg (target::count_busy (), tc); + size_t busy (ctx.count_busy ()); + size_t exec (ctx.count_executed ()); + + wait_guard wg (ctx, busy, tc); n = p - n; for (size_t i (p); i != n; ) @@ -1808,7 +1839,7 @@ namespace build2 if (mt == nullptr) continue; - target_state s (execute_async (a, *mt, target::count_busy (), tc)); + target_state s (execute_async (a, *mt, busy, tc)); if (s == target_state::postponed) { @@ -1827,8 +1858,8 @@ namespace build2 const target& mt (*ts[i]); const auto& tc (mt[a].task_count); - if (tc.load (memory_order_acquire) >= target::count_busy ()) - sched.wait (target::count_executed (), tc, scheduler::work_none); + if (tc.load (memory_order_acquire) >= busy) + ctx.sched.wait (exec, tc, scheduler::work_none); r |= mt.executed_state (a); @@ -1842,19 +1873,19 @@ namespace build2 // template LIBBUILD2_SYMEXPORT target_state straight_execute_members ( - action, atomic_count&, const target*[], size_t, size_t); + context&, action, atomic_count&, const target*[], size_t, size_t); template LIBBUILD2_SYMEXPORT target_state reverse_execute_members ( - action, atomic_count&, const target*[], size_t, size_t); + context&, action, atomic_count&, const target*[], size_t, size_t); template LIBBUILD2_SYMEXPORT target_state straight_execute_members ( - action, atomic_count&, prerequisite_target[], size_t, size_t); + context&, action, atomic_count&, prerequisite_target[], size_t, size_t); template LIBBUILD2_SYMEXPORT target_state reverse_execute_members ( - action, atomic_count&, prerequisite_target[], size_t, size_t); + context&, action, atomic_count&, prerequisite_target[], size_t, size_t); pair, const target*> execute_prerequisites (const target_type* tt, @@ -1862,7 +1893,12 @@ namespace build2 const timestamp& mt, const execute_filter& ef, size_t n) { - assert (current_mode == execution_mode::first); + context& ctx (t.ctx); + + assert (ctx.current_mode == execution_mode::first); + + size_t busy (ctx.count_busy ()); + size_t exec (ctx.count_executed ()); auto& pts (t.prerequisite_targets[a]); @@ -1873,7 +1909,7 @@ namespace build2 // target_state rs (target_state::unchanged); - wait_guard wg (target::count_busy (), t[a].task_count); + wait_guard wg (ctx, busy, t[a].task_count); for (size_t i (0); i != n; ++i) { @@ -1882,9 +1918,7 @@ namespace build2 if (pt == nullptr) // Skipped. continue; - target_state s ( - execute_async ( - a, *pt, target::count_busy (), t[a].task_count)); + target_state s (execute_async (a, *pt, busy, t[a].task_count)); if (s == target_state::postponed) { @@ -1908,8 +1942,8 @@ namespace build2 const target& pt (*p.target); const auto& tc (pt[a].task_count); - if (tc.load (memory_order_acquire) >= target::count_busy ()) - sched.wait (target::count_executed (), tc, scheduler::work_none); + if (tc.load (memory_order_acquire) >= busy) + ctx.sched.wait (exec, tc, scheduler::work_none); target_state s (pt.executed_state (a)); rs |= s; @@ -1966,6 +2000,8 @@ namespace build2 target_state group_action (action a, const target& t) { + context& ctx (t.ctx); + // If the group is busy, we wait, similar to prerequisites. // const target& g (*t.group); @@ -1973,9 +2009,9 @@ namespace build2 target_state gs (execute (a, g)); if (gs == target_state::busy) - sched.wait (target::count_executed (), - g[a].task_count, - scheduler::work_none); + ctx.sched.wait (ctx.count_executed (), + g[a].task_count, + scheduler::work_none); // Return target_state::group to signal to execute() that this target's // state comes from the group (which, BTW, can be failed). @@ -2016,9 +2052,11 @@ namespace build2 bool ed (false); path ep; - auto clean_extra = [&er, &ed, &ep] (const file& f, - const path* fp, - const clean_extras& es) + context& ctx (ft.ctx); + + auto clean_extra = [&er, &ed, &ep, &ctx] (const file& f, + const path* fp, + const clean_extras& es) { for (const char* e: es) { @@ -2058,7 +2096,7 @@ namespace build2 { dir_path dp (path_cast (p)); - switch (build2::rmdir_r (dp, true, 3)) + switch (rmdir_r (ctx, dp, true, 3)) { case rmdir_status::success: { @@ -2077,7 +2115,7 @@ namespace build2 } else { - if (rmfile (p, 3)) + if (rmfile (ctx, p, 3)) r = target_state::changed; } @@ -2105,7 +2143,7 @@ namespace build2 // depdb so for now we treat them as "to remove" but in the future we may // need to have two lists. // - bool clean (cast_true (ft[var_clean])); + bool clean (cast_true (ft[ctx.var_clean])); // Now clean the ad hoc group file members, if any. // @@ -2143,7 +2181,7 @@ namespace build2 } else { - target_state r (rmfile (*mp, 3) + target_state r (rmfile (ctx, *mp, 3) ? target_state::changed : target_state::unchanged); @@ -2180,7 +2218,7 @@ namespace build2 // if (tr != target_state::changed && er == target_state::changed) { - if (verb > (current_diag_noise ? 0 : 1) && verb < 3) + if (verb > (ctx.current_diag_noise ? 0 : 1) && verb < 3) { if (ed) text << "rm -r " << path_cast (ep); @@ -2218,7 +2256,7 @@ namespace build2 // target_state r (target_state::unchanged); - if (cast_true (g[var_clean])) + if (cast_true (g[g.ctx.var_clean])) { for (group_view gv (g.group_members (a)); gv.count != 0; --gv.count) { @@ -2239,6 +2277,8 @@ namespace build2 target_state perform_clean_group_depdb (action a, const target& g) { + context& ctx (g.ctx); + // The same twisted target state merging logic as in perform_clean_extra(). // target_state er (target_state::unchanged); @@ -2249,7 +2289,7 @@ namespace build2 { ep = gv.members[0]->as ().path () + ".d"; - if (rmfile (ep, 3)) + if (rmfile (ctx, ep, 3)) er = target_state::changed; } @@ -2257,7 +2297,7 @@ namespace build2 if (tr != target_state::changed && er == target_state::changed) { - if (verb > (current_diag_noise ? 0 : 1) && verb < 3) + if (verb > (ctx.current_diag_noise ? 0 : 1) && verb < 3) text << "rm " << ep; } diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index 4707ae7..cda464b 100644 --- a/libbuild2/algorithm.hxx +++ b/libbuild2/algorithm.hxx @@ -16,6 +16,7 @@ namespace build2 { class scope; + class context; class prerequisite; class prerequisite_key; @@ -42,7 +43,7 @@ namespace build2 search (const target&, const prerequisite_key&); LIBBUILD2_SYMEXPORT const target* - search_existing (const prerequisite_key&); + search_existing (context&, const prerequisite_key&); // Uniform search interface for prerequisite/prerequisite_member. // @@ -61,7 +62,7 @@ namespace build2 // const target& search (const target&, - const target_type& type, + const target_type&, const dir_path& dir, const dir_path& out, const string& name, @@ -70,7 +71,8 @@ namespace build2 const optional& proj = nullopt); const target* - search_existing (const target_type& type, + search_existing (context&, + const target_type&, const dir_path& dir, const dir_path& out, const string& name, @@ -611,18 +613,20 @@ namespace build2 // template target_state - straight_execute_members (action, atomic_count&, T[], size_t, size_t); + straight_execute_members (context&, action, atomic_count&, + T[], size_t, size_t); template target_state - reverse_execute_members (action, atomic_count&, T[], size_t, size_t); + reverse_execute_members (context&, action, atomic_count&, + T[], size_t, size_t); template inline target_state straight_execute_members (action a, const target& t, T ts[], size_t c, size_t s) { - return straight_execute_members (a, t[a].task_count, ts, c, s); + return straight_execute_members (t.ctx, a, t[a].task_count, ts, c, s); } template @@ -630,7 +634,7 @@ namespace build2 reverse_execute_members (action a, const target& t, T ts[], size_t c, size_t s) { - return reverse_execute_members (a, t[a].task_count, ts, c, s); + return reverse_execute_members (t.ctx, a, t[a].task_count, ts, c, s); } // Call straight or reverse depending on the current mode. @@ -757,18 +761,21 @@ namespace build2 backlink_mode = backlink_mode::link); LIBBUILD2_SYMEXPORT void - update_backlink (const path& target, + update_backlink (context&, + const path& target, const path& link, bool changed, backlink_mode = backlink_mode::link); LIBBUILD2_SYMEXPORT void - update_backlink (const path& target, + update_backlink (context&, + const path& target, const path& link, backlink_mode = backlink_mode::link); LIBBUILD2_SYMEXPORT void - clean_backlink (const path& link, + clean_backlink (context&, + const path& link, uint16_t verbosity, backlink_mode = backlink_mode::link); } diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx index 6bc771e..9593ac0 100644 --- a/libbuild2/algorithm.ixx +++ b/libbuild2/algorithm.ixx @@ -13,7 +13,8 @@ namespace build2 inline const target& search_custom (const prerequisite& p, const target& t) { - assert (phase == run_phase::match || phase == run_phase::execute); + assert (t.ctx.phase == run_phase::match || + t.ctx.phase == run_phase::execute); const target* e (nullptr); if (!p.target.compare_exchange_strong ( @@ -57,7 +58,8 @@ namespace build2 } inline const target* - search_existing (const target_type& type, + search_existing (context& ctx, + const target_type& type, const dir_path& dir, const dir_path& out, const string& name, @@ -66,6 +68,7 @@ namespace build2 const optional& proj) { return search_existing ( + ctx, prerequisite_key { proj, { @@ -272,14 +275,14 @@ namespace build2 inline void match_inc_dependens (action a, const target& t) { - dependency_count.fetch_add (1, memory_order_relaxed); + t.ctx.dependency_count.fetch_add (1, memory_order_relaxed); t[a].dependents.fetch_add (1, memory_order_release); } inline target_state match (action a, const target& t, bool fail) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); target_state r (match (a, t, 0, nullptr).second); @@ -294,7 +297,7 @@ namespace build2 inline pair try_match (action a, const target& t, bool fail) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); pair r ( match (a, t, 0, nullptr, true /* try_match */)); @@ -313,7 +316,7 @@ namespace build2 inline bool match (action a, const target& t, unmatch um) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); target_state s (match (a, t, 0, nullptr).second); @@ -353,10 +356,12 @@ namespace build2 size_t sc, atomic_count& tc, bool fail) { - assert (phase == run_phase::match); + context& ctx (t.ctx); + + assert (ctx.phase == run_phase::match); target_state r (match (a, t, sc, &tc).second); - if (fail && !keep_going && r == target_state::failed) + if (fail && !ctx.keep_going && r == target_state::failed) throw failed (); return r; @@ -365,7 +370,8 @@ namespace build2 inline void set_recipe (target_lock& l, recipe&& r) { - target::opstate& s ((*l.target)[l.action]); + target& t (*l.target); + target::opstate& s (t[l.action]); s.recipe = move (r); @@ -396,7 +402,7 @@ namespace build2 if (l.action.inner ()) { if (f == nullptr || *f != &group_action) - target_count.fetch_add (1, memory_order_relaxed); + t.ctx.target_count.fetch_add (1, memory_order_relaxed); } } } @@ -404,7 +410,7 @@ namespace build2 inline void match_recipe (target_lock& l, recipe r) { - assert (phase == run_phase::match && l.target != nullptr); + assert (l.target != nullptr && l.target->ctx.phase == run_phase::match); (*l.target)[l.action].rule = nullptr; // No rule. set_recipe (l, move (r)); @@ -414,7 +420,7 @@ namespace build2 inline recipe match_delegate (action a, target& t, const rule& dr, bool try_match) { - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); // Note: we don't touch any of the t[a] state since that was/will be set // for the delegating rule. @@ -448,7 +454,7 @@ namespace build2 if (a.outer ()) a = a.inner_action (); - switch (phase) + switch (t.ctx.phase) { case run_phase::match: { @@ -541,9 +547,9 @@ namespace build2 execute_wait (action a, const target& t) { if (execute (a, t) == target_state::busy) - sched.wait (target::count_executed (), - t[a].task_count, - scheduler::work_none); + t.ctx.sched.wait (t.ctx.count_executed (), + t[a].task_count, + scheduler::work_none); return t.executed_state (a); } @@ -555,7 +561,7 @@ namespace build2 { target_state r (execute (a, t, sc, &tc)); - if (fail && !keep_going && r == target_state::failed) + if (fail && !t.ctx.keep_going && r == target_state::failed) throw failed (); return r; @@ -598,7 +604,7 @@ namespace build2 inline target_state execute_prerequisites (action a, const target& t, size_t c) { - return current_mode == execution_mode::first + return t.ctx.current_mode == execution_mode::first ? straight_execute_prerequisites (a, t, c) : reverse_execute_prerequisites (a, t, c); } @@ -609,7 +615,8 @@ namespace build2 { assert (a.outer ()); auto& p (t.prerequisite_targets[a]); - return straight_execute_members (a.inner_action (), + return straight_execute_members (t.ctx, + a.inner_action (), t[a].task_count, p.data (), c == 0 ? p.size () - s : c, @@ -621,7 +628,8 @@ namespace build2 { assert (a.outer ()); auto& p (t.prerequisite_targets[a]); - return reverse_execute_members (a.inner_action (), + return reverse_execute_members (t.ctx, + a.inner_action (), t[a].task_count, p.data (), c == 0 ? p.size () : c, @@ -631,7 +639,7 @@ namespace build2 inline target_state execute_prerequisites_inner (action a, const target& t, size_t c) { - return current_mode == execution_mode::first + return t.ctx.current_mode == execution_mode::first ? straight_execute_prerequisites_inner (a, t, c) : reverse_execute_prerequisites_inner (a, t, c); } @@ -689,7 +697,7 @@ namespace build2 inline target_state execute_members (action a, const target& t, const target* ts[], size_t n) { - return current_mode == execution_mode::first + return t.ctx.current_mode == execution_mode::first ? straight_execute_members (a, t, ts, n, 0) : reverse_execute_members (a, t, ts, n, n); } diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx index d9bf857..2f2de2d 100644 --- a/libbuild2/bash/rule.cxx +++ b/libbuild2/bash/rule.cxx @@ -157,13 +157,13 @@ namespace build2 if (mt != timestamp_nonexistent) { - auto rp (targets.insert_locked (bash::static_type, - ap.directory (), - dir_path () /* out */, - p.name, - ext, - true /* implied */, - trace)); + auto rp (t.ctx.targets.insert_locked (bash::static_type, + ap.directory (), + dir_path () /* out */, + p.name, + ext, + true /* implied */, + trace)); bash& pt (rp.first.as ()); @@ -281,7 +281,7 @@ namespace build2 continue; } - if (const scope* rs = scopes.find (b->dir).root_scope ()) + if (const scope* rs = t.ctx.scopes.find (b->dir).root_scope ()) { const dir_path& d (pp.sub (rs->src_path ()) ? rs->src_path () diff --git a/libbuild2/bash/target.cxx b/libbuild2/bash/target.cxx index 7313316..386842b 100644 --- a/libbuild2/bash/target.cxx +++ b/libbuild2/bash/target.cxx @@ -20,8 +20,8 @@ namespace build2 &file::static_type, &target_factory, nullptr, /* fixed_extension */ - &target_extension_var, - &target_pattern_var, + &target_extension_var, + &target_pattern_var, nullptr, &file_search, false diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index b790569..6998017 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -29,8 +29,8 @@ namespace build2 l5 ([&]{trace << "for " << rs;}); - const string& mname (current_mname); - const string& oname (current_oname); + const string& mname (rs.ctx.current_mname); + const string& oname (rs.ctx.current_oname); // Only create the module if we are configuring or creating. This is a // bit tricky since the build2 core may not yet know if this is the @@ -80,7 +80,7 @@ namespace build2 assert (config_hints.empty ()); // We don't known any hints. - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); // Load config.build if one exists (we don't need to worry about // disfigure since we will never be init'ed). @@ -103,7 +103,7 @@ namespace build2 { // Assume missing version is 0. // - auto p (extract_variable (f, c_v)); + auto p (extract_variable (rs.ctx, f, c_v)); uint64_t v (p.second ? cast (p.first) : 0); if (v != module::version) @@ -126,7 +126,7 @@ namespace build2 // global scope similar to builtin rules. // { - auto& r (rs.global ().rules); + auto& r (rs.global_scope ().rules); r.insert ( configure_id, 0, "config.file", file_rule::instance); } diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index c3ce4b7..264eb93 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -28,12 +28,12 @@ namespace build2 // configure // static void - save_src_root (const scope& root) + save_src_root (const scope& rs) { - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); - path f (out_root / root.root_extra->src_root_file); + path f (out_root / rs.root_extra->src_root_file); if (verb >= 2) text << "cat >" << f; @@ -57,12 +57,12 @@ namespace build2 } static void - save_out_root (const scope& root) + save_out_root (const scope& rs) { - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); - path f (src_root / root.root_extra->out_root_file); + path f (src_root / rs.root_extra->out_root_file); if (verb) text << (verb >= 2 ? "cat >" : "save ") << f; @@ -88,14 +88,16 @@ namespace build2 using project_set = set; // Use pointers to get comparison. static void - save_config (const scope& root, const project_set& projects) + save_config (const scope& rs, const project_set& projects) { - path f (config_file (root)); + context& ctx (rs.ctx); + + path f (config_file (rs)); if (verb) text << (verb >= 2 ? "cat >" : "save ") << f; - const module& mod (*root.lookup_module (module::name)); + const module& mod (*rs.lookup_module (module::name)); try { @@ -107,7 +109,7 @@ namespace build2 ofs << "config.version = " << module::version << endl; - if (auto l = root.vars[var_amalgamation]) + if (auto l = rs.vars[ctx.var_amalgamation]) { const dir_path& d (cast (l)); @@ -130,10 +132,10 @@ namespace build2 { const variable& var (sv.var); - pair org (root.find_original (var)); + pair org (rs.find_original (var)); pair ovr (var.overrides == nullptr ? org - : root.find_override (var, org)); + : rs.find_override (var, org)); const lookup& l (ovr.first); // We definitely write values that are set on our root scope or @@ -144,7 +146,7 @@ namespace build2 if (!l.defined ()) continue; - if (!(l.belongs (root) || l.belongs (*global_scope))) + if (!(l.belongs (rs) || l.belongs (ctx.global_scope))) { // This is presumably an inherited value. But it could also be // some left-over garbage. For example, an amalgamation could @@ -162,7 +164,7 @@ namespace build2 // root. // bool found (false); - const scope* r (&root); + const scope* r (&rs); while ((r = r->parent_scope ()->root_scope ()) != nullptr) { if (l.belongs (*r)) @@ -307,14 +309,16 @@ namespace build2 } static void - configure_project (action a, const scope& root, project_set& projects) + configure_project (action a, const scope& rs, project_set& projects) { tracer trace ("configure_project"); - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + context& ctx (rs.ctx); + + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); - if (!projects.insert (&root).second) + if (!projects.insert (&rs).second) { l5 ([&]{trace << "skipping already configured " << out_root;}); return; @@ -324,8 +328,8 @@ namespace build2 // if (out_root != src_root) { - mkdir_p (out_root / root.root_extra->build_dir); - mkdir (out_root / root.root_extra->bootstrap_dir, 2); + mkdir_p (out_root / rs.root_extra->build_dir); + mkdir (out_root / rs.root_extra->bootstrap_dir, 2); } // We distinguish between a complete configure and operation- @@ -338,11 +342,11 @@ namespace build2 // Save src-root.build unless out_root is the same as src. // if (out_root != src_root) - save_src_root (root); + save_src_root (rs); // Save config.build. // - save_config (root, projects); + save_config (rs, projects); } else { @@ -350,54 +354,56 @@ namespace build2 // Configure subprojects that have been loaded. // - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[ctx.var_subprojects]) { for (auto p: cast (l)) { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); - const scope& nroot (scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find (out_nroot)); // @@ Strictly speaking we need to check whether the config // module was loaded for this subproject. // - if (nroot.out_path () != out_nroot) // This subproject not loaded. + if (nrs.out_path () != out_nroot) // This subproject not loaded. continue; - configure_project (a, nroot, projects); + configure_project (a, nrs, projects); } } } static void - configure_forward (const scope& root, project_set& projects) + configure_forward (const scope& rs, project_set& projects) { tracer trace ("configure_forward"); - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + context& ctx (rs.ctx); - if (!projects.insert (&root).second) + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); + + if (!projects.insert (&rs).second) { l5 ([&]{trace << "skipping already configured " << src_root;}); return; } - mkdir (src_root / root.root_extra->bootstrap_dir, 2); // Make sure exists. - save_out_root (root); + mkdir (src_root / rs.root_extra->bootstrap_dir, 2); // Make sure exists. + save_out_root (rs); // Configure subprojects. Since we don't load buildfiles if configuring // a forward, we do it for all known subprojects. // - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[ctx.var_subprojects]) { for (auto p: cast (l)) { dir_path out_nroot (out_root / p.second); - const scope& nroot (scopes.find (out_nroot)); - assert (nroot.out_path () == out_nroot); + const scope& nrs (ctx.scopes.find (out_nroot)); + assert (nrs.out_path () == out_nroot); - configure_forward (nroot, projects); + configure_forward (nrs, projects); } } } @@ -451,7 +457,7 @@ namespace build2 static void configure_load (const values& params, - scope& root, + scope& rs, const path& buildfile, const dir_path& out_base, const dir_path& src_base, @@ -463,19 +469,19 @@ namespace build2 // forwarding but in order to configure subprojects we have to // bootstrap them (similar to disfigure). // - create_bootstrap_inner (root); + create_bootstrap_inner (rs); - if (root.out_path () == root.src_path ()) - fail (l) << "forwarding to source directory " << root.src_path (); + if (rs.out_path () == rs.src_path ()) + fail (l) << "forwarding to source directory " << rs.src_path (); } else - load (params, root, buildfile, out_base, src_base, l); // Normal load. + load (params, rs, buildfile, out_base, src_base, l); // Normal load. } static void configure_search (const values& params, - const scope& root, - const scope& base, + const scope& rs, + const scope& bs, const path& bf, const target_key& tk, const location& l, @@ -486,10 +492,10 @@ namespace build2 // For forwarding we only collect the projects (again, similar to // disfigure). // - ts.push_back (&root); + ts.push_back (&rs); } else - search (params, root, base, bf, tk, l, ts); // Normal search. + search (params, rs, bs, bf, tk, l, ts); // Normal search. } static void @@ -515,8 +521,8 @@ namespace build2 { // Forward configuration. // - const scope& root (*static_cast (at.target)); - configure_forward (root, projects); + const scope& rs (*static_cast (at.target)); + configure_forward (rs, projects); continue; } @@ -539,6 +545,8 @@ namespace build2 if (rs == nullptr) fail << "out of project target " << t; + context& ctx (t.ctx); + const operations& ops (rs->root_extra->operations); for (operation_id id (default_id + 1); // Skip default_id. @@ -552,9 +560,9 @@ namespace build2 if (oif->id != id) continue; - set_current_oif (*oif); + ctx.current_operation (*oif); - phase_lock pl (run_phase::match); + phase_lock pl (ctx, run_phase::match); match (action (configure_id, id), t); } } @@ -586,14 +594,16 @@ namespace build2 // static bool - disfigure_project (action a, const scope& root, project_set& projects) + disfigure_project (action a, const scope& rs, project_set& projects) { tracer trace ("disfigure_project"); - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + context& ctx (rs.ctx); + + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); - if (!projects.insert (&root).second) + if (!projects.insert (&rs).second) { l5 ([&]{trace << "skipping already disfigured " << out_root;}); return false; @@ -604,16 +614,16 @@ namespace build2 // Disfigure subprojects. Since we don't load buildfiles during // disfigure, we do it for all known subprojects. // - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[ctx.var_subprojects]) { for (auto p: cast (l)) { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); - const scope& nroot (scopes.find (out_nroot)); - assert (nroot.out_path () == out_nroot); // See disfigure_load(). + const scope& nrs (ctx.scopes.find (out_nroot)); + assert (nrs.out_path () == out_nroot); // See disfigure_load(). - r = disfigure_project (a, nroot, projects) || r; + r = disfigure_project (a, nrs, projects) || r; // We use mkdir_p() to create the out_root of a subproject // which means there could be empty parent directories left @@ -625,7 +635,7 @@ namespace build2 !d.empty (); d = d.directory ()) { - rmdir_status s (rmdir (out_root / d, 2)); + rmdir_status s (rmdir (ctx, out_root / d, 2)); if (s == rmdir_status::not_empty) break; // No use trying do remove parent ones. @@ -643,21 +653,21 @@ namespace build2 { l5 ([&]{trace << "completely disfiguring " << out_root;}); - r = rmfile (config_file (root)) || r; + r = rmfile (ctx, config_file (rs)) || r; if (out_root != src_root) { - r = rmfile (out_root / root.root_extra->src_root_file, 2) || r; + r = rmfile (ctx, out_root / rs.root_extra->src_root_file, 2) || r; // Clean up the directories. // // Note: try to remove the root/ hooks directory if it is empty. // - r = rmdir (out_root / root.root_extra->root_dir, 2) || r; - r = rmdir (out_root / root.root_extra->bootstrap_dir, 2) || r; - r = rmdir (out_root / root.root_extra->build_dir, 2) || r; + r = rmdir (ctx, out_root / rs.root_extra->root_dir, 2) || r; + r = rmdir (ctx, out_root / rs.root_extra->bootstrap_dir, 2) || r; + r = rmdir (ctx, out_root / rs.root_extra->build_dir, 2) || r; - switch (rmdir (out_root)) + switch (rmdir (ctx, out_root)) { case rmdir_status::not_empty: { @@ -687,16 +697,18 @@ namespace build2 } static bool - disfigure_forward (const scope& root, project_set& projects) + disfigure_forward (const scope& rs, project_set& projects) { // Pretty similar logic to disfigure_project(). // tracer trace ("disfigure_forward"); - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + context& ctx (rs.ctx); + + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); - if (!projects.insert (&root).second) + if (!projects.insert (&rs).second) { l5 ([&]{trace << "skipping already disfigured " << src_root;}); return false; @@ -704,23 +716,23 @@ namespace build2 bool r (false); - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[ctx.var_subprojects]) { for (auto p: cast (l)) { dir_path out_nroot (out_root / p.second); - const scope& nroot (scopes.find (out_nroot)); - assert (nroot.out_path () == out_nroot); + const scope& nrs (ctx.scopes.find (out_nroot)); + assert (nrs.out_path () == out_nroot); - r = disfigure_forward (nroot, projects) || r; + r = disfigure_forward (nrs, projects) || r; } } // Remove the out-root.build file and try to remove the bootstrap/ // directory if it is empty. // - r = rmfile (src_root / root.root_extra->out_root_file) || r; - r = rmdir (src_root / root.root_extra->bootstrap_dir, 2) || r; + r = rmfile (ctx, src_root / rs.root_extra->out_root_file) || r; + r = rmdir (ctx, src_root / rs.root_extra->bootstrap_dir, 2) || r; return r; } @@ -757,14 +769,14 @@ namespace build2 static void disfigure_search (const values&, - const scope& root, + const scope& rs, const scope&, const path&, const target_key&, const location&, action_targets& ts) { - ts.push_back (&root); + ts.push_back (&rs); } static void @@ -790,23 +802,23 @@ namespace build2 // for (const action_target& at: ts) { - const scope& root (*static_cast (at.target)); + const scope& rs (*static_cast (at.target)); if (!(fwd - ? disfigure_forward ( root, projects) - : disfigure_project (a, root, projects))) + ? disfigure_forward ( rs, projects) + : disfigure_project (a, rs, projects))) { // Create a dir{$out_root/} target to signify the project's root in // diagnostics. Not very clean but seems harmless. // target& t ( - targets.insert (dir::static_type, - fwd ? root.src_path () : root.out_path (), - dir_path (), // Out tree. - "", - nullopt, - true, // Implied. - trace).first); + rs.ctx.targets.insert (dir::static_type, + fwd ? rs.src_path () : rs.out_path (), + dir_path (), // Out tree. + "", + nullopt, + true, // Implied. + trace).first); if (verb != 0 && diag >= 2) info << diag_done (a, t); @@ -836,7 +848,7 @@ namespace build2 // create // static void - save_config (const dir_path& d, const variable_overrides& var_ovs) + save_config (context& ctx, const dir_path& d) { // Since there aren't any sub-projects yet, any config.import.* values // that the user may want to specify won't be saved in config.build. So @@ -846,13 +858,13 @@ namespace build2 // going to do is bootstrap the newly created project, similar to the // way main() does it. // - scope& gs (*scope::global_); + scope& gs (ctx.global_scope.rw ()); scope& rs (load_project (gs, d, d, false /* fwd */, false /* load */)); module& m (*rs.lookup_module (module::name)); // Save all the global config.import.* variables. // - variable_pool& vp (var_pool.rw (rs)); + variable_pool& vp (ctx.var_pool.rw (rs)); for (auto p (gs.vars.find_namespace (vp.insert ("config.import"))); p.first != p.second; ++p.first) @@ -869,7 +881,7 @@ namespace build2 // Now project-specific. For now we just save all of them and let // save_config() above weed out the ones that don't apply. // - for (const variable_override& vo: var_ovs) + for (const variable_override& vo: ctx.var_overrides) { const variable& var (vo.var); @@ -879,7 +891,7 @@ namespace build2 } const string& - preprocess_create (const variable_overrides& var_ovs, + preprocess_create (context& ctx, values& params, vector_view& spec, bool lifted, @@ -916,7 +928,7 @@ namespace build2 fail (l) << "invalid module name: " << e.what (); } - current_oname = empty_string; // Make sure valid. + ctx.current_oname = empty_string; // Make sure valid. // Now handle each target in each operation spec. // @@ -986,7 +998,7 @@ namespace build2 true, /* buildfile */ "the create meta-operation"); - save_config (d, var_ovs); + save_config (ctx, d); } } diff --git a/libbuild2/config/operation.hxx b/libbuild2/config/operation.hxx index 0a88f96..8b2a29d 100644 --- a/libbuild2/config/operation.hxx +++ b/libbuild2/config/operation.hxx @@ -18,7 +18,7 @@ namespace build2 extern const meta_operation_info mo_disfigure; const string& - preprocess_create (const variable_overrides&, + preprocess_create (context&, values&, vector_view&, bool, diff --git a/libbuild2/config/utility.cxx b/libbuild2/config/utility.cxx index 746639d..355e896 100644 --- a/libbuild2/config/utility.cxx +++ b/libbuild2/config/utility.cxx @@ -46,7 +46,7 @@ namespace build2 } } - if (l.defined () && current_mif->id == configure_id) + if (l.defined () && r.ctx.current_mif->id == configure_id) save_variable (r, var); return pair (l, n); @@ -55,7 +55,7 @@ namespace build2 lookup optional (scope& r, const variable& var) { - if (current_mif->id == configure_id) + if (r.ctx.current_mif->id == configure_id) save_variable (r, var); auto l (r[var]); @@ -75,7 +75,7 @@ namespace build2 // any original values, they will be "visible"; see find_override() for // details. // - const variable& vns (var_pool.rw (r).insert ("config." + n)); + const variable& vns (r.ctx.var_pool.rw (r).insert ("config." + n)); for (scope* s (&r); s != nullptr; s = s->parent_scope ()) { for (auto p (s->vars.find_namespace (vns)); @@ -101,9 +101,9 @@ namespace build2 // Pattern-typed in boot() as bool. // const variable& var ( - var_pool.rw (rs).insert ("config." + n + ".configured")); + rs.ctx.var_pool.rw (rs).insert ("config." + n + ".configured")); - if (current_mif->id == configure_id) + if (rs.ctx.current_mif->id == configure_id) save_variable (rs, var); auto l (rs[var]); // Include inherited values. @@ -116,9 +116,9 @@ namespace build2 // Pattern-typed in boot() as bool. // const variable& var ( - var_pool.rw (rs).insert ("config." + n + ".configured")); + rs.ctx.var_pool.rw (rs).insert ("config." + n + ".configured")); - if (current_mif->id == configure_id) + if (rs.ctx.current_mif->id == configure_id) save_variable (rs, var); value& x (rs.assign (var)); @@ -135,7 +135,7 @@ namespace build2 void save_variable (scope& r, const variable& var, uint64_t flags) { - if (current_mif->id != configure_id) + if (r.ctx.current_mif->id != configure_id) return; // The project might not be using the config module. But then how @@ -148,7 +148,7 @@ namespace build2 void save_module (scope& r, const char* name, int prio) { - if (current_mif->id != configure_id) + if (r.ctx.current_mif->id != configure_id) return; if (module* m = r.lookup_module (module::name)) diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx index a063693..b1995b6 100644 --- a/libbuild2/config/utility.hxx +++ b/libbuild2/config/utility.hxx @@ -58,7 +58,7 @@ namespace build2 uint64_t save_flags = 0) { return required ( - root, var_pool[name], default_value, override, save_flags); + root, root.ctx.var_pool[name], default_value, override, save_flags); } inline pair @@ -86,7 +86,7 @@ namespace build2 inline pair omitted (scope& root, const string& name) { - return omitted (root, var_pool[name]); + return omitted (root, root.ctx.var_pool[name]); } // Set, if necessary, an optional config.* variable. In particular, an @@ -105,7 +105,7 @@ namespace build2 inline lookup optional (scope& root, const string& name) { - return optional (root, var_pool[name]); + return optional (root, root.ctx.var_pool[name]); } // Check whether there are any variables specified from the config diff --git a/libbuild2/config/utility.txx b/libbuild2/config/utility.txx index 841c408..9c1455f 100644 --- a/libbuild2/config/utility.txx +++ b/libbuild2/config/utility.txx @@ -19,7 +19,7 @@ namespace build2 { // Note: see also omitted() if changing anything here. - if (current_mif->id == configure_id) + if (root.ctx.current_mif->id == configure_id) save_variable (root, var, save_flags); pair org (root.find_original (var)); 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; } diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index ce9a996..243ad2f 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -8,7 +8,11 @@ #include #include -#include +// NOTE: this file is included by pretty much every other build state header +// (scope, target, variable, etc) so including any of them here is most +// likely a non-starter. +// +#include #include #include @@ -16,71 +20,25 @@ namespace build2 { + class context; + class scope; + class scope_map; + class target_set; - // Main scheduler. Started up and shut down in main(). - // - LIBBUILD2_SYMEXPORT extern scheduler sched; + class value; + using values = small_vector; - // In order to perform each operation the build system goes through the - // following phases: - // - // load - load the buildfiles - // match - search prerequisites and match rules - // execute - execute the matched rule - // - // The build system starts with a "serial load" phase and then continues - // with parallel match and execute. Match, however, can be interrupted - // both with load and execute. - // - // Match can be interrupted with "exclusive load" in order to load - // additional buildfiles. Similarly, it can be interrupted with (parallel) - // execute in order to build targetd required to complete the match (for - // example, generated source code or source code generators themselves). - // - // Such interruptions are performed by phase change that is protected by - // phase_mutex (which is also used to synchronize the state changes between - // phases). - // - // Serial load can perform arbitrary changes to the build state. Exclusive - // load, however, can only perform "island appends". That is, it can create - // new "nodes" (variables, scopes, etc) but not (semantically) change - // already existing nodes or invalidate any references to such (the idea - // here is that one should be able to load additional buildfiles as long as - // they don't interfere with the existing build state). The "islands" are - // identified by the load_generation number (0 for the initial/serial - // load). It is incremented in case of a phase switch and can be stored in - // various "nodes" to verify modifications are only done "within the - // islands". - // - LIBBUILD2_SYMEXPORT extern run_phase phase; - LIBBUILD2_SYMEXPORT extern size_t load_generation; + struct variable; + class variable_pool; + struct variable_override; + using variable_overrides = vector; + class variable_override_cache; + + class function_map; + + struct opspec; - // A "tri-mutex" that keeps all the threads in one of the three phases. When - // a thread wants to switch a phase, it has to wait for all the other - // threads to do the same (or release their phase locks). The load phase is - // exclusive. - // - // The interleaving match and execute is interesting: during match we read - // the "external state" (e.g., filesystem entries, modifications times, etc) - // and capture it in the "internal state" (our dependency graph). During - // execute we are modifying the external state with controlled modifications - // of the internal state to reflect the changes (e.g., update mtimes). If - // you think about it, it's pretty clear that we cannot safely perform both - // of these actions simultaneously. A good example would be running a code - // generator and header dependency extraction simultaneously: the extraction - // process may pick up headers as they are being generated. As a result, we - // either have everyone treat the external state as read-only or write-only. - // - // There is also one more complication: if we are returning from a load - // phase that has failed, then the build state could be seriously messed up - // (things like scopes not being setup completely, etc). And once we release - // the lock, other threads that are waiting will start relying on this - // messed up state. So a load phase can mark the phase_mutex as failed in - // which case all currently blocked and future lock()/relock() calls return - // false. Note that in this case we still switch to the desired phase. See - // the phase_{lock,switch,unlock} implementations for details. - // class LIBBUILD2_SYMEXPORT run_phase_mutex { public: @@ -102,12 +60,11 @@ namespace build2 bool relock (run_phase unlock, run_phase lock); - public: - run_phase_mutex () - : fail_ (false), lc_ (0), mc_ (0), ec_ (0) - { - phase = run_phase::load; - } + private: + friend class context; + + run_phase_mutex (context& c) + : ctx_ (c), fail_ (false), lc_ (0), mc_ (0), ec_ (0) {} private: friend struct phase_lock; @@ -124,6 +81,8 @@ namespace build2 // When the mutex is unlocked (all three counters become zero, the phase // is always changed to load (this is also the initial state). // + context& ctx_; + mutex m_; bool fail_; @@ -139,7 +98,311 @@ namespace build2 mutex lm_; }; - extern run_phase_mutex phase_mutex; + // @@ CTX: document (backlinks, non-overlap etc). RW story. + // + class LIBBUILD2_SYMEXPORT context + { + struct data; + unique_ptr data_; + + public: + scheduler& sched; + + // Dry run flag (see --dry-run|-n). + // + // This flag is set (based on dry_run_option) only for the final execute + // phase (as opposed to those that interrupt match) by the perform meta + // operation's execute() callback. + // + // Note that for this mode to function properly we have to use fake + // mtimes. Specifically, a rule that pretends to update a target must set + // its mtime to system_clock::now() and everyone else must use this cached + // value. In other words, there should be no mtime re-query from the + // filesystem. The same is required for "logical clean" (i.e., dry-run + // 'clean update' in order to see all the command lines). + // + // At first, it may seem like we should also "dry-run" changes to depdb. + // But that would be both problematic (some rules update it in apply() + // during the match phase) and wasteful (why discard information). Also, + // depdb may serve as an input to some commands (for example, to provide + // C++ module mapping) which means that without updating it the commands + // we print might not be runnable (think of the compilation database). + // + // One thing we need to be careful about if we are updating depdb is to + // not render the target up-to-date. But in this case the depdb file will + // be older than the target which in our model is treated as an + // interrupted update (see depdb for details). + // + // Note also that sometimes it makes sense to do a bit more than + // absolutely necessary or to discard information in order to keep the + // rule logic sane. And some rules may choose to ignore this flag + // altogether. In this case, however, the rule should be careful not to + // rely on functions (notably from filesystem) that respect this flag in + // order not to end up with a job half done. + // + bool dry_run = false; + bool dry_run_option; + + // Keep going flag. + // + // Note that setting it to false is not of much help unless we are running + // serially: in parallel we queue most of the things up before we see any + // failures. + // + bool keep_going; + + // In order to perform each operation the build system goes through the + // following phases: + // + // load - load the buildfiles + // match - search prerequisites and match rules + // execute - execute the matched rule + // + // The build system starts with a "serial load" phase and then continues + // with parallel match and execute. Match, however, can be interrupted + // both with load and execute. + // + // Match can be interrupted with "exclusive load" in order to load + // additional buildfiles. Similarly, it can be interrupted with (parallel) + // execute in order to build targetd required to complete the match (for + // example, generated source code or source code generators themselves). + // + // Such interruptions are performed by phase change that is protected by + // phase_mutex (which is also used to synchronize the state changes + // between phases). + // + // Serial load can perform arbitrary changes to the build state. Exclusive + // load, however, can only perform "island appends". That is, it can + // create new "nodes" (variables, scopes, etc) but not (semantically) + // change already existing nodes or invalidate any references to such (the + // idea here is that one should be able to load additional buildfiles as + // long as they don't interfere with the existing build state). The + // "islands" are identified by the load_generation number (0 for the + // initial/serial load). It is incremented in case of a phase switch and + // can be stored in various "nodes" to verify modifications are only done + // "within the islands". + // + run_phase phase = run_phase::load; + size_t load_generation = 0; + + // A "tri-mutex" that keeps all the threads in one of the three phases. + // When a thread wants to switch a phase, it has to wait for all the other + // threads to do the same (or release their phase locks). The load phase + // is exclusive. + // + // The interleaving match and execute is interesting: during match we read + // the "external state" (e.g., filesystem entries, modifications times, + // etc) and capture it in the "internal state" (our dependency graph). + // During execute we are modifying the external state with controlled + // modifications of the internal state to reflect the changes (e.g., + // update mtimes). If you think about it, it's pretty clear that we cannot + // safely perform both of these actions simultaneously. A good example + // would be running a code generator and header dependency extraction + // simultaneously: the extraction process may pick up headers as they are + // being generated. As a result, we either have everyone treat the + // external state as read-only or write-only. + // + // There is also one more complication: if we are returning from a load + // phase that has failed, then the build state could be seriously messed + // up (things like scopes not being setup completely, etc). And once we + // release the lock, other threads that are waiting will start relying on + // this messed up state. So a load phase can mark the phase_mutex as + // failed in which case all currently blocked and future lock()/relock() + // calls return false. Note that in this case we still switch to the + // desired phase. See the phase_{lock,switch,unlock} implementations for + // details. + // + run_phase_mutex phase_mutex; + + // Current action (meta/operation). + // + // The names unlike info are available during boot but may not yet be + // lifted. The name is always for an outer operation (or meta operation + // that hasn't been recognized as such yet). + // + string current_mname; + string current_oname; + + const meta_operation_info* current_mif; + const operation_info* current_inner_oif; + const operation_info* current_outer_oif; + + // Current operation number (1-based) in the meta-operation batch. + // + size_t current_on; + + // Note: we canote use the corresponding target::offeset_* values. + // + size_t count_base () const {return 5 * (current_on - 1);} + + size_t count_touched () const {return 1 + count_base ();} + size_t count_tried () const {return 2 + count_base ();} + size_t count_matched () const {return 3 + count_base ();} + size_t count_applied () const {return 4 + count_base ();} + size_t count_executed () const {return 5 + count_base ();} + size_t count_busy () const {return 6 + count_base ();} + + // Execution mode. + // + execution_mode current_mode; + + // Some diagnostics (for example output directory creation/removal by the + // fsdir rule) is just noise at verbosity level 1 unless it is the only + // thing that is printed. So we can only suppress it in certain situations + // (e.g., dist) where we know we have already printed something. + // + bool current_diag_noise; + + // Total number of dependency relationships and targets with non-noop + // recipe in the current action. + // + // Together with target::dependents the dependency count is incremented + // during the rule search & match phase and is decremented during + // execution with the expectation of it reaching 0. Used as a sanity + // check. + // + // The target count is incremented after a non-noop recipe is matched and + // decremented after such recipe has been executed. If such a recipe has + // skipped executing the operation, then it should increment the skip + // count. These two counters are used for progress monitoring and + // diagnostics. + // + atomic_count dependency_count; + atomic_count target_count; + atomic_count skip_count; + + // Build state (scopes, targets, variables, etc). + // + const scope_map& scopes; + const scope& global_scope; + + target_set& targets; + + const variable_pool& var_pool; + const variable_overrides& var_overrides; // Project and relative scope. + variable_override_cache& global_override_cache; + + function_map& functions; + + // Cached variables. + // + + // Note: consider printing in info meta-operation if adding anything here. + // + 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; + + // project.url + // + const variable* var_project_url; + + // project.summary + // + const variable* var_project_summary; + + // import.target + // + const variable* var_import_target; + + // [string] target visibility + // + const variable* var_extension; + + // [bool] target visibility + // + const variable* var_clean; + + // Forwarded configuration backlink mode. Valid values are: + // + // false - no link. + // true - make a link using appropriate mechanism. + // symbolic - make a symbolic link. + // hard - make a hard link. + // copy - make a copy. + // overwrite - copy over but don't remove on clean (committed gen code). + // + // Note that it can be set by a matching rule as a rule-specific variable. + // + // [string] target visibility + // + const variable* var_backlink; + + // Prerequisite inclusion/exclusion. Valid values are: + // + // false - exclude. + // true - include. + // adhoc - include but treat as an ad hoc input. + // + // If a rule uses prerequisites as inputs (as opposed to just matching + // them with the "pass-through" semantics), then the adhoc value signals + // that a prerequisite is an ad hoc input. A rule should match and execute + // such a prerequisite (whether its target type is recognized as suitable + // input or not) and assume that the rest will be handled by the user + // (e.g., it will be passed via a command line argument or some such). + // Note that this mechanism can be used to both treat unknown prerequisite + // types as inputs (for example, linker scripts) as well as prevent + // treatment of known prerequisite types as such while still matching and + // executing them (for example, plugin libraries). + // + // A rule with the "pass-through" semantics should treat the adhoc value + // the same as true. + // + // To query this value in rule implementations use the include() helpers + // from . + // + // [string] prereq visibility + // + const variable* var_include; + + // The build.* namespace. + // + // .meta_operation + // + const variable* var_build_meta_operation; + + // Known meta-operation and operation tables. + // + build2::meta_operation_table meta_operation_table; + build2::operation_table operation_table; + + // The old/new src_root remapping for subprojects. + // + dir_path old_src_root; + dir_path new_src_root; + + public: + explicit + context (scheduler&, + const strings& cmd_vars = {}, + bool dry_run = false, + bool keep_going = true); + + // Set current meta-operation and operation. + // + void + current_meta_operation (const meta_operation_info&); + + void + current_operation (const operation_info& inner, + const operation_info* outer = nullptr, + bool diag_noise = true); + + context (context&&) = delete; + context& operator= (context&&) = delete; + + context (const context&) = delete; + context& operator= (const context&) = delete; + + ~context (); + }; // Grab a new phase lock releasing it on destruction. The lock can be // "owning" or "referencing" (recursive). @@ -195,7 +458,7 @@ namespace build2 // struct LIBBUILD2_SYMEXPORT phase_lock { - explicit phase_lock (run_phase); + explicit phase_lock (context&, run_phase); ~phase_lock (); phase_lock (phase_lock&&) = delete; @@ -204,7 +467,9 @@ namespace build2 phase_lock& operator= (phase_lock&&) = delete; phase_lock& operator= (const phase_lock&) = delete; - run_phase p; + context& ctx; + phase_lock* prev; // From another context. + run_phase phase; }; // Assuming we have a lock on the current phase, temporarily release it @@ -212,7 +477,7 @@ namespace build2 // struct LIBBUILD2_SYMEXPORT phase_unlock { - phase_unlock (bool unlock = true); + phase_unlock (context&, bool unlock = true); ~phase_unlock () noexcept (false); phase_lock* l; @@ -223,10 +488,10 @@ namespace build2 // struct LIBBUILD2_SYMEXPORT phase_switch { - explicit phase_switch (run_phase); + explicit phase_switch (context&, run_phase); ~phase_switch () noexcept (false); - run_phase o, n; + run_phase old_phase, new_phase; }; // Wait for a task count optionally and temporarily unlocking the phase. @@ -237,11 +502,12 @@ namespace build2 wait_guard (); // Empty. - explicit - wait_guard (atomic_count& task_count, + wait_guard (context&, + atomic_count& task_count, bool phase = false); - wait_guard (size_t start_count, + wait_guard (context&, + size_t start_count, atomic_count& task_count, bool phase = false); @@ -256,201 +522,23 @@ namespace build2 wait_guard (const wait_guard&) = delete; wait_guard& operator= (const wait_guard&) = delete; + context* ctx; size_t start_count; atomic_count* task_count; bool phase; }; - // Current action (meta/operation). - // - // The names unlike info are available during boot but may not yet be - // lifted. The name is always for an outer operation (or meta operation - // that hasn't been recognized as such yet). - // - LIBBUILD2_SYMEXPORT extern string current_mname; - LIBBUILD2_SYMEXPORT extern string current_oname; - - LIBBUILD2_SYMEXPORT extern const meta_operation_info* current_mif; - LIBBUILD2_SYMEXPORT extern const operation_info* current_inner_oif; - LIBBUILD2_SYMEXPORT extern const operation_info* current_outer_oif; - - // Current operation number (1-based) in the meta-operation batch. - // - LIBBUILD2_SYMEXPORT extern size_t current_on; - - LIBBUILD2_SYMEXPORT extern execution_mode current_mode; - - // Some diagnostics (for example output directory creation/removal by the - // fsdir rule) is just noise at verbosity level 1 unless it is the only - // thing that is printed. So we can only suppress it in certain situations - // (e.g., dist) where we know we have already printed something. - // - LIBBUILD2_SYMEXPORT extern bool current_diag_noise; - - // Total number of dependency relationships and targets with non-noop - // recipe in the current action. - // - // Together with target::dependents the dependency count is incremented - // during the rule search & match phase and is decremented during execution - // with the expectation of it reaching 0. Used as a sanity check. - // - // The target count is incremented after a non-noop recipe is matched and - // decremented after such recipe has been executed. If such a recipe has - // skipped executing the operation, then it should increment the skip count. - // These two counters are used for progress monitoring and diagnostics. - // - LIBBUILD2_SYMEXPORT extern atomic_count dependency_count; - LIBBUILD2_SYMEXPORT extern atomic_count target_count; - LIBBUILD2_SYMEXPORT extern atomic_count skip_count; - - LIBBUILD2_SYMEXPORT void - set_current_mif (const meta_operation_info&); - - LIBBUILD2_SYMEXPORT void - set_current_oif (const operation_info& inner, - const operation_info* outer = nullptr, - bool diag_noise = true); - - // Keep going flag. - // - // Note that setting it to false is not of much help unless we are running - // serially. In parallel we queue most of the things up before we see any - // failures. - // - LIBBUILD2_SYMEXPORT extern bool keep_going; - - // Dry run flag (see --dry-run|-n). - // - // This flag is set only for the final execute phase (as opposed to those - // that interrupt match) by the perform meta operation's execute() callback. - // - // Note that for this mode to function properly we have to use fake mtimes. - // Specifically, a rule that pretends to update a target must set its mtime - // to system_clock::now() and everyone else must use this cached value. In - // other words, there should be no mtime re-query from the filesystem. The - // same is required for "logical clean" (i.e., dry-run 'clean update' in - // order to see all the command lines). - // - // At first, it may seem like we should also "dry-run" changes to depdb. But - // that would be both problematic (some rules update it in apply() during - // the match phase) and wasteful (why discard information). Also, depdb may - // serve as an input to some commands (for example, to provide C++ module - // mapping) which means that without updating it the commands we print might - // not be runnable (think of the compilation database). - // - // One thing we need to be careful about if we are updating depdb is to not - // render the target up-to-date. But in this case the depdb file will be - // older than the target which in our model is treated as an interrupted - // update (see depdb for details). - // - // Note also that sometimes it makes sense to do a bit more than absolutely - // necessary or to discard information in order to keep the rule logic sane. - // And some rules may choose to ignore this flag altogether. In this case, - // however, the rule should be careful not to rely on functions (notably - // from filesystem) that respect this flag in order not to end up with a - // job half done. - // - LIBBUILD2_SYMEXPORT extern bool dry_run; - - // Reset the build state. In particular, this removes all the targets, - // scopes, and variables. - // - LIBBUILD2_SYMEXPORT variable_overrides - reset (const strings& cmd_vars); - // Config module entry points. // LIBBUILD2_SYMEXPORT extern void (*config_save_variable) ( scope&, const variable&, uint64_t flags); LIBBUILD2_SYMEXPORT extern const string& (*config_preprocess_create) ( - const variable_overrides&, + context&, values&, vector_view&, bool lifted, const location&); - - // Cached variables. - // - - // Note: consider printing in info meta-operation if adding anything here. - // - LIBBUILD2_SYMEXPORT extern const variable* var_src_root; - LIBBUILD2_SYMEXPORT extern const variable* var_out_root; - LIBBUILD2_SYMEXPORT extern const variable* var_src_base; - LIBBUILD2_SYMEXPORT extern const variable* var_out_base; - LIBBUILD2_SYMEXPORT extern const variable* var_forwarded; - - LIBBUILD2_SYMEXPORT extern const variable* var_project; - LIBBUILD2_SYMEXPORT extern const variable* var_amalgamation; - LIBBUILD2_SYMEXPORT extern const variable* var_subprojects; - LIBBUILD2_SYMEXPORT extern const variable* var_version; - - // project.url - // - LIBBUILD2_SYMEXPORT extern const variable* var_project_url; - - // project.summary - // - LIBBUILD2_SYMEXPORT extern const variable* var_project_summary; - - // import.target - // - LIBBUILD2_SYMEXPORT extern const variable* var_import_target; - - // [bool] target visibility - // - LIBBUILD2_SYMEXPORT extern const variable* var_clean; - - // Forwarded configuration backlink mode. Valid values are: - // - // false - no link. - // true - make a link using appropriate mechanism. - // symbolic - make a symbolic link. - // hard - make a hard link. - // copy - make a copy. - // overwrite - copy over but don't remove on clean (committed gen code). - // - // Note that it can be set by a matching rule as a rule-specific variable. - // - // [string] target visibility - // - LIBBUILD2_SYMEXPORT extern const variable* var_backlink; - - // Prerequisite inclusion/exclusion. Valid values are: - // - // false - exclude. - // true - include. - // adhoc - include but treat as an ad hoc input. - // - // If a rule uses prerequisites as inputs (as opposed to just matching them - // with the "pass-through" semantics), then the adhoc value signals that a - // prerequisite is an ad hoc input. A rule should match and execute such a - // prerequisite (whether its target type is recognized as suitable input or - // not) and assume that the rest will be handled by the user (e.g., it will - // be passed via a command line argument or some such). Note that this - // mechanism can be used to both treat unknown prerequisite types as inputs - // (for example, linker scripts) as well as prevent treatment of known - // prerequisite types as such while still matching and executing them (for - // example, plugin libraries). - // - // A rule with the "pass-through" semantics should treat the adhoc value - // the same as true. - // - // To query this value in rule implementations use the include() helpers - // from . - // - // [string] prereq visibility - // - LIBBUILD2_SYMEXPORT extern const variable* var_include; - - LIBBUILD2_SYMEXPORT extern const char var_extension[10]; // "extension" - - // The build.* namespace. - // - // .meta_operation - // - LIBBUILD2_SYMEXPORT extern const variable* var_build_meta_operation; } #include diff --git a/libbuild2/context.ixx b/libbuild2/context.ixx index f947bd7..7fb85ad 100644 --- a/libbuild2/context.ixx +++ b/libbuild2/context.ixx @@ -8,19 +8,19 @@ namespace build2 // inline wait_guard:: wait_guard () - : start_count (0), task_count (nullptr), phase (false) + : ctx (nullptr), start_count (0), task_count (nullptr), phase (false) { } inline wait_guard:: - wait_guard (atomic_count& tc, bool p) - : wait_guard (0, tc, p) + wait_guard (context& c, atomic_count& tc, bool p) + : wait_guard (c, 0, tc, p) { } inline wait_guard:: - wait_guard (size_t sc, atomic_count& tc, bool p) - : start_count (sc), task_count (&tc), phase (p) + wait_guard (context& c, size_t sc, atomic_count& tc, bool p) + : ctx (&c), start_count (sc), task_count (&tc), phase (p) { } @@ -33,7 +33,10 @@ namespace build2 inline wait_guard:: wait_guard (wait_guard&& x) - : start_count (x.start_count), task_count (x.task_count), phase (x.phase) + : ctx (x.ctx), + start_count (x.start_count), + task_count (x.task_count), + phase (x.phase) { x.task_count = nullptr; } @@ -44,6 +47,7 @@ namespace build2 if (&x != this) { assert (task_count == nullptr); + ctx = x.ctx; start_count = x.start_count; task_count = x.task_count; phase = x.phase; x.task_count = nullptr; } @@ -53,8 +57,8 @@ namespace build2 inline void wait_guard:: wait () { - phase_unlock u (phase); - sched.wait (start_count, *task_count); + phase_unlock u (*ctx, phase); + ctx->sched.wait (start_count, *task_count); task_count = nullptr; } } diff --git a/libbuild2/diagnostics.cxx b/libbuild2/diagnostics.cxx index 3375e00..71f3d48 100644 --- a/libbuild2/diagnostics.cxx +++ b/libbuild2/diagnostics.cxx @@ -141,14 +141,14 @@ namespace build2 const fail_mark fail ("error"); const fail_end endf; - // diag_do(), etc. + // diag_do(), etc. // string - diag_do (const action&) + diag_do (context& ctx, const action&) { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); + const meta_operation_info& m (*ctx.current_mif); + const operation_info& io (*ctx.current_inner_oif); + const operation_info* oo (ctx.current_outer_oif); string r; @@ -181,15 +181,15 @@ namespace build2 void diag_do (ostream& os, const action& a, const target& t) { - os << diag_do (a) << ' ' << t; + os << diag_do (t.ctx, a) << ' ' << t; } string - diag_doing (const action&) + diag_doing (context& ctx, const action&) { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); + const meta_operation_info& m (*ctx.current_mif); + const operation_info& io (*ctx.current_inner_oif); + const operation_info* oo (ctx.current_outer_oif); string r; @@ -218,15 +218,15 @@ namespace build2 void diag_doing (ostream& os, const action& a, const target& t) { - os << diag_doing (a) << ' ' << t; + os << diag_doing (t.ctx, a) << ' ' << t; } string - diag_did (const action&) + diag_did (context& ctx, const action&) { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); + const meta_operation_info& m (*ctx.current_mif); + const operation_info& io (*ctx.current_inner_oif); + const operation_info* oo (ctx.current_outer_oif); string r; @@ -259,15 +259,15 @@ namespace build2 void diag_did (ostream& os, const action& a, const target& t) { - os << diag_did (a) << ' ' << t; + os << diag_did (t.ctx, a) << ' ' << t; } void diag_done (ostream& os, const action&, const target& t) { - const meta_operation_info& m (*current_mif); - const operation_info& io (*current_inner_oif); - const operation_info* oo (current_outer_oif); + const meta_operation_info& m (*t.ctx.current_mif); + const operation_info& io (*t.ctx.current_inner_oif); + const operation_info* oo (t.ctx.current_outer_oif); // perform(update(x)) -> "x is up to date" // configure(update(x)) -> "updating x is configured" diff --git a/libbuild2/diagnostics.hxx b/libbuild2/diagnostics.hxx index dbc8351..5d69132 100644 --- a/libbuild2/diagnostics.hxx +++ b/libbuild2/diagnostics.hxx @@ -439,6 +439,7 @@ namespace build2 // class scope; class target; + class context; struct action; struct diag_phrase @@ -456,7 +457,7 @@ namespace build2 } LIBBUILD2_SYMEXPORT string - diag_do (const action&); + diag_do (context&, const action&); LIBBUILD2_SYMEXPORT void diag_do (ostream&, const action&, const target&); @@ -468,7 +469,7 @@ namespace build2 } LIBBUILD2_SYMEXPORT string - diag_doing (const action&); + diag_doing (context&, const action&); LIBBUILD2_SYMEXPORT void diag_doing (ostream&, const action&, const target&); @@ -480,7 +481,7 @@ namespace build2 } LIBBUILD2_SYMEXPORT string - diag_did (const action&); + diag_did (context&, const action&); LIBBUILD2_SYMEXPORT void diag_did (ostream&, const action&, const target&); diff --git a/libbuild2/dist/init.cxx b/libbuild2/dist/init.cxx index 4729938..c6ffb67 100644 --- a/libbuild2/dist/init.cxx +++ b/libbuild2/dist/init.cxx @@ -37,7 +37,7 @@ namespace build2 // Enter module variables. Do it during boot in case they get assigned // in bootstrap.build (which is customary for, e.g., dist.package). // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); // Note: some overridable, some not. // diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index ac3912e..ad2ee7f 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -44,7 +44,8 @@ namespace build2 // Return the archive file path. // static path - archive (const dir_path& root, + archive (context& ctx, + const dir_path& root, const string& pkg, const dir_path& dir, const string& ext); @@ -54,7 +55,8 @@ namespace build2 // Return the checksum file path. // static path - checksum (const path& arc, const dir_path& dir, const string& ext); + checksum (context&, + const path& arc, const dir_path& dir, const string& ext); static operation_id dist_operation_pre (const values&, operation_id o) @@ -79,6 +81,8 @@ namespace build2 if (rs == nullptr) fail << "out of project target " << t; + context& ctx (rs->ctx); + const dir_path& out_root (rs->out_path ()); const dir_path& src_root (rs->src_path ()); @@ -167,7 +171,7 @@ namespace build2 if (operation_id pid = oif->pre (params, dist_id, loc)) { const operation_info* poif (ops[pid]); - set_current_oif (*poif, oif, false /* diag_noise */); + ctx.current_operation (*poif, oif, false /* diag_noise */); action a (dist_id, poif->id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -175,7 +179,7 @@ namespace build2 } } - set_current_oif (*oif, nullptr, false /* diag_noise */); + ctx.current_operation (*oif, nullptr, false /* diag_noise */); action a (dist_id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -186,7 +190,7 @@ namespace build2 if (operation_id pid = oif->post (params, dist_id)) { const operation_info* poif (ops[pid]); - set_current_oif (*poif, oif, false /* diag_noise */); + ctx.current_operation (*poif, oif, false /* diag_noise */); action a (dist_id, poif->id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -213,7 +217,7 @@ namespace build2 ? out_src (d, rs) : dir_path ()); - targets.insert ( + rs.ctx.targets.insert ( move (d), move (out), p.leaf ().base ().string (), @@ -226,13 +230,13 @@ namespace build2 // The same for subprojects that have been loaded. // - if (auto l = rs->vars[var_subprojects]) + if (auto l = rs->vars[ctx.var_subprojects]) { for (auto p: cast (l)) { const dir_path& pd (p.second); dir_path out_nroot (out_root / pd); - const scope& nrs (scopes.find (out_nroot)); + const scope& nrs (ctx.scopes.find (out_nroot)); if (nrs.out_path () != out_nroot) // This subproject not loaded. continue; @@ -251,9 +255,9 @@ namespace build2 // distribute") since it will be useless (too fast). // action_targets files; - const variable& dist_var (var_pool["dist"]); + const variable& dist_var (ctx.var_pool["dist"]); - for (const auto& pt: targets) + for (const auto& pt: ctx.targets) { file* ft (pt->is_a ()); @@ -304,14 +308,14 @@ namespace build2 // // Note also that we don't do any structured result printing. // - size_t on (current_on); - set_current_mif (mo_perform); - current_on = on + 1; + size_t on (ctx.current_on); + ctx.current_meta_operation (mo_perform); + ctx.current_on = on + 1; if (mo_perform.operation_pre != nullptr) mo_perform.operation_pre (params, update_id); - set_current_oif (op_update, nullptr, false /* diag_noise */); + ctx.current_operation (op_update, nullptr, false /* diag_noise */); action a (perform_id, update_id); @@ -334,7 +338,7 @@ namespace build2 // Clean up the target directory. // - if (build2::rmdir_r (td, true, 2) == rmdir_status::not_empty) + if (rmdir_r (ctx, td, true, 2) == rmdir_status::not_empty) fail << "unable to clean target directory " << td; auto_rmdir rm_td (td); // Clean it up if things go bad. @@ -367,14 +371,14 @@ namespace build2 const scope* srs (rs); const module::callbacks* cbs (&mod.callbacks_); - if (auto l = rs->vars[var_subprojects]) + if (auto l = rs->vars[ctx.var_subprojects]) { for (auto p: cast (l)) { const dir_path& pd (p.second); if (dl.sub (pd)) { - srs = &scopes.find (out_root / pd); + srs = &ctx.scopes.find (out_root / pd); if (auto* m = srs->lookup_module (module::name)) cbs = &m->callbacks_; @@ -467,14 +471,14 @@ namespace build2 for (const path& p: cast (as)) { auto ap (split (p, dist_root, "dist.archives")); - path a (archive (dist_root, dist_package, ap.first, ap.second)); + path a (archive (ctx, dist_root, dist_package, ap.first, ap.second)); if (cs) { for (const path& p: cast (cs)) { auto cp (split (p, ap.first, "dist.checksums")); - checksum (a, cp.first, cp.second); + checksum (ctx, a, cp.first, cp.second); } } } @@ -541,7 +545,8 @@ namespace build2 } static path - archive (const dir_path& root, + archive (context& ctx, + const dir_path& root, const string& pkg, const dir_path& dir, const string& e) @@ -552,7 +557,7 @@ namespace build2 // path ap (dir / an); if (exists (ap, false)) - rmfile (ap); + rmfile (ctx, ap); // Use zip for .zip archives. Also recognize and handle a few well-known // tar.xx cases (in case tar doesn't support -a or has other issues like @@ -713,7 +718,8 @@ namespace build2 } static path - checksum (const path& ap, const dir_path& dir, const string& e) + checksum (context& ctx, + const path& ap, const dir_path& dir, const string& e) { path an (ap.leaf ()); dir_path ad (ap.directory ()); @@ -724,7 +730,7 @@ namespace build2 // path cp (dir / cn); if (exists (cp, false)) - rmfile (cp); + rmfile (ctx, cp); auto_rmfile c_rm; // Note: must come first. auto_fd c_fd; diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index 7d59891..738ef36 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -275,7 +276,7 @@ namespace build2 if (size_t c = t[inner].task_count.load (memory_order_relaxed)) { - if (c == target::count_applied () || c == target::count_executed ()) + if (c == t.ctx.count_applied () || c == t.ctx.count_executed ()) { bool f (false); for (const target* pt: t.prerequisite_targets[inner]) @@ -400,7 +401,8 @@ namespace build2 // Nested scopes of which we are an immediate parent. // - for (auto e (scopes.end ()); i != e && i->second.parent_scope () == &p;) + for (auto e (p.ctx.scopes.end ()); + i != e && i->second.parent_scope () == &p; ) { if (vb) { @@ -421,7 +423,7 @@ namespace build2 // Since targets can occupy multiple lines, we separate them with a // blank line. // - for (const auto& pt: targets) + for (const auto& pt: p.ctx.targets) { const target& t (*pt); @@ -447,10 +449,10 @@ namespace build2 } void - dump (optional a) + dump (const context& c, optional a) { - auto i (scopes.cbegin ()); - assert (&i->second == global_scope); + auto i (c.scopes.cbegin ()); + assert (&i->second == &c.global_scope); // We don't lock diag_stream here as dump() is supposed to be called from // the main thread prior/after to any other threads being spawned. @@ -464,7 +466,7 @@ namespace build2 void dump (const scope& s, const char* cind) { - const scope_map_base& m (scopes); // Iterator interface. + const scope_map_base& m (s.ctx.scopes); // Iterator interface. auto i (m.find (s.out_path ())); assert (i != m.end () && &i->second == &s); diff --git a/libbuild2/dump.hxx b/libbuild2/dump.hxx index fd1886b..aead805 100644 --- a/libbuild2/dump.hxx +++ b/libbuild2/dump.hxx @@ -16,13 +16,14 @@ namespace build2 { class scope; class target; + class context; // Dump the build state to diag_stream. If action is specified, then assume // rules have been matched for this action and dump action-specific // information (like rule-specific variables). // LIBBUILD2_SYMEXPORT void - dump (optional = nullopt); + dump (const context&, optional = nullopt); LIBBUILD2_SYMEXPORT void dump (const scope&, const char* ind = ""); diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 1da9397..9140e59 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -146,20 +146,17 @@ namespace build2 return make_pair (dir_path (), false); } - dir_path old_src_root; - dir_path new_src_root; - // Remap the src_root variable value if it is inside old_src_root. // static inline void - remap_src_root (value& v) + remap_src_root (context& ctx, value& v) { - if (!old_src_root.empty ()) + if (!ctx.old_src_root.empty ()) { dir_path& d (cast (v)); - if (d.sub (old_src_root)) - d = new_src_root / d.leaf (old_src_root); + if (d.sub (ctx.old_src_root)) + d = ctx.new_src_root / d.leaf (ctx.old_src_root); } } @@ -183,7 +180,7 @@ namespace build2 l5 ([&]{trace << "sourcing " << bf;}); - parser p (boot); + parser p (root.ctx, boot); p.parse_buildfile (is, bf, root, base); } catch (const io_error& e) @@ -263,11 +260,13 @@ namespace build2 } scope_map::iterator - create_root (scope& l, const dir_path& out_root, const dir_path& src_root) + create_root (scope& s, const dir_path& out_root, const dir_path& src_root) { - auto i (scopes.rw (l).insert (out_root, true /* root */)); + auto i (s.ctx.scopes.rw (s).insert (out_root, true /* root */)); scope& rs (i->second); + context& ctx (rs.ctx); + // Set out_path. Note that src_path is set in setup_root() below. // if (rs.out_path_ != &i->first) @@ -279,7 +278,7 @@ namespace build2 // If this is already a root scope, verify that things are consistent. // { - value& v (rs.assign (var_out_root)); + value& v (rs.assign (ctx.var_out_root)); if (!v) v = out_root; @@ -295,7 +294,7 @@ namespace build2 if (!src_root.empty ()) { - value& v (rs.assign (var_src_root)); + value& v (rs.assign (ctx.var_src_root)); if (!v) v = src_root; @@ -315,9 +314,11 @@ namespace build2 void setup_root (scope& s, bool forwarded) { + context& ctx (s.ctx); + // The caller must have made sure src_root is set on this scope. // - value& v (s.assign (var_src_root)); + value& v (s.assign (ctx.var_src_root)); assert (v); const dir_path& d (cast (v)); @@ -326,7 +327,7 @@ namespace build2 else assert (s.src_path_ == &d); - s.assign (var_forwarded) = forwarded; + s.assign (ctx.var_forwarded) = forwarded; } scope& @@ -335,17 +336,18 @@ namespace build2 const dir_path& src_base) { scope& s (i->second); + context& ctx (s.ctx); // Set src/out_base variables. // - value& ov (s.assign (var_out_base)); + value& ov (s.assign (ctx.var_out_base)); if (!ov) ov = out_base; else assert (cast (ov) == out_base); - value& sv (s.assign (var_src_base)); + value& sv (s.assign (ctx.var_src_base)); if (!sv) sv = src_base; @@ -373,7 +375,7 @@ namespace build2 // First, enter the scope into the map and see if it is in any project. If // it is not, then there is nothing else to do. // - auto i (scopes.rw (root).insert (p)); + auto i (root.ctx.scopes.rw (root).insert (p)); scope& base (i->second); scope* rs (base.root_scope ()); @@ -410,7 +412,7 @@ namespace build2 } dir_path - bootstrap_fwd (const dir_path& src_root, optional& altn) + bootstrap_fwd (context& ctx, const dir_path& src_root, optional& altn) { path f (exists (src_root, std_out_root_file, alt_out_root_file, altn)); @@ -420,7 +422,7 @@ namespace build2 // We cannot just source the buildfile since there is no scope to do // this on yet. // - auto p (extract_variable (f, *var_out_root)); + auto p (extract_variable (ctx, f, *ctx.var_out_root)); if (!p.second) fail << "variable out_root expected as first line in " << f; @@ -495,7 +497,7 @@ namespace build2 } pair - extract_variable (const path& bf, const variable& var) + extract_variable (context& ctx, const path& bf, const variable& var) { try { @@ -513,8 +515,8 @@ namespace build2 return make_pair (value (), false); } - parser p; - temp_scope tmp (global_scope->rw ()); + parser p (ctx); + temp_scope tmp (ctx.global_scope.rw ()); p.parse_variable (lex, tmp, var, tt); value* v (tmp.vars.find_to_modify (var).first); @@ -533,7 +535,8 @@ namespace build2 // Extract the project name from bootstrap.build. // static project_name - find_project_name (const dir_path& out_root, + find_project_name (context& ctx, + const dir_path& out_root, const dir_path& fallback_src_root, optional out_src, // True if out_root is src_root. optional& altn) @@ -544,7 +547,7 @@ namespace build2 // in which case we will have src_root and maybe even the name. // const dir_path* src_root (nullptr); - const scope& s (scopes.find (out_root)); + const scope& s (ctx.scopes.find (out_root)); if (s.root_scope () == &s && s.out_path () == out_root) { @@ -556,7 +559,7 @@ namespace build2 assert (*altn == s.root_extra->altn); } - if (lookup l = s.vars[var_project]) + if (lookup l = s.vars[ctx.var_project]) return cast (l); src_root = s.src_path_; @@ -588,13 +591,13 @@ namespace build2 } else { - auto p (extract_variable (f, *var_src_root)); + auto p (extract_variable (ctx, f, *ctx.var_src_root)); if (!p.second) fail << "variable src_root expected as first line in " << f; src_root_v = move (p.first); - remap_src_root (src_root_v); // Remap if inside old_src_root. + remap_src_root (ctx, src_root_v); // Remap if inside old_src_root. src_root = &cast (src_root_v); l5 ([&]{trace << "extracted src_root " << *src_root @@ -610,10 +613,10 @@ namespace build2 if (f.empty ()) fail << "no build/bootstrap.build in " << *src_root; - auto p (extract_variable (f, *var_project)); + auto p (extract_variable (ctx, f, *ctx.var_project)); if (!p.second) - fail << "variable " << var_project->name << " expected " + fail << "variable " << ctx.var_project->name << " expected " << "as a first line in " << f; name = cast (move (p.first)); @@ -628,7 +631,8 @@ namespace build2 // is a subproject, then enter it into the map, handling the duplicates. // static void - find_subprojects (subprojects& sps, + find_subprojects (context& ctx, + subprojects& sps, const dir_path& d, const dir_path& root, bool out) @@ -668,7 +672,8 @@ namespace build2 // Load its name. Note that here we don't use fallback src_root // since this function is used to scan both out_root and src_root. // - project_name name (find_project_name (sd, dir_path (), src, altn)); + project_name name ( + find_project_name (ctx, sd, dir_path (), src, altn)); // If the name is empty, then is is an unnamed project. While the // 'project' variable stays empty, here we come up with a surrogate @@ -707,26 +712,28 @@ namespace build2 } bool - bootstrap_src (scope& root, optional& altn) + bootstrap_src (scope& rs, optional& altn) { tracer trace ("bootstrap_src"); + context& ctx (rs.ctx); + bool r (false); - const dir_path& out_root (root.out_path ()); - const dir_path& src_root (root.src_path ()); + const dir_path& out_root (rs.out_path ()); + const dir_path& src_root (rs.src_path ()); { path f (exists (src_root, std_bootstrap_file, alt_bootstrap_file, altn)); - if (root.root_extra == nullptr) + if (rs.root_extra == nullptr) { // If nothing so far has indicated the naming, assume standard. // if (!altn) altn = false; - setup_root_extra (root, altn); + setup_root_extra (rs, altn); } if (!f.empty ()) @@ -736,8 +743,8 @@ namespace build2 // process hard to reason about. But we may try to bootstrap the same // root scope multiple time. // - if (root.buildfiles.insert (f).second) - source (root, root, f, true); + if (rs.buildfiles.insert (f).second) + source (rs, rs, f, true); else l5 ([&]{trace << "skipping already sourced " << f;}); @@ -757,15 +764,15 @@ namespace build2 // Note: the amalgamation variable value is always a relative directory. // { - auto rp (root.vars.insert (*var_amalgamation)); // Set NULL by default. + auto rp (rs.vars.insert (*ctx.var_amalgamation)); // Set NULL by default. value& v (rp.first); if (v && v.empty ()) // Convert empty to NULL. v = nullptr; - if (scope* aroot = root.parent_scope ()->root_scope ()) + if (scope* ars = rs.parent_scope ()->root_scope ()) { - const dir_path& ad (aroot->out_path ()); + const dir_path& ad (ars->out_path ()); dir_path rd (ad.relative (out_root)); // If we already have the amalgamation variable set, verify @@ -829,7 +836,7 @@ namespace build2 // NULL value indicates that we found no subprojects. // { - auto rp (root.vars.insert (*var_subprojects)); // Set NULL by default. + auto rp (rs.vars.insert (*ctx.var_subprojects)); // Set NULL by default. value& v (rp.first); if (rp.second) @@ -846,13 +853,13 @@ namespace build2 if (exists (out_root)) { l5 ([&]{trace << "looking for subprojects in " << out_root;}); - find_subprojects (sps, out_root, out_root, true); + find_subprojects (rs.ctx, sps, out_root, out_root, true); } if (out_root != src_root) { l5 ([&]{trace << "looking for subprojects in " << src_root;}); - find_subprojects (sps, src_root, src_root, false); + find_subprojects (rs.ctx, sps, src_root, src_root, false); } if (!sps.empty ()) // Keep it NULL if no subprojects. @@ -923,7 +930,8 @@ namespace build2 // Pass fallback src_root since this is a subproject that was // specified by the user so it is most likely in our src. // - n = find_project_name (out_root / d, + n = find_project_name (rs.ctx, + out_root / d, src_root / d, nullopt /* out_src */, altn); @@ -983,13 +991,13 @@ namespace build2 } bool - bootstrapped (scope& root) + bootstrapped (scope& rs) { // Use the subprojects variable set by bootstrap_src() as an indicator. // It should either be NULL or typed (so we assume that the user will // never set it to NULL). // - auto l (root.vars[var_subprojects]); + auto l (rs.vars[rs.ctx.var_subprojects]); return l.defined () && (l->null || l->type != nullptr); } @@ -1002,6 +1010,8 @@ namespace build2 const dir_path& src_root, optional& altn) { + context& ctx (orig.ctx); + // The conditions are: // // 1. Origin is itself forwarded. @@ -1010,15 +1020,17 @@ namespace build2 // // 3. Inner/outer out-root.build exists in src_root and refers out_root. // - return (out_root != src_root && - cast_false (orig.vars[var_forwarded]) && - bootstrap_fwd (src_root, altn) == out_root); + return (out_root != src_root && + cast_false (orig.vars[ctx.var_forwarded]) && + bootstrap_fwd (ctx, src_root, altn) == out_root); } void create_bootstrap_outer (scope& root) { - auto l (root.vars[var_amalgamation]); + context& ctx (root.ctx); + + auto l (root.vars[ctx.var_amalgamation]); if (!l) return; @@ -1046,7 +1058,7 @@ namespace build2 { bootstrap_out (rs, altn); // #3 happens here (or it can be #1). - value& v (rs.assign (var_src_root)); + value& v (rs.assign (ctx.var_src_root)); if (!v) { @@ -1060,7 +1072,7 @@ namespace build2 } } else - remap_src_root (v); // Remap if inside old_src_root. + remap_src_root (ctx, v); // Remap if inside old_src_root. setup_root (rs, forwarded (root, out_root, v.as (), altn)); bootstrap_pre (rs, altn); @@ -1072,7 +1084,7 @@ namespace build2 altn = rs.root_extra->altn; if (forwarded (root, rs.out_path (), rs.src_path (), altn)) - rs.assign (var_forwarded) = true; // Only upgrade (see main()). + rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } create_bootstrap_outer (rs); @@ -1089,9 +1101,11 @@ namespace build2 scope& create_bootstrap_inner (scope& root, const dir_path& out_base) { + context& ctx (root.ctx); + scope* r (&root); - if (auto l = root.vars[var_subprojects]) + if (auto l = root.vars[ctx.var_subprojects]) { for (const auto& p: cast (l)) { @@ -1109,7 +1123,7 @@ namespace build2 { bootstrap_out (rs, altn); - value& v (rs.assign (var_src_root)); + value& v (rs.assign (ctx.var_src_root)); if (!v) { @@ -1118,7 +1132,7 @@ namespace build2 : (root.src_path () / p.second); } else - remap_src_root (v); // Remap if inside old_src_root. + remap_src_root (ctx, v); // Remap if inside old_src_root. setup_root (rs, forwarded (root, out_root, v.as (), altn)); bootstrap_pre (rs, altn); @@ -1129,7 +1143,7 @@ namespace build2 { altn = rs.root_extra->altn; if (forwarded (root, rs.out_path (), rs.src_path (), altn)) - rs.assign (var_forwarded) = true; // Only upgrade (see main()). + rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } // Check if we strongly amalgamated this inner root scope. @@ -1205,7 +1219,7 @@ namespace build2 } scope& - load_project (scope& lock, + load_project (scope& s, const dir_path& out_root, const dir_path& src_root, bool forwarded, @@ -1213,7 +1227,9 @@ namespace build2 { assert (!forwarded || out_root != src_root); - auto i (create_root (lock, out_root, src_root)); + context& ctx (s.ctx); + + auto i (create_root (s, out_root, src_root)); scope& rs (i->second); if (!bootstrapped (rs)) @@ -1228,7 +1244,7 @@ namespace build2 else { if (forwarded) - rs.assign (var_forwarded) = true; // Only upgrade (see main()). + rs.assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } if (load) @@ -1257,6 +1273,8 @@ namespace build2 return names {move (target)}; } + context& ctx (ibase.ctx); + // Otherwise, get the project name and convert the target to unqualified. // project_name proj (move (*target.proj)); @@ -1273,7 +1291,7 @@ namespace build2 // over anything that we may discover. In particular, we will prefer it // over any bundled subprojects. // - auto& vp (var_pool.rw (iroot)); + auto& vp (ibase.ctx.var_pool.rw (iroot)); for (;;) // Break-out loop. { @@ -1375,13 +1393,14 @@ namespace build2 // First check the amalgamation itself. // - if (r != &iroot && cast (r->vars[var_project]) == proj) + if (r != &iroot && + cast (r->vars[ctx.var_project]) == proj) { out_root = r->out_path (); break; } - if (auto l = r->vars[var_subprojects]) + if (auto l = r->vars[ctx.var_subprojects]) { const auto& m (cast (l)); auto i (m.find (proj)); @@ -1394,7 +1413,7 @@ namespace build2 } } - if (!r->vars[var_amalgamation]) + if (!r->vars[ctx.var_amalgamation]) break; } @@ -1429,7 +1448,7 @@ namespace build2 if (is_src_root (out_root, altn)) { src_root = move (out_root); - out_root = bootstrap_fwd (src_root, altn); + out_root = bootstrap_fwd (ctx, src_root, altn); fwd = (src_root != out_root); } @@ -1447,7 +1466,7 @@ namespace build2 // Check that the bootstrap process set src_root. // - auto l (root->vars[*var_src_root]); + auto l (root->vars[*ctx.var_src_root]); if (l) { // Note that unlike main() here we fail hard. The idea is that if @@ -1482,7 +1501,7 @@ namespace build2 src_root = root->src_path (); if (top ? fwd : forwarded (*proot, out_root, src_root, altn)) - root->assign (var_forwarded) = true; // Only upgrade (see main()). + root->assign (ctx.var_forwarded) = true; // Only upgrade (see main()). } if (top) @@ -1495,10 +1514,10 @@ namespace build2 // Now we know this project's name as well as all its subprojects. // - if (cast (root->vars[var_project]) == proj) + if (cast (root->vars[ctx.var_project]) == proj) break; - if (auto l = root->vars[var_subprojects]) + if (auto l = root->vars[ctx.var_subprojects]) { const auto& m (cast (l)); auto i (m.find (proj)); @@ -1527,13 +1546,13 @@ namespace build2 // "Pass" the imported project's roots to the stub. // - ts.assign (var_out_root) = move (out_root); - ts.assign (var_src_root) = move (src_root); + ts.assign (ctx.var_out_root) = move (out_root); + ts.assign (ctx.var_src_root) = move (src_root); // Also pass the target being imported in the import.target variable. // { - value& v (ts.assign (var_import_target)); + value& v (ts.assign (ctx.var_import_target)); if (!target.empty ()) // Otherwise leave NULL. v = target; // Can't move (need for diagnostics below). @@ -1556,7 +1575,7 @@ namespace build2 // there is a use-case for the export stub to return a qualified // name? // - parser p; + parser p (ctx); names v (p.parse_export_stub (ifs, es, iroot, ts)); // If there were no export directive executed in an export stub, assume @@ -1575,7 +1594,7 @@ namespace build2 } const target* - import (const prerequisite_key& pk, bool existing) + import (context& ctx, const prerequisite_key& pk, bool existing) { tracer trace ("import"); @@ -1610,18 +1629,18 @@ namespace build2 const exe* t ( !existing - ? &targets.insert (tt, - p.directory (), - dir_path (), // No out (out of project). - p.leaf ().base ().string (), - p.extension (), // Always specified. - trace) - : targets.find (tt, - p.directory (), - dir_path (), - p.leaf ().base ().string (), - p.extension (), - trace)); + ? &ctx.targets.insert (tt, + p.directory (), + dir_path (), // No out (not in project). + p.leaf ().base ().string (), + p.extension (), // Always specified. + trace) + : ctx.targets.find (tt, + p.directory (), + dir_path (), + p.leaf ().base ().string (), + p.extension (), + trace)); if (t != nullptr) { diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx index 48d1b63..aaa0fa0 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -69,11 +69,6 @@ namespace build2 LIBBUILD2_SYMEXPORT pair find_out_root (const dir_path&, optional& altn); - // The old/new src_root paths. See main() (where they are set) for details. - // - LIBBUILD2_SYMEXPORT extern dir_path old_src_root; - LIBBUILD2_SYMEXPORT extern dir_path new_src_root; - // If buildfile is '-', then read from STDIN. // LIBBUILD2_SYMEXPORT void @@ -91,8 +86,8 @@ namespace build2 source_once (scope& root, scope& base, const path&, scope& once); // Create project's root scope. Only set the src_root variable if the passed - // src_root value is not empty. The scope argument is only used as proof of - // lock. + // src_root value is not empty. The scope argument is only used for context + // and as a proof of lock. // LIBBUILD2_SYMEXPORT scope_map::iterator create_root (scope&, const dir_path& out_root, const dir_path& src_root); @@ -140,7 +135,7 @@ namespace build2 // argument semantics. // LIBBUILD2_SYMEXPORT dir_path - bootstrap_fwd (const dir_path& src_root, optional& altn); + bootstrap_fwd (context&, const dir_path& src_root, optional& altn); // Bootstrap the project's root scope, the out part. // @@ -201,7 +196,7 @@ namespace build2 // an indication of whether the variable was found. // LIBBUILD2_SYMEXPORT pair - extract_variable (const path&, const variable&); + extract_variable (context&, const path&, const variable&); // Import has two phases: the first is triggered by the import directive in // the buildfile. It will try to find and load the project. Failed that, it @@ -224,7 +219,7 @@ namespace build2 import (scope& base, name, const location&); const target& - import (const prerequisite_key&); + import (context&, const prerequisite_key&); // As above but only imports as an already existing target. Unlike the above // version, this one can be called during the execute phase. @@ -232,7 +227,7 @@ namespace build2 // Note: similar to search_existing(). // const target* - import_existing (const prerequisite_key&); + import_existing (context&, const prerequisite_key&); } #include diff --git a/libbuild2/file.ixx b/libbuild2/file.ixx index f8a79be..564fc11 100644 --- a/libbuild2/file.ixx +++ b/libbuild2/file.ixx @@ -13,19 +13,19 @@ namespace build2 } LIBBUILD2_SYMEXPORT const target* - import (const prerequisite_key&, bool existing); + import (context&, const prerequisite_key&, bool existing); inline const target& - import (const prerequisite_key& pk) + import (context& ctx, const prerequisite_key& pk) { - assert (phase == run_phase::match); - return *import (pk, false); + assert (ctx.phase == run_phase::match); + return *import (ctx, pk, false); } inline const target* - import_existing (const prerequisite_key& pk) + import_existing (context& ctx, const prerequisite_key& pk) { - assert (phase == run_phase::match || phase == run_phase::execute); - return import (pk, true); + assert (ctx.phase == run_phase::match || ctx.phase == run_phase::execute); + return import (ctx, pk, true); } } diff --git a/libbuild2/filesystem.cxx b/libbuild2/filesystem.cxx index 83408fa..1cbaa58 100644 --- a/libbuild2/filesystem.cxx +++ b/libbuild2/filesystem.cxx @@ -13,12 +13,12 @@ using namespace butl; namespace build2 { void - touch (const path& p, bool create, uint16_t v) + touch (context& ctx, const path& p, bool create, uint16_t v) { if (verb >= v) text << "touch " << p; - if (dry_run) + if (ctx.dry_run) return; try @@ -104,7 +104,7 @@ namespace build2 } fs_status - rmsymlink (const path& p, bool d, uint16_t v) + rmsymlink (context& ctx, const path& p, bool d, uint16_t v) { auto print = [&p, v] () { @@ -116,7 +116,7 @@ namespace build2 try { - rs = dry_run + rs = ctx.dry_run ? (butl::entry_exists (p) ? rmfile_status::success : rmfile_status::not_exist) @@ -135,7 +135,7 @@ namespace build2 } fs_status - rmdir_r (const dir_path& d, bool dir, uint16_t v) + rmdir_r (context& ctx, const dir_path& d, bool dir, uint16_t v) { using namespace butl; @@ -148,7 +148,7 @@ namespace build2 if (verb >= v) text << "rmdir -r " << d; - if (!dry_run) + if (!ctx.dry_run) { try { @@ -216,7 +216,10 @@ namespace build2 } fs_status - mkdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity) + mkdir_buildignore (context& ctx, + const dir_path& d, + const path& n, + uint16_t verbosity) { fs_status r (mkdir (d, verbosity)); @@ -225,7 +228,7 @@ namespace build2 // path p (d / n); if (r || !exists (p)) - touch (p, true /* create */, verbosity); + touch (ctx, p, true /* create */, verbosity); return r; } @@ -253,7 +256,10 @@ namespace build2 } fs_status - rmdir_buildignore (const dir_path& d, const path& n, uint16_t verbosity) + rmdir_buildignore (context& ctx, + const dir_path& d, + const path& n, + uint16_t verbosity) { // We should remove the .buildignore file only if the subsequent rmdir() // will succeed. In other words if the directory stays after the function @@ -263,12 +269,12 @@ namespace build2 // path p (d / n); if (exists (p) && empty_buildignore (d, n) && !work.sub (d)) - rmfile (p, verbosity); + rmfile (ctx, p, verbosity); // Note that in case of a system error the directory is likely to stay with // the .buildignore file already removed. Trying to restore it feels like // an overkill here. // - return rmdir (d, verbosity); + return rmdir (ctx, d, verbosity); } } diff --git a/libbuild2/filesystem.hxx b/libbuild2/filesystem.hxx index 6dca528..e7b3094 100644 --- a/libbuild2/filesystem.hxx +++ b/libbuild2/filesystem.hxx @@ -10,6 +10,8 @@ #include #include +#include + #include // Higher-level filesystem utilities built on top of . @@ -43,7 +45,7 @@ namespace build2 // create it and fail otherwise. // LIBBUILD2_SYMEXPORT void - touch (const path&, bool create, uint16_t verbosity = 1); + touch (context&, const path&, bool create, uint16_t verbosity = 1); // Return the modification time for an existing regular file and // timestamp_nonexistent otherwise. Print the diagnostics and fail on system @@ -88,22 +90,19 @@ namespace build2 fs_status rmfile (const path&, const T& target, uint16_t verbosity = 1); - inline fs_status - rmfile (const path& f, int verbosity = 1) // Literal overload (int). - { - return rmfile (f, f, static_cast (verbosity)); - } + fs_status + rmfile (context&, const path&, uint16_t verbosity = 1); - inline fs_status - rmfile (const path& f, uint16_t verbosity) // Overload (verb_never). - { - return rmfile (f, f, verbosity); - } + fs_status + rmfile (const path&, int = 1) = delete; + + fs_status + rmfile (const path&, uint16_t) = delete; // Similar to rmfile() but for symlinks. // LIBBUILD2_SYMEXPORT fs_status - rmsymlink (const path&, bool dir, uint16_t verbosity); + rmsymlink (context&, const path&, bool dir, uint16_t verbosity); // Similar to rmfile() but for directories (note: not -r). // @@ -113,27 +112,26 @@ namespace build2 fs_status rmdir (const dir_path&, const T& target, uint16_t verbosity = 1); - inline fs_status - rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int). - { - return rmdir (d, d, static_cast (verbosity)); - } + fs_status + rmdir (context&, const dir_path&, uint16_t verbosity = 1); - inline fs_status - rmdir (const dir_path& d, uint16_t verbosity) // Overload (verb_never). - { - return rmdir (d, d, verbosity); - } + fs_status + rmdir (const dir_path&, int = 1) = delete; + + fs_status + rmdir (const dir_path&, uint16_t) = delete; // Remove the directory recursively (unless dry-run) and print the standard // diagnostics starting from the specified verbosity level. Note that this // function returns not_empty if we try to remove a working directory. If // the dir argument is false, then the directory itself is not removed. // - // @@ Collides (via ADL) with butl::rmdir_r(), which sucks. - // LIBBUILD2_SYMEXPORT fs_status - rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1); + rmdir_r (context& ctx, + const dir_path&, bool dir = true, uint16_t verbosity = 1); + + fs_status + rmdir_r (const dir_path&, bool = true, uint16_t = 1) = delete; // Check for a file, directory or filesystem entry existence. Print the // diagnostics and fail on system error, unless ignore_error is true. @@ -163,7 +161,8 @@ namespace build2 // Create a directory containing an empty .buildignore file. // LIBBUILD2_SYMEXPORT fs_status - mkdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1); + mkdir_buildignore (context&, + const dir_path&, const path&, uint16_t verbosity = 1); // Return true if the directory is empty or only contains the .buildignore // file. Fail if the directory doesn't exist. @@ -174,9 +173,11 @@ namespace build2 // Remove a directory if it is empty or only contains the .buildignore file. // LIBBUILD2_SYMEXPORT fs_status - rmdir_buildignore (const dir_path&, const path&, uint16_t verbosity = 1); + rmdir_buildignore (context&, + const dir_path&, const path&, uint16_t verbosity = 1); } +#include #include #endif // LIBBUILD2_FILESYSTEM_HXX diff --git a/libbuild2/filesystem.ixx b/libbuild2/filesystem.ixx new file mode 100644 index 0000000..6dab3ad --- /dev/null +++ b/libbuild2/filesystem.ixx @@ -0,0 +1,40 @@ +// file : libbuild2/filesystem.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + template + fs_status + rmfile (context&, const path&, const T&, uint16_t); + + template + inline fs_status + rmfile (const path& f, const T& t, uint16_t v) + { + return rmfile (t.ctx, f, t, v); + } + + inline fs_status + rmfile (context& ctx, const path& f, uint16_t v) + { + return rmfile (ctx, f, f, v); + } + + template + fs_status + rmdir (context&, const dir_path&, const T&, uint16_t); + + template + inline fs_status + rmdir (const dir_path& d, const T& t, uint16_t v) + { + return rmdir (t.ctx, d, t, v); + } + + inline fs_status + rmdir (context& ctx, const dir_path& d, uint16_t v) + { + return rmdir (ctx, d, d, v); + } +} diff --git a/libbuild2/filesystem.txx b/libbuild2/filesystem.txx index 057975a..e3cdef3 100644 --- a/libbuild2/filesystem.txx +++ b/libbuild2/filesystem.txx @@ -4,14 +4,13 @@ #include // is_base_of -#include // dry_run #include namespace build2 { template fs_status - rmfile (const path& f, const T& t, uint16_t v) + rmfile (context& ctx, const path& f, const T& t, uint16_t v) { using namespace butl; @@ -34,7 +33,7 @@ namespace build2 try { - rs = dry_run + rs = ctx.dry_run ? file_exists (f) ? rmfile_status::success : rmfile_status::not_exist : try_rmfile (f); } @@ -52,7 +51,7 @@ namespace build2 template fs_status - rmdir (const dir_path& d, const T& t, uint16_t v) + rmdir (context& ctx, const dir_path& d, const T& t, uint16_t v) { using namespace butl; @@ -75,7 +74,7 @@ namespace build2 rmdir_status rs; try { - rs = dry_run + rs = ctx.dry_run ? dir_exists (d) ? rmdir_status::success : rmdir_status::not_exist : !(w = work.sub (d)) ? try_rmdir (d) : rmdir_status::not_empty; } diff --git a/libbuild2/function.cxx b/libbuild2/function.cxx index 2d4dce9..ebf880a 100644 --- a/libbuild2/function.cxx +++ b/libbuild2/function.cxx @@ -275,7 +275,7 @@ namespace build2 { size_t n (name.size ()); - for (auto i (functions.begin ()); i != functions.end (); ++i) + for (auto i (begin ()); i != end (); ++i) { const string& q (i->first); const function_overload& f (i->second); @@ -352,12 +352,12 @@ namespace build2 n.insert (0, qual); } - auto i (qn.empty () ? functions.end () : functions.insert (move (qn), f)); - auto j (functions.insert (move (n), move (f))); + auto i (qn.empty () ? map_.end () : map_.insert (move (qn), f)); + auto j (map_.insert (move (n), move (f))); // If we have both, then set alternative names. // - if (i != functions.end ()) + if (i != map_.end ()) { i->second.alt_name = j->first.c_str (); j->second.alt_name = i->first.c_str (); @@ -366,35 +366,30 @@ namespace build2 // Static-initialize the function map and populate with builtin functions. // - function_map functions; - - void builtin_functions (); // functions-builtin.cxx - void filesystem_functions (); // functions-filesystem.cxx - void name_functions (); // functions-name.cxx - void path_functions (); // functions-path.cxx - void process_functions (); // functions-process.cxx - void process_path_functions (); // functions-process-path.cxx - void regex_functions (); // functions-regex.cxx - void string_functions (); // functions-string.cxx - void target_triplet_functions (); // functions-target-triplet.cxx - void project_name_functions (); // functions-target-triplet.cxx - - struct functions_init - { - functions_init () - { - builtin_functions (); - filesystem_functions (); - name_functions (); - path_functions (); - process_functions (); - process_path_functions (); - regex_functions (); - string_functions (); - target_triplet_functions (); - project_name_functions (); - } - }; - static const functions_init init_; + void builtin_functions (function_map&); // functions-builtin.cxx + void filesystem_functions (function_map&); // functions-filesystem.cxx + void name_functions (function_map&); // functions-name.cxx + void path_functions (function_map&); // functions-path.cxx + void process_functions (function_map&); // functions-process.cxx + void process_path_functions (function_map&); // functions-process-path.cxx + void regex_functions (function_map&); // functions-regex.cxx + void string_functions (function_map&); // functions-string.cxx + void target_triplet_functions (function_map&); // functions-target-triplet.cxx + void project_name_functions (function_map&); // functions-target-triplet.cxx + + void + register_builtin_functions (function_map& m) + { + builtin_functions (m); + filesystem_functions (m); + name_functions (m); + path_functions (m); + process_functions (m); + process_path_functions (m); + regex_functions (m); + string_functions (m); + target_triplet_functions (m); + project_name_functions (m); + } } diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx index 51c17c0..bb3fe3a 100644 --- a/libbuild2/function.hxx +++ b/libbuild2/function.hxx @@ -216,7 +216,8 @@ namespace build2 map_type map_; }; - LIBBUILD2_SYMEXPORT extern function_map functions; + LIBBUILD2_SYMEXPORT void + register_builtin_functions (function_map&); class LIBBUILD2_SYMEXPORT function_family { @@ -237,9 +238,10 @@ namespace build2 // containing a leading dot is a shortcut notation for a qualified-only // name. // - explicit - function_family (string qual, function_impl* thunk = &default_thunk) - : qual_ (qual), thunk_ (thunk) {} + function_family (function_map& map, + string qual, + function_impl* thunk = &default_thunk) + : map_ (map), qual_ (move (qual)), thunk_ (thunk) {} struct entry; @@ -247,13 +249,14 @@ namespace build2 operator[] (string name) const; static bool - defined (string qual) + defined (function_map& map, string qual) { qual += '.'; - return functions.defined (qual); + return map.defined (qual); } private: + function_map& map_; const string qual_; function_impl* thunk_; }; @@ -744,6 +747,7 @@ namespace build2 struct LIBBUILD2_SYMEXPORT function_family::entry { + function_map& map_; string name; const string& qual; function_impl* thunk; @@ -898,7 +902,7 @@ namespace build2 inline auto function_family:: operator[] (string name) const -> entry { - return entry {move (name), qual_, thunk_}; + return entry {map_, move (name), qual_, thunk_}; } } diff --git a/libbuild2/function.test.cxx b/libbuild2/function.test.cxx index 2380987..bd0be62 100644 --- a/libbuild2/function.test.cxx +++ b/libbuild2/function.test.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -41,9 +42,12 @@ namespace build2 // init_diag (1); init (nullptr, argv[0]); - reset (strings ()); // No command line variables. + scheduler sched (1); // Serial execution. + context ctx (sched); - function_family f ("dummy"); + auto& functions (ctx.functions); + + function_family f (functions, "dummy"); f["fail"] = []() {fail << "failed" << endf;}; f["fail_arg"] = [](names a) {return convert (move (a[0]));}; @@ -114,9 +118,9 @@ namespace build2 try { - scope& s (*scope::global_); + scope& s (ctx.global_scope.rw ()); - parser p; + parser p (ctx); p.parse_buildfile (cin, path ("buildfile"), s, s); } catch (const failed&) diff --git a/libbuild2/functions-builtin.cxx b/libbuild2/functions-builtin.cxx index 44ae534..2acd5b4 100644 --- a/libbuild2/functions-builtin.cxx +++ b/libbuild2/functions-builtin.cxx @@ -24,9 +24,9 @@ namespace build2 } void - builtin_functions () + builtin_functions (function_map& m) { - function_family f ("builtin"); + function_family f (m, "builtin"); f["type"] = [](value* v) {return v->type != nullptr ? v->type->name : "";}; diff --git a/libbuild2/functions-filesystem.cxx b/libbuild2/functions-filesystem.cxx index d98c75d..2fcd305 100644 --- a/libbuild2/functions-filesystem.cxx +++ b/libbuild2/functions-filesystem.cxx @@ -121,9 +121,9 @@ namespace build2 } void - filesystem_functions () + filesystem_functions (function_map& m) { - function_family f ("filesystem"); + function_family f (m, "filesystem"); // path_search // diff --git a/libbuild2/functions-name.cxx b/libbuild2/functions-name.cxx index a8e08b6..8013a0c 100644 --- a/libbuild2/functions-name.cxx +++ b/libbuild2/functions-name.cxx @@ -33,9 +33,9 @@ namespace build2 } void - name_functions () + name_functions (function_map& m) { - function_family f ("name"); + function_family f (m, "name"); // These functions treat a name as a target/prerequisite name. // @@ -97,7 +97,7 @@ namespace build2 // Name-specific overloads from builtins. // - function_family b ("builtin"); + function_family b (m, "builtin"); b[".concat"] = [](dir_path d, name n) { diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx index 6e39812..03f9be3 100644 --- a/libbuild2/functions-path.cxx +++ b/libbuild2/functions-path.cxx @@ -96,9 +96,9 @@ namespace build2 } void - path_functions () + path_functions (function_map& m) { - function_family f ("path", &path_thunk); + function_family f (m, "path", &path_thunk); // string // @@ -343,7 +343,7 @@ namespace build2 // Path-specific overloads from builtins. // - function_family b ("builtin", &path_thunk); + function_family b (m, "builtin", &path_thunk); b[".concat"] = &concat_path_string; b[".concat"] = &concat_dir_path_string; diff --git a/libbuild2/functions-process-path.cxx b/libbuild2/functions-process-path.cxx index 65e426b..124bd55 100644 --- a/libbuild2/functions-process-path.cxx +++ b/libbuild2/functions-process-path.cxx @@ -10,9 +10,9 @@ using namespace std; namespace build2 { void - process_path_functions () + process_path_functions (function_map& m) { - function_family f ("process_path"); + function_family f (m, "process_path"); // As discussed in value_traits, we always have recall. // diff --git a/libbuild2/functions-process.cxx b/libbuild2/functions-process.cxx index 83188d3..2cc3385 100644 --- a/libbuild2/functions-process.cxx +++ b/libbuild2/functions-process.cxx @@ -191,9 +191,9 @@ namespace build2 } void - process_functions () + process_functions (function_map& m) { - function_family f ("process"); + function_family f (m, "process"); // $process.run([ ...]) // diff --git a/libbuild2/functions-project-name.cxx b/libbuild2/functions-project-name.cxx index 163e865..f70a1e7 100644 --- a/libbuild2/functions-project-name.cxx +++ b/libbuild2/functions-project-name.cxx @@ -10,9 +10,9 @@ using namespace std; namespace build2 { void - project_name_functions () + project_name_functions (function_map& m) { - function_family f ("project_name"); + function_family f (m, "project_name"); f["string"] = [](project_name p) {return move (p).string ();}; @@ -31,7 +31,7 @@ namespace build2 // Project name-specific overloads from builtins. // - function_family b ("builtin"); + function_family b (m, "builtin"); b[".concat"] = [](project_name n, string s) { diff --git a/libbuild2/functions-regex.cxx b/libbuild2/functions-regex.cxx index 2c478fe..339224e 100644 --- a/libbuild2/functions-regex.cxx +++ b/libbuild2/functions-regex.cxx @@ -339,9 +339,9 @@ namespace build2 } void - regex_functions () + regex_functions (function_map& m) { - function_family f ("regex"); + function_family f (m, "regex"); // $regex.match(, [, ]) // diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx index 22860cb..1e93943 100644 --- a/libbuild2/functions-string.cxx +++ b/libbuild2/functions-string.cxx @@ -10,9 +10,9 @@ using namespace std; namespace build2 { void - string_functions () + string_functions (function_map& m) { - function_family f ("string"); + function_family f (m, "string"); f["string"] = [](string s) {return s;}; @@ -23,7 +23,7 @@ namespace build2 // String-specific overloads from builtins. // - function_family b ("builtin"); + function_family b (m, "builtin"); b[".concat"] = [](string l, string r) {l += r; return l;}; diff --git a/libbuild2/functions-target-triplet.cxx b/libbuild2/functions-target-triplet.cxx index 4394c5a..9ae2514 100644 --- a/libbuild2/functions-target-triplet.cxx +++ b/libbuild2/functions-target-triplet.cxx @@ -10,15 +10,15 @@ using namespace std; namespace build2 { void - target_triplet_functions () + target_triplet_functions (function_map& m) { - function_family f ("target_triplet"); + function_family f (m, "target_triplet"); f["string"] = [](target_triplet t) {return t.string ();}; // Target triplet-specific overloads from builtins. // - function_family b ("builtin"); + function_family b (m, "builtin"); b[".concat"] = [](target_triplet l, string sr) {return l.string () + sr;}; b[".concat"] = [](string sl, target_triplet r) {return sl + r.string ();}; diff --git a/libbuild2/in/init.cxx b/libbuild2/in/init.cxx index a067ec3..8b27336 100644 --- a/libbuild2/in/init.cxx +++ b/libbuild2/in/init.cxx @@ -36,7 +36,7 @@ namespace build2 // Enter variables. // { - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); // Alternative variable substitution symbol with '$' being the // default. diff --git a/libbuild2/in/rule.cxx b/libbuild2/in/rule.cxx index 434250e..de7ad88 100644 --- a/libbuild2/in/rule.cxx +++ b/libbuild2/in/rule.cxx @@ -435,10 +435,10 @@ namespace build2 return convert ( v.type == nullptr ? move (v) - : functions.call (&t.base_scope (), - "string", - vector_view (&v, 1), - l)); + : t.ctx.functions.call (&t.base_scope (), + "string", + vector_view (&v, 1), + l)); } catch (const invalid_argument& e) { diff --git a/libbuild2/install/functions.cxx b/libbuild2/install/functions.cxx index f067918..6052dd9 100644 --- a/libbuild2/install/functions.cxx +++ b/libbuild2/install/functions.cxx @@ -14,9 +14,9 @@ namespace build2 namespace install { void - functions () + functions (function_map& m) { - function_family f ("install"); + function_family f (m, "install"); // Resolve potentially relative install.* value to an absolute directory // based on (other) install.* values visible from the calling scope. diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index 060007b..d2321b5 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -62,7 +62,7 @@ namespace build2 vn += name; } vn += var; - const variable& vr (var_pool.rw (r).insert (move (vn), true)); + const variable& vr (r.ctx.var_pool.rw (r).insert (move (vn), true)); l = dv != nullptr ? config::required (r, vr, *dv, override).first @@ -79,7 +79,7 @@ namespace build2 vn = "install."; vn += name; vn += var; - const variable& vr (var_pool.rw (r).insert (move (vn))); + const variable& vr (r.ctx.var_pool.rw (r).insert (move (vn))); value& v (r.assign (vr)); @@ -122,11 +122,12 @@ namespace build2 // This one doesn't have config.* value (only set in a buildfile). // if (!global) - var_pool.rw (r).insert (string ("install.") + n + ".subdirs"); + r.ctx.var_pool.rw (r).insert ( + string ("install.") + n + ".subdirs"); } void - functions (); // functions.cxx + functions (function_map&); // functions.cxx bool boot (scope& rs, const location&, unique_ptr&) @@ -134,11 +135,13 @@ namespace build2 tracer trace ("install::boot"); l5 ([&]{trace << "for " << rs;}); + context& ctx (rs.ctx); + // Register install function family if this is the first instance of the // install modules. // - if (!function_family::defined ("install")) - functions (); + if (!function_family::defined (ctx.functions, "install")) + functions (ctx.functions); // Register our operations. // @@ -192,7 +195,7 @@ namespace build2 // Enter module variables. // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); // Note that the set_dir() calls below enter some more. // diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx index 1135ad6..a2ad7d0 100644 --- a/libbuild2/install/operation.cxx +++ b/libbuild2/install/operation.cxx @@ -4,6 +4,8 @@ #include +#include + using namespace std; using namespace butl; diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 48a404b..7cee10e 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -698,6 +698,8 @@ namespace build2 const dir_path& d, bool verbose = true) { + context& ctx (rs.ctx); + // Here is the problem: if this is a dry-run, then we will keep showing // the same directory creation commands over and over again (because we // don't actually create them). There are two alternative ways to solve @@ -708,7 +710,7 @@ namespace build2 // with uninstall since the directories won't be empty (because we don't // actually uninstall any files). // - if (dry_run) + if (ctx.dry_run) return; dir_path chd (chroot_path (rs, d)); @@ -741,7 +743,7 @@ namespace build2 cstrings args; string reld ( - cast ((*global_scope)["build.host.class"]) == "windows" + cast (ctx.global_scope["build.host.class"]) == "windows" ? msys_path (chd) : relative (chd).string ()); @@ -780,12 +782,14 @@ namespace build2 const path& f, bool verbose) { + context& ctx (rs.ctx); + path relf (relative (f)); dir_path chd (chroot_path (rs, base.dir)); string reld ( - cast ((*global_scope)["build.host.class"]) == "windows" + cast (ctx.global_scope["build.host.class"]) == "windows" ? msys_path (chd) : relative (chd).string ()); @@ -818,7 +822,7 @@ namespace build2 else if (verb && verbose) text << "install " << t; - if (!dry_run) + if (!ctx.dry_run) run (pp, args); } @@ -829,6 +833,8 @@ namespace build2 const path& link, uint16_t verbosity) { + context& ctx (rs.ctx); + path rell (relative (chroot_path (rs, base.dir))); rell /= link; @@ -859,7 +865,7 @@ namespace build2 text << "install " << rell << " -> " << target; } - if (!dry_run) + if (!ctx.dry_run) run (pp, args); #else // The -f part. @@ -877,7 +883,7 @@ namespace build2 text << "install " << rell << " -> " << target; } - if (!dry_run) + if (!ctx.dry_run) try { // We have to go the roundabout way by adding directory to the target @@ -1014,7 +1020,7 @@ namespace build2 { // See install_d() for the rationale. // - if (dry_run) + if (rs.ctx.dry_run) return false; dir_path chd (chroot_path (rs, d)); @@ -1150,7 +1156,7 @@ namespace build2 if (verb >= verbosity && verb >= 2) text << "rm " << relf; - if (!dry_run) + if (!rs.ctx.dry_run) { try { @@ -1179,7 +1185,7 @@ namespace build2 if (verb >= verbosity && verb >= 2) print_process (args); - if (!dry_run) + if (!rs.ctx.dry_run) run (pp, args); } diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx index 13fcceb..24c82d8 100644 --- a/libbuild2/install/utility.hxx +++ b/libbuild2/install/utility.hxx @@ -24,7 +24,7 @@ namespace build2 { auto r ( s.target_vars[tt]["*"].insert ( - var_pool.rw (s).insert ("install"))); + s.ctx.var_pool.rw (s).insert ("install"))); if (r.second) // Already set by the user? r.first.get () = path_cast (move (d)); @@ -42,7 +42,7 @@ namespace build2 { auto r ( s.target_vars[tt]["*"].insert ( - var_pool.rw (s).insert ("install.mode"))); + s.ctx.var_pool.rw (s).insert ("install.mode"))); if (r.second) // Already set by the user? r.first.get () = move (m); diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx index b06b030..ff70de6 100644 --- a/libbuild2/module.cxx +++ b/libbuild2/module.cxx @@ -293,7 +293,7 @@ namespace build2 module_state {true, false, mf.init, nullptr, loc}).first; i->second.first = mf.boot (rs, loc, i->second.module); - rs.assign (var_pool.rw (rs).insert (mod + ".booted")) = true; + rs.assign (rs.ctx.var_pool.rw (rs).insert (mod + ".booted")) = true; } bool @@ -344,7 +344,7 @@ namespace build2 // buildfile-visible (where we use the term "load a module"; see the note // on terminology above) // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); value& lv (bs.assign (vp.insert (mod + ".loaded"))); value& cv (bs.assign (vp.insert (mod + ".configured"))); diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx index 200e52f..f583361 100644 --- a/libbuild2/module.hxx +++ b/libbuild2/module.hxx @@ -128,7 +128,7 @@ namespace build2 const string& name, const location&, bool optional = false, - const variable_map& config_hints = variable_map ()); + const variable_map& config_hints = empty_variable_map); // An alias to use from other modules (we could also distinguish between // boot and init). @@ -142,7 +142,7 @@ namespace build2 const string& name, const location& loc, bool optional = false, - const variable_map& config_hints = variable_map ()) + const variable_map& config_hints = empty_variable_map) { return init_module (root, base, name, loc, optional, config_hints); } diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx index 168ed5c..0bf87d5 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -81,7 +82,7 @@ namespace build2 // Create the base scope. Note that its existence doesn't mean it was // already setup as a base scope; it can be the same as root. // - auto i (scopes.rw (root).insert (out_base)); + auto i (root.ctx.scopes.rw (root).insert (out_base)); scope& base (setup_base (i, out_base, src_base)); // Load the buildfile unless it is implied. @@ -101,9 +102,10 @@ namespace build2 { tracer trace ("search"); - phase_lock pl (run_phase::match); + context& ctx (bs.ctx); + phase_lock pl (ctx, run_phase::match); - const target* t (targets.find (tk, trace)); + const target* t (ctx.targets.find (tk, trace)); // Only do the implied buildfile if we haven't loaded one. Failed that we // may try go this route even though we've concluded the implied buildfile @@ -131,8 +133,13 @@ namespace build2 { tracer trace ("match"); + if (ts.empty ()) + return; + + context& ctx (ts[0].as_target ().ctx); + { - phase_lock l (run_phase::match); + phase_lock l (ctx, run_phase::match); // Setup progress reporting if requested. // @@ -143,10 +150,10 @@ namespace build2 { size_t incr (stderr_term ? 1 : 10); // Scale depending on output type. - what = " targets to " + diag_do (a); + what = " targets to " + diag_do (ctx, a); - mg = sched.monitor ( - target_count, + mg = ctx.sched.monitor ( + ctx.target_count, incr, [incr, &what] (size_t c) -> size_t { @@ -165,7 +172,7 @@ namespace build2 size_t i (0), n (ts.size ()); { atomic_count task_count (0); - wait_guard wg (task_count, true); + wait_guard wg (ctx, task_count, true); for (; i != n; ++i) { @@ -177,7 +184,7 @@ namespace build2 // Bail out if the target has failed and we weren't instructed to // keep going. // - if (s == target_state::failed && !keep_going) + if (s == target_state::failed && !ctx.keep_going) { ++i; break; @@ -245,7 +252,7 @@ namespace build2 // Phase restored to load. // - assert (phase == run_phase::load); + assert (ctx.phase == run_phase::load); } void @@ -254,25 +261,30 @@ namespace build2 { tracer trace ("execute"); + if (ts.empty ()) + return; + + context& ctx (ts[0].as_target ().ctx); + // Reverse the order of targets if the execution mode is 'last'. // - if (current_mode == execution_mode::last) + if (ctx.current_mode == execution_mode::last) reverse (ts.begin (), ts.end ()); // Tune the scheduler. // - switch (current_inner_oif->concurrency) + switch (ctx.current_inner_oif->concurrency) { - case 0: sched.tune (1); break; // Run serially. - case 1: break; // Run as is. - default: assert (false); // Not yet supported. + case 0: ctx.sched.tune (1); break; // Run serially. + case 1: break; // Run as is. + default: assert (false); // Not yet supported. } - phase_lock pl (run_phase::execute); // Never switched. + phase_lock pl (ctx, run_phase::execute); // Never switched. // Set the dry-run flag. // - dry_run = dry_run_option; + ctx.dry_run = ctx.dry_run_option; // Setup progress reporting if requested. // @@ -281,20 +293,20 @@ namespace build2 if (prog && show_progress (1 /* max_verb */)) { - size_t init (target_count.load (memory_order_relaxed)); + size_t init (ctx.target_count.load (memory_order_relaxed)); size_t incr (init > 100 ? init / 100 : 1); // 1%. if (init != incr) { - what = "% of targets " + diag_did (a); + what = "% of targets " + diag_did (ctx, a); - mg = sched.monitor ( - target_count, + mg = ctx.sched.monitor ( + ctx.target_count, init - incr, - [init, incr, &what] (size_t c) -> size_t + [init, incr, &what, &ctx] (size_t c) -> size_t { size_t p ((init - c) * 100 / init); - size_t s (skip_count.load (memory_order_relaxed)); + size_t s (ctx.skip_count.load (memory_order_relaxed)); diag_progress_lock pl; diag_progress = ' '; @@ -318,7 +330,7 @@ namespace build2 // { atomic_count task_count (0); - wait_guard wg (task_count); + wait_guard wg (ctx, task_count); for (const action_target& at: ts) { @@ -331,7 +343,7 @@ namespace build2 // Bail out if the target has failed and we weren't instructed to keep // going. // - if (s == target_state::failed && !keep_going) + if (s == target_state::failed && !ctx.keep_going) break; } @@ -341,11 +353,11 @@ namespace build2 // We are now running serially. // - sched.tune (0); // Restore original scheduler settings. + ctx.sched.tune (0); // Restore original scheduler settings. // Clear the dry-run flag. // - dry_run = false; + ctx.dry_run = false; // Clear the progress if present. // @@ -361,9 +373,9 @@ namespace build2 // if (verb != 0) { - if (size_t s = skip_count.load (memory_order_relaxed)) + if (size_t s = ctx.skip_count.load (memory_order_relaxed)) { - text << "skipped " << diag_doing (a) << ' ' << s << " targets"; + text << "skipped " << diag_doing (ctx, a) << ' ' << s << " targets"; } } @@ -422,8 +434,8 @@ namespace build2 // We should have executed every target that we matched, provided we // haven't failed (in which case we could have bailed out early). // - assert (target_count.load (memory_order_relaxed) == 0); - assert (dependency_count.load (memory_order_relaxed) == 0); + assert (ctx.target_count.load (memory_order_relaxed) == 0); + assert (ctx.dependency_count.load (memory_order_relaxed) == 0); } const meta_operation_info mo_perform { @@ -471,7 +483,7 @@ namespace build2 if (rs.out_path () != out_base || rs.src_path () != src_base) fail (l) << "meta-operation info target must be project root directory"; - setup_base (scopes.rw (rs).insert (out_base), out_base, src_base); + setup_base (rs.ctx.scopes.rw (rs).insert (out_base), out_base, src_base); } void @@ -506,6 +518,8 @@ namespace build2 const scope& rs (*static_cast (ts[i].target)); + context& ctx (rs.ctx); + // Print [meta_]operation names. Due to the way our aliasing works, we // have to go through the [meta_]operation_table. // @@ -525,16 +539,16 @@ namespace build2 // This could be a simple project that doesn't set project name. // cout - << "project: " << cast_empty (rs[var_project]) << endl - << "version: " << cast_empty (rs[var_version]) << endl - << "summary: " << cast_empty (rs[var_project_summary]) << endl - << "url: " << cast_empty (rs[var_project_url]) << endl - << "src_root: " << cast (rs[var_src_root]) << endl - << "out_root: " << cast (rs[var_out_root]) << endl - << "amalgamation: " << cast_empty (rs[var_amalgamation]) << endl - << "subprojects: " << cast_empty (rs[var_subprojects]) << endl - << "operations:"; print_ops (rs.root_extra->operations, operation_table); cout << endl - << "meta-operations:"; print_ops (rs.root_extra->meta_operations, meta_operation_table); cout << endl; + << "project: " << cast_empty (rs[ctx.var_project]) << endl + << "version: " << cast_empty (rs[ctx.var_version]) << endl + << "summary: " << cast_empty (rs[ctx.var_project_summary]) << endl + << "url: " << cast_empty (rs[ctx.var_project_url]) << endl + << "src_root: " << cast (rs[ctx.var_src_root]) << endl + << "out_root: " << cast (rs[ctx.var_out_root]) << endl + << "amalgamation: " << cast_empty (rs[ctx.var_amalgamation]) << endl + << "subprojects: " << cast_empty (rs[ctx.var_subprojects]) << endl + << "operations:"; print_ops (rs.root_extra->operations, ctx.operation_table); cout << endl + << "meta-operations:"; print_ops (rs.root_extra->meta_operations, ctx.meta_operation_table); cout << endl; } } @@ -610,9 +624,4 @@ namespace build2 nullptr, nullptr }; - - // Tables. - // - string_table meta_operation_table; - string_table operation_table; } diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx index 86f93c6..520b37b 100644 --- a/libbuild2/operation.hxx +++ b/libbuild2/operation.hxx @@ -11,8 +11,6 @@ #include #include -#include -#include #include #include @@ -21,10 +19,15 @@ namespace build2 { class location; class scope; - class target_key; class target; + class target_key; + class context; + class include_type; struct prerequisite_member; + class value; + using values = small_vector; + struct opspec; // Meta-operation info. @@ -275,7 +278,7 @@ namespace build2 // If lifted is true then the operation name in opspec is bogus (has // been lifted) and the default/empty name should be assumed instead. // - using process_func = const string& (const variable_overrides&, + using process_func = const string& (context&, values&, vector_view&, bool lifted, @@ -295,11 +298,10 @@ namespace build2 return os << d.name; } - LIBBUILD2_SYMEXPORT extern butl::string_table - meta_operation_table; + using meta_operation_table = butl::string_table; - LIBBUILD2_SYMEXPORT extern butl::string_table operation_table; + using operation_table = butl::string_table; // These are "sparse" in the sense that we may have "holes" that // are represented as NULL pointers. Also, lookup out of bounds diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index c55d434..d346afc 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -129,13 +129,13 @@ namespace build2 tracer& tr) { auto r (process_target (p, n, o, loc)); - return targets.insert (*r.first, // target type - move (n.dir), - move (o.dir), - move (n.value), - move (r.second), // extension - implied, - tr).first; + return p.ctx.targets.insert (*r.first, // target type + move (n.dir), + move (o.dir), + move (n.value), + move (r.second), // extension + implied, + tr).first; } // Only find. @@ -148,12 +148,12 @@ namespace build2 tracer& tr) { auto r (process_target (p, n, o, loc)); - return targets.find (*r.first, // target type - n.dir, - o.dir, - n.value, - r.second, // extension - tr); + return p.ctx.targets.find (*r.first, // target type + n.dir, + o.dir, + n.value, + r.second, // extension + tr); } static pair> @@ -1750,7 +1750,7 @@ namespace build2 // Is this the 'foo=...' case? // size_t p (t.value.find ('=')); - auto& vp (var_pool.rw (*scope_)); + auto& vp (ctx.var_pool.rw (*scope_)); if (p != string::npos) var = &vp.insert (split (p), true /* overridable */); @@ -2452,7 +2452,7 @@ namespace build2 //@@ TODO: append namespace if any. } - return var_pool.rw (*scope_).insert (move (n), true /* overridable */); + return ctx.var_pool.rw (*scope_).insert (move (n), true /* overridable */); } void parser:: @@ -2649,7 +2649,7 @@ namespace build2 if (var.type == nullptr) { const bool o (true); // Allow overrides. - var_pool.update (const_cast (var), type, nullptr, &o); + ctx.var_pool.update (const_cast (var), type, nullptr, &o); } else if (var.type != type) fail (l) << "changing variable " << var << " type from " @@ -3967,7 +3967,7 @@ namespace build2 info << "use quoting to force untyped concatenation"; })); - p = functions.try_call ( + p = ctx.functions.try_call ( scope_, "builtin.concat", vector_view (a), loc); } @@ -4636,7 +4636,7 @@ namespace build2 // Note that we "move" args to call(). // - result_data = functions.call (scope_, name, args, loc); + result_data = ctx.functions.call (scope_, name, args, loc); what = "function call"; } else @@ -4744,7 +4744,7 @@ namespace build2 info (loc) << "while converting " << t << " to string"; })); - p = functions.try_call ( + p = ctx.functions.try_call ( scope_, "string", vector_view (&result_data, 1), loc); } @@ -5053,7 +5053,7 @@ namespace build2 // lexer l (is, *path_, 1 /* line */, "\'\"\\$("); lexer_ = &l; - scope_ = root_ = scope::global_; + scope_ = root_ = &ctx.global_scope.rw (); pbase_ = &work; // Use current working directory. target_ = nullptr; prerequisite_ = nullptr; @@ -5342,7 +5342,7 @@ namespace build2 // Lookup. // - const auto& var (var_pool.rw (*scope_).insert (move (name), true)); + const auto& var (ctx.var_pool.rw (*scope_).insert (move (name), true)); if (p != nullptr) { @@ -5435,12 +5435,12 @@ namespace build2 target* ct ( const_cast ( // Ok (serial execution). - targets.find (dir::static_type, // Explicit current dir target. - scope_->out_path (), - dir_path (), // Out tree target. - string (), - nullopt, - trace))); + ctx.targets.find (dir::static_type, // Explicit current dir target. + scope_->out_path (), + dir_path (), // Out tree target. + string (), + nullopt, + trace))); if (ct == nullptr) { @@ -5449,13 +5449,13 @@ namespace build2 // While this target is not explicitly mentioned in the buildfile, we // say that we behave as if it were. Thus not implied. // - ct = &targets.insert (dir::static_type, - scope_->out_path (), - dir_path (), - string (), - nullopt, - false, - trace).first; + ct = &ctx.targets.insert (dir::static_type, + scope_->out_path (), + dir_path (), + string (), + nullopt, + false, + trace).first; // Fall through. } else if (ct->implied) @@ -5487,7 +5487,7 @@ namespace build2 out = out_src (d, *root_); } - targets.insert ( + ctx.targets.insert ( move (d), move (out), p.leaf ().base ().string (), diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 79cbead..2f70a18 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -22,6 +22,7 @@ namespace build2 { class scope; class target; + class context; class prerequisite; class LIBBUILD2_SYMEXPORT parser @@ -31,7 +32,8 @@ namespace build2 // should only be bootstrapped. // explicit - parser (bool boot = false): fail ("error", &path_), boot_ (boot) {} + parser (context& c, bool boot = false) + : fail ("error", &path_), ctx (c), boot_ (boot) {} // Issue diagnostics and throw failed in case of an error. // @@ -645,6 +647,8 @@ namespace build2 const fail_mark fail; protected: + context& ctx; + bool pre_parse_ = false; bool boot_; diff --git a/libbuild2/prerequisite.cxx b/libbuild2/prerequisite.cxx index 7355323..7b815d5 100644 --- a/libbuild2/prerequisite.cxx +++ b/libbuild2/prerequisite.cxx @@ -64,7 +64,7 @@ namespace build2 ext (to_ext (t.ext ())), scope (t.base_scope ()), target (&t), - vars (false /* global */) + vars (t.ctx, false /* global */) { } @@ -92,29 +92,4 @@ namespace build2 return r; } - - // include() - // - include_type - include_impl (action a, - const target& t, - const string& v, - const prerequisite& p, - const target* m) - { - include_type r (false); - - if (v == "false") r = include_type::excluded; - else if (v == "adhoc") r = include_type::adhoc; - else if (v == "true") r = include_type::normal; - else - fail << "invalid " << var_include->name << " variable value " - << "'" << v << "' specified for prerequisite " << p; - - // Call the meta-operation override, if any (currently used by dist). - // - return current_mif->include == nullptr - ? r - : current_mif->include (a, t, prerequisite_member {p, m}, r); - } } diff --git a/libbuild2/prerequisite.hxx b/libbuild2/prerequisite.hxx index f79ce04..fe6d10a 100644 --- a/libbuild2/prerequisite.hxx +++ b/libbuild2/prerequisite.hxx @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,6 @@ namespace build2 { - class scope; class target; // Light-weight (by being shallow-pointing) prerequisite key, similar @@ -31,7 +31,7 @@ namespace build2 class prerequisite_key { public: - typedef build2::scope scope_type; + using scope_type = build2::scope; const optional& proj; target_key tk; // The .dir and .out members can be relative. @@ -117,7 +117,7 @@ namespace build2 name (move (n)), ext (move (e)), scope (s), - vars (false /* global */) {} + vars (s.ctx, false /* global */) {} // Make a prerequisite from a target. // @@ -192,38 +192,6 @@ namespace build2 } using prerequisites = vector; - - // Helpers for dealing with the prerequisite inclusion/exclusion (the - // 'include' buildfile variable, see var_include in context.hxx). - // - // Note that the include(prerequisite_member) overload is also provided. - // - // @@ Maybe this filtering should be incorporated into *_prerequisites() and - // *_prerequisite_members() logic? Could make normal > adhoc > excluded and - // then pass the "threshold". - // - class include_type - { - public: - enum value {excluded, adhoc, normal}; - - include_type (value v): v_ (v) {} - include_type (bool v): v_ (v ? normal : excluded) {} - - operator value () const {return v_;} - explicit operator bool () const {return v_ != excluded;} - - private: - value v_; - }; - - include_type - include (action, - const target&, - const prerequisite&, - const target* = nullptr); } -#include - #endif // LIBBUILD2_PREREQUISITE_HXX diff --git a/libbuild2/prerequisite.ixx b/libbuild2/prerequisite.ixx deleted file mode 100644 index d62af49..0000000 --- a/libbuild2/prerequisite.ixx +++ /dev/null @@ -1,34 +0,0 @@ -// file : libbuild2/prerequisite.ixx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -namespace build2 -{ - LIBBUILD2_SYMEXPORT include_type - include_impl (action, - const target&, - const string&, - const prerequisite&, - const target*); - - LIBBUILD2_SYMEXPORT extern const variable* var_include; // context.cxx - - inline include_type - include (action a, const target& t, const prerequisite& p, const target* m) - { - // Most of the time this variable will not be specified, so let's optimize - // for that. - // - if (p.vars.empty ()) - return true; - - const string* v (cast_null (p.vars[var_include])); - - if (v == nullptr) - return true; - - return include_impl (a, t, *v, p, m); - } -} diff --git a/libbuild2/rule.cxx b/libbuild2/rule.cxx index 0ade8a3..d69e817 100644 --- a/libbuild2/rule.cxx +++ b/libbuild2/rule.cxx @@ -192,7 +192,7 @@ namespace build2 { if (verb >= 2) text << "mkdir " << d; - else if (verb && current_diag_noise) + else if (verb && t.ctx.current_diag_noise) text << "mkdir " << t; }; @@ -279,7 +279,7 @@ namespace build2 // (or is current working directory). In this case rmdir() will issue a // warning when appropriate. // - target_state ts (rmdir (t.dir, t, current_diag_noise ? 1 : 2) + target_state ts (rmdir (t.dir, t, t.ctx.current_diag_noise ? 1 : 2) ? target_state::changed : target_state::unchanged); diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 1ad7455..6e00511 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -354,7 +354,7 @@ namespace build2 // global scope. // if (inner_proj == nullptr) - inner_proj = global_scope; + inner_proj = &ctx.global_scope; // Now find our "stem", that is, the value to which we will be appending // suffixes and prepending prefixes. This is either the original or the @@ -430,8 +430,8 @@ namespace build2 // Check the cache. // variable_override_cache& cache ( - inner_proj == global_scope - ? global_override_cache + inner_proj == &ctx.global_scope + ? ctx.global_override_cache : inner_proj->root_extra->override_cache); pair entry ( @@ -595,7 +595,7 @@ namespace build2 // for (const scope* s (this); s != nullptr; - s = s->root () ? global_scope : s->parent_scope ()) + s = s->root () ? &s->global_scope () : s->parent_scope ()) { if (s->target_types.empty ()) continue; @@ -619,7 +619,9 @@ namespace build2 { // Pretty much the same logic as in find_target_type() above. // - for (; s != nullptr; s = s->root () ? global_scope : s->parent_scope ()) + for (; + s != nullptr; + s = s->root () ? &s->global_scope () : s->parent_scope ()) { if (s->target_types.empty ()) continue; @@ -730,7 +732,7 @@ namespace build2 // factor it back into the name (this way we won't assert when printing // diagnostics; see to_stream(target_key) for details). // - if (ext && + if (ext && tt->fixed_extension == nullptr && tt->default_extension == nullptr) { @@ -743,7 +745,8 @@ namespace build2 } static target* - derived_tt_factory (const target_type& t, dir_path d, dir_path o, string n) + derived_tt_factory (context& c, + const target_type& t, dir_path d, dir_path o, string n) { // Pass our type to the base factory so that it can detect that it is // being called to construct a derived target. This can be used, for @@ -756,7 +759,7 @@ namespace build2 const target_type* bt (t.base); for (; bt->factory == &derived_tt_factory; bt = bt->base) ; - target* r (bt->factory (t, move (d), move (o), move (n))); + target* r (bt->factory (c, t, move (d), move (o), move (n))); r->derived_type = &t; return r; } @@ -798,12 +801,12 @@ namespace build2 // dt->default_extension = ext && dt->fixed_extension == nullptr - ? &target_extension_var + ? &target_extension_var : nullptr; dt->pattern = dt->fixed_extension != nullptr ? nullptr /*&target_pattern_fix*/ : - dt->default_extension != nullptr ? &target_pattern_var : + dt->default_extension != nullptr ? &target_pattern_var : nullptr; // There is actually a difference between "fixed fixed" (like man1{}) and @@ -818,23 +821,15 @@ namespace build2 return target_types.insert (name, move (dt)); } - scope* scope::global_; - scope::variable_override_cache scope::global_override_cache; - // scope_map // - scope_map scope_map::instance; - const scope_map& scope_map::cinstance = scope_map::instance; - const scope_map& scopes = scope_map::cinstance; - - const scope* global_scope; auto scope_map:: insert (const dir_path& k, bool root) -> iterator { scope_map_base& m (*this); - auto er (m.emplace (k, scope (true))); // Global. + auto er (m.emplace (k, scope (ctx, true /* global */))); scope& s (er.first->second); // If this is a new scope, update the parent chain. diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 0c4094b..35f07dd 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -29,6 +29,10 @@ namespace build2 class LIBBUILD2_SYMEXPORT scope { public: + // Context this scope belongs to. + // + context& ctx; + // Absolute and normalized. // const dir_path& out_path () const {return *out_path_;} @@ -71,6 +75,11 @@ namespace build2 scope* weak_scope (); const scope* weak_scope () const; + // Global scope. + // + scope& global_scope () {return const_cast (ctx.global_scope);} + const scope& global_scope () const {return ctx.global_scope;} + // Return true if the specified root scope is a sub-scope of this root // scope. Note that both scopes must be root. // @@ -102,7 +111,7 @@ namespace build2 lookup operator[] (const string& name) const { - const variable* var (var_pool.find (name)); + const variable* var (ctx.var_pool.find (name)); return var != nullptr ? operator[] (*var) : lookup (); } @@ -159,7 +168,7 @@ namespace build2 value& assign (string name) { - return assign (variable_pool::instance.insert (move (name))); + return assign (ctx.var_pool.rw (*this).insert (move (name))); } // Assign a typed non-overridable variable with normal visibility. @@ -168,7 +177,7 @@ namespace build2 value& assign (string name) { - return vars.assign (variable_pool::instance.insert (move (name))); + return vars.assign (ctx.var_pool.rw (*this).insert (move (name))); } // Return a value suitable for appending. If the variable does not @@ -184,20 +193,6 @@ namespace build2 // variable_type_map target_vars; - // Variable override caches. Only on project roots (in root_extra) plus a - // global one for the global scope. - // - // The key is the variable plus the innermost (scope-wise) variable map to - // which this override applies. See find_override() for details. - // - // Note: since it can be modified on any lookup (including during the - // execute phase), the cache is protected by its own mutex shard. - // - using variable_override_cache = variable_cache>; - - static variable_override_cache global_override_cache; - // Set of buildfiles already loaded for this scope. The included // buildfiles are checked against the project's root scope while // imported -- against the global scope (global_scope). @@ -301,7 +296,7 @@ namespace build2 // module_map modules; - // Variable override cache (see above). + // Variable override cache. // mutable variable_override_cache override_cache; }; @@ -333,18 +328,10 @@ namespace build2 scope& rw () const { - assert (phase == run_phase::load); + assert (ctx.phase == run_phase::load); return const_cast (*this); } - // RW access to global scope (RO via global global_scope below). - // - scope& - global () {return *global_;} - - public: - static scope* global_; // Normally not accessed directly. - private: friend class parser; friend class scope_map; @@ -356,8 +343,8 @@ namespace build2 friend LIBBUILD2_SYMEXPORT scope& create_bootstrap_inner (scope&, const dir_path&); - explicit - scope (bool global): vars (global), target_vars (global) {} + scope (context& c, bool global) + : ctx (c), vars (c, global), target_vars (c, global) {} scope* parent_; scope* root_; @@ -405,7 +392,7 @@ namespace build2 { public: temp_scope (scope& p) - : scope (false) // Not global. + : scope (p.ctx, false /* global */) { out_path_ = p.out_path_; src_path_ = p.src_path_; @@ -461,7 +448,7 @@ namespace build2 scope_map& rw () const { - assert (phase == run_phase::load); + assert (ctx.phase == run_phase::load); return const_cast (*this); } @@ -469,24 +456,21 @@ namespace build2 rw (scope&) const {return const_cast (*this);} private: - LIBBUILD2_SYMEXPORT static scope_map instance; + friend class context; + + explicit + scope_map (context& c): ctx (c) {} // Entities that can access bypassing the lock proof. // friend int main (int, char*[]); - friend LIBBUILD2_SYMEXPORT variable_overrides reset (const strings&); LIBBUILD2_SYMEXPORT scope& find (const dir_path&); - public: - // For var_pool initialization. - // - LIBBUILD2_SYMEXPORT static const scope_map& cinstance; + private: + context& ctx; }; - - LIBBUILD2_SYMEXPORT extern const scope_map& scopes; - LIBBUILD2_SYMEXPORT extern const scope* global_scope; } #include diff --git a/libbuild2/scope.ixx b/libbuild2/scope.ixx index aa1247f..14907d3 100644 --- a/libbuild2/scope.ixx +++ b/libbuild2/scope.ixx @@ -83,9 +83,9 @@ namespace build2 } inline const project_name& - project (const scope& root) + project (const scope& rs) { - auto l (root[var_project]); + auto l (rs[rs.ctx.var_project]); return l ? cast (l) : empty_project_name; } } diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx index 917d750..199bc10 100644 --- a/libbuild2/search.cxx +++ b/libbuild2/search.cxx @@ -16,7 +16,7 @@ using namespace butl; namespace build2 { const target* - search_existing_target (const prerequisite_key& pk) + search_existing_target (context& ctx, const prerequisite_key& pk) { tracer trace ("search_existing_target"); @@ -69,7 +69,8 @@ namespace build2 o.clear (); } - const target* t (targets.find (*tk.type, d, o, *tk.name, tk.ext, trace)); + const target* t ( + ctx.targets.find (*tk.type, d, o, *tk.name, tk.ext, trace)); if (t != nullptr) l5 ([&]{trace << "existing target " << *t @@ -79,7 +80,7 @@ namespace build2 } const target* - search_existing_file (const prerequisite_key& cpk) + search_existing_file (context& ctx, const prerequisite_key& cpk) { tracer trace ("search_existing_file"); @@ -184,7 +185,7 @@ namespace build2 // Find or insert. Note that we are using our updated extension. // auto r ( - targets.insert ( + ctx.targets.insert ( *tk.type, move (d), move (out), *tk.name, ext, true, trace)); // Has to be a file_target. @@ -201,7 +202,7 @@ namespace build2 } const target& - create_new_target (const prerequisite_key& pk) + create_new_target (context& ctx, const prerequisite_key& pk) { tracer trace ("create_new_target"); @@ -227,13 +228,13 @@ namespace build2 // // @@ OUT: same story as in search_existing_target() re out. // - auto r (targets.insert (*tk.type, - move (d), - *tk.out, - *tk.name, - tk.ext, - true /* implied */, - trace)); + auto r (ctx.targets.insert (*tk.type, + move (d), + *tk.out, + *tk.name, + tk.ext, + true /* implied */, + trace)); const target& t (r.first); l5 ([&]{trace << (r.second ? "new" : "existing") << " target " << t diff --git a/libbuild2/search.hxx b/libbuild2/search.hxx index b281b12..63ea6b1 100644 --- a/libbuild2/search.hxx +++ b/libbuild2/search.hxx @@ -13,12 +13,14 @@ namespace build2 { class target; + class context; class prerequisite_key; - // Search for an existing target in this prerequisite's scope. + // Search for an existing target in this prerequisite's scope. Scope can be + // NULL if directories are absolute. // LIBBUILD2_SYMEXPORT const target* - search_existing_target (const prerequisite_key&); + search_existing_target (context&, const prerequisite_key&); // Search for an existing file. If the prerequisite directory is relative, // then look in the scope's src directory. Otherwise, if the absolute @@ -30,12 +32,12 @@ namespace build2 // contains the search paths. But there wasn't any need for this yet. // LIBBUILD2_SYMEXPORT const target* - search_existing_file (const prerequisite_key&); + search_existing_file (context&, const prerequisite_key&); // Create a new target in this prerequisite's scope. // LIBBUILD2_SYMEXPORT const target& - create_new_target (const prerequisite_key&); + create_new_target (context&, const prerequisite_key&); } #endif // LIBBUILD2_SEARCH_HXX diff --git a/libbuild2/target-key.hxx b/libbuild2/target-key.hxx index e23991d..4df2b1f 100644 --- a/libbuild2/target-key.hxx +++ b/libbuild2/target-key.hxx @@ -53,7 +53,7 @@ namespace build2 else { // Note that for performance reasons here we use the specified extension - // without calling fixed_extension(). + // without calling fixed_extension() to verify it matches. // const char* xe (x.ext ? x.ext->c_str () diff --git a/libbuild2/target-type.hxx b/libbuild2/target-type.hxx index 3537c90..8b308c3 100644 --- a/libbuild2/target-type.hxx +++ b/libbuild2/target-type.hxx @@ -16,6 +16,7 @@ namespace build2 { class scope; class target; + class context; class target_key; class prerequisite_key; @@ -62,10 +63,15 @@ namespace build2 const char* name; const target_type* base; - target* (*factory) (const target_type&, dir_path, dir_path, string); + target* (*factory) (context&, + const target_type&, + dir_path, + dir_path, + string); const char* (*fixed_extension) (const target_key&, const scope* root); + optional (*default_extension) (const target_key&, const scope& base, const char*, diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index c823e85..dbca6cd 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -67,7 +67,7 @@ namespace build2 const string& target:: ext (string v) { - ulock l (targets.mutex_); + ulock l (ctx.targets.mutex_); // Once the extension is set, it is immutable. However, it is possible // that someone has already "branded" this target with a different @@ -102,7 +102,7 @@ namespace build2 // If this target is from the src tree, use its out directory to find // the scope. // - return scopes.find (out_dir ()); + return ctx.scopes.find (out_dir ()); } const scope& target:: @@ -290,10 +290,36 @@ namespace build2 } } - // target_set + // include() // - target_set targets; + include_type + include_impl (action a, + const target& t, + const string& v, + const prerequisite& p, + const target* m) + { + context& ctx (t.ctx); + + include_type r (false); + + if (v == "false") r = include_type::excluded; + else if (v == "adhoc") r = include_type::adhoc; + else if (v == "true") r = include_type::normal; + else + fail << "invalid " << ctx.var_include->name << " variable value " + << "'" << v << "' specified for prerequisite " << p; + + // Call the meta-operation override, if any (currently used by dist). + // + if (auto f = ctx.current_mif->include) + r = f (a, t, prerequisite_member {p, m}, r); + return r; + } + + // target_set + // const target* target_set:: find (const target_key& k, tracer& trace) const { @@ -367,14 +393,14 @@ namespace build2 // We sometimes call insert() even if we expect to find an existing // target in order to keep the same code (see cc/search_library()). // - assert (phase != run_phase::execute); + assert (ctx.phase != run_phase::execute); optional e ( tt.fixed_extension != nullptr ? string (tt.fixed_extension (tk, nullptr /* root scope */)) : move (tk.ext)); - t = tt.factory (tt, move (dir), move (out), move (name)); + t = tt.factory (ctx, tt, move (dir), move (out), move (name)); // Re-lock for exclusive access. In the meantime, someone could have // inserted this target so emplace() below could return false, in which @@ -392,8 +418,8 @@ namespace build2 { t->ext_ = &i->first.ext; t->implied = implied; - t->state.data[0].target_ = t; - t->state.data[1].target_ = t; + t->state.inner.target_ = t; + t->state.outer.target_ = t; return pair (*t, move (ul)); } @@ -432,7 +458,7 @@ namespace build2 { // The implied flag can only be cleared during the load phase. // - assert (phase == run_phase::load); + assert (ctx.phase == run_phase::load); // Clear the implied flag. // @@ -538,7 +564,7 @@ namespace build2 // const mtime_target* t (this); - switch (phase) + switch (ctx.phase) { case run_phase::load: break; case run_phase::match: @@ -546,10 +572,11 @@ namespace build2 // Similar logic to matched_state_impl(). // const opstate& s (state[action () /* inner */]); - size_t o (s.task_count.load (memory_order_relaxed) - // Synchronized. - target::count_base ()); - if (o != target::offset_applied && o != target::offset_executed) + // Note: already synchronized. + size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); + + if (o != offset_applied && o != offset_executed) break; } // Fall through. @@ -658,25 +685,25 @@ namespace build2 // const target* - target_search (const target&, const prerequisite_key& pk) + target_search (const target& t, const prerequisite_key& pk) { // The default behavior is to look for an existing target in the // prerequisite's directory scope. // - return search_existing_target (pk); + return search_existing_target (t.ctx, pk); } const target* - file_search (const target&, const prerequisite_key& pk) + file_search (const target& t, const prerequisite_key& pk) { // First see if there is an existing target. // - if (const target* t = search_existing_target (pk)) - return t; + if (const target* e = search_existing_target (t.ctx, pk)) + return e; // Then look for an existing file in the src tree. // - return search_existing_file (pk); + return search_existing_file (t.ctx, pk); } void @@ -753,17 +780,17 @@ namespace build2 }; static const target* - alias_search (const target&, const prerequisite_key& pk) + alias_search (const target& t, const prerequisite_key& pk) { // For an alias we don't want to silently create a target since it will do // nothing and it most likely not what the user intended. // - const target* t (search_existing_target (pk)); + const target* e (search_existing_target (t.ctx, pk)); - if (t == nullptr || t->implied) + if (e == nullptr || e->implied) fail << "no explicit target for " << pk; - return t; + return e; } const target_type alias::static_type @@ -847,16 +874,16 @@ namespace build2 } static const target* - dir_search (const target&, const prerequisite_key& pk) + dir_search (const target& t, const prerequisite_key& pk) { tracer trace ("dir_search"); // The first step is like in search_alias(): looks for an existing target. // - const target* t (search_existing_target (pk)); + const target* e (search_existing_target (t.ctx, pk)); - if (t != nullptr && !t->implied) - return t; + if (e != nullptr && !e->implied) + return e; // If not found (or is implied), then try to load the corresponding // buildfile (which would normally define this target). Failed that, see @@ -895,20 +922,20 @@ namespace build2 // bool retest (false); - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); { // Switch the phase to load. // - phase_switch ps (run_phase::load); + phase_switch ps (t.ctx, run_phase::load); // This is subtle: while we were fussing around another thread may // have loaded the buildfile. So re-test now that we are in exclusive // phase. // - if (t == nullptr) - t = search_existing_target (pk); + if (e == nullptr) + e = search_existing_target (t.ctx, pk); - if (t != nullptr && !t->implied) + if (e != nullptr && !e->implied) retest = true; else { @@ -933,23 +960,23 @@ namespace build2 } else if (exists (src_base)) { - t = dir::search_implied (base, pk, trace); - retest = (t != nullptr); + e = dir::search_implied (base, pk, trace); + retest = (e != nullptr); } } } } - assert (phase == run_phase::match); + assert (t.ctx.phase == run_phase::match); // If we loaded/implied the buildfile, examine the target again. // if (retest) { - if (t == nullptr) - t = search_existing_target (pk); + if (e == nullptr) + e = search_existing_target (t.ctx, pk); - if (t != nullptr && !t->implied) - return t; + if (e != nullptr && !e->implied) + return e; } } @@ -1100,7 +1127,10 @@ namespace build2 // Note: we are guaranteed the scope is never NULL for prerequisites // (where out/dir could be relative and none of this will work). // + // @@ CTX TODO +#if 0 root = scopes.find (tk.out->empty () ? *tk.dir : *tk.out).root_scope (); +#endif if (root == nullptr || root->root_extra == nullptr) fail << "unable to determine extension for buildfile target " << tk; diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 4bd11fe..b0d46e9 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -38,6 +38,23 @@ namespace build2 LIBBUILD2_SYMEXPORT const target* search_existing (const prerequisite&); + // Prerequisite inclusion/exclusion (see include() function below). + // + class include_type + { + public: + enum value {excluded, adhoc, normal}; + + include_type (value v): v_ (v) {} + include_type (bool v): v_ (v ? normal : excluded) {} + + operator value () const {return v_;} + explicit operator bool () const {return v_ != excluded;} + + private: + value v_; + }; + // Recipe. // // The returned target state is normally changed or unchanged. If there is @@ -123,9 +140,11 @@ namespace build2 // class LIBBUILD2_SYMEXPORT target { - optional* ext_; // Reference to value in target_key. - public: + // Context this scope belongs to. + // + context& ctx; + // For targets that are in the src tree of a project we also keep the // corresponding out directory. As a result we may end up with multiple // targets for the same file if we are building multiple configurations of @@ -140,9 +159,10 @@ namespace build2 // when src == out). We also treat out of project targets as being in the // out tree. // - const dir_path dir; // Absolute and normalized. - const dir_path out; // Empty or absolute and normalized. - const string name; + const dir_path dir; // Absolute and normalized. + const dir_path out; // Empty or absolute and normalized. + const string name; + optional* ext_; // Reference to value in target_key. const string* ext () const; // Return NULL if not specified. const string& ext (string); @@ -392,7 +412,7 @@ namespace build2 lookup operator[] (const string& name) const { - const variable* var (var_pool.find (name)); + const variable* var (ctx.var_pool.find (name)); return var != nullptr ? operator[] (*var) : lookup (); } @@ -452,6 +472,9 @@ namespace build2 // the target is synchronized, then we can access and modify (second case) // its state etc. // + // NOTE: see also the corresponding count_*() fuctions in context (must be + // kept in sync). + // static const size_t offset_touched = 1; // Target has been locked. static const size_t offset_tried = 2; // Rule match has been tried. static const size_t offset_matched = 3; // Rule has been matched. @@ -459,15 +482,6 @@ namespace build2 static const size_t offset_executed = 5; // Recipe has been executed. static const size_t offset_busy = 6; // Match/execute in progress. - static size_t count_base () {return 5 * (current_on - 1);} - - static size_t count_touched () {return offset_touched + count_base ();} - static size_t count_tried () {return offset_tried + count_base ();} - static size_t count_matched () {return offset_matched + count_base ();} - static size_t count_applied () {return offset_applied + count_base ();} - static size_t count_executed () {return offset_executed + count_base ();} - static size_t count_busy () {return offset_busy + count_base ();} - // Inner/outer operation state. See for details. // class LIBBUILD2_SYMEXPORT opstate @@ -530,7 +544,7 @@ namespace build2 lookup operator[] (const string& name) const { - const variable* var (var_pool.find (name)); + const variable* var (target_->ctx.var_pool.find (name)); return var != nullptr ? operator[] (*var) : lookup (); } @@ -563,7 +577,8 @@ namespace build2 assign (const variable* var) {return vars.assign (var);} // For cached. public: - opstate (): vars (false /* global */) {} + explicit + opstate (context& c): vars (c, false /* global */) {} private: friend class target_set; @@ -756,9 +771,11 @@ namespace build2 // Targets should be created via the targets set below. // public: - target (dir_path d, dir_path o, string n) - : dir (move (d)), out (move (o)), name (move (n)), - vars (false /* global */) {} + target (context& c, dir_path d, dir_path o, string n) + : ctx (c), + dir (move (d)), out (move (o)), name (move (n)), + vars (c, false /* global */), + state (c) {} target (target&&) = delete; target& operator= (target&&) = delete; @@ -798,6 +815,21 @@ namespace build2 uint8_t unmark (const target*&); + // Helper for dealing with the prerequisite inclusion/exclusion (the + // 'include' buildfile variable, see var_include in context.hxx). + // + // Note that the include(prerequisite_member) overload is also provided. + // + // @@ Maybe this filtering should be incorporated into *_prerequisites() and + // *_prerequisite_members() logic? Could make normal > adhoc > excluded and + // then pass the "threshold". + // + include_type + include (action, + const target&, + const prerequisite&, + const target* = nullptr); + // A "range" that presents the prerequisites of a group and one of // its members as one continuous sequence, or, in other words, as // if they were in a single container. The group's prerequisites @@ -1377,13 +1409,17 @@ namespace build2 private: friend class target; // Access to mutex. + friend class context; + + explicit + target_set (context& c): ctx (c) {} + + context& ctx; mutable shared_mutex mutex_; map_type map_; }; - LIBBUILD2_SYMEXPORT extern target_set targets; - // Modification time-based target. // class LIBBUILD2_SYMEXPORT mtime_target: public target @@ -1752,9 +1788,10 @@ namespace build2 // template target* - target_factory (const target_type&, dir_path d, dir_path o, string n) + target_factory (context& c, + const target_type&, dir_path d, dir_path o, string n) { - return new T (move (d), move (o), move (n)); + return new T (c, move (d), move (o), move (n)); } // Return fixed target extension unless one was specified. @@ -1769,14 +1806,14 @@ namespace build2 string&, optional&, const location&, bool); - // Get the extension from the variable or use the default if none set. If - // the default is NULL, then return NULL. + // Get the extension from the `extension` variable or use the default if + // none set. If the default is NULL, then return NULL. // - template + template optional target_extension_var (const target_key&, const scope&, const char*, bool); - template + template bool target_pattern_var (const target_type&, const scope&, string&, optional&, const location&, diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 1671c25..dd377b0 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -6,6 +6,8 @@ #include // mtime() +#include + namespace build2 { // target @@ -13,7 +15,7 @@ namespace build2 inline const string* target:: ext () const { - slock l (targets.mutex_); + slock l (ctx.targets.mutex_); return *ext_ ? &**ext_ : nullptr; } @@ -88,21 +90,22 @@ namespace build2 inline pair target:: matched_state_impl (action a) const { - assert (phase == run_phase::match); + assert (ctx.phase == run_phase::match); // Note that the "tried" state is "final". // const opstate& s (state[a]); - size_t o (s.task_count.load (memory_order_relaxed) - // Synchronized. - target::count_base ()); - if (o == target::offset_tried) + // Note: already synchronized. + size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); + + if (o == offset_tried) return make_pair (false, target_state::unknown); else { // Normally applied but can also be already executed. // - assert (o == target::offset_applied || o == target::offset_executed); + assert (o == offset_applied || o == offset_executed); return make_pair (true, (group_state (a) ? group->state[a] : s).state); } } @@ -110,7 +113,7 @@ namespace build2 inline target_state target:: executed_state_impl (action a) const { - assert (phase == run_phase::execute); + assert (ctx.phase == run_phase::execute); return (group_state (a) ? group->state : state)[a].state; } @@ -211,6 +214,32 @@ namespace build2 return m; } + // include() + // + LIBBUILD2_SYMEXPORT include_type + include_impl (action, + const target&, + const string&, + const prerequisite&, + const target*); + + inline include_type + include (action a, const target& t, const prerequisite& p, const target* m) + { + // Most of the time this variable will not be specified, so let's optimize + // for that. + // + if (p.vars.empty ()) + return true; + + const string* v (cast_null (p.vars[t.ctx.var_include])); + + if (v == nullptr) + return true; + + return include_impl (a, t, *v, p, m); + } + // group_prerequisites // inline group_prerequisites:: @@ -422,7 +451,7 @@ namespace build2 inline timestamp mtime_target:: load_mtime (const path& p) const { - assert (phase == run_phase::execute && + assert (ctx.phase == run_phase::execute && !group_state (action () /* inner */)); duration::rep r (mtime_.load (memory_order_consume)); @@ -440,7 +469,7 @@ namespace build2 inline bool mtime_target:: newer (timestamp mt) const { - assert (phase == run_phase::execute); + assert (ctx.phase == run_phase::execute); timestamp mp (mtime ()); diff --git a/libbuild2/target.txx b/libbuild2/target.txx index b93a403..17c38c8 100644 --- a/libbuild2/target.txx +++ b/libbuild2/target.txx @@ -90,12 +90,11 @@ namespace build2 target_extension_var_impl (const target_type& tt, const string& tn, const scope& s, - const char* var, const char* def) { // Include target type/pattern-specific variables. // - if (auto l = s.find (var_pool[var], tt, tn)) + if (auto l = s.find (*s.ctx.var_extension, tt, tn)) { // Help the user here and strip leading '.' from the extension. // @@ -106,17 +105,17 @@ namespace build2 return def != nullptr ? optional (def) : nullopt; } - template + template optional target_extension_var (const target_key& tk, const scope& s, const char*, bool) { - return target_extension_var_impl (*tk.type, *tk.name, s, var, def); + return target_extension_var_impl (*tk.type, *tk.name, s, def); } - template + template bool target_pattern_var (const target_type& tt, const scope& s, @@ -144,7 +143,7 @@ namespace build2 // Use empty name as a target since we only want target type/pattern- // specific variables that match any target ('*' but not '*.txt'). // - if ((e = target_extension_var_impl (tt, string (), s, var, def))) + if ((e = target_extension_var_impl (tt, string (), s, def))) return true; } } @@ -172,13 +171,13 @@ namespace build2 // We behave as if this target was explicitly mentioned in the (implied) // buildfile. Thus not implied. // - target& t (targets.insert (dir::static_type, - bs.out_path (), - dir_path (), - string (), - nullopt, - false, - trace).first); + target& t (bs.ctx.targets.insert (dir::static_type, + bs.out_path (), + dir_path (), + string (), + nullopt, + false, + trace).first); t.prerequisites (move (ps)); return &t; } diff --git a/libbuild2/test/init.cxx b/libbuild2/test/init.cxx index a5afea0..3fb4df6 100644 --- a/libbuild2/test/init.cxx +++ b/libbuild2/test/init.cxx @@ -39,7 +39,7 @@ namespace build2 // Enter module variables. Do it during boot in case they get assigned // in bootstrap.build. // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); common_data d { @@ -109,7 +109,7 @@ namespace build2 value& v (rs.assign (d.test_target)); if (!v || v.empty ()) - v = cast ((*global_scope)["build.host"]); + v = cast (rs.ctx.global_scope["build.host"]); } mod.reset (new module (move (d))); diff --git a/libbuild2/test/operation.cxx b/libbuild2/test/operation.cxx index 3ff7702..0ca8da0 100644 --- a/libbuild2/test/operation.cxx +++ b/libbuild2/test/operation.cxx @@ -4,6 +4,8 @@ #include +#include + using namespace std; using namespace butl; diff --git a/libbuild2/test/rule.cxx b/libbuild2/test/rule.cxx index 1d11063..c216e66 100644 --- a/libbuild2/test/rule.cxx +++ b/libbuild2/test/rule.cxx @@ -364,7 +364,7 @@ namespace build2 build2::test::script::script s (t, ts, wd); { - parser p; + parser p (t.ctx); p.pre_parse (s); default_runner r (c); @@ -384,6 +384,8 @@ namespace build2 target_state rule:: perform_script (action a, const target& t, size_t pass_n) const { + context& ctx (t.ctx); + // First pass through. // if (pass_n != 0) @@ -433,10 +435,10 @@ namespace build2 const path& buildignore_file (rs.root_extra->buildignore_file); dir_path bl; - if (cast_false (rs.vars[var_forwarded])) + if (cast_false (rs.vars[ctx.var_forwarded])) { bl = bs.src_path () / wd.leaf (bs.out_path ()); - clean_backlink (bl, verb_never); + clean_backlink (ctx, bl, verb_never); } // If this is a (potentially) multi-testscript test, then create (and @@ -477,7 +479,7 @@ namespace build2 // Remove the directory itself not to confuse the runner which tries // to detect when tests stomp on each others feet. // - build2::rmdir_r (wd, true, 2); + rmdir_r (ctx, wd, true, 2); } // Delay actually creating the directory in case all the tests are @@ -489,8 +491,8 @@ namespace build2 // wait_guard wg; - if (!dry_run) - wg = wait_guard (target::count_busy (), t[a].task_count); + if (!ctx.dry_run) + wg = wait_guard (ctx, ctx.count_busy (), t[a].task_count); // Result vector. // @@ -514,11 +516,11 @@ namespace build2 // don't clean the existing one), we are going to ignore it for // dry-run. // - if (!dry_run) + if (!ctx.dry_run) { if (mk) { - mkdir_buildignore (wd, buildignore_file, 2); + mkdir_buildignore (ctx, wd, buildignore_file, 2); mk = false; } } @@ -532,40 +534,42 @@ namespace build2 dr << ' ' << t; } - res.push_back (dry_run ? scope_state::passed : scope_state::unknown); + res.push_back (ctx.dry_run + ? scope_state::passed + : scope_state::unknown); - if (!dry_run) + if (!ctx.dry_run) { scope_state& r (res.back ()); - if (!sched.async (target::count_busy (), - t[a].task_count, - [this] (const diag_frame* ds, - scope_state& r, - const target& t, - const testscript& ts, - const dir_path& wd) - { - diag_frame::stack_guard dsg (ds); - r = perform_script_impl (t, ts, wd, *this); - }, - diag_frame::stack (), - ref (r), - cref (t), - cref (ts), - cref (wd))) + if (!ctx.sched.async (ctx.count_busy (), + t[a].task_count, + [this] (const diag_frame* ds, + scope_state& r, + const target& t, + const testscript& ts, + const dir_path& wd) + { + diag_frame::stack_guard dsg (ds); + r = perform_script_impl (t, ts, wd, *this); + }, + diag_frame::stack (), + ref (r), + cref (t), + cref (ts), + cref (wd))) { // Executed synchronously. If failed and we were not asked to // keep going, bail out. // - if (r == scope_state::failed && !keep_going) + if (r == scope_state::failed && !ctx.keep_going) break; } } } } - if (!dry_run) + if (!ctx.dry_run) wg.wait (); // Re-examine. @@ -586,7 +590,7 @@ namespace build2 // Cleanup. // - if (!dry_run) + if (!ctx.dry_run) { if (!bad && !one && !mk && after == output_after::clean) { @@ -594,7 +598,7 @@ namespace build2 fail << "working directory " << wd << " is not empty at the " << "end of the test"; - rmdir_buildignore (wd, buildignore_file, 2); + rmdir_buildignore (ctx, wd, buildignore_file, 2); } } @@ -603,8 +607,10 @@ namespace build2 // If we dry-run then presumably all tests passed and we shouldn't // have anything left unless we are keeping the output. // - if (!bl.empty () && (dry_run ? after == output_after::keep : exists (wd))) - update_backlink (wd, bl, true /* changed */); + if (!bl.empty () && (ctx.dry_run + ? after == output_after::keep + : exists (wd))) + update_backlink (ctx, wd, bl, true /* changed */); if (bad) throw failed (); @@ -677,6 +683,8 @@ namespace build2 target_state rule:: perform_test (action a, const target& tt, size_t pass_n) const { + context& ctx (tt.ctx); + // First pass through. // if (pass_n != 0) @@ -777,7 +785,7 @@ namespace build2 cat = process (process_exit (0)); // Successfully exited. - if (!dry_run) + if (!ctx.dry_run) { try { @@ -798,7 +806,7 @@ namespace build2 // If dry-run, the target may not exist. // - process_path pp (!dry_run + process_path pp (!ctx.dry_run ? run_search (p, true /* init */) : try_run_search (p, true)); args.push_back (pp.empty () ? p.string ().c_str () : pp.recall_string ()); @@ -863,7 +871,7 @@ namespace build2 else if (verb) text << "test " << tt; - if (!dry_run) + if (!ctx.dry_run) { diag_record dr; if (!run_test (tt, diff --git a/libbuild2/test/script/builtin.cxx b/libbuild2/test/script/builtin.cxx index ae979da..06b0cec 100644 --- a/libbuild2/test/script/builtin.cxx +++ b/libbuild2/test/script/builtin.cxx @@ -956,7 +956,7 @@ namespace build2 auto mv = [ops, &wd, &sp, &error] (const path& from, const path& to) { - const dir_path& rwd (sp.root->wd_path); + const dir_path& rwd (sp.root.wd_path); if (!from.sub (rwd) && !ops.force ()) error () << "'" << from << "' is out of working directory '" @@ -1158,7 +1158,7 @@ namespace build2 error () << "missing file"; const dir_path& wd (sp.wd_path); - const dir_path& rwd (sp.root->wd_path); + const dir_path& rwd (sp.root.wd_path); while (scan.more ()) { @@ -1259,7 +1259,7 @@ namespace build2 error () << "missing directory"; const dir_path& wd (sp.wd_path); - const dir_path& rwd (sp.root->wd_path); + const dir_path& rwd (sp.root.wd_path); while (scan.more ()) { @@ -1583,7 +1583,7 @@ namespace build2 // Note: can be executed synchronously. // static uint8_t - sleep (scope&, + sleep (scope& s, const strings& args, auto_fd in, auto_fd out, auto_fd err) noexcept try @@ -1637,7 +1637,7 @@ namespace build2 // If/when required we could probably support the precise sleep mode // (e.g., via an option). // - sched.sleep (chrono::seconds (n)); + s.root.test_target.ctx.sched.sleep (chrono::seconds (n)); r = 0; } diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index 260bc88..8b8f705 100644 --- a/libbuild2/test/script/parser.cxx +++ b/libbuild2/test/script/parser.cxx @@ -2866,7 +2866,7 @@ namespace build2 { try { - parser p; + parser p (scr.test_target.ctx); p.execute (s, scr, r); } catch (const failed&) @@ -2896,7 +2896,7 @@ namespace build2 if (exec_scope) { atomic_count task_count (0); - wait_guard wg (task_count); + wait_guard wg (g->root.test_target.ctx, task_count); // Start asynchronous execution of inner scopes keeping track of // how many we have handled. @@ -3016,24 +3016,24 @@ namespace build2 // UBSan workaround. // const diag_frame* df (diag_frame::stack ()); - if (!sched.async (task_count, - [] (const diag_frame* ds, - scope& s, - script& scr, - runner& r) - { - diag_frame::stack_guard dsg (ds); - execute_impl (s, scr, r); - }, - df, - ref (*chain), - ref (*script_), - ref (*runner_))) + if (!ctx.sched.async (task_count, + [] (const diag_frame* ds, + scope& s, + script& scr, + runner& r) + { + diag_frame::stack_guard dsg (ds); + execute_impl (s, scr, r); + }, + df, + ref (*chain), + ref (*script_), + ref (*runner_))) { // Bail out if the scope has failed and we weren't instructed // to keep going. // - if (chain->state == scope_state::failed && !keep_going) + if (chain->state == scope_state::failed && !ctx.keep_going) throw failed (); } } diff --git a/libbuild2/test/script/parser.hxx b/libbuild2/test/script/parser.hxx index 1beee49..e4d1f3a 100644 --- a/libbuild2/test/script/parser.hxx +++ b/libbuild2/test/script/parser.hxx @@ -16,6 +16,8 @@ namespace build2 { + class context; + namespace test { namespace script @@ -28,6 +30,8 @@ namespace build2 // Pre-parse. Issue diagnostics and throw failed in case of an error. // public: + parser (context& c): build2::parser (c) {} + void pre_parse (script&); diff --git a/libbuild2/test/script/parser.test.cxx b/libbuild2/test/script/parser.test.cxx index ab1f295..56630fe 100644 --- a/libbuild2/test/script/parser.test.cxx +++ b/libbuild2/test/script/parser.test.cxx @@ -9,7 +9,7 @@ #include #include -#include // reset() +#include #include #include @@ -155,8 +155,8 @@ namespace build2 // init_diag (1); init (nullptr, argv[0]); - sched.startup (1); // Serial execution. - reset (strings ()); // No command line variables. + scheduler sched (1); // Serial execution. + context ctx (sched); bool scope (false); bool id (false); @@ -195,32 +195,32 @@ namespace build2 // really care. // file& tt ( - targets.insert (work, - dir_path (), - "driver", - string (), - trace)); + ctx.targets.insert (work, + dir_path (), + "driver", + string (), + trace)); value& v ( tt.assign ( - var_pool.rw ().insert ( + ctx.var_pool.rw ().insert ( "test.target", variable_visibility::project))); - v = cast ((*global_scope)["build.host"]); + v = cast (ctx.global_scope["build.host"]); testscript& st ( - targets.insert (work, - dir_path (), - name.leaf ().base ().string (), - name.leaf ().extension (), - trace)); + ctx.targets.insert (work, + dir_path (), + name.leaf ().base ().string (), + name.leaf ().extension (), + trace)); tt.path (path ("driver")); st.path (name); // Parse and run. // - parser p; + parser p (ctx); script s (tt, st, dir_path (work) /= "test-driver"); p.pre_parse (cin, s); diff --git a/libbuild2/test/script/runner.cxx b/libbuild2/test/script/runner.cxx index f0c089b..53f6741 100644 --- a/libbuild2/test/script/runner.cxx +++ b/libbuild2/test/script/runner.cxx @@ -299,7 +299,7 @@ namespace build2 else { eop = path (op + ".orig"); - save (eop, transform (rd.str, false, rd.modifiers, *sp.root), ll); + save (eop, transform (rd.str, false, rd.modifiers, sp.root), ll); sp.clean_special (eop); } @@ -312,7 +312,7 @@ namespace build2 // Ignore Windows newline fluff if that's what we are running on. // - if (test_target (*sp.root).class_ == "windows") + if (test_target (sp.root).class_ == "windows") args.push_back ("--strip-trailing-cr"); args.push_back (eop.string ().c_str ()); @@ -451,14 +451,14 @@ namespace build2 if (l.regex) // Regex (possibly empty), { r += rl.intro; - r += transform (l.value, true, rd.modifiers, *sp.root); + r += transform (l.value, true, rd.modifiers, sp.root); r += rl.intro; r += l.flags; } else if (!l.special.empty ()) // Special literal. r += rl.intro; else // Textual literal. - r += transform (l.value, false, rd.modifiers, *sp.root); + r += transform (l.value, false, rd.modifiers, sp.root); r += l.special; return r; @@ -534,7 +534,7 @@ namespace build2 { try { - string s (transform (l.value, true, rd.modifiers, *sp.root)); + string s (transform (l.value, true, rd.modifiers, sp.root)); c = line_char ( char_regex (s, gf | parse_flags (l.flags)), pool); @@ -571,7 +571,7 @@ namespace build2 // Append literal line char. // rls += line_char ( - transform (l.value, false, rd.modifiers, *sp.root), pool); + transform (l.value, false, rd.modifiers, sp.root), pool); } for (char c: l.special) @@ -693,12 +693,14 @@ namespace build2 bool default_runner:: test (scope& s) const { - return common_.test (s.root->test_target, s.id_path); + return common_.test (s.root.test_target, s.id_path); } void default_runner:: enter (scope& sp, const location&) { + context& ctx (sp.root.target_scope.ctx); + auto df = make_diag_frame ( [&sp](const diag_record& dr) { @@ -723,8 +725,9 @@ namespace build2 fs_status r ( sp.parent == nullptr ? mkdir_buildignore ( + ctx, sp.wd_path, - sp.root->target_scope.root_scope ()->root_extra->buildignore_file, + sp.root.target_scope.root_scope ()->root_extra->buildignore_file, 2) : mkdir (sp.wd_path, 2)); @@ -744,6 +747,8 @@ namespace build2 void default_runner:: leave (scope& sp, const location& ll) { + context& ctx (sp.root.target_scope.ctx); + auto df = make_diag_frame ( [&sp](const diag_record& dr) { @@ -766,7 +771,7 @@ namespace build2 { // Remove the file if exists. Fail otherwise. // - if (rmfile (p, 3) == rmfile_status::not_exist) + if (rmfile (ctx, p, 3) == rmfile_status::not_exist) fail (ll) << "registered for cleanup special file " << p << " does not exist"; } @@ -800,9 +805,8 @@ namespace build2 { bool removed (false); - auto rm = [&cp, recursive, &removed, &sp, &ll] (path&& pe, - const string&, - bool interm) + auto rm = [&cp, recursive, &removed, &sp, &ll, &ctx] + (path&& pe, const string&, bool interm) { if (!interm) { @@ -819,7 +823,7 @@ namespace build2 if (!recursive) { - rmdir_status r (rmdir (d, 3)); + rmdir_status r (rmdir (ctx, d, 3)); if (r != rmdir_status::not_empty) return true; @@ -839,9 +843,7 @@ namespace build2 // Cast to uint16_t to avoid ambiguity with // libbutl::rmdir_r(). // - rmdir_status r (rmdir_r (d, - d != sp.wd_path, - static_cast (3))); + rmdir_status r (rmdir_r (ctx, d, d != sp.wd_path, 3)); if (r != rmdir_status::not_empty) return true; @@ -854,7 +856,7 @@ namespace build2 } } else - rmfile (pe, 3); + rmfile (ctx, pe, 3); } return true; @@ -921,13 +923,14 @@ namespace build2 // rmdir_status r ( recursive - ? rmdir_r (d, !wd, static_cast (v)) + ? rmdir_r (ctx, d, !wd, static_cast (v)) : (wd && sp.parent == nullptr ? rmdir_buildignore ( + ctx, d, - sp.root->target_scope.root_scope ()->root_extra->buildignore_file, + sp.root.target_scope.root_scope ()->root_extra->buildignore_file, v) - : rmdir (d, v))); + : rmdir (ctx, d, v))); if (r == rmdir_status::success || (r == rmdir_status::not_exist && t == cleanup_type::maybe)) @@ -948,7 +951,7 @@ namespace build2 // Remove the file if exists. Fail otherwise. Removal of // non-existing file is not an error for 'maybe' cleanup type. // - if (rmfile (p, 3) == rmfile_status::not_exist && + if (rmfile (ctx, p, 3) == rmfile_status::not_exist && t == cleanup_type::always) fail (ll) << "registered for cleanup file " << p << " does not exist"; @@ -1097,8 +1100,8 @@ namespace build2 // locking as the variable pool is an associative container // (underneath) and we are only adding new variables into it. // - ulock ul (sp.root->var_pool_mutex); - const variable& var (sp.root->var_pool.insert (move (vname))); + ulock ul (sp.root.var_pool_mutex); + const variable& var (sp.root.var_pool.insert (move (vname))); ul.unlock (); value& lhs (sp.assign (var)); @@ -1123,7 +1126,7 @@ namespace build2 dr << info (ll) << "while parsing attributes '" << *ats << "'"; }); - parser p; + parser p (sp.root.test_target.ctx); p.apply_value_attributes (&var, lhs, value (move (ns)), @@ -1175,7 +1178,7 @@ namespace build2 const string& ls (np.leaf ().string ()); bool wc (ls == "*" || ls == "**" || ls == "***"); const path& cp (wc ? np.directory () : np); - const dir_path& wd (sp.root->wd_path); + const dir_path& wd (sp.root.wd_path); if (!cp.sub (wd)) fail (ll) << (wc @@ -1360,7 +1363,7 @@ namespace build2 isp = std_path ("stdin"); save ( - isp, transform (in.str, false, in.modifiers, *sp.root), ll); + isp, transform (in.str, false, in.modifiers, sp.root), ll); sp.clean_special (isp); diff --git a/libbuild2/test/script/script.cxx b/libbuild2/test/script/script.cxx index b879eb4..af9ba82 100644 --- a/libbuild2/test/script/script.cxx +++ b/libbuild2/test/script/script.cxx @@ -418,12 +418,12 @@ namespace build2 // scope // scope:: - scope (const string& id, scope* p, script* r) + scope (const string& id, scope* p, script& r) : parent (p), root (r), - vars (false /* global */), - id_path (cast (assign (root->id_var) = path ())), - wd_path (cast (assign (root->wd_var) = dir_path ())) + vars (r.test_target.ctx, false /* global */), + id_path (cast (assign (root.id_var) = path ())), + wd_path (cast (assign (root.wd_var) = dir_path ())) { // Construct the id_path as a string to ensure POSIX form. In fact, @@ -455,7 +455,7 @@ namespace build2 assert (!implicit || c.type == cleanup_type::always); const path& p (c.path); - if (!p.sub (root->wd_path)) + if (!p.sub (root.wd_path)) { if (implicit) return; @@ -481,8 +481,11 @@ namespace build2 // script_base // script_base:: - script_base () - : // Enter the test.* variables with the same variable types as in + script_base (const target& tt, const testscript& st) + : test_target (tt), + target_scope (tt.base_scope ()), + script_target (st), + // Enter the test.* variables with the same variable types as in // buildfiles except for test: while in buildfiles it can be a // target name, in testscripts it should be resolved to a path. // @@ -515,10 +518,8 @@ namespace build2 script (const target& tt, const testscript& st, const dir_path& rwd) - : group (st.name == "testscript" ? string () : st.name, this), - test_target (tt), - target_scope (tt.base_scope ()), - script_target (st) + : script_base (tt, st), + group (st.name == "testscript" ? string () : st.name, *this) { // Set the script working dir ($~) to $out_base/test/ (id_path // for root is just the id which is empty if st is 'testscript'). @@ -634,12 +635,11 @@ namespace build2 // in parallel). Plus, if there is no such variable, then we cannot // possibly find any value. // - const variable* pvar (build2::var_pool.find (n)); + const variable* pvar (root.test_target.ctx.var_pool.find (n)); if (pvar == nullptr) return lookup (); - const script& s (static_cast (*root)); const variable& var (*pvar); // First check the target we are testing. @@ -649,12 +649,12 @@ namespace build2 // value. In this case, presumably the override also affects the // script target and we will pick it up there. A bit fuzzy. // - auto p (s.test_target.find_original (var, target_only)); + auto p (root.test_target.find_original (var, target_only)); if (p.first) { if (var.overrides != nullptr) - p = s.target_scope.find_override (var, move (p), true); + p = root.target_scope.find_override (var, move (p), true); return p.first; } @@ -665,7 +665,7 @@ namespace build2 // in different scopes which brings the question of which scopes we // should search. // - return s.script_target[var]; + return root.script_target[var]; } value& scope:: @@ -696,30 +696,30 @@ namespace build2 s.insert (s.end (), v.begin (), v.end ()); }; - if (lookup l = find (root->test_var)) + if (lookup l = find (root.test_var)) s.push_back (cast (l).representation ()); - if (lookup l = find (root->options_var)) + if (lookup l = find (root.options_var)) append (cast (l)); - if (lookup l = find (root->arguments_var)) + if (lookup l = find (root.arguments_var)) append (cast (l)); // Keep redirects/cleanups out of $N. // size_t n (s.size ()); - if (lookup l = find (root->redirects_var)) + if (lookup l = find (root.redirects_var)) append (cast (l)); - if (lookup l = find (root->cleanups_var)) + if (lookup l = find (root.cleanups_var)) append (cast (l)); // Set the $N values if present. // for (size_t i (0); i <= 9; ++i) { - value& v (assign (*root->cmdN_var[i])); + value& v (assign (*root.cmdN_var[i])); if (i < n) { @@ -734,7 +734,7 @@ namespace build2 // Set $*. // - assign (root->cmd_var) = move (s); + assign (root.cmd_var) = move (s); } } } diff --git a/libbuild2/test/script/script.hxx b/libbuild2/test/script/script.hxx index e3f8251..8b34be8 100644 --- a/libbuild2/test/script/script.hxx +++ b/libbuild2/test/script/script.hxx @@ -343,7 +343,7 @@ namespace build2 { public: scope* const parent; // NULL for the root (script) scope. - script* const root; // Self for the root (script) scope. + script& root; // Self for the root (script) scope. // The chain of if-else scope alternatives. See also if_cond_ below. // @@ -424,7 +424,7 @@ namespace build2 ~scope () = default; protected: - scope (const string& id, scope* parent, script* root); + scope (const string& id, scope* parent, script& root); // Pre-parse data. // @@ -452,7 +452,7 @@ namespace build2 group (const string& id, group& p): scope (id, &p, p.root) {} protected: - group (const string& id, script* r): scope (id, nullptr, r) {} + group (const string& id, script& r): scope (id, nullptr, r) {} // Pre-parse data. // @@ -505,7 +505,13 @@ namespace build2 class script_base // Make sure certain things are initialized early. { protected: - script_base (); + script_base (const target& test_target, + const testscript& script_target); + + public: + const target& test_target; // Target we are testing. + const build2::scope& target_scope; // Base scope of test target. + const testscript& script_target; // Target of the testscript file. public: variable_pool var_pool; @@ -535,11 +541,6 @@ namespace build2 script& operator= (script&&) = delete; script& operator= (const script&) = delete; - public: - const target& test_target; // Target we are testing. - const build2::scope& target_scope; // Base scope of test target. - const testscript& script_target; // Target of the testscript file. - // Pre-parse data. // private: diff --git a/libbuild2/types.hxx b/libbuild2/types.hxx index 61880ed..b14a365 100644 --- a/libbuild2/types.hxx +++ b/libbuild2/types.hxx @@ -334,7 +334,6 @@ namespace build2 LIBBUILD2_SYMEXPORT ostream& operator<< (ostream&, run_phase); // utility.cxx - LIBBUILD2_SYMEXPORT extern run_phase phase; } // In order to be found (via ADL) these have to be either in std:: or in diff --git a/libbuild2/utility.cxx b/libbuild2/utility.cxx index 63fa609..ba50c5a 100644 --- a/libbuild2/utility.cxx +++ b/libbuild2/utility.cxx @@ -81,7 +81,6 @@ namespace build2 : (to_string (build_version.major ()) + '.' + to_string (build_version.minor ()))); - bool dry_run_option; optional mtime_check_option; optional config_sub; @@ -495,15 +494,14 @@ namespace build2 void init (void (*t) (bool), const char* a0, - bool kg, bool dr, optional mc, - optional cs, optional cg) + optional mc, + optional cs, + optional cg) { terminate = t; argv0 = process::path_search (a0, true); - keep_going = kg; - dry_run_option = dr; mtime_check_option = mc; config_sub = move (cs); diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx index ed94a08..a6a7ac3 100644 --- a/libbuild2/utility.hxx +++ b/libbuild2/utility.hxx @@ -124,8 +124,6 @@ namespace build2 LIBBUILD2_SYMEXPORT void init (void (*terminate) (bool), const char* argv0, - bool keep_going = false, - bool dry_run = false, optional mtime_check = nullopt, optional config_sub = nullopt, optional config_guess = nullopt); @@ -144,8 +142,6 @@ namespace build2 LIBBUILD2_SYMEXPORT extern const standard_version build_version; LIBBUILD2_SYMEXPORT extern const string build_version_interface; - LIBBUILD2_SYMEXPORT extern bool dry_run_option; // --dry-run - // --[no-]mtime-check // LIBBUILD2_SYMEXPORT extern optional mtime_check_option; @@ -169,6 +165,9 @@ namespace build2 // state dump). If it is empty, then relative() below returns the original // path. // + // @@ CTX: this could be an issue if changed concurrently from several + // contexts. + // LIBBUILD2_SYMEXPORT extern const dir_path* relative_base; // If possible and beneficial, translate an absolute, normalized path into diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index c921bbd..86109d2 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -1243,7 +1243,7 @@ namespace build2 const bool* o, bool pat) { - assert (!global_ || phase == run_phase::load); + assert (!global_ || global_->phase == run_phase::load); // Apply pattern. // @@ -1318,7 +1318,7 @@ namespace build2 bool retro, bool match) { - assert (!global_ || phase == run_phase::load); + assert (!global_ || global_->phase == run_phase::load); size_t pn (p.size ()); @@ -1380,12 +1380,10 @@ namespace build2 } } - variable_pool variable_pool::instance (true); - const variable_pool& variable_pool::cinstance = variable_pool::instance; - const variable_pool& var_pool = variable_pool::cinstance; - // variable_map // + const variable_map empty_variable_map (nullptr /* context */); + auto variable_map:: find (const variable& var, bool typed) const -> pair @@ -1434,7 +1432,7 @@ namespace build2 pair, bool> variable_map:: insert (const variable& var, bool typed) { - assert (!global_ || phase == run_phase::load); + assert (!global_ || ctx->phase == run_phase::load); auto p (m_.emplace (var, value_data (typed ? var.type : nullptr))); value_data& r (p.first->second); diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx index 9a106b5..3eed61e 100644 --- a/libbuild2/variable.hxx +++ b/libbuild2/variable.hxx @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -1018,7 +1019,7 @@ namespace build2 // Variable pool. // - // The global version is protected by the phase mutex. + // The global (as in, context-wide) version is protected by the phase mutex. // class variable_pool { @@ -1161,23 +1162,26 @@ namespace build2 void clear () {map_.clear ();} - variable_pool (): variable_pool (false) {} + variable_pool (): variable_pool (nullptr) {} - // RW access. + // RW access (only for the global pool). // variable_pool& rw () const { - assert (phase == run_phase::load); + assert (global_->phase == run_phase::load); return const_cast (*this); } variable_pool& rw (scope&) const {return const_cast (*this);} - private: - LIBBUILD2_SYMEXPORT static variable_pool instance; + // Entities that can access bypassing the lock proof. + // + friend class parser; + friend class scope; + private: LIBBUILD2_SYMEXPORT variable& insert (string name, const value_type*, @@ -1191,17 +1195,6 @@ namespace build2 const variable_visibility* = nullptr, const bool* = nullptr) const; - // Entities that can access bypassing the lock proof. - // - friend class parser; - friend class scope; - friend LIBBUILD2_SYMEXPORT variable_overrides reset (const strings&); - - public: - // For var_pool initialization. - // - LIBBUILD2_SYMEXPORT static const variable_pool& cinstance; - // Variable map. // private: @@ -1258,16 +1251,16 @@ namespace build2 private: std::multiset patterns_; - // Global pool flag. + // Global pool flag/context. // private: + friend class context; + explicit - variable_pool (bool global): global_ (global) {} + variable_pool (context* global): global_ (global) {} - bool global_; + context* global_; }; - - LIBBUILD2_SYMEXPORT extern const variable_pool& var_pool; } // variable_map @@ -1361,7 +1354,9 @@ namespace build2 lookup operator[] (const string& name) const { - const variable* var (var_pool.find (name)); + const variable* var (ctx != nullptr + ? ctx->var_pool.find (name) + : nullptr); return var != nullptr ? operator[] (*var) : lookup (); } @@ -1402,7 +1397,7 @@ namespace build2 // Note that the variable is expected to have already been registered. // value& - assign (const string& name) {return insert (var_pool[name]).first;} + assign (const string& name) {return insert (ctx->var_pool[name]).first;} // As above but also return an indication of whether the new value (which // will be NULL) was actually inserted. Similar to find(), if typed is @@ -1436,11 +1431,18 @@ namespace build2 // (e.g., scopes, etc). // explicit - variable_map (bool global = false): global_ (global) {} + variable_map (context& c, bool global = false) + : ctx (&c), global_ (global) {} void clear () {m_.clear ();} + // Implementation details. + // + public: + explicit + variable_map (context* c): ctx (c) {} + private: friend class variable_type_map; @@ -1448,10 +1450,13 @@ namespace build2 typify (const value_data&, const variable&) const; private: - bool global_; + context* ctx; map_type m_; + bool global_; }; + LIBBUILD2_SYMEXPORT extern const variable_map empty_variable_map; + // Value caching. Used for overrides as well as target type/pattern-specific // append/prepend. // @@ -1519,6 +1524,18 @@ namespace build2 map_type m_; }; + // Variable override cache. Only on project roots (in scope::root_extra) + // plus a global one (in context) for the global scope. + // + // The key is the variable plus the innermost (scope-wise) variable map to + // which this override applies. See scope::find_override() for details. + // + // Note: since it can be modified on any lookup (including during the + // execute phase), the cache is protected by a mutex shard. + // + class variable_override_cache: + public variable_cache> {}; + // Target type/pattern-specific variables. // class variable_pattern_map @@ -1528,13 +1545,13 @@ namespace build2 using const_iterator = map_type::const_iterator; using const_reverse_iterator = map_type::const_reverse_iterator; - explicit - variable_pattern_map (bool global): global_ (global) {} + variable_pattern_map (context& c, bool global) + : ctx (c), global_ (global) {} variable_map& operator[] (const string& v) { - return map_.emplace (v, variable_map (global_)).first->second; + return map_.emplace (v, variable_map (ctx, global_)).first->second; } const_iterator begin () const {return map_.begin ();} @@ -1544,8 +1561,10 @@ namespace build2 bool empty () const {return map_.empty ();} private: - bool global_; + context& ctx; map_type map_; + bool global_; + }; class LIBBUILD2_SYMEXPORT variable_type_map @@ -1555,13 +1574,13 @@ namespace build2 variable_pattern_map>; using const_iterator = map_type::const_iterator; - explicit - variable_type_map (bool global): global_ (global) {} + variable_type_map (context& c, bool global): ctx (c), global_ (global) {} variable_pattern_map& operator[] (const target_type& t) { - return map_.emplace (t, variable_pattern_map (global_)).first->second; + return map_.emplace ( + t, variable_pattern_map (ctx, global_)).first->second; } const_iterator begin () const {return map_.begin ();} @@ -1585,8 +1604,9 @@ namespace build2 cache; private: - bool global_; + context& ctx; map_type map_; + bool global_; }; } diff --git a/libbuild2/variable.ixx b/libbuild2/variable.ixx index f0bde09..e5353ed 100644 --- a/libbuild2/variable.ixx +++ b/libbuild2/variable.ixx @@ -764,7 +764,7 @@ namespace build2 { // We assume typification is not modification so no version increment. // - if (phase == run_phase::load) + if (ctx->phase == run_phase::load) { if (v.type != var.type) build2::typify (const_cast (v), *var.type, &var); diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx index a4e41d6..123dc65 100644 --- a/libbuild2/version/init.cxx +++ b/libbuild2/version/init.cxx @@ -37,6 +37,8 @@ namespace build2 tracer trace ("version::boot"); l5 ([&]{trace << "for " << rs;}); + context& ctx (rs.ctx); + // Extract the version from the manifest file. As well as summary and // url while at it. // @@ -67,7 +69,7 @@ namespace build2 { if (nv.name == "name") { - auto& pn (cast (rs.vars[var_project])); + auto& pn (cast (rs.vars[ctx.var_project])); if (nv.value != pn.string ()) { @@ -219,7 +221,7 @@ namespace build2 // Set all the version.* variables. // - auto& vp (var_pool.rw (rs)); + auto& vp (ctx.var_pool.rw (rs)); auto set = [&vp, &rs] (const char* var, auto val) { @@ -228,8 +230,8 @@ namespace build2 rs.assign (v) = move (val); }; - if (!sum.empty ()) rs.assign (var_project_summary) = move (sum); - if (!url.empty ()) rs.assign (var_project_url) = move (url); + if (!sum.empty ()) rs.assign (ctx.var_project_summary) = move (sum); + if (!url.empty ()) rs.assign (ctx.var_project_url) = move (url); set ("version", v.string ()); // Project version (var_version). @@ -268,7 +270,7 @@ namespace build2 // Create the module. // - mod.reset (new module (cast (rs.vars[var_project]), + mod.reset (new module (cast (rs.vars[ctx.var_project]), move (v), committed, rewritten, @@ -294,6 +296,8 @@ namespace build2 if (!first) fail (l) << "multiple version module initializations"; + context& ctx (rs.ctx); + // Load in.base (in.* variables, in{} target type). // if (!cast_false (rs["in.base.loaded"])) @@ -320,7 +324,7 @@ namespace build2 if (!val) { - string p (cast (rs.vars[var_project]).string ()); + string p (cast (rs.vars[ctx.var_project]).string ()); p += '-'; p += v.string (); val = move (p); @@ -370,7 +374,8 @@ namespace build2 // try { - auto_rmfile t (fixup_manifest (f, + auto_rmfile t (fixup_manifest (rs.ctx, + f, path::temp_path ("manifest"), m.version)); diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx index 37e6b0f..fe999b3 100644 --- a/libbuild2/version/rule.cxx +++ b/libbuild2/version/rule.cxx @@ -328,7 +328,8 @@ namespace build2 // the out tree. Somehow the latter feels more appropriate (even though // if we crash in between, we won't clean it up). // - return fixup_manifest (p, rs.out_path () / "manifest.t", m.version); + return fixup_manifest ( + t.ctx, p, rs.out_path () / "manifest.t", m.version); } } } diff --git a/libbuild2/version/utility.cxx b/libbuild2/version/utility.cxx index 70daab1..0669da7 100644 --- a/libbuild2/version/utility.cxx +++ b/libbuild2/version/utility.cxx @@ -17,11 +17,14 @@ namespace build2 namespace version { auto_rmfile - fixup_manifest (const path& in, path out, const standard_version& v) + fixup_manifest (context& ctx, + const path& in, + path out, + const standard_version& v) { - auto_rmfile r (move (out), !dry_run /* active */); + auto_rmfile r (move (out), !ctx.dry_run /* active */); - if (!dry_run) + if (!ctx.dry_run) { try { diff --git a/libbuild2/version/utility.hxx b/libbuild2/version/utility.hxx index 16e8c78..170488d 100644 --- a/libbuild2/version/utility.hxx +++ b/libbuild2/version/utility.hxx @@ -8,6 +8,7 @@ #include #include +#include #include namespace build2 @@ -18,7 +19,10 @@ namespace build2 // not preserve comments. Probably acceptable for snapshots. // auto_rmfile - fixup_manifest (const path& in, path out, const standard_version&); + fixup_manifest (context&, + const path& in, + path out, + const standard_version&); } } diff --git a/tests/libbuild2/driver.cxx b/tests/libbuild2/driver.cxx index 706d276..a70e707 100644 --- a/tests/libbuild2/driver.cxx +++ b/tests/libbuild2/driver.cxx @@ -5,7 +5,7 @@ #include #include -#include // sched, reset() +#include #include #include @@ -26,8 +26,8 @@ main (int, char* argv[]) in::build2_in_load (); version::build2_version_load (); - sched.startup (1); // Serial execution. - reset (strings ()); // No command line variables. + scheduler sched (1); // Serial execution. + context ctx (sched); return 0; } -- cgit v1.1