diff options
Diffstat (limited to 'libbuild2/target.ixx')
-rw-r--r-- | libbuild2/target.ixx | 295 |
1 files changed, 246 insertions, 49 deletions
diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx index 1c8dd8d..39b81e7 100644 --- a/libbuild2/target.ixx +++ b/libbuild2/target.ixx @@ -3,26 +3,152 @@ #include <cstring> // memcpy() -#include <libbuild2/filesystem.hxx> // mtime() - #include <libbuild2/export.hxx> namespace build2 { + LIBBUILD2_SYMEXPORT timestamp + mtime (const char*); // filesystem.cxx + + // target_key + // + inline const string& target_key:: + effective_name (string& r, bool force_ext) const + { + const target_type& tt (*type); + + // Note that if the name is not empty, then we always use that, even + // if the type is dir/fsdir. + // + if (name->empty () && (tt.is_a<build2::dir> () || tt.is_a<fsdir> ())) + { + r = dir->leaf ().string (); + } + // If we have the extension and the type expects the extension to be + // always specified explicitly by the user, then add it to the name. + // + // Overall, we have the following cases: + // + // 1. Extension is fixed: man1{}. + // + // 2. Extension is always specified by the user: file{}. + // + // 3. Default extension that may be overridden by the user: hxx{}. + // + // 4. Extension assigned by the rule but may be overridden by the + // user: obje{}. + // + // By default we only include the extension for (2). + // + else if (ext && !ext->empty () && + (force_ext || + tt.fixed_extension == &target_extension_none || + tt.fixed_extension == &target_extension_must)) + { + r = *name + '.' + *ext; + } + else + return *name; // Use name as is. + + return r; + } + + // rule_hints + // + inline const string& rule_hints:: + find (const target_type& tt, operation_id o, bool ut) const + { + // Look for fallback during the same iteration. + // + const value_type* f (nullptr); + + for (const value_type& v: map) + { + if (!(v.type == nullptr ? ut : tt.is_a (*v.type))) + continue; + + if (v.operation == o) + return v.hint; + + if (f == nullptr && + v.operation == default_id && + (o == update_id || o == clean_id)) + f = &v; + } + + return f != nullptr ? f->hint : empty_string; + } + + inline void rule_hints:: + insert (const target_type* tt, operation_id o, string h) + { + auto i (find_if (map.begin (), map.end (), + [tt, o] (const value_type& v) + { + return v.operation == o && v.type == tt; + })); + + if (i == map.end ()) + map.push_back (value_type {tt, o, move (h)}); + else + i->hint = move (h); + } + + inline const string& target:: + find_hint (operation_id o) const + { + using flag = target_type::flag; + + const target_type& tt (type ()); + + // First check the target itself. + // + if (!rule_hints.empty ()) + { + // If this is a group that "gave" its untyped hints to the members, then + // ignore untyped entries. + // + bool ut ((tt.flags & flag::member_hint) != flag::member_hint); + + const string& r (rule_hints.find (tt, o, ut)); + if (!r.empty ()) + return r; + } + + // Then check the group. + // + if (const target* g = group) + { + if (!g->rule_hints.empty ()) + { + // If the group "gave" its untyped hints to the members, then don't + // ignore untyped entries. + // + bool ut ((g->type ().flags & flag::member_hint) == flag::member_hint); + + return g->rule_hints.find (tt, o, ut); + } + } + + return empty_string; + } + // match_extra // inline void match_extra:: - init (bool f) + reinit (bool f) { + clear_data (); fallback = f; - buffer.clear (); + cur_options = all_options; + new_options = 0; + posthoc_prerequisite_targets = nullptr; } inline void match_extra:: free () { - string s; - buffer.swap (s); + clear_data (); } // target @@ -112,24 +238,41 @@ namespace build2 } inline bool target:: - matched (action a) const + matched (action a, memory_order mo) const { - assert (ctx.phase == run_phase::execute); + assert (ctx.phase == run_phase::match || + ctx.phase == run_phase::execute); const opstate& s (state[a]); + size_t c (s.task_count.load (mo)); + size_t b (ctx.count_base ()); // Note: cannot do (c - b)! - // Note that while the target could be being executed, we should see at - // least offset_matched since it must have been "achieved" before the - // phase switch. - // - size_t c (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); - - return c >= offset_matched; + if (ctx.phase == run_phase::match) + { + // While it will normally be applied, it could also be already executed + // or being relocked to reapply match options (see lock_impl() for + // background). + // + // Note that we can't just do >= offset_applied since offset_busy can + // also mean it is being matched. + // + // See also matched_state_impl(), mtime() for similar logic. + // + return (c == (b + offset_applied) || + c == (b + offset_executed) || + (c >= (b + offset_busy) && + s.match_extra.cur_options_.load (memory_order_relaxed) != 0)); + } + else + { + // Note that while the target could be being executed, we should see at + // least offset_matched since it must have been "achieved" before the + // phase switch. + // + return c >= (b + offset_matched); + } } - LIBBUILD2_SYMEXPORT target_state - group_action (action, const target&); // <libbuild2/algorithm.hxx> - inline bool target:: group_state (action a) const { @@ -137,16 +280,32 @@ namespace build2 // raw state is not group provided the recipe is group_recipe and the // state is unknown (see mtime() for a discussion on why we do it). // + // Note that additionally s.state may not be target_state::group even + // after execution due to deferment (see execute_impl() for details). + // + // @@ Hm, I wonder why not just return s.recipe_group_action now that we + // cache it. + // + + // This special hack allows us to do things like query an ad hoc member's + // state or mtime without matching/executing the member, only the group. + // Requiring matching/executing the member would be too burdensome and + // this feels harmless (ad hoc membership cannot be changed during the + // execute phase). + // + // Note: this test must come first since the member may not be matched and + // thus its state uninitialized. + // + if (ctx.phase == run_phase::execute && adhoc_group_member ()) + return true; + const opstate& s (state[a]); if (s.state == target_state::group) return true; if (s.state == target_state::unknown && group != nullptr) - { - if (recipe_function* const* f = s.recipe.target<recipe_function*> ()) - return *f == &group_action; - } + return s.recipe_group_action; return false; } @@ -160,15 +319,22 @@ namespace build2 // Note: already synchronized. // - size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); + size_t c (s.task_count.load (memory_order_relaxed)); + size_t b (ctx.count_base ()); // Note: cannot do (c - b)! - if (o == offset_tried) + if (c == (b + offset_tried)) return make_pair (false, target_state::unknown); else { - // Normally applied but can also be already executed. + // The same semantics as in target::matched(). Note that in the executed + // case we are guaranteed to be synchronized since we are in the match + // phase. // - assert (o == offset_applied || o == offset_executed); + assert (c == (b + offset_applied) || + c == (b + offset_executed) || + (c >= (b + offset_busy) && + s.match_extra.cur_options_.load (memory_order_relaxed) != 0)); + return make_pair (true, (group_state (a) ? group->state[a] : s).state); } } @@ -287,27 +453,27 @@ namespace build2 // include() // LIBBUILD2_SYMEXPORT include_type - include_impl (action, - const target&, - const string&, - const prerequisite&, - const target*); + include_impl (action, const target&, + const prerequisite&, const target*, + lookup*); inline include_type - include (action a, const target& t, const prerequisite& p, const target* m) + include (action a, const target& t, const prerequisite& p, lookup* l) { - // Most of the time this variable will not be specified, so let's optimize - // for that. + // Most of the time no prerequisite-specific variables will be specified, + // so let's optimize for that. // - if (p.vars.empty ()) - return true; - - const string* v (cast_null<string> (p.vars[t.ctx.var_include])); - - if (v == nullptr) - return true; + return p.vars.empty () + ? include_type (true) + : include_impl (a, t, p, nullptr, l); + } - return include_impl (a, t, *v, p, m); + inline include_type + include (action a, const target& t, const prerequisite_member& pm, lookup* l) + { + return pm.prerequisite.vars.empty () + ? include_type (true) + : include_impl (a, t, pm.prerequisite, pm.member, l); } // group_prerequisites @@ -392,7 +558,12 @@ namespace build2 // assert (!member->adhoc_group_member ()); - return prerequisite_type (*member); + // Feels like copying the prerequisite's variables to member is more + // correct than not (consider for_install, for example). + // + prerequisite_type p (*member); + p.vars = prerequisite.vars; + return p; } inline prerequisite_key prerequisite_member:: @@ -425,6 +596,25 @@ namespace build2 } template <typename T> + inline void prerequisite_members_range<T>::iterator:: + switch_mode () + { + g_ = resolve_members (*i_); + + if (g_.members != nullptr) + { + // See empty see through groups as groups. + // + for (j_ = 1; j_ <= g_.count && g_.members[j_ - 1] == nullptr; ++j_) ; + + if (j_ > g_.count) + g_.count = 0; + } + else + assert (r_->mode_ != members_mode::always); // Group can't be resolved. + } + + template <typename T> inline auto prerequisite_members_range<T>::iterator:: operator++ () -> iterator& { @@ -449,7 +639,7 @@ namespace build2 if (r_->mode_ != members_mode::never && i_ != r_->e_ && - i_->type.see_through) + i_->type.see_through ()) switch_mode (); } @@ -556,15 +746,20 @@ namespace build2 inline timestamp mtime_target:: load_mtime (const path& p) const { - assert (ctx.phase == run_phase::execute && - !group_state (action () /* inner */)); + // We can only enforce "not group state" during the execute phase. During + // match (e.g., the target is being matched), we will just have to pay + // attention. + // + assert (ctx.phase == run_phase::match || + (ctx.phase == run_phase::execute && + !group_state (action () /* inner */))); duration::rep r (mtime_.load (memory_order_consume)); if (r == timestamp_unknown_rep) { assert (!p.empty ()); - r = build2::mtime (p).time_since_epoch ().count (); + r = build2::mtime (p.string ().c_str ()).time_since_epoch ().count (); mtime_.store (r, memory_order_release); } @@ -574,6 +769,8 @@ namespace build2 inline bool mtime_target:: newer (timestamp mt, target_state s) const { + assert (s != target_state::unknown); // Should be executed. + timestamp mp (mtime ()); // What do we do if timestamps are equal? This can happen, for example, @@ -594,13 +791,13 @@ namespace build2 // path_target // inline const path& path_target:: - path () const + path (memory_order mo) const { // You may be wondering why don't we spin the transition out? The reason // is it shouldn't matter since were we called just a moment earlier, we // wouldn't have seen it. // - return path_state_.load (memory_order_acquire) == 2 ? path_ : empty_path; + return path_state_.load (mo) == 2 ? path_ : empty_path; } inline const path& path_target:: |