diff options
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&); } } |