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