From 4f5b6cb7ed4e05e98cce7e692462f49e24b7a39a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 22 Aug 2019 14:38:57 +0200 Subject: Targets, scopes, vars --- build2/b.cxx | 52 +- build2/bin/init.cxx | 12 +- build2/bin/target.cxx | 36 +- build2/c/init.cxx | 2 +- build2/cc/common.cxx | 43 +- build2/cc/common.hxx | 4 +- build2/cc/compile-rule.cxx | 56 +- build2/cc/init.cxx | 5 +- build2/cc/link-rule.cxx | 13 +- build2/cc/module.cxx | 16 +- build2/cc/msvc.cxx | 9 +- build2/cc/pkgconfig.cxx | 4 +- build2/cc/utility.cxx | 2 +- build2/cli/init.cxx | 2 +- build2/cli/rule.cxx | 4 +- build2/cli/target.cxx | 11 +- build2/cxx/init.cxx | 7 +- libbuild2/algorithm.cxx | 38 +- libbuild2/algorithm.hxx | 8 +- libbuild2/algorithm.ixx | 4 +- libbuild2/bash/rule.cxx | 16 +- libbuild2/config/init.cxx | 6 +- libbuild2/config/operation.cxx | 182 ++--- libbuild2/config/operation.hxx | 2 +- libbuild2/config/utility.cxx | 6 +- libbuild2/config/utility.hxx | 6 +- libbuild2/context.cxx | 1167 +++++++++++++++++---------------- libbuild2/context.hxx | 78 ++- libbuild2/dist/init.cxx | 2 +- libbuild2/dist/operation.cxx | 22 +- libbuild2/dump.cxx | 14 +- libbuild2/dump.hxx | 3 +- libbuild2/file.cxx | 98 +-- libbuild2/file.hxx | 12 +- libbuild2/file.ixx | 10 +- libbuild2/function.test.cxx | 7 +- libbuild2/in/init.cxx | 2 +- libbuild2/install/init.cxx | 9 +- libbuild2/install/rule.cxx | 4 +- libbuild2/install/utility.hxx | 4 +- libbuild2/module.cxx | 4 +- libbuild2/module.hxx | 4 +- libbuild2/operation.cxx | 6 +- libbuild2/operation.hxx | 7 +- libbuild2/parser.cxx | 64 +- libbuild2/parser.hxx | 6 +- libbuild2/prerequisite.cxx | 2 +- libbuild2/prerequisite.hxx | 6 +- libbuild2/scope.cxx | 26 +- libbuild2/scope.hxx | 44 +- libbuild2/search.cxx | 25 +- libbuild2/search.hxx | 10 +- libbuild2/target-key.hxx | 2 +- libbuild2/target-type.hxx | 8 +- libbuild2/target.cxx | 57 +- libbuild2/target.hxx | 41 +- libbuild2/target.ixx | 2 +- libbuild2/target.txx | 16 +- libbuild2/test/init.cxx | 4 +- libbuild2/test/rule.cxx | 2 +- libbuild2/test/script/builtin.cxx | 6 +- libbuild2/test/script/parser.cxx | 2 +- libbuild2/test/script/parser.hxx | 4 + libbuild2/test/script/parser.test.cxx | 30 +- libbuild2/test/script/runner.cxx | 28 +- libbuild2/test/script/script.cxx | 46 +- libbuild2/test/script/script.hxx | 19 +- libbuild2/variable.cxx | 6 +- libbuild2/variable.hxx | 65 +- libbuild2/version/init.cxx | 2 +- tests/libbuild2/driver.cxx | 6 +- 71 files changed, 1335 insertions(+), 1193 deletions(-) diff --git a/build2/b.cxx b/build2/b.cxx index f359dbc..6a49cb7 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -590,6 +590,8 @@ main (int argc, char* argv[]) ? 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 +608,11 @@ 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 (new context (sched, cmd_vars)); // Parse the buildspec. // @@ -619,7 +622,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 +748,14 @@ 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); + ctx = nullptr; + ctx.reset (new context (sched, cmd_vars)); dirty = false; } @@ -778,15 +782,16 @@ main (int argc, char* argv[]) // 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); + mname = 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 (var_build_meta_operation) = mname; for (auto oit (opspecs.begin ()); oit != opspecs.end (); ++oit) { @@ -978,7 +983,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 +1028,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)); @@ -1238,7 +1242,7 @@ main (int argc, char* argv[]) fail (l) << "unexpected parameters for meta-operation " << mif->name; - set_current_mif (*mif); + ctx->current_mif (*mif); dirty = true; } @@ -1427,9 +1431,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 +1454,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 +1508,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 +1550,7 @@ main (int argc, char* argv[]) } // target if (dump_load) - dump (); + dump (*ctx); // Finally, match the rules and perform the operation. // @@ -1558,7 +1562,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_oif (*pre_oif, oif); action a (mid, pre_oid, oid); @@ -1570,7 +1574,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 +1589,7 @@ main (int argc, char* argv[]) tgs.reset (); } - set_current_oif (*oif, outer_oif); + ctx->current_oif (*oif, outer_oif); action a (mid, oid, oif->outer_id); @@ -1597,7 +1601,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 +1617,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_oif (*post_oif, oif); action a (mid, post_oid, oid); @@ -1625,7 +1629,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..2ef3d87 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; @@ -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)) + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); A* a (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); S* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + ? 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)) + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); libus* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + ? 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; @@ -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)) + ? const_cast (ctx.targets.find (dir, out, n)) : nullptr); libs* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) + ? 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; 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..9dede21 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); }; @@ -2161,7 +2167,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 +2207,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 +2893,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,7 +5107,7 @@ 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) { @@ -5112,7 +5118,7 @@ namespace build2 // 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 +5206,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 +5243,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 +5302,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 +5314,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 diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index e22ece7..3ff59a1 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -77,7 +77,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 +276,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..09109c2 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); @@ -1163,7 +1164,7 @@ namespace build2 bool u; if ((u = pt->is_a ()) || pt->is_a ()) { - const variable& var (var_pool["bin.whole"]); // @@ Cache. + const variable& var (t.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() @@ -1473,7 +1474,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 +1576,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); } @@ -1966,7 +1967,7 @@ namespace build2 const string& cs ( cast ( rs[tsys == "win32-msvc" - ? var_pool["bin.ld.checksum"] + ? t.ctx.var_pool["bin.ld.checksum"] : x_checksum])); if (dd.expect (cs) != nullptr) 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..13cc752 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 (), diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 3271f7c..ff807c9 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -72,7 +72,7 @@ namespace build2 // return 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/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..f6c0763 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 ()}); } diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx index 096295a..caa12b0 100644 --- a/build2/cli/target.cxx +++ b/build2/cli/target.cxx @@ -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/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 50db5d3..298f945 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -41,7 +41,7 @@ namespace build2 if (r == nullptr) { - r = search_existing (p.key ()); + r = search_existing (p.scope.ctx, p.key ()); if (r != nullptr) search_custom (p, *r); @@ -59,20 +59,22 @@ namespace build2 // 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); - return pk.proj ? import_existing (pk) : search_existing_target (pk); + return pk.proj + ? import_existing (ctx, pk) + : search_existing_target (ctx, pk); } const target& @@ -130,7 +132,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 @@ -266,13 +270,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 +307,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]); @@ -897,7 +901,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 +977,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 ()) { @@ -1401,7 +1405,7 @@ namespace build2 // stops at the project boundary). // if (!l.defined ()) - l = global_scope->find (*var_backlink, t.key ()); + l = t.ctx.global_scope.find (*var_backlink, t.key ()); return l ? backlink_test (t, l) : nullopt; } diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx index 4707ae7..2ca83e3 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, diff --git a/libbuild2/algorithm.ixx b/libbuild2/algorithm.ixx index 6bc771e..8bd69c9 100644 --- a/libbuild2/algorithm.ixx +++ b/libbuild2/algorithm.ixx @@ -57,7 +57,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 +67,7 @@ namespace build2 const optional& proj) { return search_existing ( + ctx, prerequisite_key { proj, { 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/config/init.cxx b/libbuild2/config/init.cxx index b790569..9bdfef9 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -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..e235a3a 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,14 @@ 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)); + 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 +107,7 @@ namespace build2 ofs << "config.version = " << module::version << endl; - if (auto l = root.vars[var_amalgamation]) + if (auto l = rs.vars[var_amalgamation]) { const dir_path& d (cast (l)); @@ -130,10 +130,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 +144,7 @@ namespace build2 if (!l.defined ()) continue; - if (!(l.belongs (root) || l.belongs (*global_scope))) + if (!(l.belongs (rs) || l.belongs (rs.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 +162,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 +307,14 @@ 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 ()); + 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 +324,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 +338,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 +350,54 @@ namespace build2 // Configure subprojects that have been loaded. // - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[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 (rs.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 ()); + 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 " << 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[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 (rs.ctx.scopes.find (out_nroot)); + assert (nrs.out_path () == out_nroot); - configure_forward (nroot, projects); + configure_forward (nrs, projects); } } } @@ -451,7 +451,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 +463,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 +486,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 +515,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; } @@ -552,7 +552,7 @@ namespace build2 if (oif->id != id) continue; - set_current_oif (*oif); + rs->ctx.current_oif (*oif); phase_lock pl (run_phase::match); match (action (configure_id, id), t); @@ -586,14 +586,14 @@ 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 ()); + 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 +604,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[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 (rs.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 @@ -643,19 +643,19 @@ namespace build2 { l5 ([&]{trace << "completely disfiguring " << out_root;}); - r = rmfile (config_file (root)) || r; + r = rmfile (config_file (rs)) || r; if (out_root != src_root) { - r = rmfile (out_root / root.root_extra->src_root_file, 2) || r; + r = rmfile (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 (out_root / rs.root_extra->root_dir, 2) || r; + r = rmdir (out_root / rs.root_extra->bootstrap_dir, 2) || r; + r = rmdir (out_root / rs.root_extra->build_dir, 2) || r; switch (rmdir (out_root)) { @@ -687,16 +687,16 @@ 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 ()); + 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 +704,23 @@ namespace build2 bool r (false); - if (auto l = root.vars[var_subprojects]) + if (auto l = rs.vars[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 (rs.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 (src_root / rs.root_extra->out_root_file) || r; + r = rmdir (src_root / rs.root_extra->bootstrap_dir, 2) || r; return r; } @@ -757,14 +757,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 +790,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 +836,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 +846,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 +869,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 +879,7 @@ namespace build2 } const string& - preprocess_create (const variable_overrides& var_ovs, + preprocess_create (context& ctx, values& params, vector_view& spec, bool lifted, @@ -986,7 +986,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..a89fac6 100644 --- a/libbuild2/config/utility.cxx +++ b/libbuild2/config/utility.cxx @@ -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,7 +101,7 @@ 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) save_variable (rs, var); @@ -116,7 +116,7 @@ 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) save_variable (rs, var); 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/context.cxx b/libbuild2/context.cxx index 1cc8bbc..720e8d8 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include // uncaught_exceptions @@ -25,541 +27,197 @@ using namespace butl; namespace build2 { - scheduler sched; + // Create global scope. Note that the empty path is a prefix for any other + // path. See the comment in for details. + // + static inline scope& + create_global_scope (scope_map& m) + { + auto i (m.insert (dir_path ())); + scope& r (i->second); + r.out_path_ = &i->first; + return r; + }; - run_phase phase; - run_phase_mutex phase_mutex; + struct context::data + { + scope_map scopes; + target_set targets; + variable_pool var_pool; + variable_overrides var_overrides; + + data (context& c): scopes (c), targets (c), var_pool (true /* global */) {} + }; + + context:: + context (scheduler& s, const strings& cmd_vars) + : data_ (new data (*this)), + sched (s), + scopes (data_->scopes), + global_scope (create_global_scope (data_->scopes)), + targets (data_->targets), + var_pool (data_->var_pool), + var_overrides (data_->var_overrides) + { + tracer trace ("context"); - size_t load_generation; + l6 ([&]{trace << "initializing build state";}); - bool run_phase_mutex:: - lock (run_phase p) - { - bool r; + scope_map& sm (data_->scopes); + variable_pool& vp (data_->var_pool); - { - mlock l (m_); - bool u (lc_ == 0 && mc_ == 0 && ec_ == 0); // Unlocked. + // Initialize the meta/operation tables. Note that the order should match + // the id constants in . + // + meta_operation_table.insert ("noop"); + meta_operation_table.insert ("perform"); + meta_operation_table.insert ("configure"); + meta_operation_table.insert ("disfigure"); - // 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 (config_preprocess_create != nullptr) + meta_operation_table.insert ( + meta_operation_data ("create", config_preprocess_create)); - // 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_; - } + meta_operation_table.insert ("dist"); + meta_operation_table.insert ("info"); - // In case of load, acquire the exclusive access mutex. + operation_table.clear (); + operation_table.insert ("default"); + operation_table.insert ("update"); + operation_table.insert ("clean"); + operation_table.insert ("test"); + operation_table.insert ("update-for-test"); + operation_table.insert ("install"); + operation_table.insert ("uninstall"); + operation_table.insert ("update-for-install"); + + // Setup the global scope before parsing any variable overrides since they + // may reference these things. // - if (p == run_phase::load) - { - lm_.lock (); - r = !fail_; // Re-query. - } + scope& gs (global_scope.rw ()); - return r; - } + gs.assign ("build.work") = work; + gs.assign ("build.home") = home; - void run_phase_mutex:: - unlock (run_phase p) - { - // In case of load, release the exclusive access mutex. + // Build system driver process path. // - if (p == run_phase::load) - lm_.unlock (); + gs.assign ("build.path") = + process_path (nullptr, // Will be filled by value assignment. + path (argv0.recall_string ()), + path (argv0.effect)); + // Build system verbosity level. + // + gs.assign ("build.verbosity") = verb; + + // Build system version (similar to what we do in the version module + // except here we don't include package epoch/revision). + // { - mlock l (m_); + const standard_version& v (build_version); - // Decrement the counter and see if this phase has become unlocked. - // - bool u (false); - switch (p) + auto set = [&gs, &vp] (const char* var, auto val) { - case run_phase::load: u = (--lc_ == 0); break; - case run_phase::match: u = (--mc_ == 0); break; - case run_phase::execute: u = (--ec_ == 0); break; - } + using T = decltype (val); + gs.assign (vp.insert (var)) = move (val); + }; - // 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. + // Note: here we assume epoch will always be 1 and therefore omit the + // project_ prefix in a few places. // - if (u) - { - condition_variable* v; + set ("build.version", v.string_project ()); - 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;} + set ("build.version.number", v.version); + set ("build.version.id", v.string_project_id ()); - if (v != nullptr) - { - l.unlock (); - v->notify_all (); - } - } - } - } + set ("build.version.major", uint64_t (v.major ())); + set ("build.version.minor", uint64_t (v.minor ())); + set ("build.version.patch", uint64_t (v.patch ())); - 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); + optional a (v.alpha ()); + optional b (v.beta ()); - bool r; + set ("build.version.alpha", a.has_value ()); + set ("build.version.beta", b.has_value ()); + set ("build.version.pre_release", v.pre_release ().has_value ()); + set ("build.version.pre_release_string", v.string_pre_release ()); + set ("build.version.pre_release_number", uint64_t (a ? *a : b ? *b : 0)); - if (o == run_phase::load) - lm_.unlock (); + set ("build.version.snapshot", v.snapshot ()); // bool + set ("build.version.snapshot_sn", v.snapshot_sn); // uint64 + set ("build.version.snapshot_id", v.snapshot_id); // string + set ("build.version.snapshot_string", v.string_snapshot ()); - { - mlock l (m_); - bool u (false); + // Build system interface version. In particular, it is embedded into + // build system modules as load_suffix. + // + set ("build.version.interface", build_version_interface); - 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; - } + // Allow detection (for example, in tests) whether this is a staged + // toolchain. + // + // Note that it is either staged or public, without queued, since we do + // not re-package things during the queued-to-public transition. + // + set ("build.version.stage", LIBBUILD2_STAGE); + } - // Set if will be waiting or notifying others. + // Enter the host information. Rather than jumping through hoops like + // config.guess, for now we are just going to use the compiler target we + // 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, macos). + // + { + // Did the user ask us to use config.guess? // - 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; - } + string orig (config_guess + ? run (3, + *config_guess, + [](string& l, bool) {return move (l);}) + : BUILD2_HOST_TRIPLET); - if (u) + l5 ([&]{trace << "original host: '" << orig << "'";}); + + try { - phase = n; - r = !fail_; + target_triplet t (orig); - // Notify others that could be waiting for this phase. + l5 ([&]{trace << "canonical host: '" << t.string () << "'; " + << "class: " << t.class_;}); + + // Also enter as build.host.{cpu,vendor,system,version,class} for + // convenience of access. // - if (v != nullptr) - { - l.unlock (); - v->notify_all (); - } + gs.assign ("build.host.cpu") = t.cpu; + gs.assign ("build.host.vendor") = t.vendor; + gs.assign ("build.host.system") = t.system; + gs.assign ("build.host.version") = t.version; + gs.assign ("build.host.class") = t.class_; + + gs.assign ("build.host") = move (t); } - else // phase != n + catch (const invalid_argument& e) { - sched.deactivate (false /* external */); - for (; phase != n; v->wait (l)) ; - r = !fail_; - l.unlock (); // Important: activate() can block. - sched.activate (false /* external */); + fail << "unable to parse build host '" << orig << "': " << e << + info << "consider using the --config-guess option"; } } - if (n == run_phase::load) + // Register builtin target types. + // { - lm_.lock (); - r = !fail_; // Re-query. - } + target_type_map& t (gs.target_types); - 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 - // - 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) - { - 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 - set_current_oif (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); - } - - variable_overrides - reset (const strings& cmd_vars) - { - 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";}); - - auto& vp (variable_pool::instance); - auto& sm (scope_map::instance); - - variable_overrides vos; - - targets.clear (); - sm.clear (); - vp.clear (); - - // Reset 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"); - meta_operation_table.insert ("disfigure"); - - if (config_preprocess_create != nullptr) - meta_operation_table.insert ( - meta_operation_data ("create", config_preprocess_create)); - - meta_operation_table.insert ("dist"); - meta_operation_table.insert ("info"); - - operation_table.clear (); - operation_table.insert ("default"); - operation_table.insert ("update"); - operation_table.insert ("clean"); - operation_table.insert ("test"); - operation_table.insert ("update-for-test"); - operation_table.insert ("install"); - 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. - // - - gs.assign ("build.work") = work; - gs.assign ("build.home") = home; - - // Build system driver process path. - // - gs.assign ("build.path") = - process_path (nullptr, // Will be filled by value assignment. - path (argv0.recall_string ()), - path (argv0.effect)); - - // Build system verbosity level. - // - gs.assign ("build.verbosity") = verb; - - // Build system version (similar to what we do in the version module - // except here we don't include package epoch/revision). - // - { - const standard_version& v (build_version); - - auto set = [&gs] (const char* var, auto val) - { - using T = decltype (val); - gs.assign (variable_pool::instance.insert (var)) = move (val); - }; - - // Note: here we assume epoch will always be 1 and therefore omit the - // project_ prefix in a few places. - // - set ("build.version", v.string_project ()); - - set ("build.version.number", v.version); - set ("build.version.id", v.string_project_id ()); - - set ("build.version.major", uint64_t (v.major ())); - set ("build.version.minor", uint64_t (v.minor ())); - set ("build.version.patch", uint64_t (v.patch ())); - - optional a (v.alpha ()); - optional b (v.beta ()); - - set ("build.version.alpha", a.has_value ()); - set ("build.version.beta", b.has_value ()); - set ("build.version.pre_release", v.pre_release ().has_value ()); - set ("build.version.pre_release_string", v.string_pre_release ()); - set ("build.version.pre_release_number", uint64_t (a ? *a : b ? *b : 0)); - - set ("build.version.snapshot", v.snapshot ()); // bool - set ("build.version.snapshot_sn", v.snapshot_sn); // uint64 - set ("build.version.snapshot_id", v.snapshot_id); // string - set ("build.version.snapshot_string", v.string_snapshot ()); - - // Build system interface version. In particular, it is embedded into - // build system modules as load_suffix. - // - set ("build.version.interface", build_version_interface); - - // Allow detection (for example, in tests) whether this is a staged - // toolchain. - // - // Note that it is either staged or public, without queued, since we do - // not re-package things during the queued-to-public transition. - // - set ("build.version.stage", LIBBUILD2_STAGE); - } - - // Enter the host information. Rather than jumping through hoops like - // config.guess, for now we are just going to use the compiler target we - // 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). - // - { - // Did the user ask us to use config.guess? - // - string orig (config_guess - ? run (3, - *config_guess, - [](string& l, bool) {return move (l);}) - : BUILD2_HOST_TRIPLET); - - l5 ([&]{trace << "original host: '" << orig << "'";}); - - try - { - target_triplet t (orig); - - l5 ([&]{trace << "canonical host: '" << t.string () << "'; " - << "class: " << t.class_;}); - - // Also enter as build.host.{cpu,vendor,system,version,class} for - // convenience of access. - // - gs.assign ("build.host.cpu") = t.cpu; - gs.assign ("build.host.vendor") = t.vendor; - gs.assign ("build.host.system") = t.system; - gs.assign ("build.host.version") = t.version; - gs.assign ("build.host.class") = t.class_; - - gs.assign ("build.host") = move (t); - } - catch (const invalid_argument& e) - { - fail << "unable to parse build host '" << orig << "': " << e << - info << "consider using the --config-guess option"; - } - } - - // Register builtin target types. - // - { - target_type_map& t (gs.target_types); - - t.insert (); - t.insert (); - t.insert (); - t.insert (); - t.insert (); - t.insert (); - t.insert (); - t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); { auto& tt (t.insert ()); @@ -735,125 +393,476 @@ namespace build2 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; - pair r (p.parse_variable_value (l, gs, &work, var)); + // 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_clean = &vp.insert ("clean", v_t); + var_backlink = &vp.insert ("backlink", v_t); + var_include = &vp.insert ("include", v_q); + + vp.insert (var_extension, v_t); + + // 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_mif (const meta_operation_info& mif) + { + if (current_mname != mif.name) + { + current_mname = mif.name; + global_scope.rw ().assign (var_build_meta_operation) = mif.name; + } + + build2::current_mif = &mif; + current_on = 0; // Reset. + } + + void context:: + current_oif (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); + } + + 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); - if (r.second.type != token_type::eos) - fail << "unexpected " << r.second << " in variable assignment " - << "'" << s << "'"; + bool r; - // Make sure the value is not typed. - // - if (r.first.type != nullptr) - fail << "typed override of variable " << n; + if (o == run_phase::load) + lm_.unlock (); - // Global and absolute scope overrides we can enter directly. Project - // and relative scope ones will be entered by the caller for each - // amalgamation/project. + { + 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. // - if (c == '!' || (dir && dir->absolute ())) + condition_variable* v (nullptr); + switch (n) { - scope& s (c == '!' ? gs : sm.insert (*dir)->second); + 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; + } - auto p (s.vars.insert (*o)); - assert (p.second); // Variable name is unique. + if (u) + { + phase = n; + r = !fail_; - value& v (p.first); - v = move (r.first); + // 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 */); } - else - vos.push_back ( - variable_override {var, *o, move (dir), move (r.first)}); } - // Enter builtin variables and patterns. - // + if (n == run_phase::load) + { + lm_.lock (); + r = !fail_; // Re-query. + } - // All config. variables are by default overridable. - // - vp.insert_pattern ("config.**", nullopt, true, nullopt, true, false); + return r; + } - // 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); + // 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 + } - // module.cxx:load_module(). - // + // 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 { - auto v_p (variable_visibility::project); + if (!phase_mutex.lock (p)) + { + phase_mutex.unlock (p); + throw failed (); + } - vp.insert_pattern ("**.booted", false, v_p); - vp.insert_pattern ("**.loaded", false, v_p); - vp.insert_pattern ("**.configured", false, v_p); + phase_lock_instance = this; + + //text << this_thread::get_id () << " phase acquire " << p; } + } + phase_lock:: + ~phase_lock () + { + if (phase_lock_instance == this) { - auto v_p (variable_visibility::project); - auto v_t (variable_visibility::target); - auto v_q (variable_visibility::prereq); + phase_lock_instance = nullptr; + phase_mutex.unlock (p); - 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"); + //text << this_thread::get_id () << " phase release " << p; + } + } - var_forwarded = &vp.insert ("forwarded", v_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); - // Note that subprojects is not typed since the value requires - // pre-processing (see file.cxx). + //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. // - 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 (run_phase n) + : o (phase), n (n) + { + if (!phase_mutex.relock (o, n)) + { + phase_mutex.relock (n, o); + throw failed (); + } - var_clean = &vp.insert ("clean", v_t); - var_backlink = &vp.insert ("backlink", v_t); - var_include = &vp.insert ("include", v_q); + phase_lock_instance->p = n; - vp.insert (var_extension, v_t); + if (n == run_phase::load) // Note: load lock is exclusive. + load_generation++; - // 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"; + //text << this_thread::get_id () << " phase switch " << o << " " << n; + } - var_build_meta_operation = &vp.insert ("build.meta_operation"); + 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; } - // Register builtin rules. + bool r (phase_mutex.relock (n, o)); + phase_lock_instance->p = o; + + // Similar logic to ~phase_unlock(). // - { - rule_map& r (gs.rules); // Note: global scope! + if (!r && !uncaught_exception ()) + throw failed (); - //@@ outer - r.insert (perform_id, 0, "alias", alias_rule::instance); + //text << this_thread::get_id () << " phase restore " << n << " " << o; + } - r.insert (perform_update_id, "fsdir", fsdir_rule::instance); - r.insert (perform_clean_id, "fsdir", fsdir_rule::instance); + string current_mname; + string current_oname; - r.insert (perform_update_id, "file", file_rule::instance); - r.insert (perform_clean_id, "file", file_rule::instance); - } + 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; - return vos; - } + atomic_count dependency_count; + atomic_count target_count; + atomic_count skip_count; + + bool keep_going = false; + bool dry_run = false; 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, diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index ce9a996..f51645e 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -8,8 +8,11 @@ #include #include -#include -#include +// NOTE: this file is included by pretty much every other "data model" header +// (scope, target, variable, etc) so including any of them here is +// probably a non-starter. +// +#include #include #include @@ -17,11 +20,66 @@ namespace build2 { class scope; + class scope_map; + class target_set; + + class variable; + class variable_pool; + struct variable_override; + using variable_overrides = vector; + class value; + using values = small_vector; + + struct meta_operation_info; + struct operation_info; + + struct opspec; // Main scheduler. Started up and shut down in main(). // + // @@ CTX: move to main(). + // LIBBUILD2_SYMEXPORT extern scheduler sched; + // @@ CTX: document (backlinks, non-overlap etc). RW story. + // + class LIBBUILD2_SYMEXPORT context + { + struct data; + unique_ptr data_; + + public: + scheduler& sched; + + 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. + + public: + explicit + context (scheduler&, const strings& cmd_vars = {}); + + void + current_mif (const meta_operation_info&); + + void + current_oif (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 (); + }; + // In order to perform each operation the build system goes through the // following phases: // @@ -303,14 +361,6 @@ namespace build2 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 @@ -352,19 +402,13 @@ namespace build2 // 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, 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..cdeb9ee 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -79,6 +79,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 +169,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_oif (*poif, oif, false /* diag_noise */); action a (dist_id, poif->id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -175,7 +177,7 @@ namespace build2 } } - set_current_oif (*oif, nullptr, false /* diag_noise */); + ctx.current_oif (*oif, nullptr, false /* diag_noise */); action a (dist_id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -186,7 +188,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_oif (*poif, oif, false /* diag_noise */); action a (dist_id, poif->id, oif->id); match (params, a, ts, 1 /* diag (failures only) */, @@ -213,7 +215,7 @@ namespace build2 ? out_src (d, rs) : dir_path ()); - targets.insert ( + rs.ctx.targets.insert ( move (d), move (out), p.leaf ().base ().string (), @@ -232,7 +234,7 @@ namespace build2 { 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 +253,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 ()); @@ -305,13 +307,13 @@ namespace build2 // Note also that we don't do any structured result printing. // size_t on (current_on); - set_current_mif (mo_perform); + ctx.current_mif (mo_perform); 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_oif (op_update, nullptr, false /* diag_noise */); action a (perform_id, update_id); @@ -374,7 +376,7 @@ namespace build2 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_; diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index 7d59891..27cd9e2 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -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..207f569 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -183,7 +183,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,9 +263,9 @@ 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); // Set out_path. Note that src_path is set in setup_root() below. @@ -373,7 +373,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 +410,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 +420,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, *var_out_root)); if (!p.second) fail << "variable out_root expected as first line in " << f; @@ -495,7 +495,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 +513,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 +533,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 +545,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) { @@ -588,7 +589,7 @@ namespace build2 } else { - auto p (extract_variable (f, *var_src_root)); + auto p (extract_variable (ctx, f, *var_src_root)); if (!p.second) fail << "variable src_root expected as first line in " << f; @@ -610,7 +611,7 @@ 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, *var_project)); if (!p.second) fail << "variable " << var_project->name << " expected " @@ -628,7 +629,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 +670,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 +710,26 @@ namespace build2 } bool - bootstrap_src (scope& root, optional& altn) + bootstrap_src (scope& rs, optional& altn) { tracer trace ("bootstrap_src"); 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 +739,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 +760,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 (*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 +832,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 (*var_subprojects)); // Set NULL by default. value& v (rp.first); if (rp.second) @@ -846,13 +849,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 +926,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); @@ -1012,7 +1016,7 @@ namespace build2 // return (out_root != src_root && cast_false (orig.vars[var_forwarded]) && - bootstrap_fwd (src_root, altn) == out_root); + bootstrap_fwd (orig.ctx, src_root, altn) == out_root); } void @@ -1273,7 +1277,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. { @@ -1411,6 +1415,8 @@ namespace build2 return names {move (target)}; } + context& ctx (ibase.ctx); + // Bootstrap the imported root scope. This is pretty similar to what we do // in main() except that here we don't try to guess src_root. // @@ -1429,7 +1435,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); } @@ -1556,7 +1562,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 +1581,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 +1616,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..3909643 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -91,8 +91,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 +140,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 +201,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 +224,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 +232,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..59b53bb 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); + 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); + return import (ctx, pk, true); } } diff --git a/libbuild2/function.test.cxx b/libbuild2/function.test.cxx index 2380987..670114e 100644 --- a/libbuild2/function.test.cxx +++ b/libbuild2/function.test.cxx @@ -41,7 +41,8 @@ namespace build2 // init_diag (1); init (nullptr, argv[0]); - reset (strings ()); // No command line variables. + sched.startup (1); // Serial execution. + context ctx (sched); function_family f ("dummy"); @@ -114,9 +115,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/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/install/init.cxx b/libbuild2/install/init.cxx index 060007b..dac337f 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,7 +122,8 @@ 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 @@ -192,7 +193,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/rule.cxx b/libbuild2/install/rule.cxx index 48a404b..478aadd 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -741,7 +741,7 @@ namespace build2 cstrings args; string reld ( - cast ((*global_scope)["build.host.class"]) == "windows" + cast (rs.ctx.global_scope["build.host.class"]) == "windows" ? msys_path (chd) : relative (chd).string ()); @@ -785,7 +785,7 @@ namespace build2 dir_path chd (chroot_path (rs, base.dir)); string reld ( - cast ((*global_scope)["build.host.class"]) == "windows" + cast (rs.ctx.global_scope["build.host.class"]) == "windows" ? msys_path (chd) : relative (chd).string ()); 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 1feb121..2ed3671 100644 --- a/libbuild2/module.cxx +++ b/libbuild2/module.cxx @@ -282,7 +282,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 @@ -333,7 +333,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..879d416 100644 --- a/libbuild2/operation.cxx +++ b/libbuild2/operation.cxx @@ -81,7 +81,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. @@ -103,7 +103,7 @@ namespace build2 phase_lock pl (run_phase::match); - const target* t (targets.find (tk, trace)); + const target* t (bs.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 @@ -471,7 +471,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 diff --git a/libbuild2/operation.hxx b/libbuild2/operation.hxx index 86f93c6..0d56219 100644 --- a/libbuild2/operation.hxx +++ b/libbuild2/operation.hxx @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -21,8 +20,10 @@ namespace build2 { class location; class scope; - class target_key; class target; + class target_key; + class context; + class include_type; struct prerequisite_member; struct opspec; @@ -275,7 +276,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, diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index c55d434..a05c898 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 " @@ -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..b73aa4e 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 */) { } diff --git a/libbuild2/prerequisite.hxx b/libbuild2/prerequisite.hxx index f79ce04..3183a29 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. // diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 1ad7455..ab81333 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,7 +430,7 @@ namespace build2 // Check the cache. // variable_override_cache& cache ( - inner_proj == global_scope + inner_proj == &ctx.global_scope ? global_override_cache : inner_proj->root_extra->override_cache); @@ -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; } @@ -818,23 +821,18 @@ namespace build2 return target_types.insert (name, move (dt)); } - scope* scope::global_; + //@@ CTX ??? sounds like should be in context! 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..c3816dd 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 @@ -337,14 +346,6 @@ namespace build2 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 +357,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 +406,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_; @@ -469,24 +470,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/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..9749e94 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:: @@ -292,8 +292,6 @@ namespace build2 // target_set // - target_set targets; - const target* target_set:: find (const target_key& k, tracer& trace) const { @@ -374,7 +372,7 @@ namespace build2 ? 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 @@ -658,25 +656,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 +751,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 +845,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 @@ -905,10 +903,10 @@ namespace build2 // 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,8 +931,8 @@ 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); } } } @@ -945,11 +943,11 @@ namespace build2 // 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 +1098,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..5be1aab 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -123,9 +123,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 +142,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 +395,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 (); } @@ -530,7 +533,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 +566,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 +760,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 {opstate (c), opstate (c)} {} target (target&&) = delete; target& operator= (target&&) = delete; @@ -1377,13 +1383,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 +1762,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. diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 1671c25..fea87f3 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -13,7 +13,7 @@ namespace build2 inline const string* target:: ext () const { - slock l (targets.mutex_); + slock l (ctx.targets.mutex_); return *ext_ ? &**ext_ : nullptr; } diff --git a/libbuild2/target.txx b/libbuild2/target.txx index b93a403..8b17383 100644 --- a/libbuild2/target.txx +++ b/libbuild2/target.txx @@ -95,7 +95,7 @@ namespace build2 { // Include target type/pattern-specific variables. // - if (auto l = s.find (var_pool[var], tt, tn)) + if (auto l = s.find (s.ctx.var_pool[var], tt, tn)) { // Help the user here and strip leading '.' from the extension. // @@ -172,13 +172,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/rule.cxx b/libbuild2/test/rule.cxx index 1d11063..50eb105 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); diff --git a/libbuild2/test/script/builtin.cxx b/libbuild2/test/script/builtin.cxx index ae979da..a725e17 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 ()) { diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx index 260bc88..a4aeff2 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&) 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..e45674b 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 @@ -156,7 +156,7 @@ namespace build2 init_diag (1); init (nullptr, argv[0]); sched.startup (1); // Serial execution. - reset (strings ()); // No command line variables. + 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..c27ad39 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,7 +693,7 @@ 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:: @@ -724,7 +724,7 @@ namespace build2 sp.parent == nullptr ? mkdir_buildignore ( 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)); @@ -925,7 +925,7 @@ namespace build2 : (wd && sp.parent == nullptr ? rmdir_buildignore ( 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))); @@ -1097,8 +1097,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 +1123,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 +1175,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 +1360,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/variable.cxx b/libbuild2/variable.cxx index c921bbd..84c1fb3 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -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 diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx index 9a106b5..d153cb0 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 { @@ -1175,9 +1176,12 @@ namespace build2 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: @@ -1261,13 +1254,13 @@ namespace build2 // Global pool flag. // private: + friend class context; + explicit variable_pool (bool global): global_ (global) {} bool 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_; }; + extern const variable_map empty_variable_map; + // Value caching. Used for overrides as well as target type/pattern-specific // append/prepend. // @@ -1528,13 +1533,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 +1549,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 +1562,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 +1592,10 @@ namespace build2 cache; private: - bool global_; + context& ctx; map_type map_; + bool global_; + }; } diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx index a4e41d6..faa6e38 100644 --- a/libbuild2/version/init.cxx +++ b/libbuild2/version/init.cxx @@ -219,7 +219,7 @@ namespace build2 // Set all the version.* variables. // - auto& vp (var_pool.rw (rs)); + auto& vp (rs.ctx.var_pool.rw (rs)); auto set = [&vp, &rs] (const char* var, auto val) { diff --git a/tests/libbuild2/driver.cxx b/tests/libbuild2/driver.cxx index 706d276..8ae83d5 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. + sched.startup (1); // Serial execution. + context ctx (sched); return 0; } -- cgit v1.1