diff options
58 files changed, 1935 insertions, 1899 deletions
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 7ef5267..0e57c08 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -100,7 +100,7 @@ namespace build2 return q ? import_existing (pk) : search_existing_target (pk); } - // If the work_queue is not present, then we don't wait. + // If the work_queue is absent, then we don't wait. // target_lock lock_impl (action a, const target& ct, optional<scheduler::work_queue> wq) @@ -113,27 +113,25 @@ namespace build2 size_t b (target::count_base ()); size_t e (b + target::offset_touched - 1); - size_t lock (b + target::offset_locked); + size_t appl (b + target::offset_applied); size_t busy (b + target::offset_busy); - for (;;) + atomic_count& task_count (ct[a].task_count); + + while (!task_count.compare_exchange_strong ( + e, + busy, + memory_order_acq_rel, // Synchronize on success. + memory_order_acquire)) // Synchronize on failure. { - // First try to grab the spin lock which we later may upgrade to busy. + // Wait for the count to drop below busy if someone is already working + // on this target. // - if (ct.task_count.compare_exchange_strong ( - e, - lock, - memory_order_acq_rel, // Synchronize on success. - memory_order_acquire)) // Synchronize on failure. + if (e >= busy) { - break; - } + if (!wq) + return target_lock {a, nullptr, e - b}; - while (e == lock || e >= busy) - { - // Wait for the count to drop below busy if someone is already working - // on this target. - // // We also unlock the phase for the duration of the wait. Why? // Consider this scenario: we are trying to match a dir{} target whose // buildfile still needs to be loaded. Let's say someone else started @@ -141,118 +139,54 @@ namespace build2 // to switch the phase to load. Which would result in a deadlock // unless we release the phase. // - if (e >= busy) - { - if (!wq) - return target_lock {nullptr, e - b}; - - phase_unlock ul; - e = sched.wait (busy - 1, ct.task_count, *wq); - } - - // Spin if it is locked. - // - for (; e == lock; e = ct.task_count.load (memory_order_acquire)) - this_thread::yield (); + phase_unlock ul; + e = sched.wait (busy - 1, task_count, *wq); } + + // We don't lock already applied or executed targets. + // + if (e >= appl) + return target_lock {a, nullptr, e - b}; } - // We now have the sping lock. Analyze the old value and decide what to - // do. + // We now have the lock. Analyze the old value and decide what to do. // target& t (const_cast<target&> (ct)); + target::opstate& s (t[a]); size_t offset; if (e <= b) { // First lock for this operation. // - t.action = a; - t.rule = nullptr; - t.dependents.store (0, memory_order_release); + s.rule = nullptr; + s.dependents.store (0, memory_order_release); + offset = target::offset_touched; } else { offset = e - b; - - switch (offset) - { - case target::offset_executed: - { - if (t.action >= a) - { - // We don't lock already executed targets. - // - t.task_count.store (e, memory_order_release); - return target_lock {nullptr, target::offset_executed}; - } - - // Override, fall through. - // - assert (a > t.action); - } - case target::offset_touched: - case target::offset_tried: - case target::offset_matched: - case target::offset_applied: - { - if (a > t.action) - { - // Only noop_recipe can be overridden. - // - if (offset >= target::offset_applied) - { - recipe_function** f (t.recipe_.target<recipe_function*> ()); - assert (f != nullptr && *f == &noop_action); - } - - t.action = a; - t.rule = nullptr; - offset = target::offset_touched; // Back to just touched. - } - else - { - assert (t.action >= a); - - // Release the lock if already applied for this action. This is - // necessary no to confuse execute since otherwise it might see - // that the target is busy and assume that someone is already - // executing it. Note that we cannot handle this case in the loop - // above since we need to lock target::action. - // - if (offset == target::offset_applied || t.action > a) - { - // Release the spin lock. - // - t.task_count.store (e, memory_order_release); - return target_lock {nullptr, offset}; - } - } - - break; - } - default: - assert (false); - } + assert (offset == target::offset_touched || + offset == target::offset_tried || + offset == target::offset_matched); } - // We are keeping it so upgrade to busy. - // - t.task_count.store (busy, memory_order_release); - return target_lock (&t, offset); + return target_lock (a, &t, offset); } void - unlock_impl (target& t, size_t offset) + unlock_impl (action a, target& t, size_t offset) { assert (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. // - t.task_count.store (offset + target::count_base (), memory_order_release); - sched.resume (t.task_count); + task_count.store (offset + target::count_base (), memory_order_release); + sched.resume (task_count); } target_lock @@ -283,160 +217,137 @@ namespace build2 return l; }; - // Return the matching rule and the recipe action. + // Return the matching rule or NULL if no match and try_match is true. // - pair<const pair<const string, reference_wrapper<const rule>>*, action> + const rule_match* match_impl (action a, target& t, const rule* skip, bool try_match) { - // Clear the resolved targets list before calling match(). The rule is - // free to modify this list in match() (provided that it matches) in order - // to, for example, prepare it for apply(). + // If this is an outer operation (Y_for_X), then we look for rules + // registered for the outer id (X). Note that we still pass the original + // action to the rule's match() function so that it can distinguish + // between a pre/post operation (Y_for_X) and the actual operation (X). // - t.clear_data (); - t.prerequisite_targets.clear (); + meta_operation_id mo (a.meta_operation ()); + operation_id o (a.inner () ? a.operation () : a.outer_operation ()); - // If this is a nested operation, first try the outer operation. - // This allows a rule to implement a "precise match", that is, - // both inner and outer operations match. - // - for (operation_id oo (a.outer_operation ()), io (a.operation ()), - o (oo != 0 ? oo : io); - o != 0; - o = (oo != 0 && o != io ? io : 0)) + const scope& bs (t.base_scope ()); + + for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) { - // Adjust action for recipe: on the first iteration we want it - // {inner, outer} (which is the same as 'a') while on the second - // -- {inner, 0}. Note that {inner, 0} is the same or "stronger" - // (i.e., overrides; see action::operator<()) than 'a'. This - // allows "unconditional inner" to override "inner for outer" - // recipes. + // Search scopes outwards, stopping at the project root. // - action ra (a.meta_operation (), io, o != oo ? 0 : oo); + for (const scope* s (&bs); + s != nullptr; + s = s->root () ? global_scope : s->parent_scope ()) + { + const operation_rule_map* om (s->rules[mo]); - const scope& bs (t.base_scope ()); + if (om == nullptr) + continue; // No entry for this meta-operation id. - for (auto tt (&t.type ()); tt != nullptr; tt = tt->base) - { - // Search scopes outwards, stopping at the project root. + // First try the map for the actual operation. If that doesn't yeld + // anything, try the wildcard map. // - for (const scope* s (&bs); - s != nullptr; - s = s->root () ? global_scope : s->parent_scope ()) + for (operation_id oi (o), oip (o); oip != 0; oip = oi, oi = 0) { - const operation_rule_map* om (s->rules[a.meta_operation ()]); + const target_type_rule_map* ttm ((*om)[oi]); - if (om == nullptr) - continue; // No entry for this meta-operation id. + if (ttm == nullptr) + continue; // No entry for this operation id. - // First try the map for the actual operation. If that doesn't yeld - // anything, try the wildcard map. - // - for (operation_id oi (o), oip (o); oip != 0; oip = oi, oi = 0) - { - const target_type_rule_map* ttm ((*om)[oi]); + if (ttm->empty ()) + continue; // Empty map for this operation id. - if (ttm == nullptr) - continue; // No entry for this operation id. + auto i (ttm->find (tt)); - if (ttm->empty ()) - continue; // Empty map for this operation id. + if (i == ttm->end () || i->second.empty ()) + continue; // No rules registered for this target type. - auto i (ttm->find (tt)); + const auto& rules (i->second); // Hint map. - if (i == ttm->end () || i->second.empty ()) - continue; // No rules registered for this target type. + // @@ TODO + // + // Different rules can be used for different operations (update vs + // test is a good example). So, at some point, we will probably have + // to support a list of hints or even an operation-hint map (e.g., + // 'hint=cxx test=foo' if cxx supports the test operation but we + // want the foo rule instead). This is also the place where the + // '{build clean}=cxx' construct (which we currently do not support) + // can come handy. + // + // Also, ignore the hint (that is most likely ment for a different + // operation) if this is a unique match. + // + string hint; + auto rs (rules.size () == 1 + ? make_pair (rules.begin (), rules.end ()) + : rules.find_sub (hint)); - const auto& rules (i->second); // Hint map. + for (auto i (rs.first); i != rs.second; ++i) + { + const auto& r (*i); + const string& n (r.first); + const rule& ru (r.second); - // @@ TODO - // - // Different rules can be used for different operations (update - // vs test is a good example). So, at some point, we will probably - // have to support a list of hints or even an operation-hint map - // (e.g., 'hint=cxx test=foo' if cxx supports the test operation - // but we want the foo rule instead). This is also the place where - // the '{build clean}=cxx' construct (which we currently do not - // support) can come handy. - // - // Also, ignore the hint (that is most likely ment for a different - // operation) if this is a unique match. - // - string hint; - auto rs (rules.size () == 1 - ? make_pair (rules.begin (), rules.end ()) - : rules.find_sub (hint)); + if (&ru == skip) + continue; - for (auto i (rs.first); i != rs.second; ++i) { - const auto& r (*i); - const string& n (r.first); - const rule& ru (r.second); + auto df = make_diag_frame ( + [a, &t, &n](const diag_record& dr) + { + if (verb != 0) + dr << info << "while matching rule " << n << " to " + << diag_do (a, t); + }); - if (&ru == skip) + if (!ru.match (a, t, hint)) continue; + } + + // Do the ambiguity test. + // + bool ambig (false); + + diag_record dr; + for (++i; i != rs.second; ++i) + { + const string& n1 (i->first); + const rule& ru1 (i->second); - match_result m (false); { auto df = make_diag_frame ( - [ra, &t, &n](const diag_record& dr) + [a, &t, &n1](const diag_record& dr) { if (verb != 0) - dr << info << "while matching rule " << n << " to " - << diag_do (ra, t); + dr << info << "while matching rule " << n1 << " to " + << diag_do (a, t); }); - if (!(m = ru.match (ra, t, hint))) + // @@ TODO: this makes target state in match() undetermined + // so need to fortify rules that modify anything in match + // to clear things. + // + // @@ Can't we temporarily swap things out in target? + // + if (!ru1.match (a, t, hint)) continue; - - if (m.recipe_action.valid ()) - assert (m.recipe_action > ra); - else - m.recipe_action = ra; // Default, if not set. } - // Do the ambiguity test. - // - bool ambig (false); - - diag_record dr; - for (++i; i != rs.second; ++i) + if (!ambig) { - const string& n1 (i->first); - const rule& ru1 (i->second); - - { - auto df = make_diag_frame ( - [ra, &t, &n1](const diag_record& dr) - { - if (verb != 0) - dr << info << "while matching rule " << n1 << " to " - << diag_do (ra, t); - }); - - // @@ TODO: this makes target state in match() undetermined - // so need to fortify rules that modify anything in match - // to clear things. - // - if (!ru1.match (ra, t, hint)) - continue; - } - - if (!ambig) - { - dr << fail << "multiple rules matching " - << diag_doing (ra, t) - << info << "rule " << n << " matches"; - ambig = true; - } - - dr << info << "rule " << n1 << " also matches"; + dr << fail << "multiple rules matching " << diag_doing (a, t) + << info << "rule " << n << " matches"; + ambig = true; } - if (!ambig) - return make_pair (&r, m.recipe_action); - else - dr << info << "use rule hint to disambiguate this match"; + dr << info << "rule " << n1 << " also matches"; } + + if (!ambig) + return &r; + else + dr << info << "use rule hint to disambiguate this match"; } } } @@ -451,14 +362,13 @@ namespace build2 dr << info << "re-run with --verbose 4 for more information"; } - return pair<const pair<const string, reference_wrapper<const rule>>*, - action> {nullptr, a}; + return nullptr; } recipe - apply_impl (target& t, - const pair<const string, reference_wrapper<const rule>>& r, - action a) + apply_impl (action a, + target& t, + const pair<const string, reference_wrapper<const rule>>& r) { auto df = make_diag_frame ( [a, &t, &r](const diag_record& dr) @@ -468,9 +378,6 @@ namespace build2 << diag_do (a, t); }); - // @@ We could also allow the rule to change the recipe action in - // apply(). Could be useful with delegates. - // return r.second.get ().apply (a, t); } @@ -480,13 +387,15 @@ namespace build2 // the first half of the result. // static pair<bool, target_state> - match_impl (action a, - target_lock& l, + match_impl (target_lock& l, bool step = false, bool try_match = false) { assert (l.target != nullptr); + + action a (l.action); target& t (*l.target); + target::opstate& s (t[a]); try { @@ -506,29 +415,38 @@ namespace build2 { // Match. // - auto mr (match_impl (a, t, nullptr, try_match)); - if (mr.first == nullptr) // Not found (try_match == true). + // Clear the resolved targets list and the data pad before calling + // match(). The rule is free to modify these in its match() + // (provided that it matches) in order to, for example, convey some + // information to apply(). + // + t.prerequisite_targets[a].clear (); + if (a.inner ()) t.clear_data (); + + const rule_match* r (match_impl (a, t, nullptr, try_match)); + + if (r == nullptr) // Not found (try_match == true). { l.offset = target::offset_tried; return make_pair (false, target_state::unknown); } - t.rule = mr.first; - t.action = mr.second; // In case overriden. + s.rule = r; l.offset = target::offset_matched; if (step) - // t.state_ is not yet set. - // + // Note: s.state is still undetermined. return make_pair (true, target_state::unknown); + + // Otherwise ... } // Fall through. case target::offset_matched: { // Apply. // - t.recipe (apply_impl (t, *t.rule, t.action)); + set_recipe (l, apply_impl (a, t, *s.rule)); l.offset = target::offset_applied; break; } @@ -541,14 +459,14 @@ namespace build2 // As a sanity measure clear the target data since it can be incomplete // or invalid (mark()/unmark() should give you some ideas). // - t.clear_data (); - t.prerequisite_targets.clear (); + t.prerequisite_targets[a].clear (); + if (a.inner ()) t.clear_data (); - t.state_ = target_state::failed; + s.state = target_state::failed; l.offset = target::offset_applied; } - return make_pair (true, t.state_); + return make_pair (true, s.state); } // If try_match is true, then indicate whether there is a rule match with @@ -599,7 +517,7 @@ namespace build2 return make_pair (false, target_state::unknown); if (task_count == nullptr) - return match_impl (a, l, false /* step */, try_match); + return match_impl (l, false /* step */, try_match); // Pass "disassembled" lock since the scheduler queue doesn't support // task destruction. Also pass our diagnostics stack (this is safe since @@ -617,8 +535,8 @@ namespace build2 { phase_lock pl (run_phase::match); // Can throw. { - target_lock l {&t, offset}; // Reassemble. - match_impl (a, l, false /* step */, try_match); + target_lock l {a, &t, offset}; // Reassemble. + match_impl (l, false /* step */, try_match); // Unlock withing the match phase. } } @@ -651,7 +569,7 @@ namespace build2 { // Match (locked). // - if (match_impl (a, l, true).second == target_state::failed) + if (match_impl (l, true).second == target_state::failed) throw failed (); if ((r = g.group_members (a)).members != nullptr) @@ -666,10 +584,14 @@ namespace build2 // not seem like it will be easy to fix (we don't know whether // someone else will execute this target). // + // @@ What if we always do match & execute together? After all, + // if a group can be resolved in apply(), then it can be + // resolved in match()! + // // Apply (locked). // - if (match_impl (a, l, true).second == target_state::failed) + if (match_impl (l, true).second == target_state::failed) throw failed (); if ((r = g.group_members (a)).members != nullptr) @@ -707,12 +629,12 @@ namespace build2 static void match_prerequisite_range (action a, target& t, R&& r, const scope* s) { - auto& pts (t.prerequisite_targets); + auto& pts (t.prerequisite_targets[a]); // Start asynchronous matching of prerequisites. Wait with unlocked phase // to allow phase switching. // - wait_guard wg (target::count_busy (), t.task_count, true); + wait_guard wg (target::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)) @@ -722,7 +644,7 @@ namespace build2 if (s != nullptr && !pt.in (*s)) continue; - match_async (a, pt, target::count_busy (), t.task_count); + match_async (a, pt, target::count_busy (), t[a].task_count); pts.push_back (&pt); } @@ -751,12 +673,12 @@ namespace build2 template <typename T> void - match_members (action a, target& t, T ts[], size_t n) + match_members (action a, target& t, T const* ts, size_t n) { // Pretty much identical to match_prerequisite_range() except we don't // search. // - wait_guard wg (target::count_busy (), t.task_count, true); + wait_guard wg (target::count_busy (), t[a].task_count, true); for (size_t i (0); i != n; ++i) { @@ -765,7 +687,7 @@ namespace build2 if (m == nullptr || marked (m)) continue; - match_async (a, *m, target::count_busy (), t.task_count); + match_async (a, *m, target::count_busy (), t[a].task_count); } wg.wait (); @@ -786,11 +708,11 @@ namespace build2 // Instantiate only for what we need. // template void - match_members<const target*> (action, target&, const target*[], size_t); + match_members<const target*> (action, target&, const target* const*, size_t); template void match_members<prerequisite_target> ( - action, target&, prerequisite_target[], size_t); + action, target&, prerequisite_target const*, size_t); const fsdir* inject_fsdir (action a, target& t, bool parent) @@ -844,7 +766,7 @@ namespace build2 if (r != nullptr) { match (a, *r); - t.prerequisite_targets.emplace_back (r); + t.prerequisite_targets[a].emplace_back (r); } return r; @@ -922,16 +844,16 @@ namespace build2 // Note that if the result is group, then the group's state can be // failed. // - switch (t.state_ = ts) + switch (t[a].state = ts) { case target_state::changed: case target_state::unchanged: break; case target_state::postponed: - ts = t.state_ = target_state::unchanged; + ts = t[a].state = target_state::unchanged; break; case target_state::group: - ts = t.group->state_; + ts = (*t.group)[a].state; break; default: assert (false); @@ -939,7 +861,7 @@ namespace build2 } catch (const failed&) { - ts = t.state_ = target_state::failed; + ts = t[a].state = target_state::failed; } return ts; @@ -948,15 +870,17 @@ namespace build2 static target_state execute_impl (action a, target& t) { - assert (t.task_count.load (memory_order_consume) == target::count_busy () - && t.state_ == target_state::unknown); + target::opstate& s (t[a]); - target_state ts (execute_recipe (a, t, t.recipe_)); + assert (s.task_count.load (memory_order_consume) == target::count_busy () + && s.state == target_state::unknown); - // Decrement the target count (see target::recipe() for details). + target_state ts (execute_recipe (a, t, s.recipe)); + + // Decrement the target count (see set_recipe() for details). // { - recipe_function** f (t.recipe_.target<recipe_function*> ()); + recipe_function** f (s.recipe.target<recipe_function*> ()); if (f == nullptr || *f != &group_action) target_count.fetch_sub (1, memory_order_relaxed); } @@ -964,11 +888,11 @@ namespace build2 // Decrement the task count (to count_executed) and wake up any threads // that might be waiting for this target. // - size_t tc (t.task_count.fetch_sub ( + size_t tc (s.task_count.fetch_sub ( target::offset_busy - target::offset_executed, memory_order_release)); assert (tc == target::count_busy ()); - sched.resume (t.task_count); + sched.resume (s.task_count); return ts; } @@ -980,11 +904,12 @@ namespace build2 atomic_count* task_count) { target& t (const_cast<target&> (ct)); // MT-aware. + target::opstate& s (t[a]); // Update dependency counts and make sure they are not skew. // size_t gd (dependency_count.fetch_sub (1, memory_order_relaxed)); - size_t td (t.dependents.fetch_sub (1, memory_order_release)); + size_t td (s.dependents.fetch_sub (1, memory_order_release)); assert (td != 0 && gd != 0); td--; @@ -1012,133 +937,103 @@ namespace build2 if (current_mode == execution_mode::last && td != 0) return target_state::postponed; - // Try to atomically change applied to busy. Note that we are in the - // execution phase so the target shall not be spin-locked. + // Try to atomically change applied to busy. // - size_t touc (target::count_touched ()); - size_t matc (target::count_matched ()); - size_t appc (target::count_applied ()); + size_t tc (target::count_applied ()); + size_t exec (target::count_executed ()); size_t busy (target::count_busy ()); - for (size_t tc (appc);;) + if (s.task_count.compare_exchange_strong ( + tc, + busy, + memory_order_acq_rel, // Synchronize on success. + memory_order_acquire)) // Synchronize on failure. { - if (t.task_count.compare_exchange_strong ( - tc, - busy, - memory_order_acq_rel, // Synchronize on success. - memory_order_acquire)) // Synchronize on failure. + // Handle the noop recipe. + // + if (s.state == target_state::unchanged) { - // Overriden touch/tried/match-only or noop recipe. + // There could still be scope operations. // - if ((tc >= touc && tc <= matc) || t.state_ == target_state::unchanged) - { - // If we have a noop recipe, there could still be scope operations. - // - if (tc == appc && t.is_a<dir> ()) - execute_recipe (a, t, nullptr /* recipe */); - else - t.state_ = target_state::unchanged; - - t.task_count.store (exec, memory_order_release); - sched.resume (t.task_count); - } - else - { - if (task_count == nullptr) - return execute_impl (a, t); + if (t.is_a<dir> ()) + execute_recipe (a, t, nullptr /* recipe */); - // 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] (target& t, const diag_frame* ds) - { - diag_frame df (ds); - execute_impl (a, t); - }, - ref (t), - diag_frame::stack)) - return target_state::unknown; // Queued. - - // Executed synchronously, fall through. - } + s.task_count.store (exec, memory_order_release); + sched.resume (s.task_count); } else { - // Normally either busy or already executed. + if (task_count == nullptr) + return execute_impl (a, t); + + // Pass our diagnostics stack (this is safe since we expect the + // caller to wait for completion before unwinding its diag stack). // - if (tc >= busy) return target_state::busy; - else if (tc != exec) - { - // This can happen if we touched/tried/matched (a noop) recipe which - // then got overridden as part of group resolution but not all the - // way to applied. In this case we treat it as noop. - // - assert ((tc >= touc && tc <= matc) && t.action > a); - continue; - } + if (sched.async (start_count, + *task_count, + [a] (target& t, const diag_frame* ds) + { + diag_frame df (ds); + execute_impl (a, t); + }, + ref (t), + diag_frame::stack)) + return target_state::unknown; // Queued. + + // Executed synchronously, fall through. } - - break; + } + else + { + // Either busy or already executed. + // + if (tc >= busy) return target_state::busy; + else assert (tc == exec); } - return t.executed_state (false); + return t.executed_state (a, false); } target_state execute_direct (action a, const target& ct) { target& t (const_cast<target&> (ct)); // MT-aware. + target::opstate& s (t[a]); - // Similar logic to match() above. + // Similar logic to match() above except we execute synchronously. // - size_t touc (target::count_touched ()); - size_t matc (target::count_matched ()); - size_t appc (target::count_applied ()); + size_t tc (target::count_applied ()); + size_t exec (target::count_executed ()); size_t busy (target::count_busy ()); - for (size_t tc (appc);;) + if (s.task_count.compare_exchange_strong ( + tc, + busy, + memory_order_acq_rel, // Synchronize on success. + memory_order_acquire)) // Synchronize on failure. { - if (t.task_count.compare_exchange_strong ( - tc, - busy, - memory_order_acq_rel, // Synchronize on success. - memory_order_acquire)) // Synchronize on failure. + if (s.state == target_state::unchanged) { - if ((tc >= touc && tc <= matc) || t.state_ == target_state::unchanged) - { - // If we have a noop recipe, there could still be scope operations. - // - if (tc == appc && t.is_a<dir> ()) - execute_recipe (a, t, nullptr /* recipe */); - else - t.state_ = target_state::unchanged; + if (t.is_a<dir> ()) + execute_recipe (a, t, nullptr /* recipe */); - t.task_count.store (exec, memory_order_release); - sched.resume (t.task_count); - } - else - execute_impl (a, t); + s.task_count.store (exec, memory_order_release); + sched.resume (s.task_count); } else - { + execute_impl (a, t); + } + else + { // If the target is busy, wait for it. // - if (tc >= busy) sched.wait (exec, t.task_count, scheduler::work_none); - else if (tc != exec) - { - assert ((tc >= touc && tc <= matc) && t.action > a); - continue; - } - } - - break; + if (tc >= busy) sched.wait (exec, s.task_count, scheduler::work_none); + else assert (tc == exec); } - return t.executed_state (); + return t.executed_state (a); } template <typename T> @@ -1149,7 +1044,7 @@ namespace build2 // Start asynchronous execution of prerequisites. // - wait_guard wg (target::count_busy (), t.task_count); + wait_guard wg (target::count_busy (), t[a].task_count); for (size_t i (0); i != n; ++i) { @@ -1160,7 +1055,7 @@ namespace build2 target_state s ( execute_async ( - a, *mt, target::count_busy (), t.task_count)); + a, *mt, target::count_busy (), t[a].task_count)); if (s == target_state::postponed) { @@ -1177,18 +1072,18 @@ namespace build2 // for (size_t i (0); i != n; ++i) { - const target*& mt (ts[i]); - - if (mt == nullptr) + if (ts[i] == nullptr) continue; + const target& mt (*ts[i]); + // If the target is still busy, wait for its completion. // - if (mt->task_count.load (memory_order_acquire) >= target::count_busy ()) - sched.wait ( - target::count_executed (), mt->task_count, scheduler::work_none); + 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); - r |= mt->executed_state (); + r |= mt.executed_state (a); } return r; @@ -1202,18 +1097,18 @@ namespace build2 // target_state r (target_state::unchanged); - wait_guard wg (target::count_busy (), t.task_count); + wait_guard wg (target::count_busy (), t[a].task_count); - for (size_t i (n); i != 0; --i) + for (size_t i (n); i != 0; ) { - const target*& mt (ts[i - 1]); + const target*& mt (ts[--i]); if (mt == nullptr) continue; target_state s ( execute_async ( - a, *mt, target::count_busy (), t.task_count)); + a, *mt, target::count_busy (), t[a].task_count)); if (s == target_state::postponed) { @@ -1224,18 +1119,18 @@ namespace build2 wg.wait (); - for (size_t i (n); i != 0; --i) + for (size_t i (n); i != 0; ) { - const target*& mt (ts[i - 1]); - - if (mt == nullptr) + if (ts[--i] == nullptr) continue; - if (mt->task_count.load (memory_order_acquire) >= target::count_busy ()) - sched.wait ( - target::count_executed (), mt->task_count, scheduler::work_none); + 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); - r |= mt->executed_state (); + r |= mt.executed_state (a); } return r; @@ -1267,7 +1162,7 @@ namespace build2 { assert (current_mode == execution_mode::first); - auto& pts (const_cast<target&> (t).prerequisite_targets); // MT-aware. + auto& pts (t.prerequisite_targets[a]); if (n == 0) n = pts.size (); @@ -1276,7 +1171,7 @@ namespace build2 // target_state rs (target_state::unchanged); - wait_guard wg (target::count_busy (), t.task_count); + wait_guard wg (target::count_busy (), t[a].task_count); for (size_t i (0); i != n; ++i) { @@ -1287,7 +1182,7 @@ namespace build2 target_state s ( execute_async ( - a, *pt, target::count_busy (), t.task_count)); + a, *pt, target::count_busy (), t[a].task_count)); if (s == target_state::postponed) { @@ -1303,25 +1198,25 @@ namespace build2 for (size_t i (0); i != n; ++i) { - const target*& pt (pts[i]); - - if (pt == nullptr) + if (pts[i] == nullptr) continue; - if (pt->task_count.load (memory_order_acquire) >= target::count_busy ()) - sched.wait ( - target::count_executed (), pt->task_count, scheduler::work_none); + const target& pt (*pts[i]); + + 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); - target_state s (pt->executed_state ()); + target_state s (pt.executed_state (a)); rs |= s; // Should we compare the timestamp to this target's? // - if (!e && (!pf || pf (*pt, i))) + if (!e && (!pf || pf (pt, i))) { // If this is an mtime-based target, then compare timestamps. // - if (auto mpt = dynamic_cast<const mtime_target*> (pt)) + if (const mtime_target* mpt = pt.is_a<mtime_target> ()) { timestamp mp (mpt->mtime ()); @@ -1340,8 +1235,8 @@ namespace build2 } } - if (rt == nullptr && pt->is_a (*tt)) - rt = pt; + if (rt == nullptr && pt.is_a (*tt)) + rt = &pt; } assert (rt != nullptr); @@ -1355,7 +1250,7 @@ namespace build2 noop_action (action a, const target& t) { text << "noop action triggered for " << diag_doing (a, t); - assert (false); // We shouldn't be called (see target::recipe()). + assert (false); // We shouldn't be called (see set_recipe()). return target_state::unchanged; } @@ -1367,8 +1262,9 @@ namespace build2 const target& g (*t.group); if (execute (a, g) == target_state::busy) - sched.wait ( - target::count_executed (), g.task_count, scheduler::work_none); + sched.wait (target::count_executed (), + g[a].task_count, + scheduler::work_none); // Indicate to execute() that this target's state comes from the group // (which, BTW, can be failed). @@ -1488,7 +1384,7 @@ namespace build2 // for (const target* m (ft.member); m != nullptr; m = m->member) { - const file* fm (dynamic_cast<const file*> (m)); + const file* fm (m->is_a<file> ()); const path* fp (fm != nullptr ? &fm->path () : nullptr); if (fm == nullptr || fp->empty ()) diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx index ff70b21..8978eba 100644 --- a/build2/algorithm.hxx +++ b/build2/algorithm.hxx @@ -102,33 +102,39 @@ namespace build2 const scope&, const dir_path& out = dir_path ()); - // Target match lock: a non-const target reference as well as the - // target::offset_* state that has already been "achieved". + // Target match lock: a non-const target reference and the target::offset_* + // state that has already been "achieved". Note that target::task_count + // itself is set to busy for the duration or the lock. // struct target_lock { + using action_type = build2::action; using target_type = build2::target; + action_type action; target_type* target = nullptr; - size_t offset = 0; + size_t offset = 0; explicit operator bool () const {return target != nullptr;} - void unlock (); - target_type* release (); + void + unlock (); target_lock () = default; - target_lock (target_lock&&); target_lock& operator= (target_lock&&); - // Implementation details. - // target_lock (const target_lock&) = delete; target_lock& operator= (const target_lock&) = delete; - target_lock (target_type* t, size_t o): target (t), offset (o) {} + // Implementation details. + // ~target_lock (); + target_lock (action_type a, target_type* t, size_t o) + : action (a), target (t), offset (o) {} + + target_type* + release () {auto r (target); target = nullptr; return r;} }; // If the target is already applied (for this action ) or executed, then no @@ -222,13 +228,23 @@ namespace build2 // Match a "delegate rule" from withing another rules' apply() function // avoiding recursive matches (thus the third argument). Unless try_match is - // true, fail if not rule is found. Otherwise return empty recipe. Note that - // unlike match(), this function does not increment the dependents - // count. See also the companion execute_delegate(). + // true, fail if no rule is found. Otherwise return empty recipe. Note that + // unlike match(), this function does not increment the dependents count and + // the two rules must coordinate who is using the target's data pad and/or + // prerequisite_targets. See also the companion execute_delegate(). // recipe match_delegate (action, target&, const rule&, bool try_match = false); + // Match a rule for the inner operation from withing the outer rule's + // apply() function. See also the companion execute_inner(). + // + target_state + match_inner (action, const target&); + + bool + match_inner (action, const target&, unmatch); + // The standard prerequisite search and match implementations. They call // search() and then match() for each prerequisite in a loop omitting out of // project prerequisites for the clean operation. If this target is a member @@ -259,7 +275,7 @@ namespace build2 // template <typename T> void - match_members (action, target&, T[], size_t); + match_members (action, target&, T const*, size_t); template <size_t N> inline void @@ -269,7 +285,10 @@ namespace build2 } inline void - match_members (action a, target& t, prerequisite_targets& ts, size_t start) + match_members (action a, + target& t, + prerequisite_targets& ts, + size_t start = 0) { match_members (a, t, ts.data () + start, ts.size () - start); } @@ -279,6 +298,13 @@ namespace build2 // member's list might still not be available (e.g., if some wildcard/ // fallback rule matched). // + // If the action is is for an outer operation, then it is changed to inner + // which means the members are always resolved by the inner (e.g., update) + // rule. This feels right since this is the rule that will normally do the + // work (e.g., update) and therefore knows what it will produce (and if we + // don't do this, then the group resolution will be racy since we will use + // two different task_count instances for synchronization). + // group_view resolve_group_members (action, const target&); @@ -307,6 +333,12 @@ namespace build2 target_state execute (action, const target&); + // As above but wait for completion if the target is busy and translate + // target_state::failed to the failed exception. + // + target_state + execute_wait (action, const target&); + // As above but start asynchronous execution. Return target_state::unknown // if the asynchrounous execution has been started and target_state::busy if // the target has already been busy. @@ -328,6 +360,18 @@ namespace build2 target_state execute_delegate (const recipe&, action, const target&); + // Execute the inner operation matched with match_inner(). Note that the + // returned target state is for the inner operation. The appropriate usage + // is to call this function from the outer operation's recipe and to factor + // the obtained state into the one returned (similar to how we do it for + // prerequisites). + // + // Note: waits for the completion if the target is busy and translates + // target_state::failed to the failed exception. + // + target_state + execute_inner (action, const target&); + // A special version of the above that should be used for "direct" and "now" // execution, that is, side-stepping the normal target-prerequisite // relationship (so no dependents count is decremented) and execution order @@ -344,30 +388,29 @@ namespace build2 // for their completion. Return target_state::changed if any of them were // changed and target_state::unchanged otherwise. If a prerequisite's // execution is postponed, then set its pointer in prerequisite_targets to - // NULL (since its state cannot be queried MT-safely). - // - // Note that this function can be used as a recipe. + // NULL (since its state cannot be queried MT-safely). If count is not 0, + // then only the first count prerequisites are executed. // target_state - straight_execute_prerequisites (action, const target&); + straight_execute_prerequisites (action, const target&, size_t count = 0); // As above but iterates over the prerequisites in reverse. // target_state - reverse_execute_prerequisites (action, const target&); + reverse_execute_prerequisites (action, const target&, size_t count = 0); // Call straight or reverse depending on the current mode. // target_state - execute_prerequisites (action, const target&); + execute_prerequisites (action, const target&, size_t count = 0); // A version of the above that also determines whether the action needs to // be executed on the target based on the passed timestamp and filter. // // The filter is passed each prerequisite target and is expected to signal // which ones should be used for timestamp comparison. If the filter is - // NULL, then all the prerequisites are used. If the count is not 0, then - // only the first count prerequisites are executed. + // NULL, then all the prerequisites are used. If count is not 0, then only + // the first count prerequisites are executed. // // Note that the return value is an optional target state. If the target // needs updating, then the value absent. Otherwise it is the state that diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx index 565414a..9baa650 100644 --- a/build2/algorithm.ixx +++ b/build2/algorithm.ixx @@ -129,26 +129,18 @@ namespace build2 lock_impl (action, const target&, optional<scheduler::work_queue>); void - unlock_impl (target&, size_t); + unlock_impl (action, target&, size_t); inline void target_lock:: unlock () { if (target != nullptr) { - unlock_impl (*target, offset); + unlock_impl (action, *target, offset); target = nullptr; } } - inline target* target_lock:: - release () - { - target_type* r (target); - target = nullptr; - return r; - } - inline target_lock:: ~target_lock () { @@ -157,8 +149,9 @@ namespace build2 inline target_lock:: target_lock (target_lock&& x) - : target (x.release ()), offset (x.offset) + : action (x.action), target (x.target), offset (x.offset) { + x.target = nullptr; } inline target_lock& target_lock:: @@ -167,8 +160,10 @@ namespace build2 if (this != &x) { unlock (); - target = x.release (); + action = x.action; + target = x.target; offset = x.offset; + x.target = nullptr; } return *this; } @@ -185,13 +180,11 @@ namespace build2 return r; } - pair<const pair<const string, reference_wrapper<const rule>>*, action> + const rule_match* match_impl (action, target&, const rule* skip, bool try_match = false); recipe - apply_impl (target&, - const pair<const string, reference_wrapper<const rule>>&, - action); + apply_impl (action, target&, const rule_match&); pair<bool, target_state> match (action, const target&, size_t, atomic_count*, bool try_match = false); @@ -206,7 +199,7 @@ namespace build2 if (r != target_state::failed) { dependency_count.fetch_add (1, memory_order_relaxed); - t.dependents.fetch_add (1, memory_order_release); + t[a].dependents.fetch_add (1, memory_order_release); } else if (fail) throw failed (); @@ -227,7 +220,7 @@ namespace build2 if (r.second != target_state::failed) { dependency_count.fetch_add (1, memory_order_relaxed); - t.dependents.fetch_add (1, memory_order_release); + t[a].dependents.fetch_add (1, memory_order_release); } else if (fail) throw failed (); @@ -236,7 +229,6 @@ namespace build2 return r; } - inline bool match (action a, const target& t, unmatch um) { @@ -261,8 +253,8 @@ namespace build2 { // Safe if unchanged or someone else is also a dependent. // - if (s == target_state::unchanged || - t.dependents.load (memory_order_consume) != 0) + if (s == target_state::unchanged || + t[a].dependents.load (memory_order_consume) != 0) return true; break; @@ -270,7 +262,7 @@ namespace build2 } dependency_count.fetch_add (1, memory_order_relaxed); - t.dependents.fetch_add (1, memory_order_release); + t[a].dependents.fetch_add (1, memory_order_release); return false; } @@ -290,24 +282,76 @@ namespace build2 } inline void + set_recipe (target_lock& l, recipe&& r) + { + target::opstate& s ((*l.target)[l.action]); + + s.recipe = move (r); + + // If this is a noop recipe, then mark the target unchanged to allow for + // some optimizations. + // + recipe_function** f (s.recipe.target<recipe_function*> ()); + + if (f != nullptr && *f == &noop_action) + s.state = target_state::unchanged; + else + { + s.state = target_state::unknown; + + // This gets tricky when we start considering direct execution, etc. So + // here seems like the best place to do it. + // + // We also ignore the group recipe since it is used for ad hoc groups + // (which are not executed). Plus, group action means real recipe is in + // the group so this also feels right conceptually. + // + // Note that we will increment this count twice for the same target if + // we have non-noop recipes for both inner and outer operations. While + // not ideal, the alternative (trying to "merge" the count keeping track + // whether inner and/or outer is noop) gets hairy rather quickly. + // + if (f == nullptr || *f != &group_action) + target_count.fetch_add (1, memory_order_relaxed); + } + } + + inline void match_recipe (target_lock& l, recipe r) { assert (phase == run_phase::match && l.target != nullptr); - target& t (*l.target); - t.rule = nullptr; // No rule. - t.recipe (move (r)); + (*l.target)[l.action].rule = nullptr; // No rule. + set_recipe (l, move (r)); l.offset = target::offset_applied; } inline recipe - match_delegate (action a, target& t, const rule& r, bool try_match) + match_delegate (action a, target& t, const rule& dr, bool try_match) { assert (phase == run_phase::match); - auto mr (match_impl (a, t, &r, try_match)); - return mr.first != nullptr - ? apply_impl (t, *mr.first, mr.second) - : empty_recipe; + + // Note: we don't touch any of the t[a] state since that was/will be set + // for the delegating rule. + // + const rule_match* r (match_impl (a, t, &dr, try_match)); + return r != nullptr ? apply_impl (a, t, *r) : empty_recipe; + } + + inline target_state + match_inner (action a, const target& t) + { + // In a sense this is like any other dependency. + // + assert (a.outer ()); + return match (action (a.meta_operation (), a.operation ()), t); + } + + inline bool + match_inner (action a, const target& t, unmatch um) + { + assert (a.outer ()); + return match (action (a.meta_operation (), a.operation ()), t, um); } group_view @@ -318,6 +362,9 @@ namespace build2 { group_view r; + if (a.outer ()) + a = action (a.meta_operation (), a.operation ()); + // We can be called during execute though everything should have been // already resolved. // @@ -395,6 +442,17 @@ namespace build2 } inline target_state + 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); + + return t.executed_state (a); + } + + inline target_state execute_async (action a, const target& t, size_t sc, atomic_count& tc, bool fail) @@ -414,26 +472,32 @@ namespace build2 } inline target_state - straight_execute_prerequisites (action a, const target& t) + execute_inner (action a, const target& t) + { + assert (a.outer ()); + return execute_wait (action (a.meta_operation (), a.operation ()), t); + } + + inline target_state + straight_execute_prerequisites (action a, const target& t, size_t c) { - auto& p (const_cast<target&> (t).prerequisite_targets); // MT-aware. - return straight_execute_members (a, t, p.data (), p.size ()); + auto& p (t.prerequisite_targets[a]); + return straight_execute_members (a, t, p.data (), c == 0 ? p.size () : c); } inline target_state - reverse_execute_prerequisites (action a, const target& t) + reverse_execute_prerequisites (action a, const target& t, size_t c) { - auto& p (const_cast<target&> (t).prerequisite_targets); // MT-aware. - return reverse_execute_members (a, t, p.data (), p.size ()); + auto& p (t.prerequisite_targets[a]); + return reverse_execute_members (a, t, p.data (), c == 0 ? p.size () : c); } inline target_state - execute_prerequisites (action a, const target& t) + execute_prerequisites (action a, const target& t, size_t c) { - auto& p (const_cast<target&> (t).prerequisite_targets); // MT-aware. return current_mode == execution_mode::first - ? straight_execute_members (a, t, p.data (), p.size ()) - : reverse_execute_members (a, t, p.data (), p.size ()); + ? straight_execute_prerequisites (a, t, c) + : reverse_execute_prerequisites (a, t, c); } // If the first argument is NULL, then the result is treated as a boolean diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index d9f3c0e..565936f 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -11,6 +11,10 @@ #include <build2/diagnostics.hxx> #include <build2/config/utility.hxx> + +#include <build2/test/module.hxx> + +#include <build2/install/rule.hxx> #include <build2/install/utility.hxx> #include <build2/bin/rule.hxx> @@ -456,17 +460,24 @@ namespace build2 r.insert<libu> (perform_update_id, "bin.libu", fail_); r.insert<libu> (perform_clean_id, "bin.libu", fail_); - r.insert<lib> (perform_update_id, "bin.lib", lib_); - r.insert<lib> (perform_clean_id, "bin.lib", lib_); - - // Configure members. + // Similar to alias. // - r.insert<lib> (configure_update_id, "bin.lib", lib_); + r.insert<lib> (perform_id, 0, "bin.lib", lib_); + r.insert<lib> (configure_id, 0, "bin.lib", lib_); + // Treat as a see through group for install and test. + // if (install_loaded) { - r.insert<lib> (perform_install_id, "bin.lib", lib_); - r.insert<lib> (perform_uninstall_id, "bin.lib", lib_); + auto& gr (install::group_rule::instance); + + r.insert<lib> (perform_install_id, "bin.lib", gr); + r.insert<lib> (perform_uninstall_id, "bin.lib", gr); + } + + if (const test::module* m = rs.modules.lookup<test::module> ("test")) + { + r.insert<lib> (perform_test_id, "bin.lib", m->group_rule ()); } } diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index bb9036b..79270c3 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -19,7 +19,7 @@ namespace build2 { // fail_rule // - match_result fail_rule:: + bool fail_rule:: match (action a, target& t, const string&) const { const char* n (t.dynamic_type ().name); // Ignore derived type. @@ -37,8 +37,8 @@ namespace build2 // The whole logic is pretty much as if we had our two group members as // our prerequisites. // - match_result lib_rule:: - match (action act, target& xt, const string&) const + bool lib_rule:: + match (action, target& xt, const string&) const { lib& t (xt.as<lib> ()); @@ -57,35 +57,27 @@ namespace build2 t.a = a ? &search<liba> (t, t.dir, t.out, t.name) : nullptr; t.s = s ? &search<libs> (t, t.dir, t.out, t.name) : nullptr; - match_result mr (true); - - // If there is an outer operation, indicate that we match - // unconditionally so that we don't override ourselves. - // - if (act.outer_operation () != 0) - mr.recipe_action = action (act.meta_operation (), act.operation ()); - - return mr; + return true; } recipe lib_rule:: - apply (action act, target& xt) const + apply (action a, target& xt) const { lib& t (xt.as<lib> ()); const target* m[] = {t.a, t.s}; - match_members (act, t, m); + match_members (a, t, m); return &perform; } target_state lib_rule:: - perform (action act, const target& xt) + perform (action a, const target& xt) { const lib& t (xt.as<lib> ()); const target* m[] = {t.a, t.s}; - return execute_members (act, t, m); + return execute_members (a, t, m); } } } diff --git a/build2/bin/rule.hxx b/build2/bin/rule.hxx index b4835dc..6385830 100644 --- a/build2/bin/rule.hxx +++ b/build2/bin/rule.hxx @@ -14,26 +14,29 @@ namespace build2 { namespace bin { - // Fail rule for obj{}, bmi{}, and libu{}. + // "Fail rule" for obj{}, bmi{}, and libu{} that issues diagnostics if + // someone tries to build any of these groups directly. // class fail_rule: public rule { public: fail_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe apply (action, target&) const override; }; + // Pass-through to group members rule, similar to alias. + // class lib_rule: public rule { public: lib_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index 533da43..2bcb8bc 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -281,7 +281,7 @@ namespace build2 // lib // group_view lib:: - group_members (action_type) const + group_members (action) const { static_assert (sizeof (lib_members) == sizeof (const target*) * 2, "member layout incompatible with array"); @@ -321,7 +321,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + false // Note: not see-through ("alternatives" group). }; // libi diff --git a/build2/bin/target.hxx b/build2/bin/target.hxx index 790d1f0..329b4a9 100644 --- a/build2/bin/target.hxx +++ b/build2/bin/target.hxx @@ -226,7 +226,7 @@ namespace build2 using libx::libx; virtual group_view - group_members (action_type) const override; + group_members (action) const override; public: static const target_type static_type; diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index 3daada1..e4dbfe8 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -46,7 +46,7 @@ namespace build2 // void common:: process_libraries ( - action act, + action a, const scope& top_bs, linfo top_li, const dir_paths& top_sysd, @@ -222,23 +222,23 @@ namespace build2 // if (impl && !c_e_libs.defined () && !x_e_libs.defined ()) { - for (auto pt: l.prerequisite_targets) + for (const prerequisite_target& pt: l.prerequisite_targets[a]) { if (pt == nullptr) continue; - bool a; + bool la; const file* f; - if ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || - ( f = pt->is_a<libs> ())) + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + ( f = pt->is_a<libs> ())) { if (sysd == nullptr) find_sysd (); if (!li) find_linfo (); - process_libraries (act, bs, *li, *sysd, - *f, a, pt.data, + process_libraries (a, bs, *li, *sysd, + *f, la, pt.data, proc_impl, proc_lib, proc_opt, true); } } @@ -275,7 +275,7 @@ namespace build2 &proc_impl, &proc_lib, &proc_opt, &sysd, &usrd, &find_sysd, &find_linfo, &sys_simple, - &bs, act, &li, this] (const lookup& lu) + &bs, a, &li, this] (const lookup& lu) { const vector<name>* ns (cast_null<vector<name>> (lu)); if (ns == nullptr || ns->empty ()) @@ -300,7 +300,7 @@ namespace build2 if (sysd == nullptr) find_sysd (); if (!li) find_linfo (); - const file& t (resolve_library (act, bs, n, *li, *sysd, usrd)); + const file& t (resolve_library (a, bs, n, *li, *sysd, usrd)); if (proc_lib) { @@ -324,7 +324,7 @@ namespace build2 // @@ Where can we get the link flags? Should we try to find them // in the library's prerequisites? What about installed stuff? // - process_libraries (act, bs, *li, *sysd, + process_libraries (a, bs, *li, *sysd, t, t.is_a<liba> () || t.is_a<libux> (), 0, proc_impl, proc_lib, proc_opt, true); } @@ -402,7 +402,7 @@ namespace build2 // that's the only way to guarantee it will be up-to-date. // const file& common:: - resolve_library (action act, + resolve_library (action a, const scope& s, name n, linfo li, @@ -439,7 +439,7 @@ namespace build2 // dir_path out; prerequisite_key pk {n.proj, {tt, &n.dir, &out, &n.value, ext}, &s}; - xt = search_library_existing (act, sysd, usrd, pk); + xt = search_library_existing (a, sysd, usrd, pk); if (xt == nullptr) { @@ -454,7 +454,7 @@ namespace build2 // If this is lib{}/libu{}, pick appropriate member. // if (const libx* l = xt->is_a<libx> ()) - xt = &link_member (*l, act, li); // Pick lib*{e,a,s}{}. + xt = &link_member (*l, a, li); // Pick lib*{e,a,s}{}. return xt->as<file> (); } diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 5ed7173..5952df6 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -225,7 +225,7 @@ namespace build2 bool = false) const; const target* - search_library (action act, + search_library (action a, const dir_paths& sysd, optional<dir_paths>& usrd, const prerequisite& p) const @@ -234,7 +234,7 @@ namespace build2 if (r == nullptr) { - if ((r = search_library (act, sysd, usrd, p.key ())) != nullptr) + if ((r = search_library (a, sysd, usrd, p.key ())) != nullptr) { const target* e (nullptr); if (!p.target.compare_exchange_strong ( @@ -274,12 +274,12 @@ namespace build2 bool existing = false) const; const target* - search_library_existing (action act, + search_library_existing (action a, const dir_paths& sysd, optional<dir_paths>& usrd, const prerequisite_key& pk) const { - return search_library (act, sysd, usrd, pk, true); + return search_library (a, sysd, usrd, pk, true); } dir_paths diff --git a/build2/cc/compile.cxx b/build2/cc/compile-rule.cxx index 94b3478..df84547 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile-rule.cxx @@ -1,8 +1,8 @@ -// file : build2/cc/compile.cxx -*- C++ -*- +// file : build2/cc/compile-rule.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include <build2/cc/compile.hxx> +#include <build2/cc/compile-rule.hxx> #include <cstdlib> // exit() #include <cstring> // strlen() @@ -124,7 +124,7 @@ namespace build2 throw invalid_argument ("invalid preprocessed value '" + s + "'"); } - struct compile::match_data + struct compile_rule::match_data { explicit match_data (translation_type t, const prerequisite_member& s) @@ -141,16 +141,16 @@ namespace build2 module_positions mods = {0, 0, 0}; }; - compile:: - compile (data&& d) + compile_rule:: + compile_rule (data&& d) : common (move (d)), rule_id (string (x) += ".compile 4") { - static_assert (sizeof (compile::match_data) <= target::data_size, + static_assert (sizeof (match_data) <= target::data_size, "insufficient space"); } - const char* compile:: + const char* compile_rule:: langopt (const match_data& md) const { bool m (md.type == translation_type::module_iface); @@ -204,7 +204,7 @@ namespace build2 return nullptr; } - inline void compile:: + inline void compile_rule:: append_symexport_options (cstrings& args, const target& t) const { // With VC if a BMI is compiled with dllexport, then when such BMI is @@ -216,10 +216,10 @@ namespace build2 : "-D__symexport="); } - match_result compile:: - match (action act, target& t, const string&) const + bool compile_rule:: + match (action a, target& t, const string&) const { - tracer trace (x, "compile::match"); + tracer trace (x, "compile_rule::match"); bool mod (t.is_a<bmie> () || t.is_a<bmia> () || t.is_a<bmis> ()); @@ -235,7 +235,7 @@ namespace build2 // file specified for a member overrides the one specified for the // group. Also "see through" groups. // - for (prerequisite_member p: reverse_group_prerequisite_members (act, t)) + for (prerequisite_member p: reverse_group_prerequisite_members (a, t)) { if (p.is_a (mod ? *x_mod : x_src)) { @@ -257,11 +257,11 @@ namespace build2 // Append or hash library options from a pair of *.export.* variables // (first one is cc.export.*) recursively, prerequisite libraries first. // - void compile:: + void compile_rule:: append_lib_options (const scope& bs, cstrings& args, + action a, const target& t, - action act, linfo li) const { // See through utility libraries. @@ -290,33 +290,33 @@ namespace build2 const function<bool (const file&, bool)> impf (imp); const function<void (const file&, const string&, bool, bool)> optf (opt); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { // Should be already searched and matched for libraries. // if (const target* pt = p.load ()) { if (const libx* l = pt->is_a<libx> ()) - pt = &link_member (*l, act, li); + pt = &link_member (*l, a, li); - bool a; - if (!((a = pt->is_a<liba> ()) || - (a = pt->is_a<libux> ()) || + bool la; + if (!((la = pt->is_a<liba> ()) || + (la = pt->is_a<libux> ()) || pt->is_a<libs> ())) continue; - process_libraries (act, bs, li, sys_lib_dirs, - pt->as<file> (), a, 0, // Hack: lflags unused. + process_libraries (a, bs, li, sys_lib_dirs, + pt->as<file> (), la, 0, // Hack: lflags unused. impf, nullptr, optf); } } } - void compile:: + void compile_rule:: hash_lib_options (const scope& bs, sha256& cs, + action a, const target& t, - action act, linfo li) const { auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; @@ -340,21 +340,21 @@ namespace build2 const function<bool (const file&, bool)> impf (imp); const function<void (const file&, const string&, bool, bool)> optf (opt); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { if (const target* pt = p.load ()) { if (const libx* l = pt->is_a<libx> ()) - pt = &link_member (*l, act, li); + pt = &link_member (*l, a, li); - bool a; - if (!((a = pt->is_a<liba> ()) || - (a = pt->is_a<libux> ()) || + bool la; + if (!((la = pt->is_a<liba> ()) || + (la = pt->is_a<libux> ()) || pt->is_a<libs> ())) continue; - process_libraries (act, bs, li, sys_lib_dirs, - pt->as<file> (), a, 0, // Hack: lflags unused. + process_libraries (a, bs, li, sys_lib_dirs, + pt->as<file> (), la, 0, // Hack: lflags unused. impf, nullptr, optf); } } @@ -363,11 +363,11 @@ namespace build2 // Append library prefixes based on the *.export.poptions variables // recursively, prerequisite libraries first. // - void compile:: + void compile_rule:: append_lib_prefixes (const scope& bs, prefix_map& m, + action a, target& t, - action act, linfo li) const { auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; @@ -391,21 +391,21 @@ namespace build2 const function<bool (const file&, bool)> impf (imp); const function<void (const file&, const string&, bool, bool)> optf (opt); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { if (const target* pt = p.load ()) { if (const libx* l = pt->is_a<libx> ()) - pt = &link_member (*l, act, li); + pt = &link_member (*l, a, li); - bool a; - if (!((a = pt->is_a<liba> ()) || - (a = pt->is_a<libux> ()) || + bool la; + if (!((la = pt->is_a<liba> ()) || + (la = pt->is_a<libux> ()) || pt->is_a<libs> ())) continue; - process_libraries (act, bs, li, sys_lib_dirs, - pt->as<file> (), a, 0, // Hack: lflags unused. + process_libraries (a, bs, li, sys_lib_dirs, + pt->as<file> (), la, 0, // Hack: lflags unused. impf, nullptr, optf); } } @@ -427,14 +427,14 @@ namespace build2 // file is known to be up to date. So we do the update "smartly". // static bool - update (tracer& trace, action act, const target& t, timestamp ts) + update (tracer& trace, action a, const target& t, timestamp ts) { const path_target* pt (t.is_a<path_target> ()); if (pt == nullptr) ts = timestamp_unknown; - target_state os (t.matched_state (act)); + target_state os (t.matched_state (a)); if (os == target_state::unchanged) { @@ -444,7 +444,7 @@ namespace build2 { // We expect the timestamp to be known (i.e., existing file). // - timestamp mt (pt->mtime ()); // @@ MT perf: know target state. + timestamp mt (pt->mtime ()); assert (mt != timestamp_unknown); return mt > ts; } @@ -460,7 +460,7 @@ namespace build2 // any generated header. // phase_switch ps (run_phase::execute); - target_state ns (execute_direct (act, t)); + target_state ns (execute_direct (a, t)); if (ns != os && ns != target_state::unchanged) { @@ -474,10 +474,10 @@ namespace build2 } } - recipe compile:: - apply (action act, target& xt) const + recipe compile_rule:: + apply (action a, target& xt) const { - tracer trace (x, "compile::apply"); + tracer trace (x, "compile_rule::apply"); file& t (xt.as<file> ()); // Either obj*{} or bmi*{}. @@ -569,7 +569,7 @@ namespace build2 // (e.g., foo.mxx and foo.cxx) which means obj*{} targets could // collide. So we add the module extension to the target name. // - target_lock obj (add_adhoc_member (act, t, tt.obj, e.c_str ())); + target_lock obj (add_adhoc_member (a, t, tt.obj, e.c_str ())); obj.target->as<file> ().derive_path (o); match_recipe (obj, group_recipe); // Set recipe and unlock. } @@ -579,7 +579,7 @@ namespace build2 // Inject dependency on the output directory. // - const fsdir* dir (inject_fsdir (act, t)); + const fsdir* dir (inject_fsdir (a, t)); // Match all the existing prerequisites. The injection code takes care // of the ones it is adding. @@ -587,16 +587,16 @@ namespace build2 // When cleaning, ignore prerequisites that are not in the same or a // subdirectory of our project root. // - auto& pts (t.prerequisite_targets); + auto& pts (t.prerequisite_targets[a]); optional<dir_paths> usr_lib_dirs; // Extract lazily. // Start asynchronous matching of prerequisites. Wait with unlocked // phase to allow phase switching. // - wait_guard wg (target::count_busy (), t.task_count, true); + wait_guard wg (target::count_busy (), t[a].task_count, true); size_t start (pts.size ()); // Index of the first to be added. - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { const target* pt (nullptr); @@ -609,7 +609,7 @@ namespace build2 p.is_a<libs> () || p.is_a<libux> ()) { - if (act.operation () == update_id) + if (a.operation () == update_id) { // Handle (phase two) imported libraries. We know that for such // libraries we don't need to do match() in order to get options @@ -617,7 +617,7 @@ namespace build2 // if (p.proj ()) { - if (search_library (act, + if (search_library (a, sys_lib_dirs, usr_lib_dirs, p.prerequisite) != nullptr) @@ -627,7 +627,7 @@ namespace build2 pt = &p.search (t); if (const libx* l = pt->is_a<libx> ()) - pt = &link_member (*l, act, li); + pt = &link_member (*l, a, li); } else continue; @@ -644,11 +644,11 @@ namespace build2 { pt = &p.search (t); - if (act.operation () == clean_id && !pt->dir.sub (rs.out_path ())) + if (a.operation () == clean_id && !pt->dir.sub (rs.out_path ())) continue; } - match_async (act, *pt, target::count_busy (), t.task_count); + match_async (a, *pt, target::count_busy (), t[a].task_count); pts.push_back (pt); } @@ -668,7 +668,7 @@ namespace build2 // an obj?{} target directory. // if (build2::match ( - act, + a, *pt, pt->is_a<liba> () || pt->is_a<libs> () || pt->is_a<libux> () ? unmatch::safe @@ -680,7 +680,7 @@ namespace build2 // since chances are we will have to update some of our prerequisites in // the process (auto-generated source code). // - if (act == perform_update_id) + if (a == perform_update_id) { // The cached prerequisite target should be the same as what is in // t.prerequisite_targets since we used standard search() and match() @@ -722,7 +722,7 @@ namespace build2 // this can very well be happening in parallel. But that's not a // problem since fsdir{}'s update is idempotent. // - fsdir_rule::perform_update_direct (act, t); + fsdir_rule::perform_update_direct (a, t); } // Note: the leading '@' is reserved for the module map prefix (see @@ -764,7 +764,7 @@ namespace build2 // Hash *.export.poptions from prerequisite libraries. // - hash_lib_options (bs, cs, t, act, li); + hash_lib_options (bs, cs, a, t, li); // Extra system header dirs (last). // @@ -821,14 +821,14 @@ namespace build2 if (pt == nullptr || pt == dir) continue; - u = update (trace, act, *pt, u ? timestamp_unknown : mt) || u; + u = update (trace, a, *pt, u ? timestamp_unknown : mt) || u; } // Check if the source is already preprocessed to a certain degree. // This determines which of the following steps we perform and on // what source (original or preprocessed). // - // Note: must be set of the src target. + // Note: must be set on the src target. // if (const string* v = cast_null<string> (src[x_preprocessed])) try @@ -846,7 +846,7 @@ namespace build2 // pair<auto_rmfile, bool> psrc (auto_rmfile (), false); if (md.pp < preprocessed::includes) - psrc = extract_headers (act, bs, t, li, src, md, dd, u, mt); + psrc = extract_headers (a, bs, t, li, src, md, dd, u, mt); // Next we "obtain" the translation unit information. What exactly // "obtain" entails is tricky: If things changed, then we re-parse the @@ -869,7 +869,7 @@ namespace build2 { if (u) { - auto p (parse_unit (act, t, li, src, psrc.first, md)); + auto p (parse_unit (a, t, li, src, psrc.first, md)); if (cs != p.second) { @@ -948,7 +948,7 @@ namespace build2 // NOTE: assumes that no further targets will be added into // t.prerequisite_targets! // - extract_modules (act, bs, t, li, tt, src, md, move (tu.mod), dd, u); + extract_modules (a, bs, t, li, tt, src, md, move (tu.mod), dd, u); } // If anything got updated, then we didn't rely on the cache. However, @@ -1002,7 +1002,7 @@ namespace build2 md.mt = u ? timestamp_nonexistent : dd.mtime (); } - switch (act) + switch (a) { case perform_update_id: return [this] (action a, const target& t) { @@ -1018,7 +1018,7 @@ namespace build2 // Reverse-lookup target type from extension. // - const target_type* compile:: + const target_type* compile_rule:: map_extension (const scope& s, const string& n, const string& e) const { // We will just have to try all of the possible ones, in the "most @@ -1047,10 +1047,10 @@ namespace build2 return nullptr; } - void compile:: + void compile_rule:: append_prefixes (prefix_map& m, const target& t, const variable& var) const { - tracer trace (x, "compile::append_prefixes"); + tracer trace (x, "compile_rule::append_prefixes"); // If this target does not belong to any project (e.g, an "imported as // installed" library), then it can't possibly generate any headers for @@ -1187,10 +1187,10 @@ namespace build2 } } - auto compile:: + auto compile_rule:: build_prefix_map (const scope& bs, + action a, target& t, - action act, linfo li) const -> prefix_map { prefix_map m; @@ -1202,7 +1202,7 @@ namespace build2 // Then process the include directories from prerequisite libraries. // - append_lib_prefixes (bs, m, t, act, li); + append_lib_prefixes (bs, m, a, t, li); return m; } @@ -1405,8 +1405,8 @@ namespace build2 // file as well as an indication if it is usable for compilation (see // below for details). // - pair<auto_rmfile, bool> compile:: - extract_headers (action act, + pair<auto_rmfile, bool> compile_rule:: + extract_headers (action a, const scope& bs, file& t, linfo li, @@ -1416,7 +1416,7 @@ namespace build2 bool& updating, timestamp mt) const { - tracer trace (x, "compile::extract_headers"); + tracer trace (x, "compile_rule::extract_headers"); l5 ([&]{trace << "target: " << t;}); @@ -1628,7 +1628,7 @@ namespace build2 // Return NULL if the dependency information goes to stdout and a // pointer to the temporary file path otherwise. // - auto init_args = [&t, act, li, + auto init_args = [&t, a, li, &src, &md, &psrc, &sense_diag, &rs, &bs, pp, &env, &args, &args_gen, &args_i, &out, &drm, @@ -1677,7 +1677,7 @@ namespace build2 // Add *.export.poptions from prerequisite libraries. // - append_lib_options (bs, args, t, act, li); + append_lib_options (bs, args, a, t, li); append_options (args, t, c_poptions); append_options (args, t, x_poptions); @@ -2055,7 +2055,7 @@ namespace build2 // extraction process should be restarted. // auto add = [&trace, &pfx_map, &so_map, - act, &t, li, + a, &t, li, &dd, &updating, &skip_count, &bs, this] (path f, bool cache, timestamp mt) -> bool @@ -2185,7 +2185,7 @@ namespace build2 l4 ([&]{trace << "non-existent header '" << f << "'";}); if (!pfx_map) - pfx_map = build_prefix_map (bs, t, act, li); + pfx_map = build_prefix_map (bs, a, t, li); // First try the whole file. Then just the directory. // @@ -2300,8 +2300,8 @@ namespace build2 // will lead to the match failure which we translate to a restart. // if (!cache) - build2::match (act, *pt); - else if (!build2::try_match (act, *pt).first) + build2::match (a, *pt); + else if (!build2::try_match (a, *pt).first) { dd.write (); // Invalidate this line. updating = true; @@ -2310,7 +2310,7 @@ namespace build2 // Update. // - bool restart (update (trace, act, *pt, mt)); + bool restart (update (trace, a, *pt, mt)); // Verify/add it to the dependency database. We do it after update in // order not to add bogus files (non-existent and without a way to @@ -2321,7 +2321,7 @@ namespace build2 // Add to our prerequisite target list. // - t.prerequisite_targets.push_back (pt); + t.prerequisite_targets[a].push_back (pt); skip_count++; updating = updating || restart; @@ -2796,15 +2796,15 @@ namespace build2 return make_pair (move (psrc), puse); } - pair<translation_unit, string> compile:: - parse_unit (action act, + pair<translation_unit, string> compile_rule:: + parse_unit (action a, file& t, linfo lo, const file& src, auto_rmfile& psrc, const match_data& md) const { - tracer trace (x, "compile::parse_unit"); + tracer trace (x, "compile_rule::parse_unit"); // If things go wrong give the user a bit extra context. // @@ -2844,7 +2844,7 @@ namespace build2 // args.push_back (cpath.recall_string ()); - append_lib_options (t.base_scope (), args, t, act, lo); + append_lib_options (t.base_scope (), args, a, t, lo); append_options (args, t, c_poptions); append_options (args, t, x_poptions); @@ -3071,8 +3071,8 @@ namespace build2 // Extract and inject module dependencies. // - void compile:: - extract_modules (action act, + void compile_rule:: + extract_modules (action a, const scope& bs, file& t, linfo li, @@ -3083,7 +3083,7 @@ namespace build2 depdb& dd, bool& updating) const { - tracer trace (x, "compile::extract_modules"); + tracer trace (x, "compile_rule::extract_modules"); l5 ([&]{trace << "target: " << t;}); // If things go wrong, give the user a bit extra context. @@ -3131,7 +3131,7 @@ namespace build2 sha256 cs; if (!mi.imports.empty ()) - md.mods = search_modules (act, bs, t, li, tt.bmi, src, mi.imports, cs); + md.mods = search_modules (a, bs, t, li, tt.bmi, src, mi.imports, cs); if (dd.expect (cs.string ()) != nullptr) updating = true; @@ -3201,8 +3201,8 @@ namespace build2 // Resolve imported modules to bmi*{} targets. // - module_positions compile:: - search_modules (action act, + module_positions compile_rule:: + search_modules (action a, const scope& bs, file& t, linfo li, @@ -3211,7 +3211,7 @@ namespace build2 module_imports& imports, sha256& cs) const { - tracer trace (x, "compile::search_modules"); + tracer trace (x, "compile_rule::search_modules"); // So we have a list of imports and a list of "potential" module // prerequisites. They are potential in the sense that they may or may @@ -3317,7 +3317,7 @@ namespace build2 return m.size () - mi; }; - auto& pts (t.prerequisite_targets); + auto& pts (t.prerequisite_targets[a]); size_t start (pts.size ()); // Index of the first to be added. // We have two parallel vectors: module names/scores in imports and @@ -3476,7 +3476,7 @@ namespace build2 return r; }; - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { const target* pt (p.load ()); // Should be cached for libraries. @@ -3485,7 +3485,7 @@ namespace build2 const target* lt (nullptr); if (const libx* l = pt->is_a<libx> ()) - lt = &link_member (*l, act, li); + lt = &link_member (*l, a, li); else if (pt->is_a<liba> () || pt->is_a<libs> () || pt->is_a<libux> ()) lt = pt; @@ -3493,7 +3493,7 @@ namespace build2 // if (lt != nullptr) { - for (const target* bt: lt->prerequisite_targets) + for (const target* bt: lt->prerequisite_targets[a]) { if (bt == nullptr) continue; @@ -3528,7 +3528,7 @@ namespace build2 continue; if (const target** p = check_exact (*n)) - *p = &make_module_sidebuild (act, bs, *lt, *bt, *n); + *p = &make_module_sidebuild (a, bs, *lt, *bt, *n); } else continue; @@ -3563,7 +3563,7 @@ namespace build2 // Find the mxx{} prerequisite and extract its "file name" for the // fuzzy match unless the user specified the module name explicitly. // - for (prerequisite_member p: group_prerequisite_members (act, *pt)) + for (prerequisite_member p: group_prerequisite_members (a, *pt)) { if (p.is_a (*x_mod)) { @@ -3642,7 +3642,7 @@ namespace build2 // Match in parallel and wait for completion. // - match_members (act, t, pts, start); + match_members (a, t, pts, start); // Post-process the list of our (direct) imports. While at it, calculate // the checksum of all (direct and indirect) bmi{} paths. @@ -3675,7 +3675,7 @@ namespace build2 if (in != mn) { - for (prerequisite_member p: group_prerequisite_members (act, *bt)) + for (prerequisite_member p: group_prerequisite_members (a, *bt)) { if (p.is_a (*x_mod)) // Got to be there. { @@ -3702,9 +3702,10 @@ namespace build2 // Hard to say whether we should reserve or not. We will probably // get quite a bit of duplications. // - for (size_t m (bt->prerequisite_targets.size ()); j != m; ++j) + auto& bpts (bt->prerequisite_targets[a]); + for (size_t m (bpts.size ()); j != m; ++j) { - const target* et (bt->prerequisite_targets[j]); + const target* et (bpts[j]); if (et == nullptr) continue; // Unresolved (std.*). @@ -3745,14 +3746,14 @@ namespace build2 // Synthesize a dependency for building a module binary interface on // the side. // - const target& compile:: - make_module_sidebuild (action act, + const target& compile_rule:: + make_module_sidebuild (action a, const scope& bs, const target& lt, const target& mt, const string& mn) const { - tracer trace (x, "compile::make_module_sidebuild"); + tracer trace (x, "compile_rule::make_module_sidebuild"); // First figure out where we are going to build. We want to avoid // multiple sidebuilds so the outermost scope that has loaded the @@ -3891,7 +3892,7 @@ namespace build2 // synthesizing dependencies for bmi{}'s. // ps.push_back (prerequisite (lt)); - for (prerequisite_member p: group_prerequisite_members (act, lt)) + for (prerequisite_member p: group_prerequisite_members (a, lt)) { // @@ TODO: will probably need revision if using sidebuild for // non-installed libraries (e.g., direct BMI dependencies @@ -3927,10 +3928,11 @@ namespace build2 void msvc_filter_cl (ifdstream&, const path& src); - void compile:: + void compile_rule:: append_modules (environment& env, cstrings& args, strings& stor, + action a, const file& t, const match_data& md) const { @@ -3939,6 +3941,8 @@ namespace build2 dir_path stdifc; // See the VC case below. + auto& pts (t.prerequisite_targets[a]); + #if 0 switch (cid) { @@ -3959,7 +3963,7 @@ namespace build2 // if (md.type == translation_type::module_impl) { - const file& f (t.prerequisite_targets[ms.start]->as<file> ()); + const file& f (pts[ms.start]->as<file> ()); string s (relative (f.path ()).string ()); s.insert (0, "-fmodule-file="); stor.push_back (move (s)); @@ -3974,11 +3978,11 @@ namespace build2 } case compiler_id::msvc: { - for (size_t i (ms.start), n (t.prerequisite_targets.size ()); + for (size_t i (ms.start), n (pts.size ()); i != n; ++i) { - const target* pt (t.prerequisite_targets[i]); + const target* pt (pts[i]); if (pt == nullptr) continue; @@ -4021,7 +4025,7 @@ namespace build2 assert (false); } #else - size_t n (t.prerequisite_targets.size ()); + size_t n (pts.size ()); // Clang embeds module file references so we only need to specify // our direct imports. @@ -4040,7 +4044,7 @@ namespace build2 for (size_t i (ms.start); i != n; ++i) { - const target* pt (t.prerequisite_targets[i]); + const target* pt (pts[i]); if (pt == nullptr) continue; @@ -4130,8 +4134,8 @@ namespace build2 env.push_back ("IFCPATH"); } - target_state compile:: - perform_update (action act, const target& xt) const + target_state compile_rule:: + perform_update (action a, const target& xt) const { const file& t (xt.as<file> ()); const path& tp (t.path ()); @@ -4146,7 +4150,7 @@ namespace build2 auto pr ( execute_prerequisites<file> ( (mod ? *x_mod : x_src), - act, t, + a, t, md.mt, [s = md.mods.start] (const target&, size_t i) { @@ -4203,7 +4207,7 @@ namespace build2 // Add *.export.poptions from prerequisite libraries. // - append_lib_options (bs, args, t, act, li); + append_lib_options (bs, args, a, t, li); // Extra system header dirs (last). // @@ -4270,7 +4274,7 @@ namespace build2 args.push_back ("/MD"); if (md.mods.start != 0) - append_modules (env, args, mods, t, md); + append_modules (env, args, mods, a, t, md); // The presence of /Zi or /ZI causes the compiler to write debug info // to the .pdb file. By default it is a shared file called vcNN.pdb @@ -4335,7 +4339,7 @@ namespace build2 } if (md.mods.start != 0) - append_modules (env, args, mods, t, md); + append_modules (env, args, mods, a, t, md); // Note: the order of the following options is relied upon below. // @@ -4604,7 +4608,7 @@ namespace build2 return target_state::changed; } - target_state compile:: + target_state compile_rule:: perform_clean (action a, const target& xt) const { const file& t (xt.as<file> ()); diff --git a/build2/cc/compile.hxx b/build2/cc/compile-rule.hxx index 2878e3d..6bf63bf 100644 --- a/build2/cc/compile.hxx +++ b/build2/cc/compile-rule.hxx @@ -1,9 +1,9 @@ -// file : build2/cc/compile.hxx -*- C++ -*- +// file : build2/cc/compile-rule.hxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef BUILD2_CC_COMPILE_HXX -#define BUILD2_CC_COMPILE_HXX +#ifndef BUILD2_CC_COMPILE_RULE_HXX +#define BUILD2_CC_COMPILE_RULE_HXX #include <libbutl/path-map.mxx> @@ -37,12 +37,12 @@ namespace build2 size_t copied; // First copied-over bmi*{}, 0 if none. }; - class compile: public rule, virtual common + class compile_rule: public rule, virtual common { public: - compile (data&&); + compile_rule (data&&); - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -61,14 +61,16 @@ namespace build2 void append_lib_options (const scope&, cstrings&, + action, const target&, - action, linfo) const; + linfo) const; void hash_lib_options (const scope&, sha256&, + action, const target&, - action, linfo) const; + linfo) const; // Mapping of include prefixes (e.g., foo in <foo/bar>) for auto- // generated headers to directories where they will be generated. @@ -97,11 +99,12 @@ namespace build2 void append_lib_prefixes (const scope&, prefix_map&, + action, target&, - action, linfo) const; + linfo) const; prefix_map - build_prefix_map (const scope&, target&, action, linfo) const; + build_prefix_map (const scope&, action, target&, linfo) const; // Reverse-lookup target type from extension. // @@ -134,7 +137,7 @@ namespace build2 void append_modules (environment&, cstrings&, strings&, - const file&, const match_data&) const; + action, const file&, const match_data&) const; // Language selection option (for VC) or the value for the -x option. // @@ -150,4 +153,4 @@ namespace build2 } } -#endif // BUILD2_CC_COMPILE_HXX +#endif // BUILD2_CC_COMPILE_RULE_HXX diff --git a/build2/cc/install.cxx b/build2/cc/install-rule.cxx index fcaf626..4e232ff 100644 --- a/build2/cc/install.cxx +++ b/build2/cc/install-rule.cxx @@ -1,15 +1,15 @@ -// file : build2/cc/install.cxx -*- C++ -*- +// file : build2/cc/install-rule.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include <build2/cc/install.hxx> +#include <build2/cc/install-rule.hxx> #include <build2/algorithm.hxx> #include <build2/bin/target.hxx> -#include <build2/cc/link.hxx> // match() #include <build2/cc/utility.hxx> +#include <build2/cc/link-rule.hxx> // match() using namespace std; @@ -19,16 +19,16 @@ namespace build2 { using namespace bin; - // file_install + // install_rule // - file_install:: - file_install (data&& d, const link& l): common (move (d)), link_ (l) {} + install_rule:: + install_rule (data&& d, const link_rule& l) + : common (move (d)), link_ (l) {} - const target* file_install:: + const target* install_rule:: filter (action a, const target& t, prerequisite_member p) const { - // NOTE: see also alias_install::filter() below if changing anything - // here. + // NOTE: see libux_install_rule::filter() if changing anything here. otype ot (link_type (t).type); @@ -72,7 +72,7 @@ namespace build2 const target* pt (&p.search (t)); // If this is the lib{}/libu{} group, pick a member which we would - // link. For libu{} we want to the "see through" logic. + // link. For libu{} we want the "see through" logic. // if (const libx* l = pt->is_a<libx> ()) pt = &link_member (*l, a, link_info (t.base_scope (), ot)); @@ -90,7 +90,7 @@ namespace build2 return file_rule::filter (a, t, p); } - match_result file_install:: + bool install_rule:: match (action a, target& t, const string& hint) const { // @@ How do we split the hint between the two? @@ -99,20 +99,38 @@ namespace build2 // We only want to handle installation if we are also the ones building // this target. So first run link's match(). // - match_result r (link_.match (a, t, hint)); - return r ? file_rule::match (a, t, "") : r; + return link_.match (a, t, hint) && file_rule::match (a, t, ""); } - recipe file_install:: + recipe install_rule:: apply (action a, target& t) const { recipe r (file_rule::apply (a, t)); - // Derive shared library paths and cache them in the target's aux - // storage if we are (un)installing (used in *_extra() functions below). - // - if (a.operation () == install_id || a.operation () == uninstall_id) + if (a.operation () == update_id) + { + // Signal to the link rule that this is update for install. And if the + // update has already been executed, verify it was done for install. + // + auto& md (t.data<link_rule::match_data> ()); + + if (md.for_install) + { + if (!*md.for_install) + fail << "target " << t << " already updated but not for install"; + } + else + md.for_install = true; + } + else // install or uninstall { + // Derive shared library paths and cache them in the target's aux + // storage if we are un/installing (used in *_extra() functions + // below). + // + static_assert (sizeof (link_rule::libs_paths) <= target::data_size, + "insufficient space"); + file* f; if ((f = t.is_a<libs> ()) != nullptr && tclass != "windows") { @@ -128,34 +146,39 @@ namespace build2 return r; } - void file_install:: + bool install_rule:: install_extra (const file& t, const install_dir& id) const { + bool r (false); + if (t.is_a<libs> () && tclass != "windows") { // Here we may have a bunch of symlinks that we need to install. // const scope& rs (t.root_scope ()); - auto& lp (t.data<link::libs_paths> ()); + auto& lp (t.data<link_rule::libs_paths> ()); auto ln = [&rs, &id] (const path& f, const path& l) { install_l (rs, id, f.leaf (), l.leaf (), false); + return true; }; const path& lk (lp.link); const path& so (lp.soname); const path& in (lp.interm); - const path* f (&lp.real); + const path* f (lp.real); - if (!in.empty ()) {ln (*f, in); f = ∈} - if (!so.empty ()) {ln (*f, so); f = &so;} - if (!lk.empty ()) {ln (*f, lk);} + if (!in.empty ()) {r = ln (*f, in) || r; f = ∈} + if (!so.empty ()) {r = ln (*f, so) || r; f = &so;} + if (!lk.empty ()) {r = ln (*f, lk) || r; } } + + return r; } - bool file_install:: + bool install_rule:: uninstall_extra (const file& t, const install_dir& id) const { bool r (false); @@ -165,7 +188,7 @@ namespace build2 // Here we may have a bunch of symlinks that we need to uninstall. // const scope& rs (t.root_scope ()); - auto& lp (t.data<link::libs_paths> ()); + auto& lp (t.data<link_rule::libs_paths> ()); auto rm = [&rs, &id] (const path& l) { @@ -184,15 +207,16 @@ namespace build2 return r; } - // alias_install + // libux_install_rule // - alias_install:: - alias_install (data&& d, const link& l): common (move (d)), link_ (l) {} + libux_install_rule:: + libux_install_rule (data&& d, const link_rule& l) + : common (move (d)), link_ (l) {} - const target* alias_install:: + const target* libux_install_rule:: filter (action a, const target& t, prerequisite_member p) const { - // The "see through" semantics that should be parallel to file_install + // The "see through" semantics that should be parallel to install_rule // above. In particular, here we use libue/libua/libus{} as proxies for // exe/liba/libs{} there. @@ -233,14 +257,13 @@ namespace build2 return alias_rule::filter (a, t, p); } - match_result alias_install:: + bool libux_install_rule:: match (action a, target& t, const string& hint) const { // We only want to handle installation if we are also the ones building // this target. So first run link's match(). // - match_result r (link_.match (a, t, hint)); - return r ? alias_rule::match (a, t, "") : r; + return link_.match (a, t, hint) && alias_rule::match (a, t, ""); } } } diff --git a/build2/cc/install-rule.hxx b/build2/cc/install-rule.hxx new file mode 100644 index 0000000..ac2f93a --- /dev/null +++ b/build2/cc/install-rule.hxx @@ -0,0 +1,77 @@ +// file : build2/cc/install-rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_INSTALL_RULE_HXX +#define BUILD2_CC_INSTALL_RULE_HXX + +#include <build2/types.hxx> +#include <build2/utility.hxx> + +#include <build2/install/rule.hxx> + +#include <build2/cc/types.hxx> +#include <build2/cc/common.hxx> + +namespace build2 +{ + namespace cc + { + class link_rule; + + // Installation rule for exe{} and lib*{}. Here we do: + // + // 1. Signal to the link rule that this is update for install. + // + // 2. Additional filtering of prerequisites (e.g., headers of an exe{}). + // + // 3. Extra un/installation (e.g., libs{} symlinks). + // + class install_rule: public install::file_rule, virtual common + { + public: + install_rule (data&&, const link_rule&); + + virtual const target* + filter (action, const target&, prerequisite_member) const override; + + virtual bool + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + virtual bool + install_extra (const file&, const install_dir&) const override; + + virtual bool + uninstall_extra (const file&, const install_dir&) const override; + + private: + const link_rule& link_; + }; + + // Installation rule for libu*{}. + // + // While libu*{} themselves are not installable, we need to see through + // them in case they depend on stuff that we need to install (e.g., + // headers). Note that we use the alias_rule as a base. + // + class libux_install_rule: public install::alias_rule, virtual common + { + public: + libux_install_rule (data&&, const link_rule&); + + virtual const target* + filter (action, const target&, prerequisite_member) const override; + + virtual bool + match (action, target&, const string&) const override; + + private: + const link_rule& link_; + }; + } +} + +#endif // BUILD2_CC_INSTALL_RULE_HXX diff --git a/build2/cc/install.hxx b/build2/cc/install.hxx deleted file mode 100644 index 28a0a94..0000000 --- a/build2/cc/install.hxx +++ /dev/null @@ -1,67 +0,0 @@ -// file : build2/cc/install.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_INSTALL_HXX -#define BUILD2_CC_INSTALL_HXX - -#include <build2/types.hxx> -#include <build2/utility.hxx> - -#include <build2/install/rule.hxx> - -#include <build2/cc/types.hxx> -#include <build2/cc/common.hxx> - -namespace build2 -{ - namespace cc - { - class link; - - // Installation rule for exe{}, lib*{}, etc. - // - class file_install: public install::file_rule, virtual common - { - public: - file_install (data&&, const link&); - - virtual const target* - filter (action, const target&, prerequisite_member) const override; - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - virtual void - install_extra (const file&, const install_dir&) const override; - - virtual bool - uninstall_extra (const file&, const install_dir&) const override; - - private: - const link& link_; - }; - - // Installation rule for libux{}. - // - class alias_install: public install::alias_rule, virtual common - { - public: - alias_install (data&&, const link&); - - virtual const target* - filter (action, const target&, prerequisite_member) const override; - - virtual match_result - match (action, target&, const string&) const override; - - private: - const link& link_; - }; - } -} - -#endif // BUILD2_CC_INSTALL_HXX diff --git a/build2/cc/link.cxx b/build2/cc/link-rule.cxx index f69d549..d06a835 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link-rule.cxx @@ -1,8 +1,8 @@ -// file : build2/cc/link.cxx -*- C++ -*- +// file : build2/cc/link-rule.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include <build2/cc/link.hxx> +#include <build2/cc/link-rule.hxx> #include <map> #include <cstdlib> // exit() @@ -32,29 +32,21 @@ namespace build2 { using namespace bin; - link:: - link (data&& d) + link_rule:: + link_rule (data&& d) : common (move (d)), rule_id (string (x) += ".link 1") { + static_assert (sizeof (match_data) <= target::data_size, + "insufficient space"); } - match_result link:: - match (action act, target& t, const string& hint) const + bool link_rule:: + match (action a, target& t, const string& hint) const { - tracer trace (x, "link::match"); + tracer trace (x, "link_rule::match"); - // @@ TODO: - // - // - if path already assigned, verify extension? - // - // @@ Q: - // - // - if there is no .o, are we going to check if the one derived - // from target exist or can be built? A: No. - // What if there is a library. Probably ok if static, not if shared, - // (i.e., a utility library). - // + // NOTE: may be called multiple times (see install rules). ltype lt (link_type (t)); otype ot (lt.type); @@ -77,7 +69,7 @@ namespace build2 // bool seen_x (false), seen_c (false), seen_obj (false), seen_lib (false); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { if (p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod))) { @@ -141,7 +133,7 @@ namespace build2 return true; } - auto link:: + auto link_rule:: derive_libs_paths (file& ls, const char* pfx, const char* sfx) const -> libs_paths { @@ -286,19 +278,21 @@ namespace build2 const path& re (ls.derive_path (move (b))); - return libs_paths {move (lk), move (so), move (in), re, move (cp)}; + return libs_paths {move (lk), move (so), move (in), &re, move (cp)}; } - recipe link:: - apply (action act, target& xt) const + recipe link_rule:: + apply (action a, target& xt) const { - static_assert (sizeof (link::libs_paths) <= target::data_size, - "insufficient space"); - - tracer trace (x, "link::apply"); + tracer trace (x, "link_rule::apply"); file& t (xt.as<file> ()); + // Note that for_install is signalled by install_rule and therefore + // can only be relied upon during execute. + // + match_data& md (t.data (match_data ())); + const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -417,9 +411,9 @@ namespace build2 // the DLL and we add libi{} import library as its member. // if (tclass == "windows") - libi = add_adhoc_member<bin::libi> (act, t); + libi = add_adhoc_member<bin::libi> (a, t); - t.data (derive_libs_paths (t, p, s)); // Cache in target. + md.libs_data = derive_libs_paths (t, p, s); if (libi) match_recipe (libi, group_recipe); // Set recipe and unlock. @@ -439,7 +433,7 @@ namespace build2 // Note: add after the import library if any. // target_lock pdb ( - add_adhoc_member (act, t, *bs.find_target_type ("pdb"))); + add_adhoc_member (a, t, *bs.find_target_type ("pdb"))); // We call it foo.{exe,dll}.pdb rather than just foo.pdb because // we can have both foo.exe and foo.dll in the same directory. @@ -453,16 +447,16 @@ namespace build2 // // Note that we do it here regardless of whether we are installing // or not for two reasons. Firstly, it is not easy to detect this - // situation in apply() since the action may (and is) overridden to - // unconditional install. Secondly, always having the member takes - // care of cleanup automagically. The actual generation happens in - // the install rule. + // situation in apply() since the for_install hasn't yet been + // communicated by install_rule. Secondly, always having the member + // takes care of cleanup automagically. The actual generation + // happens in perform_update() below. // if (ot != otype::e) { target_lock pc ( add_adhoc_member ( - act, t, + a, t, ot == otype::a ? pca::static_type : pcs::static_type)); // Note that here we always use the lib name prefix, even on @@ -482,7 +476,7 @@ namespace build2 // Inject dependency on the output directory. // - inject_fsdir (act, t); + inject_fsdir (a, t); // Process prerequisites, pass 1: search and match prerequisite // libraries, search obj/bmi{} targets, and search targets we do rule @@ -507,23 +501,24 @@ namespace build2 optional<dir_paths> usr_lib_dirs; // Extract lazily. compile_target_types tt (compile_types (ot)); - auto skip = [&act, &rs] (const target*& pt) + auto skip = [&a, &rs] (const target*& pt) { - if (act.operation () == clean_id && !pt->dir.sub (rs.out_path ())) + if (a.operation () == clean_id && !pt->dir.sub (rs.out_path ())) pt = nullptr; return pt == nullptr; }; - size_t start (t.prerequisite_targets.size ()); + auto& pts (t.prerequisite_targets[a]); + size_t start (pts.size ()); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { // We pre-allocate a NULL slot for each (potential; see clean) // prerequisite target. // - t.prerequisite_targets.push_back (nullptr); - const target*& pt (t.prerequisite_targets.back ()); + pts.push_back (nullptr); + const target*& pt (pts.back ()); uint8_t m (0); // Mark: lib (0), src (1), mod (2), obj/bmi (3). @@ -603,7 +598,7 @@ namespace build2 // if (p.proj ()) pt = search_library ( - act, sys_lib_dirs, usr_lib_dirs, p.prerequisite); + a, sys_lib_dirs, usr_lib_dirs, p.prerequisite); // The rest is the same basic logic as in search_and_match(). // @@ -617,7 +612,7 @@ namespace build2 // member. // if (const libx* l = pt->is_a<libx> ()) - pt = &link_member (*l, act, li); + pt = &link_member (*l, a, li); } else { @@ -639,7 +634,7 @@ namespace build2 // Match lib{} (the only unmarked) in parallel and wait for completion. // - match_members (act, t, t.prerequisite_targets, start); + match_members (a, t, pts, start); // Process prerequisites, pass 2: finish rule chaining but don't start // matching anything yet since that may trigger recursive matching of @@ -648,11 +643,11 @@ namespace build2 // Parallel prerequisite_targets loop. // - size_t i (start), n (t.prerequisite_targets.size ()); - for (prerequisite_member p: group_prerequisite_members (act, t)) + size_t i (start), n (pts.size ()); + for (prerequisite_member p: group_prerequisite_members (a, t)) { - const target*& pt (t.prerequisite_targets[i].target); - uintptr_t& pd (t.prerequisite_targets[i++].data); + const target*& pt (pts[i].target); + uintptr_t& pd (pts[i++].data); if (pt == nullptr) continue; @@ -710,9 +705,9 @@ namespace build2 // Note: have similar logic in make_module_sidebuild(). // size_t j (start); - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { - const target* pt (t.prerequisite_targets[j++]); + const target* pt (pts[j++]); if (p.is_a<libx> () || p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> () || @@ -760,7 +755,7 @@ namespace build2 : (group ? obj::static_type : tt.obj)); bool src (false); - for (prerequisite_member p1: group_prerequisite_members (act, *pt)) + for (prerequisite_member p1: group_prerequisite_members (a, *pt)) { // Most of the time we will have just a single source so fast- // path that case. @@ -843,18 +838,18 @@ namespace build2 // Wait with unlocked phase to allow phase switching. // - wait_guard wg (target::count_busy (), t.task_count, true); + wait_guard wg (target::count_busy (), t[a].task_count, true); for (i = start; i != n; ++i) { - const target*& pt (t.prerequisite_targets[i]); + const target*& pt (pts[i]); if (pt == nullptr) continue; if (uint8_t m = unmark (pt)) { - match_async (act, *pt, target::count_busy (), t.task_count); + match_async (a, *pt, target::count_busy (), t[a].task_count); mark (pt, m); } } @@ -865,9 +860,9 @@ namespace build2 // that we may have bailed out early (thus the parallel i/n for-loop). // i = start; - for (prerequisite_member p: group_prerequisite_members (act, t)) + for (prerequisite_member p: group_prerequisite_members (a, t)) { - const target*& pt (t.prerequisite_targets[i++]); + const target*& pt (pts[i++]); // Skipped or not marked for completion. // @@ -875,7 +870,7 @@ namespace build2 if (pt == nullptr || (m = unmark (pt)) == 0) continue; - build2::match (act, *pt); + build2::match (a, *pt); // Nothing else to do if not marked for verification. // @@ -887,7 +882,7 @@ namespace build2 // bool mod (x_mod != nullptr && p.is_a (*x_mod)); - for (prerequisite_member p1: group_prerequisite_members (act, *pt)) + for (prerequisite_member p1: group_prerequisite_members (a, *pt)) { if (p1.is_a (mod ? *x_mod : x_src) || p1.is_a<c> ()) { @@ -915,7 +910,7 @@ namespace build2 } } - switch (act) + switch (a) { case perform_update_id: return [this] (action a, const target& t) { @@ -929,10 +924,10 @@ namespace build2 } } - void link:: + void link_rule:: append_libraries (strings& args, const file& l, bool la, lflags lf, - const scope& bs, action act, linfo li) const + const scope& bs, action a, linfo li) const { // Note: lack of the "small function object" optimization will really // kill us here since we are called in a loop. @@ -996,14 +991,14 @@ namespace build2 }; process_libraries ( - act, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); + a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); } - void link:: + void link_rule:: hash_libraries (sha256& cs, bool& update, timestamp mt, const file& l, bool la, lflags lf, - const scope& bs, action act, linfo li) const + const scope& bs, action a, linfo li) const { auto imp = [] (const file&, bool la) {return la;}; @@ -1053,14 +1048,14 @@ namespace build2 }; process_libraries ( - act, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); + a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); } - void link:: + void link_rule:: rpath_libraries (strings& args, const target& t, const scope& bs, - action act, + action a, linfo li, bool for_install) const { @@ -1158,16 +1153,16 @@ namespace build2 const function<bool (const file&, bool)> impf (imp); const function<void (const file*, const string&, lflags, bool)> libf (lib); - for (auto pt: t.prerequisite_targets) + for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - bool a; + bool la; const file* f; - if ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || - ( f = pt->is_a<libs> ())) + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + ( f = pt->is_a<libs> ())) { - if (!for_install && !a) + if (!for_install && !la) { // Top-level sharen library dependency. It is either matched or // imported so should be a cc library. @@ -1177,8 +1172,8 @@ namespace build2 "-Wl,-rpath," + f->path ().directory ().string ()); } - process_libraries (act, bs, li, sys_lib_dirs, - *f, a, pt.data, + process_libraries (a, bs, li, sys_lib_dirs, + *f, la, pt.data, impf, libf, nullptr); } } @@ -1194,17 +1189,24 @@ namespace build2 const char* msvc_machine (const string& cpu); // msvc.cxx - target_state link:: - perform_update (action act, const target& xt) const + target_state link_rule:: + perform_update (action a, const target& xt) const { - tracer trace (x, "link::perform_update"); - - auto oop (act.outer_operation ()); - bool for_install (oop == install_id || oop == uninstall_id); + tracer trace (x, "link_rule::perform_update"); const file& t (xt.as<file> ()); const path& tp (t.path ()); + match_data& md (t.data<match_data> ()); + + // Unless the outer install rule signalled that this is update for + // install, signal back that we've performed plain update. + // + if (!md.for_install) + md.for_install = false; + + bool for_install (*md.for_install); + const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -1217,7 +1219,7 @@ namespace build2 // bool update (false); timestamp mt (t.load_mtime ()); - target_state ts (straight_execute_prerequisites (act, t)); + target_state ts (straight_execute_prerequisites (a, t)); // If targeting Windows, take care of the manifest. // @@ -1231,7 +1233,7 @@ namespace build2 // it if we are updating for install. // if (!for_install) - rpath_timestamp = windows_rpath_timestamp (t, bs, act, li); + rpath_timestamp = windows_rpath_timestamp (t, bs, a, li); pair<path, bool> p ( windows_manifest (t, @@ -1450,7 +1452,7 @@ namespace build2 // if (lt.shared_library ()) { - const libs_paths& paths (t.data<libs_paths> ()); + const libs_paths& paths (md.libs_data); const string& leaf (paths.effect_soname ().leaf ().string ()); if (tclass == "macos") @@ -1486,7 +1488,7 @@ namespace build2 // rpath of the imported libraries (i.e., we assume they are also // installed). But we add -rpath-link for some platforms. // - rpath_libraries (sargs, t, bs, act, li, for_install); + rpath_libraries (sargs, t, bs, a, li, for_install); if (auto l = t["bin.rpath"]) for (const dir_path& p: cast<dir_paths> (l)) @@ -1519,7 +1521,7 @@ namespace build2 { sha256 cs; - for (auto p: t.prerequisite_targets) + for (const prerequisite_target& p: t.prerequisite_targets[a]) { const target* pt (p.target); @@ -1532,15 +1534,15 @@ namespace build2 } const file* f; - bool a (false), s (false); + bool la (false), ls (false); if ((f = pt->is_a<obje> ()) || (f = pt->is_a<obja> ()) || (f = pt->is_a<objs> ()) || (!lt.static_library () && // @@ UTL: TODO libua to liba link. - ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || - (s = (f = pt->is_a<libs> ()))))) + ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + (ls = (f = pt->is_a<libs> ()))))) { // Link all the dependent interface libraries (shared) or interface // and implementation (static), recursively. @@ -1551,9 +1553,9 @@ namespace build2 // reason to re-archive the utility but those who link the utility // have to "see through" the changes in the shared library. // - if (a || s) + if (la || ls) { - hash_libraries (cs, update, mt, *f, a, p.data, bs, act, li); + hash_libraries (cs, update, mt, *f, la, p.data, bs, a, li); f = nullptr; // Timestamp checked by hash_libraries(). } else @@ -1603,22 +1605,16 @@ namespace build2 // // Also, if you are wondering why don't we just always produce this .pc, // install or no install, the reason is unless and until we are updating - // for install, we have no idea where to things will be installed. + // for install, we have no idea where-to things will be installed. // if (for_install) { - bool a; + bool la; const file* f; - if ((a = (f = t.is_a<liba> ())) || - ( f = t.is_a<libs> ())) - { - // @@ Hack: this should really be in install:update_extra() where we - // (should) what we are installing and what not. - // - if (rs["install.root"]) - pkgconfig_save (act, *f, a); - } + if ((la = (f = t.is_a<liba> ())) || + ( f = t.is_a<libs> ())) + pkgconfig_save (a, *f, la); } // If nothing changed, then we are done. @@ -1810,7 +1806,7 @@ namespace build2 // The same logic as during hashing above. // - for (auto p: t.prerequisite_targets) + for (const prerequisite_target& p: t.prerequisite_targets[a]) { const target* pt (p.target); @@ -1821,21 +1817,21 @@ namespace build2 } const file* f; - bool a (false), s (false); + bool la (false), ls (false); if ((f = pt->is_a<obje> ()) || (f = pt->is_a<obja> ()) || (f = pt->is_a<objs> ()) || (!lt.static_library () && // @@ UTL: TODO libua to liba link. - ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || - (s = (f = pt->is_a<libs> ()))))) + ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + (ls = (f = pt->is_a<libs> ()))))) { // Link all the dependent interface libraries (shared) or interface // and implementation (static), recursively. // - if (a || s) - append_libraries (sargs, *f, a, p.data, bs, act, li); + if (la || ls) + append_libraries (sargs, *f, la, p.data, bs, a, li); else sargs.push_back (relative (f->path ()).string ()); // string()&& } @@ -1864,7 +1860,7 @@ namespace build2 // if (lt.shared_library ()) { - const libs_paths& paths (t.data<libs_paths> ()); + const libs_paths& paths (md.libs_data); const path& p (paths.clean); if (!p.empty ()) @@ -1886,7 +1882,7 @@ namespace build2 return s.empty () || m.string ().compare (0, s.size (), s) != 0; }; - if (test (paths.real) && + if (test (*paths.real) && test (paths.interm) && test (paths.soname) && test (paths.link)) @@ -2004,7 +2000,7 @@ namespace build2 // install). // if (lt.executable () && !for_install) - windows_rpath_assembly (t, bs, act, li, + windows_rpath_assembly (t, bs, a, li, cast<string> (rs[x_target_cpu]), rpath_timestamp, scratch); @@ -2031,13 +2027,13 @@ namespace build2 } }; - const libs_paths& paths (t.data<libs_paths> ()); + const libs_paths& paths (md.libs_data); const path& lk (paths.link); const path& so (paths.soname); const path& in (paths.interm); - const path* f (&paths.real); + const path* f (paths.real); if (!in.empty ()) {ln (f->leaf (), in); f = ∈} if (!so.empty ()) {ln (f->leaf (), so); f = &so;} @@ -2054,8 +2050,8 @@ namespace build2 return target_state::changed; } - target_state link:: - perform_clean (action act, const target& xt) const + target_state link_rule:: + perform_clean (action a, const target& xt) const { const file& t (xt.as<file> ()); ltype lt (link_type (t)); @@ -2066,13 +2062,13 @@ namespace build2 { if (tsys == "mingw32") return clean_extra ( - act, t, {".d", ".dlls/", ".manifest.o", ".manifest"}); + a, t, {".d", ".dlls/", ".manifest.o", ".manifest"}); else // Assuming it's VC or alike. Clean up .ilk in case the user // enabled incremental linking (note that .ilk replaces .exe). // return clean_extra ( - act, t, {".d", ".dlls/", ".manifest", "-.ilk"}); + a, t, {".d", ".dlls/", ".manifest", "-.ilk"}); } } else if (lt.shared_library ()) @@ -2085,16 +2081,16 @@ namespace build2 // versioning their bases may not be the same. // if (tsys != "mingw32") - return clean_extra (act, t, {{".d", "-.ilk"}, {"-.exp"}}); + return clean_extra (a, t, {{".d", "-.ilk"}, {"-.exp"}}); } else { // Here we can have a bunch of symlinks that we need to remove. If // the paths are empty, then they will be ignored. // - const libs_paths& paths (t.data<libs_paths> ()); + const libs_paths& paths (t.data<match_data> ().libs_data); - return clean_extra (act, t, {".d", + return clean_extra (a, t, {".d", paths.link.string ().c_str (), paths.soname.string ().c_str (), paths.interm.string ().c_str ()}); @@ -2102,7 +2098,7 @@ namespace build2 } // For static library it's just the defaults. - return clean_extra (act, t, {".d"}); + return clean_extra (a, t, {".d"}); } } } diff --git a/build2/cc/link.hxx b/build2/cc/link-rule.hxx index c26102d..ba40410 100644 --- a/build2/cc/link.hxx +++ b/build2/cc/link-rule.hxx @@ -1,9 +1,9 @@ -// file : build2/cc/link.hxx -*- C++ -*- +// file : build2/cc/link-rule.hxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#ifndef BUILD2_CC_LINK_HXX -#define BUILD2_CC_LINK_HXX +#ifndef BUILD2_CC_LINK_RULE_HXX +#define BUILD2_CC_LINK_RULE_HXX #include <set> @@ -19,12 +19,12 @@ namespace build2 { namespace cc { - class link: public rule, virtual common + class link_rule: public rule, virtual common { public: - link (data&&); + link_rule (data&&); - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -37,8 +37,8 @@ namespace build2 perform_clean (action, const target&) const; private: - friend class file_install; - friend class alias_install; + friend class install_rule; + friend class libux_install_rule; // Shared library paths. // @@ -51,27 +51,56 @@ namespace build2 // The libs{} path is always the real path. On Windows the link path // is the import library. // - const path link; // What we link: libfoo.so - const path soname; // SONAME: libfoo-1.so, libfoo.so.1 - const path interm; // Intermediate: libfoo.so.1.2 - const path& real; // Real: libfoo.so.1.2.3 + path link; // What we link: libfoo.so + path soname; // SONAME: libfoo-1.so, libfoo.so.1 + path interm; // Intermediate: libfoo.so.1.2 + const path* real; // Real: libfoo.so.1.2.3 inline const path& effect_link () const {return link.empty () ? effect_soname () : link;} inline const path& - effect_soname () const {return soname.empty () ? real : soname;} + effect_soname () const {return soname.empty () ? *real : soname;} // Cleanup pattern used to remove previous versions. If empty, no // cleanup is performed. The above (current) names are automatically // filtered out. // - const path clean; + path clean; }; libs_paths derive_libs_paths (file&, const char*, const char*) const; + struct match_data + { + // The "for install" condition is signalled to us by install_rule when + // it is matched for the update operation. It also verifies that if we + // have already been executed, then it was for install. + // + // This has an interesting implication: it means that this rule cannot + // be used to update targets during match. Specifically, we cannot be + // executed for group resolution purposes (not a problem) nor as part + // of the generated source update. The latter case can be a problem: + // imagine a code generator that itself may need to be updated before + // it can be used to re-generate some out-of-date source code. As an + // aside, note that even if we were somehow able to communicate the + // "for install" in this case, the result of such an update may not + // actually be "usable" (e.g., not runnable because of the missing + // rpaths). There is another prominent case where the result may not + // be usable: cross-compilation. + // + // So the current (admittedly fuzzy) thinking is that a project shall + // not try to use its own build for update since it may not be usable + // (because of cross-compilations, being "for install", etc). Instead, + // it should rely on another, "usable" build of itself (this, BTW, is + // related to bpkg's build-time vs run-time dependencies). + // + optional<bool> for_install; + + libs_paths libs_data; + }; + // Library handling. // void @@ -134,4 +163,4 @@ namespace build2 } } -#endif // BUILD2_CC_LINK_HXX +#endif // BUILD2_CC_LINK_RULE_HXX diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index c56bca9..ae64220 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -499,8 +499,8 @@ namespace build2 // We register for configure so that we detect unresolved imports // during configuration rather that later, e.g., during update. // - const compile& cr (*this); - const link& lr (*this); + const compile_rule& cr (*this); + const link_rule& lr (*this); r.insert<obje> (perform_update_id, x_compile, cr); r.insert<obje> (perform_clean_id, x_compile, cr); @@ -559,26 +559,27 @@ namespace build2 // if (install_loaded) { - const file_install& fr (*this); - const alias_install& ar (*this); + const install_rule& ir (*this); - r.insert<exe> (perform_install_id, x_install, fr); - r.insert<exe> (perform_uninstall_id, x_uninstall, fr); + r.insert<exe> (perform_install_id, x_install, ir); + r.insert<exe> (perform_uninstall_id, x_uninstall, ir); - r.insert<liba> (perform_install_id, x_install, fr); - r.insert<liba> (perform_uninstall_id, x_uninstall, fr); + r.insert<liba> (perform_install_id, x_install, ir); + r.insert<liba> (perform_uninstall_id, x_uninstall, ir); - r.insert<libs> (perform_install_id, x_install, fr); - r.insert<libs> (perform_uninstall_id, x_uninstall, fr); + r.insert<libs> (perform_install_id, x_install, ir); + r.insert<libs> (perform_uninstall_id, x_uninstall, ir); - r.insert<libue> (perform_install_id, x_install, ar); - r.insert<libue> (perform_uninstall_id, x_uninstall, ar); + const libux_install_rule& lr (*this); - r.insert<libua> (perform_install_id, x_install, ar); - r.insert<libua> (perform_uninstall_id, x_uninstall, ar); + r.insert<libue> (perform_install_id, x_install, lr); + r.insert<libue> (perform_uninstall_id, x_uninstall, lr); - r.insert<libus> (perform_install_id, x_install, ar); - r.insert<libus> (perform_uninstall_id, x_uninstall, ar); + r.insert<libua> (perform_install_id, x_install, lr); + r.insert<libua> (perform_uninstall_id, x_uninstall, lr); + + r.insert<libus> (perform_install_id, x_install, lr); + r.insert<libus> (perform_uninstall_id, x_uninstall, lr); } } } diff --git a/build2/cc/module.hxx b/build2/cc/module.hxx index de61611..58aa184 100644 --- a/build2/cc/module.hxx +++ b/build2/cc/module.hxx @@ -13,9 +13,9 @@ #include <build2/cc/common.hxx> -#include <build2/cc/compile.hxx> -#include <build2/cc/link.hxx> -#include <build2/cc/install.hxx> +#include <build2/cc/compile-rule.hxx> +#include <build2/cc/link-rule.hxx> +#include <build2/cc/install-rule.hxx> namespace build2 { @@ -76,19 +76,19 @@ namespace build2 }; class module: public module_base, public virtual common, - link, - compile, - file_install, - alias_install + link_rule, + compile_rule, + install_rule, + libux_install_rule { public: explicit module (data&& d) : common (move (d)), - link (move (d)), - compile (move (d)), - file_install (move (d), *this), - alias_install (move (d), *this) {} + link_rule (move (d)), + compile_rule (move (d)), + install_rule (move (d), *this), + libux_install_rule (move (d), *this) {} void init (scope&, const location&, const variable_map&); diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 0ffd135..697a60e 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -26,8 +26,8 @@ #include <build2/cc/utility.hxx> #include <build2/cc/common.hxx> -#include <build2/cc/compile.hxx> -#include <build2/cc/link.hxx> +#include <build2/cc/compile-rule.hxx> +#include <build2/cc/link-rule.hxx> using namespace std; using namespace butl; @@ -451,7 +451,7 @@ namespace build2 // #ifndef BUILD2_BOOTSTRAP bool common:: - pkgconfig_load (action act, + pkgconfig_load (action a, const scope& s, lib& lt, liba* at, @@ -592,12 +592,13 @@ namespace build2 // Extract --cflags and set them as lib?{}:export.poptions. Note that we // still pass --static in case this is pkgconf which has Cflags.private. // - auto parse_cflags = [&trace, this] (target& t, const pkgconf& pc, bool a) + auto parse_cflags = + [&trace, this] (target& t, const pkgconf& pc, bool la) { strings pops; bool arg (false); - for (auto& o: pc.cflags (a)) + for (auto& o: pc.cflags (la)) { if (arg) { @@ -646,8 +647,8 @@ namespace build2 // Parse --libs into loptions/libs (interface and implementation). If // ps is not NULL, add each resolves library target as a prerequisite. // - auto parse_libs = [act, &s, top_sysd, this] - (target& t, const pkgconf& pc, bool a, prerequisites* ps) + auto parse_libs = [a, &s, top_sysd, this] + (target& t, const pkgconf& pc, bool la, prerequisites* ps) { strings lops; vector<name> libs; @@ -664,7 +665,7 @@ namespace build2 // library names (without -l) after seeing an unknown option. // bool arg (false), first (true), known (true), have_L; - for (auto& o: pc.libs (a)) + for (auto& o: pc.libs (la)) { if (arg) { @@ -726,10 +727,10 @@ namespace build2 // Space-separated list of escaped library flags. // - auto lflags = [&pc, a] () -> string + auto lflags = [&pc, la] () -> string { string r; - for (const auto& o: pc.libs (a)) + for (const auto& o: pc.libs (la)) { if (!r.empty ()) r += ' '; @@ -831,7 +832,7 @@ namespace build2 prerequisite_key pk { nullopt, {&lib::static_type, &out, &out, &name, nullopt}, &s}; - if (const target* lt = search_library (act, top_sysd, usrd, pk)) + if (const target* lt = search_library (a, top_sysd, usrd, pk)) { // We used to pick a member but that doesn't seem right since the // same target could be used with different link orders. @@ -1112,8 +1113,8 @@ namespace build2 #endif - void link:: - pkgconfig_save (action act, const file& l, bool la) const + void link_rule:: + pkgconfig_save (action a, const file& l, bool la) const { tracer trace (x, "pkgconfig_save"); @@ -1258,7 +1259,7 @@ namespace build2 os << " -L" << escape (ld.string ()); // Now process ourselves as if we were being linked to something (so - // pretty similar to link::append_libraries()). + // pretty similar to link_rule::append_libraries()). // bool priv (false); auto imp = [&priv] (const file&, bool la) {return priv && la;}; @@ -1307,7 +1308,7 @@ namespace build2 // linfo li {otype::e, la ? lorder::a_s : lorder::s_a}; - process_libraries (act, bs, li, sys_lib_dirs, + process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. imp, lib, opt, true); os << endl; @@ -1317,7 +1318,7 @@ namespace build2 os << "Libs.private:"; priv = true; - process_libraries (act, bs, li, sys_lib_dirs, + process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. imp, lib, opt, false); os << endl; @@ -1339,7 +1340,7 @@ namespace build2 }; vector<module> modules; - for (const target* pt: l.prerequisite_targets) + for (const target* pt: l.prerequisite_targets[a]) { // @@ UTL: we need to (recursively) see through libux{} (and // also in search_modules()). @@ -1354,7 +1355,7 @@ namespace build2 // the first mxx{} target that we see. // const target* mt (nullptr); - for (const target* t: pt->prerequisite_targets) + for (const target* t: pt->prerequisite_targets[a]) { if ((mt = t->is_a (*x_mod))) break; diff --git a/build2/cc/windows-manifest.cxx b/build2/cc/windows-manifest.cxx index 4393fbf..ae33f66 100644 --- a/build2/cc/windows-manifest.cxx +++ b/build2/cc/windows-manifest.cxx @@ -9,7 +9,7 @@ #include <build2/filesystem.hxx> #include <build2/diagnostics.hxx> -#include <build2/cc/link.hxx> +#include <build2/cc/link-rule.hxx> using namespace std; using namespace butl; @@ -39,10 +39,10 @@ namespace build2 // file corresponding to the exe{} target. Return the manifest file path // as well as whether it was changed. // - pair<path, bool> link:: + pair<path, bool> link_rule:: windows_manifest (const file& t, bool rpath_assembly) const { - tracer trace (x, "link::windows_manifest"); + tracer trace (x, "link_rule::windows_manifest"); const scope& rs (t.root_scope ()); diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index b28ce42..8854542 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -13,7 +13,7 @@ #include <build2/bin/target.hxx> -#include <build2/cc/link.hxx> +#include <build2/cc/link-rule.hxx> using namespace std; using namespace butl; @@ -46,10 +46,10 @@ namespace build2 // Return the greatest (newest) timestamp of all the DLLs that we will be // adding to the assembly or timestamp_nonexistent if there aren't any. // - timestamp link:: + timestamp link_rule:: windows_rpath_timestamp (const file& t, const scope& bs, - action act, + action a, linfo li) const { timestamp r (timestamp_nonexistent); @@ -103,19 +103,19 @@ namespace build2 r = t; }; - for (auto pt: t.prerequisite_targets) + for (const prerequisite_target& pt: t.prerequisite_targets[a]) { if (pt == nullptr) continue; - bool a; + bool la; const file* f; - if ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || // See through. + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || // See through. ( f = pt->is_a<libs> ())) - process_libraries (act, bs, li, sys_lib_dirs, - *f, a, pt.data, + process_libraries (a, bs, li, sys_lib_dirs, + *f, la, pt.data, imp, lib, nullptr, true); } @@ -125,10 +125,10 @@ namespace build2 // Like *_timestamp() but actually collect the DLLs (and weed out the // duplicates). // - auto link:: + auto link_rule:: windows_rpath_dlls (const file& t, const scope& bs, - action act, + action a, linfo li) const -> windows_dlls { windows_dlls r; @@ -193,19 +193,19 @@ namespace build2 } }; - for (auto pt: t.prerequisite_targets) + for (const prerequisite_target& pt: t.prerequisite_targets[a]) { if (pt == nullptr) continue; - bool a; + bool la; const file* f; - if ((a = (f = pt->is_a<liba> ())) || - (a = (f = pt->is_a<libux> ())) || // See through. - ( f = pt->is_a<libs> ())) - process_libraries (act, bs, li, sys_lib_dirs, - *f, a, pt.data, + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || // See through. + ( f = pt->is_a<libs> ())) + process_libraries (a, bs, li, sys_lib_dirs, + *f, la, pt.data, imp, lib, nullptr, true); } @@ -223,10 +223,10 @@ namespace build2 // unnecessary work by comparing the DLLs timestamp against the assembly // manifest file. // - void link:: + void link_rule:: windows_rpath_assembly (const file& t, const scope& bs, - action act, + action a, linfo li, const string& tcpu, timestamp ts, @@ -264,7 +264,7 @@ namespace build2 windows_dlls dlls; if (!empty) - dlls = windows_rpath_dlls (t, bs, act, li); + dlls = windows_rpath_dlls (t, bs, a, li); // Clean the assembly directory and make sure it exists. Maybe it would // have been faster to overwrite the existing manifest rather than diff --git a/build2/cli/init.cxx b/build2/cli/init.cxx index a4403a9..df123ba 100644 --- a/build2/cli/init.cxx +++ b/build2/cli/init.cxx @@ -23,7 +23,7 @@ namespace build2 { namespace cli { - static const compile compile_; + static const compile_rule compile_rule_; bool config_init (scope& rs, @@ -306,10 +306,10 @@ namespace build2 auto reg = [&r] (meta_operation_id mid, operation_id oid) { - r.insert<cli_cxx> (mid, oid, "cli.compile", compile_); - r.insert<cxx::hxx> (mid, oid, "cli.compile", compile_); - r.insert<cxx::cxx> (mid, oid, "cli.compile", compile_); - r.insert<cxx::ixx> (mid, oid, "cli.compile", compile_); + r.insert<cli_cxx> (mid, oid, "cli.compile", compile_rule_); + r.insert<cxx::hxx> (mid, oid, "cli.compile", compile_rule_); + r.insert<cxx::cxx> (mid, oid, "cli.compile", compile_rule_); + r.insert<cxx::ixx> (mid, oid, "cli.compile", compile_rule_); }; reg (perform_id, update_id); diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx index d05b190..42f2176 100644 --- a/build2/cli/rule.cxx +++ b/build2/cli/rule.cxx @@ -43,10 +43,10 @@ namespace build2 return false; } - match_result compile:: + bool compile_rule:: match (action a, target& xt, const string&) const { - tracer trace ("cli::compile::match"); + tracer trace ("cli::compile_rule::match"); if (cli_cxx* pt = xt.is_a<cli_cxx> ()) { @@ -149,7 +149,7 @@ namespace build2 } } - recipe compile:: + recipe compile_rule:: apply (action a, target& xt) const { if (cli_cxx* pt = xt.is_a<cli_cxx> ()) @@ -208,7 +208,7 @@ namespace build2 } } - target_state compile:: + target_state compile_rule:: perform_update (action a, const target& xt) { const cli_cxx& t (xt.as<cli_cxx> ()); diff --git a/build2/cli/rule.hxx b/build2/cli/rule.hxx index 9af1da4..ba6337a 100644 --- a/build2/cli/rule.hxx +++ b/build2/cli/rule.hxx @@ -16,12 +16,12 @@ namespace build2 { // @@ Redo as two separate rules? // - class compile: public rule + class compile_rule: public rule { public: - compile () {} + compile_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx index c35ee18..be3098c 100644 --- a/build2/cli/target.cxx +++ b/build2/cli/target.cxx @@ -33,7 +33,7 @@ namespace build2 // cli.cxx // group_view cli_cxx:: - group_members (action_type) const + group_members (action) const { static_assert (sizeof (cli_cxx_members) == sizeof (const target*) * 3, "member layout incompatible with array"); diff --git a/build2/cli/target.hxx b/build2/cli/target.hxx index 1247172..d595856 100644 --- a/build2/cli/target.hxx +++ b/build2/cli/target.hxx @@ -41,7 +41,7 @@ namespace build2 using mtime_target::mtime_target; virtual group_view - group_members (action_type) const override; + group_members (action) const override; public: static const target_type static_type; diff --git a/build2/context.hxx b/build2/context.hxx index d2daad6..71035ad 100644 --- a/build2/context.hxx +++ b/build2/context.hxx @@ -33,7 +33,7 @@ namespace build2 // 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. + // 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 diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx index b288a66..a131000 100644 --- a/build2/dist/rule.cxx +++ b/build2/dist/rule.cxx @@ -15,7 +15,7 @@ namespace build2 { namespace dist { - match_result rule:: + bool rule:: match (action, target&, const string&) const { return true; // We always match. diff --git a/build2/dist/rule.hxx b/build2/dist/rule.hxx index 0524029..ffeed9e 100644 --- a/build2/dist/rule.hxx +++ b/build2/dist/rule.hxx @@ -19,7 +19,6 @@ namespace build2 // This is the default rule that simply matches all the prerequisites. // // A custom rule (usually the same as perform_update) may be necessary to - // enter ad hoc prerequisites (like generated test input/output) or // establishing group links (so that we see the dist variable set on a // group). // @@ -28,7 +27,7 @@ namespace build2 public: rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe diff --git a/build2/dump.cxx b/build2/dump.cxx index f88dcfd..263f1b5 100644 --- a/build2/dump.cxx +++ b/build2/dump.cxx @@ -243,12 +243,14 @@ namespace build2 // Note: running serial and task_count is 0 before any operation has // started. // - if (size_t c = t.task_count.load (memory_order_relaxed)) + action inner; // @@ Only for the inner part of the action currently. + + if (size_t c = t[inner].task_count.load (memory_order_relaxed)) { if (c == target::count_applied () || c == target::count_executed ()) { bool f (false); - for (const target* pt: t.prerequisite_targets) + for (const target* pt: t.prerequisite_targets[inner]) { if (pt == nullptr) // Skipped. continue; diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 0eb9521..a11f3e5 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -217,7 +217,7 @@ namespace build2 bs.rules.insert<alias> (perform_uninstall_id, "uninstall.alias", ar); bs.rules.insert<file> (perform_install_id, "install.file", fr); - bs.rules.insert<file> (perform_uninstall_id, "uinstall.file", fr); + bs.rules.insert<file> (perform_uninstall_id, "uninstall.file", fr); } // Configuration. diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 79287f8..4d4cb51 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -40,9 +40,15 @@ namespace build2 // const alias_rule alias_rule::instance; - match_result alias_rule:: + bool alias_rule:: match (action, target&, const string&) const { + // We always match. + // + // Note that we are called both as the outer part during the "update for + // un/install" pre-operation and as the inner part during the + // un/install operation itself. + // return true; } @@ -57,78 +63,134 @@ namespace build2 { tracer trace ("install::alias_rule::apply"); + // Pass-through to our installable prerequisites. + // + // @@ Shouldn't we do match in parallel (here and below)? + // + auto& pts (t.prerequisite_targets[a]); for (prerequisite_member p: group_prerequisite_members (a, t)) { + // Ignore unresolved targets that are imported from other projects. + // We are definitely not installing those. + // + if (p.proj ()) + continue; + // Let a customized rule have its say. // const target* pt (filter (a, t, p)); if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); continue; + } - // Check if this prerequisite is explicitly "not installable", - // that is, there is the 'install' variable and its value is - // false. + // Check if this prerequisite is explicitly "not installable", that + // is, there is the 'install' variable and its value is false. // - // At first, this might seem redundand since we could have let - // the file_rule below take care of it. The nuance is this: this - // prerequsite can be in a different subproject that hasn't loaded - // the install module (and therefore has no file_rule registered). - // The typical example would be the 'tests' subproject. + // At first, this might seem redundand since we could have let the + // file_rule below take care of it. The nuance is this: this + // prerequsite can be in a different subproject that hasn't loaded the + // install module (and therefore has no file_rule registered). The + // typical example would be the 'tests' subproject. // // Note: not the same as lookup() above. // auto l ((*pt)["install"]); if (l && cast<path> (l).string () == "false") { - l5 ([&]{trace << "ignoring " << *pt;}); + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); continue; } build2::match (a, *pt); - t.prerequisite_targets.push_back (pt); + pts.push_back (pt); } return default_recipe; } - // file_rule + // group_rule // - const file_rule file_rule::instance; + const group_rule group_rule::instance; - struct match_data + const target* group_rule:: + filter (action, const target&, const target& m) const { - bool install; - }; - - static_assert (sizeof (match_data) <= target::data_size, - "insufficient space"); + return &m; + } - match_result file_rule:: - match (action a, target& t, const string&) const + recipe group_rule:: + apply (action a, target& t) const { - // First determine if this target should be installed (called - // "installable" for short). + tracer trace ("install::group_rule::apply"); + + // Resolve group members. + // + // Remember that we are called twice: first during update for install + // (pre-operation) and then during install. During the former, we rely + // on the normall update rule to resolve the group members. During the + // latter, there will be no rule to do this but the group will already + // have been resolved by the pre-operation. // - match_data md {lookup_install<path> (t, "install") != nullptr}; - match_result mr (true); + // If the rule could not resolve the group, then we ignore it. + // + group_view gv (a.outer () + ? resolve_group_members (a, t) + : t.group_members (a)); - if (a.operation () == update_id) + if (gv.members != nullptr) { - // If this is the update pre-operation and the target is installable, - // change the recipe action to (update, 0) (i.e., "unconditional - // update") so that we don't get matched for its prerequisites. - // - if (md.install) - mr.recipe_action = action (a.meta_operation (), update_id); - else - // Otherwise, signal that we don't match so that some other rule can - // take care of it. + auto& pts (t.prerequisite_targets[a]); + for (size_t i (0); i != gv.count; ++i) + { + const target* m (gv.members[i]); + + if (m == nullptr) + continue; + + // Let a customized rule have its say. // - return false; + const target* mt (filter (a, t, *m)); + if (mt == nullptr) + { + l5 ([&]{trace << "ignoring " << *m << " (filtered out)";}); + continue; + } + + // See if we were explicitly instructed not to touch this target. + // + // Note: not the same as lookup() above. + // + auto l ((*mt)["install"]); + if (l && cast<path> (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); + continue; + } + + build2::match (a, *mt); + pts.push_back (mt); + } } - t.data (md); // Save the data in the target's auxilary storage. - return mr; + // Delegate to the base rule. + // + return alias_rule::apply (a, t); + } + + + // file_rule + // + const file_rule file_rule::instance; + + bool file_rule:: + match (action, target&, const string&) const + { + // We always match, even if this target is not installable (so that we + // can ignore it; see apply()). + // + return true; } const target* file_rule:: @@ -141,28 +203,27 @@ namespace build2 recipe file_rule:: apply (action a, target& t) const { - match_data md (move (t.data<match_data> ())); - t.clear_data (); // In case delegated-to rule (or the rule that overrides - // us; see cc/install) also uses aux storage. + tracer trace ("install::file_rule::apply"); - if (!md.install) // Not installable. - return noop_recipe; - - // Ok, if we are here, then this means: + // Note that we are called both as the outer part during the "update for + // un/install" pre-operation and as the inner part during the + // un/install operation itself. // - // 1. This target is installable. - // 2. The action is either - // a. (perform, [un]install, 0) or - // b. (*, update, [un]install) + // In both cases we first determine if the target is installable and + // return noop if it's not. Otherwise, in the first case (update for + // un/install) we delegate to the normal update and in the second + // (un/install) -- perform the test. // + if (lookup_install<path> (t, "install") == nullptr) + return noop_recipe; + // In both cases, the next step is to search, match, and collect all the // installable prerequisites. // - // @@ Perhaps if [noinstall] will be handled by the - // group_prerequisite_members machinery, then we can just - // run standard search_and_match()? Will need an indicator - // that it was forced (e.g., [install]) for filter() below. + // @@ Unconditional group? How does it work for cli? Change to maybe + // same like test? If so, also in alias_rule. // + auto& pts (t.prerequisite_targets[a]); for (prerequisite_member p: group_prerequisite_members (a, t)) { // Ignore unresolved targets that are imported from other projects. @@ -175,84 +236,70 @@ namespace build2 // const target* pt (filter (a, t, p)); if (pt == nullptr) + { + l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); continue; + } // See if we were explicitly instructed not to touch this target. // + // Note: not the same as lookup() above. + // auto l ((*pt)["install"]); if (l && cast<path> (l).string () == "false") + { + l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); continue; + } - // If the matched rule returned noop_recipe, then the target - // state will be set to unchanged as an optimization. Use this - // knowledge to optimize things on our side as well since this - // will help a lot in case of any static installable content - // (headers, documentation, etc). + // If the matched rule returned noop_recipe, then the target state is + // set to unchanged as an optimization. Use this knowledge to optimize + // things on our side as well since this will help a lot when updating + // static installable content (headers, documentation, etc). // if (!build2::match (a, *pt, unmatch::unchanged)) - t.prerequisite_targets.push_back (pt); + pts.push_back (pt); } - // This is where we diverge depending on the operation. In the - // update pre-operation, we need to make sure that this target - // as well as all its installable prerequisites are up to date. - // if (a.operation () == update_id) { - // Save the prerequisite targets that we found since the - // call to match_delegate() below will wipe them out. - // - prerequisite_targets p; - - if (!t.prerequisite_targets.empty ()) - p.swap (t.prerequisite_targets); - - // Find the "real" update rule, that is, the rule that would - // have been found if we signalled that we do not match from - // match() above. - // - recipe d (match_delegate (a, t, *this)); - - // If we have no installable prerequisites, then simply redirect - // to it. + // For the update pre-operation match the inner rule (actual update). // - if (p.empty ()) - return d; - - // Ok, the worst case scenario: we need to cause update of - // prerequisite targets and also delegate to the real update. - // - return [pt = move (p), dr = move (d)] ( - action a, const target& t) mutable -> target_state + if (match_inner (a, t, unmatch::unchanged)) { - // Do the target update first. - // - target_state r (execute_delegate (dr, a, t)); - - // Swap our prerequisite targets back in and execute. - // - t.prerequisite_targets.swap (pt); - r |= straight_execute_prerequisites (a, t); - pt.swap (t.prerequisite_targets); // In case we get re-executed. + return pts.empty () ? noop_recipe : default_recipe; + } - return r; - }; + return &perform_update; } - else if (a.operation () == install_id) - return [this] (action a, const target& t) - { - return perform_install (a, t); - }; else + { return [this] (action a, const target& t) { - return perform_uninstall (a, t); + return a.operation () == install_id + ? perform_install (a, t) + : perform_uninstall (a, t); }; + } } - void file_rule:: + target_state file_rule:: + perform_update (action a, const target& t) + { + // First execute the inner recipe then prerequisites. + // + target_state ts (execute_inner (a, t)); + + if (t.prerequisite_targets[a].size () != 0) + ts |= straight_execute_prerequisites (a, t); + + return ts; + } + + bool file_rule:: install_extra (const file&, const install_dir&) const { + return false; } bool file_rule:: diff --git a/build2/install/rule.hxx b/build2/install/rule.hxx index 642ab96..ffab206 100644 --- a/build2/install/rule.hxx +++ b/build2/install/rule.hxx @@ -19,21 +19,39 @@ namespace build2 class alias_rule: public rule { public: - static const alias_rule instance; - - alias_rule () {} - - virtual match_result + virtual bool match (action, target&, const string&) const override; - virtual recipe - apply (action, target&) const override; - // Return NULL if this prerequisite should be ignored and pointer to its // target otherwise. The default implementation accepts all prerequsites. // virtual const target* filter (action, const target&, prerequisite_member) const; + + virtual recipe + apply (action, target&) const override; + + alias_rule () {} + static const alias_rule instance; + }; + + // In addition to the alias rule's semantics, this rule sees through to + // the group's members. + // + class group_rule: public alias_rule + { + public: + // Return NULL if this group member should be ignored and pointer to its + // target otherwise. The default implementation accepts all members. + // + virtual const target* + filter (action, const target&, const target& group_member) const; + + virtual recipe + apply (action, target&) const override; + + group_rule () {} + static const group_rule instance; }; struct install_dir; @@ -41,41 +59,38 @@ namespace build2 class file_rule: public rule { public: - static const file_rule instance; - - file_rule () {} - - virtual match_result + virtual bool match (action, target&, const string&) const override; - virtual recipe - apply (action, target&) const override; - // Return NULL if this prerequisite should be ignored and pointer to its - // target otherwise. The default implementation ignores prerequsites that - // are outside of this target's project. + // target otherwise. The default implementation ignores prerequsites + // that are outside of this target's project. // virtual const target* filter (action, const target&, prerequisite_member) const; - // Extra installation hooks. + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + // Extra un/installation hooks. Return true if anything was + // un/installed. // - using install_dir = install::install_dir; + using install_dir = install::install_dir; // For derived rules. - virtual void + virtual bool install_extra (const file&, const install_dir&) const; - // Return true if anything was uninstalled. - // virtual bool uninstall_extra (const file&, const install_dir&) const; - // Installation "commands". + // Installation/uninstallation "commands". // // If verbose is false, then only print the command at verbosity level 2 // or higher. - // - public: + // Install a symlink: base/link -> target. // static void @@ -96,16 +111,18 @@ namespace build2 static bool uninstall_f (const scope& rs, const install_dir& base, - const file* t, + const file* target, const path& name, bool verbose); - protected: target_state perform_install (action, const target&) const; target_state perform_uninstall (action, const target&) const; + + static const file_rule instance; + file_rule () {} }; } } diff --git a/build2/operation.cxx b/build2/operation.cxx index 698edbb..a43e20a 100644 --- a/build2/operation.cxx +++ b/build2/operation.cxx @@ -191,20 +191,9 @@ namespace build2 action_target& at (ts[j]); const target& t (at.as_target ()); - // Finish matching targets that we have started. Note that we use the - // state for the "final" action that will be executed and not our - // action. Failed that, we may fail to find a match for a "stronger" - // action but will still get unchanged for the original one. - // - target_state s; - if (j < i) - { - match (a, t, false); - s = t.serial_state (false); - } - else - s = target_state::postponed; - + target_state s (j < i + ? match (a, t, false) + : target_state::postponed); switch (s) { case target_state::postponed: @@ -369,7 +358,7 @@ namespace build2 { const target& t (at.as_target ()); - switch ((at.state = t.executed_state (false))) + switch ((at.state = t.executed_state (a, false))) { case target_state::unknown: { diff --git a/build2/operation.hxx b/build2/operation.hxx index a65fc3d..8c9818e 100644 --- a/build2/operation.hxx +++ b/build2/operation.hxx @@ -52,9 +52,6 @@ namespace build2 { action (): inner_id (0), outer_id (0) {} // Invalid action. - bool - valid () const {return inner_id != 0;} - // If this is not a nested operation, then outer should be 0. // action (meta_operation_id m, operation_id inner, operation_id outer = 0) @@ -70,6 +67,9 @@ namespace build2 operation_id outer_operation () const {return outer_id & 0xF;} + bool inner () const {return outer_id == 0;} + bool outer () const {return outer_id != 0;} + // Implicit conversion operator to action_id for the switch() statement, // etc. Most places only care about the inner operation. // @@ -88,24 +88,25 @@ namespace build2 inline bool operator!= (action x, action y) {return !(x == y);} - // This is an "overrides" comparison, i.e., it returns true if the recipe - // for x overrides recipe for y. The idea is that for the same inner - // operation, action with an outer operation is "weaker" than the one - // without. - // - inline bool - operator> (action x, action y) - { - return x.inner_id != y.inner_id || - (x.outer_id != y.outer_id && y.outer_id != 0); - } - - inline bool - operator>= (action x, action y) {return x == y || x > y;} + bool operator> (action, action) = delete; + bool operator< (action, action) = delete; + bool operator>= (action, action) = delete; + bool operator<= (action, action) = delete; ostream& operator<< (ostream&, action); + // Inner/outer operation state container. + // + template <typename T> + struct action_state + { + T states[2]; // [0] -- inner, [1] -- outer. + + T& operator[] (action a) {return states[a.inner () ? 0 : 1];} + const T& operator[] (action a) const {return states[a.inner () ? 0 : 1];} + }; + // Id constants for build-in and pre-defined meta/operations. // const meta_operation_id noop_id = 1; // nomop? diff --git a/build2/parser.cxx b/build2/parser.cxx index b21d51c..5687b0f 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -747,14 +747,15 @@ namespace build2 // small_vector<reference_wrapper<target>, 1> tgs; - for (auto& tn: ns) + for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i) { - if (tn.qualified ()) - fail (nloc) << "project name in target " << tn; + name& n (*i); - // @@ OUT TODO - // - enter_target tg (*this, move (tn), name (), false, nloc, trace); + if (n.qualified ()) + fail (nloc) << "project name in target " << n; + + name o (n.pair ? move (*++i) : name ()); + enter_target tg (*this, move (n), move (o), false, nloc, trace); if (default_target_ == nullptr) default_target_ = target_; diff --git a/build2/rule-map.hxx b/build2/rule-map.hxx index f63d4b9..e754283 100644 --- a/build2/rule-map.hxx +++ b/build2/rule-map.hxx @@ -79,6 +79,8 @@ namespace build2 insert<T> (a >> 4, a & 0x0F, hint, r); } + // 0 oid is a wildcard. + // template <typename T> void insert (meta_operation_id mid, diff --git a/build2/rule.cxx b/build2/rule.cxx index 2ed0bbd..ef1d3a4 100644 --- a/build2/rule.cxx +++ b/build2/rule.cxx @@ -25,7 +25,7 @@ namespace build2 // that normal implementations should follow. So you probably shouldn't // use it as a guide to implement your own, normal, rules. // - match_result file_rule:: + bool file_rule:: match (action a, target& t, const string&) const { tracer trace ("file_rule::match"); @@ -131,7 +131,7 @@ namespace build2 // alias_rule // - match_result alias_rule:: + bool alias_rule:: match (action, target&, const string&) const { return true; @@ -153,7 +153,7 @@ namespace build2 // fsdir_rule // - match_result fsdir_rule:: + bool fsdir_rule:: match (action, target&, const string&) const { return true; @@ -220,7 +220,7 @@ namespace build2 // First update prerequisites (e.g. create parent directories) then create // this directory. // - if (!t.prerequisite_targets.empty ()) + if (!t.prerequisite_targets[a].empty ()) ts = straight_execute_prerequisites (a, t); // The same code as in perform_update_direct() below. @@ -243,9 +243,9 @@ namespace build2 { // First create the parent directory. If present, it is always first. // - const target* p (t.prerequisite_targets.empty () + const target* p (t.prerequisite_targets[a].empty () ? nullptr - : t.prerequisite_targets[0]); + : t.prerequisite_targets[a][0]); if (p != nullptr && p->is_a<fsdir> ()) perform_update_direct (a, *p); @@ -272,7 +272,7 @@ namespace build2 ? target_state::changed : target_state::unchanged); - if (!t.prerequisite_targets.empty ()) + if (!t.prerequisite_targets[a].empty ()) ts |= reverse_execute_prerequisites (a, t); return ts; diff --git a/build2/rule.hxx b/build2/rule.hxx index f6e23f6..8e43ca6 100644 --- a/build2/rule.hxx +++ b/build2/rule.hxx @@ -13,48 +13,16 @@ namespace build2 { - class match_result - { - public: - bool result; - - // If set, then this is a recipe's action. It must override the original - // action. Normally it is "unconditional inner operation". Only - // noop_recipe can be overridden. - // - // It is passed to rule::apply() so that prerequisites are matched for - // this action. It is also passed to target::recipe() so that if someone - // is matching this target for this action, we won't end-up re-matching - // it. However, the recipe itself is executed with the original action - // so that it can adjust its logic, if necessary. - // - action recipe_action = action (); - - explicit - operator bool () const {return result;} - - // Note that the from-bool constructor is intentionally implicit so that - // we can return true/false from match(). - // - match_result (bool r): result (r) {} - match_result (bool r, action a): result (r), recipe_action (a) {} - }; - // Once a rule is registered (for a scope), it is treated as immutable. If // you need to modify some state (e.g., counters or some such), then make // sure it is MT-safe. // - // Note that match() may not be followed by apply() or be called several - // times before the following apply() (see resolve_group_members()) which - // means that it should be idempotent. The target_data object in the call - // to match() may not be the same as target. - // - // match() can also be called by another rules (see cc/install). + // Note: match() is only called once but may not be followed by apply(). // class rule { public: - virtual match_result + virtual bool match (action, target&, const string& hint) const = 0; virtual recipe @@ -68,7 +36,7 @@ namespace build2 public: file_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -82,7 +50,7 @@ namespace build2 public: alias_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -96,7 +64,7 @@ namespace build2 public: fsdir_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -124,7 +92,7 @@ namespace build2 public: fallback_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override { return true; diff --git a/build2/target.cxx b/build2/target.cxx index a5316ce..201bd15 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -92,7 +92,7 @@ namespace build2 } group_view target:: - group_members (action_type) const + group_members (action) const { assert (false); // Not a group or doesn't expose its members. return group_view {nullptr, 0}; @@ -117,83 +117,6 @@ namespace build2 return *r; } - pair<bool, target_state> target:: - state (action_type a) const - { - assert (phase == run_phase::match); - - // The tricky aspect here is that we my be re-matching the target for - // another (overriding action). Since it is only valid to call this - // function after the target has been matched (for this action), we assume - // that if the target is busy, then it is being overriden (note that it - // cannot be being executed since we are in the match phase). - // - // But that's not the end of it: in case of a re-match the task count - // might have been reset to, say, applied. The only way to know for sure - // that there isn't another match "underneath" is to compare actions. But - // that can only be done safely if we lock the target. At least we will be - // quick (and we don't need to wait since if it's busy, we know it is a - // re-match). This logic is similar to lock_impl(). - // - size_t b (target::count_base ()); - size_t e (task_count.load (memory_order_acquire)); - - size_t exec (b + target::offset_executed); - size_t lock (b + target::offset_locked); - size_t busy (b + target::offset_busy); - - for (;;) - { - for (; e == lock; e = task_count.load (memory_order_acquire)) - this_thread::yield (); - - if (e >= busy) // Override in progress. - return make_pair (true, target_state::unchanged); - - // Unlike lock_impl(), we are only called after being matched for this - // action so if we see executed, then it means executed for this action - // (or noop). - // - if (e == exec) - return make_pair (true, group_state () ? group->state_ : state_); - - // Try to grab the spin-lock. - // - if (task_count.compare_exchange_strong ( - e, - lock, - memory_order_acq_rel, // Synchronize on success. - memory_order_acquire)) // Synchronize on failure. - { - break; - } - - // Task count changed, try again. - } - - // We have the spin-lock. Quickly get the matched action and unlock. - // - action_type ma (action); - bool mf (state_ == target_state::failed); - task_count.store (e, memory_order_release); - - if (ma > a) // Overriden. - return make_pair (true, // Override may have failed but we had the rule. - mf ? target_state::failed: target_state::unchanged); - - // Otherwise we should have a matched target. - // - assert (ma == a); - - if (e == b + target::offset_tried) - return make_pair (false, target_state::unknown); - else - { - assert (e == b + target::offset_applied || e == exec); - return make_pair (true, group_state () ? group->state_ : state_); - } - } - pair<lookup, size_t> target:: find_original (const variable& var, bool target_only) const { @@ -519,26 +442,19 @@ namespace build2 case run_phase::load: break; case run_phase::match: { - // Similar logic to state(action) except here we don't distinguish - // between original/overridden actions (an overridable action is by - // definition a noop and should never need to query the mtime). - // - size_t c (task_count.load (memory_order_acquire)); // For group_state() - - // Wait out the spin lock to get the meaningful count. + // Similar logic to matched_state_impl(). // - for (size_t lock (target::count_locked ()); - c == lock; - c = task_count.load (memory_order_acquire)) - this_thread::yield (); + const opstate& s (state[action () /* inner */]); + size_t o (s.task_count.load (memory_order_relaxed) - // Synchronized. + target::count_base ()); - if (c != target::count_applied () && c != target::count_executed ()) + if (o != target::offset_applied && o != target::offset_executed) break; } // Fall through. case run_phase::execute: { - if (group_state ()) + if (group_state (action () /* inner */)) t = &group->as<mtime_target> (); break; diff --git a/build2/target.hxx b/build2/target.hxx index e15b970..105cc4b 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -104,6 +104,10 @@ namespace build2 }; using prerequisite_targets = vector<prerequisite_target>; + // A rule match is an element of hint_rule_map. + // + using rule_match = pair<const string, reference_wrapper<const rule>>; + // Target. // class target @@ -135,6 +139,15 @@ namespace build2 const dir_path& out_dir () const {return out.empty () ? dir : out;} + // A target that is not (yet) entered as part of a real dependency + // declaration (for example, that is entered as part of a target-specific + // variable assignment, dependency extraction, etc) is called implied. + // + // The implied flag should only be cleared during the load phase via the + // MT-safe target_set::insert(). + // + bool implied; + // Target group to which this target belongs, if any. Note that we assume // that the group and all its members are in the same scope (for example, // in variable lookup). We also don't support nested groups (with a small @@ -178,7 +191,6 @@ namespace build2 // const target* group = nullptr; - // What has been described above is a "normal" group. That is, there is // a dedicated target type that explicitly serves as a group and there // is an explicit mechanism for discovering the group's members. @@ -240,13 +252,11 @@ namespace build2 } public: - using action_type = build2::action; - // You should not call this function directly; rather use // resolve_group_members() from <build2/algorithm.hxx>. // virtual group_view - group_members (action_type) const; + group_members (action) const; // Note that the returned key "tracks" the target (except for the // extension). @@ -395,17 +405,7 @@ namespace build2 value& append (const variable&); - // A target that is not (yet) entered as part of a real dependency - // declaration (for example, that is entered as part of a target-specific - // variable assignment, dependency extraction, etc) is called implied. - // - // The implied flag should only be cleared during the load phase via the - // MT-safe target_set::insert(). - // - public: - bool implied; - - // Target state. + // Target operation state. // public: // Atomic task count that is used during match and execution to track the @@ -419,10 +419,6 @@ namespace build2 // automatically resetting the target to "not yet touched" state for this // operation. // - // For match we have a further complication in that we may re-match the - // target and override with a "stronger" recipe thus re-setting the state - // from, say, applied back to touched. - // // The target is said to be synchronized (in this thread) if we have // either observed the task count to reach applied or executed or we have // successfully changed it (via compare_exchange) to locked or busy. If @@ -434,8 +430,7 @@ namespace build2 static const size_t offset_matched = 3; // Rule has been matched. static const size_t offset_applied = 4; // Rule has been applied. static const size_t offset_executed = 5; // Recipe has been executed. - static const size_t offset_locked = 6; // Fast (spin) lock. - static const size_t offset_busy = 7; // Slow (wait) lock. + static const size_t offset_busy = 6; // Match/execute in progress. static size_t count_base () {return 5 * (current_on - 1);} @@ -444,51 +439,70 @@ namespace build2 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_locked () {return offset_locked + count_base ();} static size_t count_busy () {return offset_busy + count_base ();} - mutable atomic_count task_count {0}; // Start offset_touched - 1. + // Inner/outer operation state. + // + struct opstate + { + mutable atomic_count task_count {0}; // Start offset_touched - 1. + + // Number of direct targets that depend on this target in the current + // operation. It is incremented during match and then decremented during + // execution, before running the recipe. As a result, the recipe can + // detect the last chance (i.e., last dependent) to execute the command + // (see also the first/last execution modes in <operation.hxx>). + // + mutable atomic_count dependents {0}; + + // Matched rule (pointer to hint_rule_map element). Note that in case of + // a direct recipe assignment we may not have a rule (NULL). + // + const rule_match* rule; + + // Applied recipe. + // + build2::recipe recipe; + + // Target state for this operation. Note that it is undetermined until + // a rule is matched and recipe applied (see set_recipe()). + // + target_state state; + }; + + action_state<opstate> state; + + opstate& operator[] (action a) {return state[a];} + const opstate& operator[] (action a) const {return state[a];} // This function should only be called during match if we have observed // (synchronization-wise) that this target has been matched (i.e., the // rule has been applied) for this action. // target_state - matched_state (action a, bool fail = true) const; + matched_state (action, bool fail = true) const; // See try_match(). // pair<bool, target_state> - try_matched_state (action a, bool fail = true) const; + try_matched_state (action, bool fail = true) const; - // This function should only be called during execution if we have - // observed (synchronization-wise) that this target has been executed. + // After the target has been matched and synchronized, check if the target + // is known to be unchanged. Used for optimizations during search & match. // - target_state - executed_state (bool fail = true) const; + bool + unchanged (action a) const + { + return matched_state_impl (a).second == target_state::unchanged; + } - // This function should only be called between match and execute while - // running serially. It returns the group state for the "final" action - // that has been matched (and that will be executed). + // This function should only be called during execution if we have + // observed (synchronization-wise) that this target has been executed. // target_state - serial_state (bool fail = true) const; - - // Number of direct targets that depend on this target in the current - // operation. It is incremented during match and then decremented during - // execution, before running the recipe. As a result, the recipe can - // detect the last chance (i.e., last dependent) to execute the command - // (see also the first/last execution modes in <operation>). - // - mutable atomic_count dependents; + executed_state (action, bool fail = true) const; protected: - // Return fail-untranslated (but group-translated) state assuming the - // target is executed and synchronized. - // - target_state - state () const; - // Version that should be used during match after the target has been // matched for this action (see the recipe override). // @@ -496,51 +510,22 @@ namespace build2 // result (see try_match()). // pair<bool, target_state> - state (action a) const; + matched_state_impl (action) const; + + // Return fail-untranslated (but group-translated) state assuming the + // target is executed and synchronized. + // + target_state + executed_state_impl (action) const; // Return true if the state comes from the group. Target must be at least // matched. // bool - group_state () const; - - // Raw state, normally not accessed directly. - // - public: - target_state state_ = target_state::unknown; + group_state (action a) const; - // Recipe. - // public: - using recipe_type = build2::recipe; - using rule_type = build2::rule; - - action_type action; // Action the rule/recipe is for. - - // Matched rule (pointer to hint_rule_map element). Note that in case of a - // direct recipe assignment we may not have a rule. - // - const pair<const string, reference_wrapper<const rule_type>>* rule; - - // Applied recipe. - // - recipe_type recipe_; - - // Note that the target must be locked in order to set the recipe. - // - void - recipe (recipe_type); - - // After the target has been matched and synchronized, check if the target - // is known to be unchanged. Used for optimizations during search & match. - // - bool - unchanged (action_type a) const - { - return state (a).second == target_state::unchanged; - } - - // Targets to which prerequisites resolve for this recipe. Note that + // Targets to which prerequisites resolve for this action. Note that // unlike prerequisite::target, these can be resolved to group members. // NULL means the target should be skipped (or the rule may simply not add // such a target to the list). @@ -552,7 +537,7 @@ namespace build2 // // Note that the recipe may modify this list. // - mutable build2::prerequisite_targets prerequisite_targets; + mutable action_state<build2::prerequisite_targets> prerequisite_targets; // Auxilary data storage. // @@ -573,7 +558,8 @@ namespace build2 // // Currenly the data is not destroyed until the next match. // - // Note that the recipe may modify the data. + // Note that the recipe may modify the data. Currently reserved for the + // inner part of the action. // static constexpr size_t data_size = sizeof (string) * 16; mutable std::aligned_storage<data_size>::type data_pad; @@ -1330,9 +1316,13 @@ namespace build2 // which racing updates happen because we do not modify the external state // (which is the source of timestemps) while updating the internal. // + // The modification time is reserved for the inner operation thus there is + // no action argument. + // // The rule for groups that utilize target_state::group is as follows: if // it has any members that are mtime_targets, then the group should be - // mtime_target and the members get the mtime from it. + // mtime_target and the members get the mtime from it. During match and + // execute the target should be synchronized. // // Note that this function can be called before the target is matched in // which case the value always comes from the target itself. In other diff --git a/build2/target.ixx b/build2/target.ixx index 6225aef..16dbf61 100644 --- a/build2/target.ixx +++ b/build2/target.ixx @@ -62,26 +62,48 @@ namespace build2 } } + inline pair<bool, target_state> target:: + matched_state_impl (action a) const + { + assert (phase == run_phase::match); + + const opstate& s (state[a]); + size_t o (s.task_count.load (memory_order_relaxed) - // Synchronized. + target::count_base ()); + + if (o == target::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); + return make_pair (true, (group_state (a) ? group->state[a] : s).state); + } + } + inline target_state target:: - state () const + executed_state_impl (action a) const { assert (phase == run_phase::execute); - return group_state () ? group->state_ : state_; + return (group_state (a) ? group->state : state)[a].state; } inline bool target:: - group_state () const + group_state (action a) const { // We go an extra step and short-circuit to the target state even if the // raw state is not group provided the recipe is group_recipe and the // state is not failed. + // + const opstate& s (state[a]); - if (state_ == target_state::group) + if (s.state == target_state::group) return true; - if (state_ != target_state::failed && group != nullptr) + if (s.state != target_state::failed && group != nullptr) { - if (recipe_function* const* f = recipe_.target<recipe_function*> ()) + if (recipe_function* const* f = s.recipe.target<recipe_function*> ()) return *f == &group_action; } @@ -89,11 +111,11 @@ namespace build2 } inline target_state target:: - matched_state (action_type a, bool fail) const + matched_state (action a, bool fail) const { // Note that the target could be being asynchronously re-matched. // - pair<bool, target_state> r (state (a)); + pair<bool, target_state> r (matched_state_impl (a)); if (fail && (!r.first || r.second == target_state::failed)) throw failed (); @@ -102,9 +124,9 @@ namespace build2 } inline pair<bool, target_state> target:: - try_matched_state (action_type a, bool fail) const + try_matched_state (action a, bool fail) const { - pair<bool, target_state> r (state (a)); + pair<bool, target_state> r (matched_state_impl (a)); if (fail && r.first && r.second == target_state::failed) throw failed (); @@ -113,22 +135,9 @@ namespace build2 } inline target_state target:: - executed_state (bool fail) const - { - target_state r (state ()); - - if (fail && r == target_state::failed) - throw failed (); - - return r; - } - - inline target_state target:: - serial_state (bool fail) const + executed_state (action a, bool fail) const { - //assert (sched.serial ()); - - target_state r (group_state () ? group->state_ : state_); + target_state r (executed_state_impl (a)); if (fail && r == target_state::failed) throw failed (); @@ -136,44 +145,6 @@ namespace build2 return r; } - extern atomic_count target_count; // context.hxx - - inline void target:: - recipe (recipe_type r) - { - recipe_ = move (r); - - // Do not clear the failed target state in case of an override (and we - // should never see the failed state from the previous operation since we - // should abort the execution in this case). - // - if (state_ != target_state::failed) - { - state_ = target_state::unknown; - - // If this is a noop recipe, then mark the target unchanged to allow for - // some optimizations. - // - recipe_function** f (recipe_.target<recipe_function*> ()); - - if (f != nullptr && *f == &noop_action) - state_ = target_state::unchanged; - else - { - // This gets tricky when we start considering overrides (which can - // only happen for noop recipes), direct execution, etc. So here seems - // like the best place to do this. - // - // We also ignore the group recipe since it is used for ad hoc - // groups (which are not executed). Plus, group action means real - // recipe is in the group so this also feels right conceptually. - // - if (f == nullptr || *f != &group_action) - target_count.fetch_add (1, memory_order_relaxed); - } - } - } - // mark()/unmark() // @@ -323,7 +294,8 @@ namespace build2 inline timestamp mtime_target:: load_mtime (const path& p) const { - assert (phase == run_phase::execute && !group_state ()); + assert (phase == run_phase::execute && + !group_state (action () /* inner */)); duration::rep r (mtime_.load (memory_order_consume)); if (r == timestamp_unknown_rep) @@ -349,7 +321,9 @@ namespace build2 // much we can do here except detect the case where the target was // changed on this run. // - return mt < mp || (mt == mp && state () == target_state::changed); + return mt < mp || (mt == mp && + executed_state_impl (action () /* inner */) == + target_state::changed); } // path_target diff --git a/build2/test/common.hxx b/build2/test/common.hxx index 89b7581..81cccb7 100644 --- a/build2/test/common.hxx +++ b/build2/test/common.hxx @@ -17,7 +17,24 @@ namespace build2 enum class output_before {fail, warn, clean}; enum class output_after {clean, keep}; - struct common + struct common_data + { + const variable& config_test; + const variable& config_test_output; + + const variable& var_test; + const variable& test_options; + const variable& test_arguments; + + const variable& test_stdin; + const variable& test_stdout; + const variable& test_roundtrip; + const variable& test_input; + + const variable& test_target; + }; + + struct common: common_data { // The config.test.output values. // @@ -45,6 +62,9 @@ namespace build2 // bool test (const target& test_target, const path& id_path) const; + + explicit + common (common_data&& d): common_data (move (d)) {} }; } } diff --git a/build2/test/init.cxx b/build2/test/init.cxx index 6119ae0..556de00 100644 --- a/build2/test/init.cxx +++ b/build2/test/init.cxx @@ -23,7 +23,7 @@ namespace build2 namespace test { bool - boot (scope& rs, const location&, unique_ptr<module_base>&) + boot (scope& rs, const location&, unique_ptr<module_base>& mod) { tracer trace ("test::boot"); @@ -38,53 +38,78 @@ namespace build2 // auto& vp (var_pool.rw (rs)); - // Tests to execute. - // - // Specified as <target>@<path-id> pairs with both sides being optional. - // The variable is untyped (we want a list of name-pairs), overridable, - // and inheritable. The target is relative (in essence a prerequisite) - // which is resolved from the (root) scope where the config.test value - // is defined. - // - vp.insert ("config.test", true); + common_data d { - // Test working directory before/after cleanup (see Testscript spec for - // semantics). - // - vp.insert<name_pair> ("config.test.output", true); + // Tests to execute. + // + // Specified as <target>@<path-id> pairs with both sides being + // optional. The variable is untyped (we want a list of name-pairs), + // overridable, and inheritable. The target is relative (in essence a + // prerequisite) which is resolved from the (root) scope where the + // config.test value is defined. + // + vp.insert ("config.test", true), - // Note: none are overridable. - // - // The test variable is a name which can be a path (with the - // true/false special values) or a target name. - // - vp.insert<name> ("test", variable_visibility::target); - vp.insert<name> ("test.input", variable_visibility::project); - vp.insert<name> ("test.output", variable_visibility::project); - vp.insert<name> ("test.roundtrip", variable_visibility::project); - vp.insert<strings> ("test.options", variable_visibility::project); - vp.insert<strings> ("test.arguments", variable_visibility::project); + // Test working directory before/after cleanup (see Testscript spec + // for semantics). + // + vp.insert<name_pair> ("config.test.output", true), + + // The test variable is a name which can be a path (with the + // true/false special values) or a target name. + // + // Note: none are overridable. + // + vp.insert<name> ("test", variable_visibility::target), + vp.insert<strings> ("test.options", variable_visibility::project), + vp.insert<strings> ("test.arguments", variable_visibility::project), + + // Prerequisite-specific. + // + // test.stdin and test.stdout can be used to mark a prerequisite as a + // file to redirect stdin from and to compare stdout to, respectively. + // test.roundtrip is a shortcut to mark a prerequisite as both stdin + // and stdout. + // + // Prerequisites marked with test.input are treated as additional test + // inputs: they are made sure to be up to date and their paths are + // passed as additional command line arguments (after test.options and + // test.arguments). Their primary use is to pass inputs that may have + // varying file names/paths, for example: + // + // exe{parent}: exe{child}: test.input = true + // + // Note that currently this mechanism is only available to simple + // tests though we could also support it for testscript (e.g., by + // appending the input paths to test.arguments or by passing them in a + // separate test.inputs variable). + // + vp.insert<bool> ("test.stdin", variable_visibility::target), + vp.insert<bool> ("test.stdout", variable_visibility::target), + vp.insert<bool> ("test.roundtrip", variable_visibility::target), + vp.insert<bool> ("test.input", variable_visibility::target), + + // Test target platform. + // + vp.insert<target_triplet> ("test.target", variable_visibility::project) + }; // These are only used in testscript. // vp.insert<strings> ("test.redirects", variable_visibility::project); vp.insert<strings> ("test.cleanups", variable_visibility::project); - // Test target platform. - // // Unless already set, default test.target to build.host. Note that it // can still be overriden by the user, e.g., in root.build. // { - value& v ( - rs.assign ( - vp.insert<target_triplet> ( - "test.target", variable_visibility::project))); + value& v (rs.assign (d.test_target)); if (!v || v.empty ()) v = cast<target_triplet> ((*global_scope)["build.host"]); } + mod.reset (new module (move (d))); return false; } @@ -108,8 +133,7 @@ namespace build2 const dir_path& out_root (rs.out_path ()); l5 ([&]{trace << "for " << out_root;}); - assert (mod == nullptr); - mod.reset (new module ()); + assert (mod != nullptr); module& m (static_cast<module&> (*mod)); // Configure. @@ -123,7 +147,7 @@ namespace build2 // config.test // - if (lookup l = config::omitted (rs, "config.test").first) + if (lookup l = config::omitted (rs, m.config_test).first) { // Figure out which root scope it came from. // @@ -139,7 +163,7 @@ namespace build2 // config.test.output // - if (lookup l = config::omitted (rs, "config.test.output").first) + if (lookup l = config::omitted (rs, m.config_test_output).first) { const name_pair& p (cast<name_pair> (l)); @@ -180,22 +204,13 @@ namespace build2 t.insert<testscript> (); } - // Register rules. + // Register our test running rule. // { - const rule& r (m); - const alias_rule& ar (m); + default_rule& dr (m); - // Register our test running rule. - // - rs.rules.insert<target> (perform_test_id, "test", r); - rs.rules.insert<alias> (perform_test_id, "test", ar); - - // Register our rule for the dist meta-operation. We need to do this - // because we may have ad hoc prerequisites (test input/output files) - // that need to be entered into the target list. - // - rs.rules.insert<target> (dist_id, test_id, "test", r); + rs.rules.insert<target> (perform_test_id, "test", dr); + rs.rules.insert<alias> (perform_test_id, "test", dr); } return true; diff --git a/build2/test/module.hxx b/build2/test/module.hxx index 3c9539f..529f826 100644 --- a/build2/test/module.hxx +++ b/build2/test/module.hxx @@ -17,8 +17,19 @@ namespace build2 { namespace test { - struct module: module_base, virtual common, rule, alias_rule + struct module: module_base, virtual common, default_rule, group_rule { + const test::group_rule& + group_rule () const + { + return *this; + } + + explicit + module (common_data&& d) + : common (move (d)), + test::default_rule (move (d)), + test::group_rule (move (d)) {} }; } } diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx index 96941e6..9a00d84 100644 --- a/build2/test/rule.cxx +++ b/build2/test/rule.cxx @@ -23,38 +23,66 @@ namespace build2 { namespace test { - struct match_data + bool rule:: + match (action, target&, const string&) const { - bool pass; // Pass-through to prerequsites (for alias only). - bool test; - - bool script; - }; - - static_assert (sizeof (match_data) <= target::data_size, - "insufficient space"); + // We always match, even if this target is not testable (so that we can + // ignore it; see apply()). + // + return true; + } - match_result rule_common:: - match (action a, target& t, const string&) const + recipe rule:: + apply (action a, target& t) const { - // The (admittedly twisted) logic of this rule tries to achieve the - // following: If the target is testable, then we want both first update - // it and then test it. Otherwise, we want to ignore it for both - // operations. To achieve this the rule will be called to match during - // both operations. For update, if the target is not testable, then the - // rule matches with a noop recipe. If the target is testable, then the - // rule also matches but delegates to the real update rule. In this case - // (and this is tricky) the rule also changes the operation to - // unconditional update to make sure it doesn't match any prerequisites - // (which, if not testable, it will noop). + // Note that we are called both as the outer part during the "update for + // test" pre-operation and as the inner part during the test operation + // itself. + // + // In both cases we first determine if the target is testable and return + // noop if it's not. Otherwise, in the first case (update for test) we + // delegate to the normal update and in the second (test) -- perform the + // test. // // And to add a bit more complexity, we want to handle aliases slightly // differently: we may not want to ignore their prerequisites if the // alias is not testable since their prerequisites could be. + // + // Here is the state matrix: + // + // test'able | pass'able | neither + // | | + // update for test delegate (& pass) | pass | noop + // ---------------------------------------+-------------+--------- + // test test (& pass) | pass | noop + // + + // If we are passing-through, then match our prerequisites. + // + // Note that we may already have stuff in prerequisite_targets (see + // group_rule). + // + if (t.is_a<alias> () && pass (t)) + { + // For the test operation we have to implement our own search and + // match because we need to ignore prerequisites that are outside of + // our project. They can be from projects that don't use the test + // module (and thus won't have a suitable rule). Or they can be from + // no project at all (e.g., installed). Also, generally, not testing + // stuff that's not ours seems right. + // + match_prerequisites (a, t, t.root_scope ()); + } - match_data md {t.is_a<alias> () && pass (t), false, false}; + auto& pts (t.prerequisite_targets[a]); + size_t pass_n (pts.size ()); // Number of pass-through prerequisites. - if (test (t)) + // See if it's testable and if so, what kind. + // + bool test (false); + bool script (false); + + if (this->test (t)) { // We have two very different cases: testscript and simple test (plus // it may not be a testable target at all). So as the first step @@ -75,292 +103,223 @@ namespace build2 { if (p.is_a<testscript> ()) { - md.script = true; + if (!script) + { + script = true; - // We treat this target as testable unless the test variable is - // explicitly set to false. + // We treat this target as testable unless the test variable is + // explicitly set to false. + // + const name* n (cast_null<name> (t[var_test])); + test = (n == nullptr || !n->simple () || n->value != "false"); + + if (!test) + break; + } + + // If this is the test operation, collect testscripts after the + // pass-through prerequisites. + // + // Note that we don't match nor execute them relying on update to + // assign their paths and make sure they are up to date. // - const name* n (cast_null<name> (t["test"])); - md.test = n == nullptr || !n->simple () || n->value != "false"; - break; + if (a.operation () != test_id) + break; + + pts.push_back (&p.search (t)); } } // If this is not a script, then determine if it is a simple test. - // Ignore aliases and testscripts files themselves at the outset. + // Ignore aliases and testscript files themselves at the outset. // - if (!md.script && !t.is_a<alias> () && !t.is_a<testscript> ()) + if (!script && !t.is_a<alias> () && !t.is_a<testscript> ()) { // For the simple case whether this is a test is controlled by the // test variable. Also, it feels redundant to specify, say, "test = - // true" and "test.output = test.out" -- the latter already says this + // true" and "test.stdout = test.out" -- the latter already says this // is a test. // + const name* n (cast_null<name> (t[var_test])); - // Use lookup depths to figure out who "overrides" whom. + // If the test variable is explicitly set to false then we treat + // it as not testable regardless of what other test.* variables + // or prerequisites we might have. // - auto p (t.find (var_pool["test"])); - const name* n (cast_null<name> (p.first)); - - // Note that test can be set to an "override" target. + // Note that the test variable can be set to an "override" target + // (which means 'true' for our purposes). // - if (n != nullptr && (!n->simple () || n->value != "false")) - md.test = true; + if (n != nullptr && n->simple () && n->value == "false") + test = false; else { - auto test = [&t, &p] (const char* var) + // Look for test input/stdin/stdout prerequisites. The same + // group logic as in the testscript case above. + // + for (prerequisite_member p: + group_prerequisite_members (a, t, members_mode::maybe)) { - return t.find (var_pool[var]).second < p.second; - }; - - md.test = - test ("test.input") || - test ("test.output") || - test ("test.roundtrip") || - test ("test.options") || - test ("test.arguments"); + const auto& vars (p.prerequisite.vars); + + if (vars.empty ()) // Common case. + continue; + + bool rt ( cast_false<bool> (vars[test_roundtrip])); + bool si (rt || cast_false<bool> (vars[test_stdin])); + bool so (rt || cast_false<bool> (vars[test_stdout])); + bool in ( cast_false<bool> (vars[test_input])); + + if (si || so || in) + { + // Note that we don't match nor execute them relying on update + // to assign their paths and make sure they are up to date. + // + const target& pt (p.search (t)); + + // Verify it is file-based. + // + if (!pt.is_a<file> ()) + { + fail << "test." << (si ? "stdin" : so ? "stdout" : "input") + << " prerequisite " << p << " of target " << t + << " is not a file"; + } + + if (!test) + { + test = true; + + if (a.operation () != test_id) + break; + + // First matching prerequisite. Establish the structure in + // pts: the first element (after pass_n) is stdin (can be + // NULL), the second is stdout (can be NULL), and everything + // after that (if any) is inputs. + // + pts.push_back (nullptr); // stdin + pts.push_back (nullptr); // stdout + } + + if (si) + { + if (pts[pass_n] != nullptr) + fail << "multiple test.stdin prerequisites for target " + << t; + + pts[pass_n] = &pt; + } + + if (so) + { + if (pts[pass_n + 1] != nullptr) + fail << "multiple test.stdout prerequisites for target " + << t; + + pts[pass_n + 1] = &pt; + } + + if (in) + pts.push_back (&pt); + } + } + + if (!test) + test = (n != nullptr); // We have the test variable. + + if (!test) + test = t[test_options] || t[test_arguments]; } } } - match_result mr (true); - - // Theoretically if this target is testable and this is the update - // pre-operation, then all we need to do is say we are not a match and - // the standard matching machinery will find the rule to update this - // target. The problem with this approach is that the matching will - // still happen for "update for test" which means this rule may still - // match prerequisites (e.g., non-existent files) which we don't want. - // - // Also, for the simple case there is one more complication: test - // input/output. While normally they will be existing (in src_base) - // files, they could also be auto-generated. In fact, they could only be - // needed for testing, which means the normall update won't even know - // about them (nor clean, for that matter; this is why we need - // cleantest). - // - // @@ Maybe we should just say if input/output are generated, then they - // must be explicitly listed as prerequisites? Then no need for - // cleantest but they will be updated even when not needed. - // - // To make generated input/output work we will have to cause their - // update ourselves. In other words, we may have to do some actual work - // for (update, test), and not simply "guide" (update, 0) as to which - // targets need updating. For how exactly we are going to do it, see - // apply() below. - - // Change the recipe action to (update, 0) (i.e., "unconditional - // update") for "leaf" tests to make sure we won't match any - // prerequisites. Note that this doesn't cover the case where an alias - // is both a test and a pass for a test prerequisite with generated - // input/output. - // - if (a.operation () == update_id && md.test) - mr.recipe_action = action (a.meta_operation (), update_id); - - // Note that we match even if this target is not testable so that we can - // ignore it (see apply()). - // - t.data (md); // Save the data in the target's auxilary storage. - return mr; - } - - recipe alias_rule:: - apply (action a, target& t) const - { - match_data md (move (t.data<match_data> ())); - t.clear_data (); // In case delegated-to rule also uses aux storage. - - // We can only test an alias via a testscript, not a simple test. + // Neither testing nor passing-through. // - assert (!md.test || md.script); - - if (!md.pass && !md.test) + if (!test && pass_n == 0) return noop_recipe; - // If this is the update pre-operation then simply redirect to the - // standard alias rule. - // - if (a.operation () == update_id) - return match_delegate (a, t, *this); - - // For the test operation we have to implement our own search and match - // because we need to ignore prerequisites that are outside of our - // project. They can be from projects that don't use the test module - // (and thus won't have a suitable rule). Or they can be from no project - // at all (e.g., installed). Also, generally, not testing stuff that's - // not ours seems right. Note that we still want to make sure they are - // up to date (via the above delegate) since our tests might use them. + // If we are only passing-through, then use the default recipe (which + // will execute all the matched prerequisites). // - match_prerequisites (a, t, t.root_scope ()); + if (!test) + return default_recipe; - // If not a test then also redirect to the alias rule. + // Being here means we are definitely testing and maybe passing-through. // - return md.test - ? [this] (action a, const target& t) {return perform_test (a, t);} - : default_recipe; - } - - recipe rule:: - apply (action a, target& t) const - { - tracer trace ("test::rule::apply"); - - match_data md (move (t.data<match_data> ())); - t.clear_data (); // In case delegated-to rule also uses aux storage. - - if (!md.test) - return noop_recipe; - - // If we are here, then the target is testable and the action is either - // a. (perform, test, 0) or - // b. (*, update, 0) - // - if (md.script) + if (a.operation () == update_id) { - if (a.operation () == update_id) - return match_delegate (a, t, *this); - - // Collect all the testscript targets in prerequisite_targets. + // For the update pre-operation match the inner rule (actual update). + // Note that here we assume it will update (if required) all the + // testscript and input/output prerequisites. // - for (prerequisite_member p: - group_prerequisite_members (a, t, members_mode::maybe)) - { - if (p.is_a<testscript> ()) - t.prerequisite_targets.push_back (&p.search (t)); - } - - return [this] (action a, const target& t) - { - return perform_script (a, t); - }; + match_inner (a, t); + return &perform_update; } else { - // In both cases, the next step is to see if we have test.{input, - // output,roundtrip}. - // - - // We should have either arguments or input/roundtrip. Again, use - // lookup depth to figure out who takes precedence. - // - auto ip (t.find (var_pool["test.input"])); - auto op (t.find (var_pool["test.output"])); - auto rp (t.find (var_pool["test.roundtrip"])); - auto ap (t.find (var_pool["test.arguments"])); - - auto test = [&t] (pair<lookup, size_t>& x, const char* xn, - pair<lookup, size_t>& y, const char* yn) + if (script) { - if (x.first && y.first) + return [pass_n, this] (action a, const target& t) { - if (x.second == y.second) - fail << "both " << xn << " and " << yn << " specified for " - << "target " << t; - - (x.second < y.second ? y : x) = make_pair (lookup (), size_t (~0)); - } - }; - - test (ip, "test.input", ap, "test.arguments"); - test (rp, "test.roundtrip", ap, "test.arguments"); - test (ip, "test.input", rp, "test.roundtrip"); - test (op, "test.output", rp, "test.roundtrip"); - - const name* in; - const name* on; - - // Reduce the roundtrip case to input/output. - // - if (rp.first) - { - in = on = &cast<name> (rp.first); + return perform_script (a, t, pass_n); + }; } else { - in = ip.first ? &cast<name> (ip.first) : nullptr; - on = op.first ? &cast<name> (op.first) : nullptr; + return [pass_n, this] (action a, const target& t) + { + return perform_test (a, t, pass_n); + }; } + } + } - // Resolve them to targets, which normally would be existing files - // but could also be targets that need updating. - // - const scope& bs (t.base_scope ()); - - // @@ OUT: what if this is a @-qualified pair or names? - // - const target* it (in != nullptr ? &search (t, *in, bs) : nullptr); - const target* ot (on != nullptr - ? in == on ? it : &search (t, *on, bs) - : nullptr); + recipe group_rule:: + apply (action a, target& t) const + { + // Resolve group members. + // + // Remember that we are called twice: first during update for test + // (pre-operation) and then during test. During the former, we rely on + // the normall update rule to resolve the group members. During the + // latter, there will be no rule to do this but the group will already + // have been resolved by the pre-operation. + // + // If the rule could not resolve the group, then we ignore it. + // + group_view gv (a.outer () + ? resolve_group_members (a, t) + : t.group_members (a)); - if (a.operation () == update_id) + if (gv.members != nullptr) + { + auto& pts (t.prerequisite_targets[a]); + for (size_t i (0); i != gv.count; ++i) { - // First see if input/output are existing, up-to-date files. This - // is a common case optimization. - // - if (it != nullptr) - { - if (build2::match (a, *it, unmatch::unchanged)) - it = nullptr; - } + if (const target* m = gv.members[i]) + pts.push_back (m); + } - if (ot != nullptr) - { - if (in != on) - { - if (build2::match (a, *ot, unmatch::unchanged)) - ot = nullptr; - } - else - ot = it; - } + match_members (a, t, pts); + } - // Find the "real" update rule, that is, the rule that would have - // been found if we signalled that we do not match from match() - // above. - // - recipe d (match_delegate (a, t, *this)); + // Delegate to the base rule. + // + return rule::apply (a, t); + } - // If we have no input/output that needs updating, then simply - // redirect to it. - // - if (it == nullptr && ot == nullptr) - return d; + target_state rule:: + perform_update (action a, const target& t) + { + // First execute the inner recipe, then, if passing-through, execute + // prerequisites. + // + target_state ts (execute_inner (a, t)); - // Ok, time to handle the worst case scenario: we need to cause - // update of input/output targets and also delegate to the real - // update. - // - return [it, ot, dr = move (d)] ( - action a, const target& t) -> target_state - { - // Do the general update first. - // - target_state r (execute_delegate (dr, a, t)); + if (t.prerequisite_targets[a].size () != 0) + ts |= straight_execute_prerequisites (a, t); - const target* ts[] = {it, ot}; - return r |= straight_execute_members (a, t, ts); - }; - } - else - { - // Cache the targets in our prerequsite targets lists where they can - // be found by perform_test(). If we have either or both, then the - // first entry is input and the second -- output (either can be - // NULL). - // - if (it != nullptr || ot != nullptr) - { - auto& pts (t.prerequisite_targets); - pts.resize (2, nullptr); - pts[0] = it; - pts[1] = ot; - } - - return &perform_test; - } - } + return ts; } static script::scope_state @@ -404,31 +363,33 @@ namespace build2 return r; } - target_state rule_common:: - perform_script (action, const target& t) const + target_state rule:: + perform_script (action a, const target& t, size_t pass_n) const { + // First pass through. + // + if (pass_n != 0) + straight_execute_prerequisites (a, t, pass_n); + // Figure out whether the testscript file is called 'testscript', in // which case it should be the only one. // + auto& pts (t.prerequisite_targets[a]); + size_t pts_n (pts.size ()); + bool one; - size_t count (0); { optional<bool> o; - for (const target* pt: t.prerequisite_targets) + for (size_t i (pass_n); i != pts_n; ++i) { - // In case we are using the alias rule's list (see above). - // - if (const testscript* ts = pt->is_a<testscript> ()) - { - count++; + const testscript& ts (*pts[i]->is_a<testscript> ()); - bool r (ts->name == "testscript"); + bool r (ts.name == "testscript"); - if ((r && o) || (!r && o && *o)) - fail << "both 'testscript' and other names specified for " << t; + if ((r && o) || (!r && o && *o)) + fail << "both 'testscript' and other names specified for " << t; - o = r; - } + o = r; } assert (o); // We should have a testscript or we wouldn't be here. @@ -487,57 +448,56 @@ namespace build2 // Start asynchronous execution of the testscripts. // - wait_guard wg (target::count_busy (), t.task_count); + wait_guard wg (target::count_busy (), t[a].task_count); // Result vector. // using script::scope_state; vector<scope_state> result; - result.reserve (count); // Make sure there are no reallocations. + result.reserve (pts_n - pass_n); // Make sure there are no reallocations. - for (const target* pt: t.prerequisite_targets) + for (size_t i (pass_n); i != pts_n; ++i) { - if (const testscript* ts = pt->is_a<testscript> ()) + const testscript& ts (*pts[i]->is_a<testscript> ()); + + // If this is just the testscript, then its id path is empty (and it + // can only be ignored by ignoring the test target, which makes sense + // since it's the only testscript file). + // + if (one || test (t, path (ts.name))) { - // If this is just the testscript, then its id path is empty (and it - // can only be ignored by ignoring the test target, which makes - // sense since it's the only testscript file). - // - if (one || test (t, path (ts->name))) + if (mk) { - if (mk) - { - mkdir (wd, 2); - mk = false; - } + mkdir (wd, 2); + mk = false; + } - result.push_back (scope_state::unknown); - scope_state& r (result.back ()); - - if (!sched.async (target::count_busy (), - t.task_count, - [this] (scope_state& r, - const target& t, - const testscript& ts, - const dir_path& wd, - const diag_frame* ds) - { - diag_frame df (ds); - r = perform_script_impl (t, ts, wd, *this); - }, - ref (r), - cref (t), - cref (*ts), - cref (wd), - diag_frame::stack)) - { - // Executed synchronously. If failed and we were not asked to - // keep going, bail out. - // - if (r == scope_state::failed && !keep_going) - throw failed (); - } + result.push_back (scope_state::unknown); + scope_state& r (result.back ()); + + if (!sched.async (target::count_busy (), + t[a].task_count, + [this] (scope_state& r, + const target& t, + const testscript& ts, + const dir_path& wd, + const diag_frame* ds) + { + diag_frame df (ds); + r = perform_script_impl (t, ts, wd, *this); + }, + ref (r), + cref (t), + cref (ts), + cref (wd), + diag_frame::stack)) + { + // Executed synchronously. If failed and we were not asked to keep + // going, bail out. + // + if (r == scope_state::failed && !keep_going) + throw failed (); } } } @@ -597,28 +557,15 @@ namespace build2 try { - if (prev == nullptr) - { - // First process. - // - process p (args, 0, out); - pr = *next == nullptr || run_test (t, dr, next, &p); - p.wait (); + process p (prev == nullptr + ? process (args, 0, out) // First process. + : process (args, *prev, out)); // Next process. - assert (p.exit); - pe = *p.exit; - } - else - { - // Next process. - // - process p (args, *prev, out); - pr = *next == nullptr || run_test (t, dr, next, &p); - p.wait (); + pr = *next == nullptr || run_test (t, dr, next, &p); + p.wait (); - assert (p.exit); - pe = *p.exit; - } + assert (p.exit); + pe = *p.exit; } catch (const process_error& e) { @@ -646,10 +593,12 @@ namespace build2 } target_state rule:: - perform_test (action, const target& tt) + perform_test (action a, const target& tt, size_t pass_n) const { - // @@ Would be nice to print what signal/core was dumped. + // First pass through. // + if (pass_n != 0) + straight_execute_prerequisites (a, tt, pass_n); // See if we have the test executable override. // @@ -657,7 +606,7 @@ namespace build2 { // Note that the test variable's visibility is target. // - lookup l (tt["test"]); + lookup l (tt[var_test]); // Note that we have similar code for scripted tests. // @@ -686,7 +635,7 @@ namespace build2 { // Must be a target name. // - // @@ OUT: what if this is a @-qualified pair or names? + // @@ OUT: what if this is a @-qualified pair of names? // t = search_existing (*n, tt.base_scope ()); @@ -722,41 +671,74 @@ namespace build2 } } - process_path pp (run_search (p, true)); - cstrings args {pp.recall_string ()}; - - // Do we have options? + // See apply() for the structure of prerequisite_targets in the presence + // of test.{input,stdin,stdout}. // - if (auto l = tt["test.options"]) - append_options (args, cast<strings> (l)); + auto& pts (tt.prerequisite_targets[a]); + size_t pts_n (pts.size ()); + + cstrings args; - // Do we have input? + // Do we have stdin? // - auto& pts (tt.prerequisite_targets); - if (pts.size () != 0 && pts[0] != nullptr) + // We simulate stdin redirect (<file) with a fake (already terminate) + // cat pipe (cat file |). + // + bool stdin (pass_n != pts_n && pts[pass_n] != nullptr); + + process cat; + if (stdin) { - const file& it (pts[0]->as<file> ()); + const file& it (pts[pass_n]->as<file> ()); const path& ip (it.path ()); assert (!ip.empty ()); // Should have been assigned by update. + + try + { + cat.in_ofd = fdopen (ip, fdopen_mode::in); + } + catch (const io_error& e) + { + fail << "unable to open " << ip << ": " << e; + } + + // Purely for diagnostics. + // + args.push_back ("cat"); args.push_back (ip.string ().c_str ()); + args.push_back (nullptr); } - // Maybe arguments then? + + process_path pp (run_search (p, true)); + args.push_back (pp.recall_string ()); + + // Do we have options and/or arguments? // - else + if (auto l = tt[test_options]) + append_options (args, cast<strings> (l)); + + if (auto l = tt[test_arguments]) + append_options (args, cast<strings> (l)); + + // Do we have inputs? + // + for (size_t i (pass_n + 2); i < pts_n; ++i) { - if (auto l = tt["test.arguments"]) - append_options (args, cast<strings> (l)); + const file& it (pts[i]->as<file> ()); + const path& ip (it.path ()); + assert (!ip.empty ()); // Should have been assigned by update. + args.push_back (ip.string ().c_str ()); } args.push_back (nullptr); - // Do we have output? + // Do we have stdout? // path dp ("diff"); process_path dpp; - if (pts.size () != 0 && pts[1] != nullptr) + if (pass_n != pts_n && pts[pass_n + 1] != nullptr) { - const file& ot (pts[1]->as<file> ()); + const file& ot (pts[pass_n + 1]->as<file> ()); const path& op (ot.path ()); assert (!op.empty ()); // Should have been assigned by update. @@ -775,7 +757,7 @@ namespace build2 // Ignore Windows newline fluff if that's what we are running on. // - if (cast<target_triplet> (tt["test.target"]).class_ == "windows") + if (cast<target_triplet> (tt[test_target]).class_ == "windows") args.push_back ("--strip-trailing-cr"); args.push_back (op.string ().c_str ()); @@ -791,7 +773,10 @@ namespace build2 text << "test " << tt; diag_record dr; - if (!run_test (tt, dr, args.data ())) + if (!run_test (tt, + dr, + args.data () + (stdin ? 3 : 0), // Skip cat. + stdin ? &cat : nullptr)) { dr << info << "test command line: "; print_process (dr, args); @@ -800,18 +785,5 @@ namespace build2 return target_state::changed; } - - target_state alias_rule:: - perform_test (action a, const target& t) const - { - // Run the alias recipe first then the test. - // - target_state r (straight_execute_prerequisites (a, t)); - - // Note that we reuse the prerequisite_targets prepared by the standard - // search and match. - // - return r |= perform_script (a, t); - } } } diff --git a/build2/test/rule.hxx b/build2/test/rule.hxx index 819121c..b331263 100644 --- a/build2/test/rule.hxx +++ b/build2/test/rule.hxx @@ -17,34 +17,46 @@ namespace build2 { namespace test { - class rule_common: public build2::rule, protected virtual common + class rule: public build2::rule, protected virtual common { public: - virtual match_result + explicit + rule (common_data&& d): common (move (d)) {} + + virtual bool match (action, target&, const string&) const override; + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + target_state + perform_test (action, const target&, size_t) const; + target_state - perform_script (action, const target&) const; + perform_script (action, const target&, size_t) const; }; - class rule: public rule_common + class default_rule: public rule // For disambiguation in module. { public: - virtual recipe - apply (action, target&) const override; - - static target_state - perform_test (action, const target&); + explicit + default_rule (common_data&& d): common (move (d)), rule (move (d)) {} }; - class alias_rule: public rule_common + // In addition to the above rule's semantics, this rule sees through to + // the group's members. + // + class group_rule: public rule { public: + explicit + group_rule (common_data&& d): common (move (d)), rule (move (d)) {} + virtual recipe apply (action, target&) const override; - - target_state - perform_test (action, const target&) const; }; } } diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index cddd3a7..9588ac2 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -181,6 +181,8 @@ namespace build2 // For targets other than Windows leave the string intact. // + // @@ Would be nice to use cached value from test::common_data. + // if (cast<target_triplet> (scr.test_target["test.target"]).class_ != "windows") return s; @@ -294,6 +296,8 @@ namespace build2 // Ignore Windows newline fluff if that's what we are running on. // + // @@ Would be nice to use cached value from test::common_data. + // if (cast<target_triplet> ( sp.root->test_target["test.target"]).class_ == "windows") args.push_back ("--strip-trailing-cr"); diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index 51c08cb..0516b0f 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -503,6 +503,8 @@ namespace build2 // buildfiles except for test: while in buildfiles it can be a // target name, in testscripts it should be resolved to a path. // + // Note: entering in a custom variable pool. + // test_var (var_pool.insert<path> ("test")), options_var (var_pool.insert<strings> ("test.options")), arguments_var (var_pool.insert<strings> ("test.arguments")), @@ -527,7 +529,9 @@ namespace build2 // script // script:: - script (const target& tt, const testscript& st, const dir_path& rwd) + script (const target& tt, + const testscript& st, + const dir_path& rwd) : group (st.name == "testscript" ? string () : st.name, this), test_target (tt), script_target (st) @@ -574,7 +578,7 @@ namespace build2 { // Must be a target name. // - // @@ OUT: what if this is a @-qualified pair or names? + // @@ OUT: what if this is a @-qualified pair of names? // t = search_existing (*n, tt.base_scope ()); diff --git a/build2/variable.cxx b/build2/variable.cxx index d1f95c5..eb74aad 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -302,7 +302,7 @@ namespace build2 } void - typify (value& v, const value_type& t, const variable* var) + typify (value& v, const value_type& t, const variable* var, memory_order mo) { if (v.type == nullptr) { @@ -312,11 +312,16 @@ namespace build2 // names ns (move (v).as<names> ()); v = nullptr; - v.type = &t; - v.assign (move (ns), var); + + // Use value_type::assign directly to delay v.type change. + // + t.assign (v, move (ns), var); + v.null = false; } else v.type = &t; + + v.type.store (&t, mo); } else if (v.type != &t) { @@ -342,8 +347,10 @@ namespace build2 variable_cache_mutex_shard[ hash<value*> () (&v) % variable_cache_mutex_shard_size]); + // Note: v.type is rechecked by typify() under lock. + // ulock l (m); - typify (v, t, var); // v.type is rechecked by typify(), stored under lock. + typify (v, t, var, memory_order_release); } void diff --git a/build2/variable.ixx b/build2/variable.ixx index fd6b7b2..dcc1304 100644 --- a/build2/variable.ixx +++ b/build2/variable.ixx @@ -237,6 +237,16 @@ namespace build2 typify (v, t, var); } + void + typify (value&, const value_type&, const variable*, memory_order); + + inline void + typify (value& v, const value_type& t, const variable* var) + { + typify (v, t, var, memory_order_relaxed); + } + + inline vector_view<const name> reverse (const value& v, names& storage) { diff --git a/build2/version/init.cxx b/build2/version/init.cxx index 63e32ab..7b8bd01 100644 --- a/build2/version/init.cxx +++ b/build2/version/init.cxx @@ -27,8 +27,8 @@ namespace build2 { static const path manifest ("manifest"); - static const version_doc version_doc_; - static const version_in version_in_; + static const doc_rule doc_rule_; + static const in_rule in_rule_; bool boot (scope& rs, const location& l, unique_ptr<module_base>& mod) @@ -311,13 +311,13 @@ namespace build2 { auto& r (rs.rules); - r.insert<doc> (perform_update_id, "version.doc", version_doc_); - r.insert<doc> (perform_clean_id, "version.doc", version_doc_); - r.insert<doc> (configure_update_id, "version.doc", version_doc_); + r.insert<doc> (perform_update_id, "version.doc", doc_rule_); + r.insert<doc> (perform_clean_id, "version.doc", doc_rule_); + r.insert<doc> (configure_update_id, "version.doc", doc_rule_); - r.insert<file> (perform_update_id, "version.in", version_in_); - r.insert<file> (perform_clean_id, "version.in", version_in_); - r.insert<file> (configure_update_id, "version.in", version_in_); + r.insert<file> (perform_update_id, "version.in", in_rule_); + r.insert<file> (perform_clean_id, "version.in", in_rule_); + r.insert<file> (configure_update_id, "version.in", in_rule_); } return true; diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx index 9e127ca..bbfe1f6 100644 --- a/build2/version/rule.cxx +++ b/build2/version/rule.cxx @@ -45,12 +45,12 @@ namespace build2 return d == rs.src_path (); } - // version_doc + // doc_rule // - match_result version_doc:: + bool doc_rule:: match (action a, target& xt, const string&) const { - tracer trace ("version::version_doc::match"); + tracer trace ("version::doc_rule::match"); doc& t (static_cast<doc&> (xt)); @@ -76,7 +76,7 @@ namespace build2 return false; } - recipe version_doc:: + recipe doc_rule:: apply (action a, target& xt) const { doc& t (static_cast<doc&> (xt)); @@ -101,7 +101,7 @@ namespace build2 } } - target_state version_doc:: + target_state doc_rule:: perform_update (action a, const target& xt) { const doc& t (xt.as<const doc&> ()); @@ -168,12 +168,12 @@ namespace build2 return target_state::changed; } - // version_in + // in_rule // - match_result version_in:: + bool in_rule:: match (action a, target& xt, const string&) const { - tracer trace ("version::version_in::match"); + tracer trace ("version::in_rule::match"); file& t (static_cast<file&> (xt)); const scope& rs (t.root_scope ()); @@ -195,7 +195,7 @@ namespace build2 return fm && fi; } - recipe version_in:: + recipe in_rule:: apply (action a, target& xt) const { file& t (static_cast<file&> (xt)); @@ -220,10 +220,10 @@ namespace build2 } } - target_state version_in:: + target_state in_rule:: perform_update (action a, const target& xt) { - tracer trace ("version::version_in::perform_update"); + tracer trace ("version::in_rule::perform_update"); const file& t (xt.as<const file&> ()); const path& tp (t.path ()); diff --git a/build2/version/rule.hxx b/build2/version/rule.hxx index e686694..9172ba3 100644 --- a/build2/version/rule.hxx +++ b/build2/version/rule.hxx @@ -16,12 +16,12 @@ namespace build2 { // Generate a version file. // - class version_doc: public rule + class doc_rule: public rule { public: - version_doc () {} + doc_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe @@ -33,12 +33,12 @@ namespace build2 // Preprocess an .in file. // - class version_in: public rule + class in_rule: public rule { public: - version_in () {} + in_rule () {} - virtual match_result + virtual bool match (action, target&, const string&) const override; virtual recipe diff --git a/doc/testscript.cli b/doc/testscript.cli index 7b3d472..666d553 100644 --- a/doc/testscript.cli +++ b/doc/testscript.cli @@ -39,10 +39,10 @@ target as a test along with passing options and arguments, providing the result. For example: \ +exe{hello}: file{names.txt}: test.stdin = true +exe{hello}: file{greetings.txt}: test.stdout = true exe{hello}: test.options = --greeting 'Hi' exe{hello}: test.arguments = - # Read names from stdin. -exe{hello}: test.input = names.txt -exe{hello}: test.output = greetings.txt \ This works well for simple, single-run tests. If, however, our testing diff --git a/tests/cc/preprocessed/testscript b/tests/cc/preprocessed/testscript index fb69e65..644e121 100644 --- a/tests/cc/preprocessed/testscript +++ b/tests/cc/preprocessed/testscript @@ -12,7 +12,7 @@ test.arguments = config.cxx="$recall($cxx.path)" update # trace: cxx::compile::extract_(headers|modules): target: .../obje{(test).o...} # filter = sed -n -e \ - \''s/^trace: cxx::compile::extract_([^:]+): target:[^{]+\{([^.]+).*/\1 \2/p'\' + \''s/^trace: cxx::compile_rule::extract_([^:]+): target:[^{]+\{([^.]+).*/\1 \2/p'\' +cat <<EOI >=build/root.build cxx.std = latest diff --git a/tests/test/config-test/testscript b/tests/test/config-test/testscript index 0d08eb0..7423322 100644 --- a/tests/test/config-test/testscript +++ b/tests/test/config-test/testscript @@ -82,8 +82,7 @@ EOI +touch proj/units/simple/driver +cat <<EOI >=proj/units/simple/buildfile driver = $src_root/../../exe{driver} -#@@ TMP file{driver}@./: $driver -./: file{driver} $driver +file{driver}@./: $driver file{driver}@./: test = $driver file{driver}@./: test.arguments = units/simple EOI |