diff options
Diffstat (limited to 'libbuild2/target.ixx')
-rw-r--r-- | libbuild2/target.ixx | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/libbuild2/target.ixx b/libbuild2/target.ixx new file mode 100644 index 0000000..4570558 --- /dev/null +++ b/libbuild2/target.ixx @@ -0,0 +1,496 @@ +// file : libbuild2/target.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <cstring> // memcpy() + +#include <libbuild2/filesystem.hxx> // mtime() + +namespace build2 +{ + // target + // + inline const string* target:: + ext () const + { + slock l (targets.mutex_); + return *ext_ ? &**ext_ : nullptr; + } + + inline target_key target:: + key () const + { + const string* e (ext ()); + return target_key { + &type (), + &dir, + &out, + &name, + e != nullptr ? optional<string> (*e) : nullopt}; + } + + inline auto target:: + prerequisites () const -> const prerequisites_type& + { + return prerequisites_state_.load (memory_order_acquire) == 2 + ? prerequisites_ + : empty_prerequisites_; + } + + inline bool target:: + prerequisites (prerequisites_type&& p) const + { + target& x (const_cast<target&> (*this)); // MT-aware. + + uint8_t e (0); + if (x.prerequisites_state_.compare_exchange_strong ( + e, + 1, + memory_order_acq_rel, + memory_order_acquire)) + { + x.prerequisites_ = move (p); + x.prerequisites_state_.fetch_add (1, memory_order_release); + return true; + } + else + { + // Spin the transition out so that prerequisites() doesn't return empty. + // + for (; e == 1; e = prerequisites_state_.load (memory_order_acquire)) + /*this_thread::yield ()*/ ; + + return false; + } + } + + inline bool target:: + 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 unknown (see mtime() for a discussion on why we do it). + // + 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 false; + } + + inline pair<bool, target_state> target:: + matched_state_impl (action a) const + { + assert (phase == run_phase::match); + + // Note that the "tried" state is "final". + // + const opstate& s (state[a]); + size_t o (s.task_count.load (memory_order_relaxed) - // Synchronized. + target::count_base ()); + + if (o == target::offset_tried) + 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:: + executed_state_impl (action a) const + { + assert (phase == run_phase::execute); + return (group_state (a) ? group->state : state)[a].state; + } + + inline target_state target:: + matched_state (action a, bool fail) const + { + // Note that the target could be being asynchronously re-matched. + // + pair<bool, target_state> r (matched_state_impl (a)); + + if (fail && (!r.first || r.second == target_state::failed)) + throw failed (); + + return r.second; + } + + inline pair<bool, target_state> target:: + try_matched_state (action a, bool fail) const + { + pair<bool, target_state> r (matched_state_impl (a)); + + if (fail && r.first && r.second == target_state::failed) + throw failed (); + + return r; + } + + inline target_state target:: + executed_state (action a, bool fail) const + { + target_state r (executed_state_impl (a)); + + if (fail && r == target_state::failed) + throw failed (); + + return r; + } + + inline bool target:: + has_prerequisites () const + { + return !prerequisites ().empty (); + } + + inline bool target:: + has_group_prerequisites () const + { + return has_prerequisites () || + (group != nullptr && !group->has_prerequisites ()); + } + + inline bool target:: + unchanged (action a) const + { + return matched_state_impl (a).second == target_state::unchanged; + } + + inline ostream& + operator<< (ostream& os, const target& t) + { + return os << t.key (); + } + + // mark()/unmark() + // + + // VC15 doesn't like if we use (abstract) target here. + // + static_assert (alignof (file) % 4 == 0, "unexpected target alignment"); + + inline void + mark (const target*& p, uint8_t m) + { + uintptr_t i (reinterpret_cast<uintptr_t> (p)); + i |= m & 0x03; + p = reinterpret_cast<const target*> (i); + } + + inline uint8_t + marked (const target* p) + { + uintptr_t i (reinterpret_cast<uintptr_t> (p)); + return uint8_t (i & 0x03); + } + + inline uint8_t + unmark (const target*& p) + { + uintptr_t i (reinterpret_cast<uintptr_t> (p)); + uint8_t m (i & 0x03); + + if (m != 0) + { + i &= ~uintptr_t (0x03); + p = reinterpret_cast<const target*> (i); + } + + return m; + } + + // group_prerequisites + // + inline group_prerequisites:: + group_prerequisites (const target& t) + : t_ (t), + g_ (t_.group == nullptr || + t_.group->member != nullptr || // Ad hoc group member. + t_.group->prerequisites ().empty () + ? nullptr : t_.group) + { + } + + inline group_prerequisites:: + group_prerequisites (const target& t, const target* g) + : t_ (t), + g_ (g == nullptr || + g->prerequisites ().empty () + ? nullptr : g) + { + } + + inline auto group_prerequisites:: + begin () const -> iterator + { + auto& c ((g_ != nullptr ? *g_ : t_).prerequisites ()); + return iterator (&t_, g_, &c, c.begin ()); + } + + inline auto group_prerequisites:: + end () const -> iterator + { + auto& c (t_.prerequisites ()); + return iterator (&t_, g_, &c, c.end ()); + } + + inline size_t group_prerequisites:: + size () const + { + return t_.prerequisites ().size () + + (g_ != nullptr ? g_->prerequisites ().size () : 0); + } + + // group_prerequisites::iterator + // + inline auto group_prerequisites::iterator:: + operator++ () -> iterator& + { + if (++i_ == c_->end () && c_ != &t_->prerequisites ()) + { + c_ = &t_->prerequisites (); + i_ = c_->begin (); + } + return *this; + } + + + inline auto group_prerequisites::iterator:: + operator-- () -> iterator& + { + if (i_ == c_->begin () && c_ == &t_->prerequisites ()) + { + c_ = &g_->prerequisites (); + i_ = c_->end (); + } + + --i_; + return *this; + } + + // prerequisite_member + // + inline prerequisite prerequisite_member:: + as_prerequisite () const + { + if (member == nullptr) + return prerequisite; + + // An ad hoc group member cannot be used as a prerequisite (use the whole + // group instead). + // + assert (!member->adhoc_member ()); + + return prerequisite_type (*member); + } + + inline prerequisite_key prerequisite_member:: + key () const + { + return member != nullptr + ? prerequisite_key {prerequisite.proj, member->key (), nullptr} + : prerequisite.key (); + } + + // prerequisite_members + // + group_view + resolve_members (action, const target&); // algorithm.hxx + + template <typename T> + inline auto prerequisite_members_range<T>::iterator:: + operator++ () -> iterator& + { + if (k_ != nullptr) // Iterating over an ad hoc group. + k_ = k_->member; + + if (k_ == nullptr && g_.count != 0) // Iterating over a normal group. + { + if (g_.members == nullptr || // Special case, see leave_group(). + ++j_ > g_.count) + g_.count = 0; + } + + if (k_ == nullptr && g_.count == 0) // Iterating over the range. + { + ++i_; + + if (r_->mode_ != members_mode::never && + i_ != r_->e_ && + i_->type.see_through) + switch_mode (); + } + + return *this; + } + + template <typename T> + inline bool prerequisite_members_range<T>::iterator:: + enter_group () + { + assert (k_ == nullptr); // No nested ad hoc group entering. + + // First see if we are about to enter an ad hoc group. + // + const target* t (g_.count != 0 + ? j_ != 0 ? g_.members[j_ - 1] : nullptr + : i_->target.load (memory_order_consume)); + + if (t != nullptr && t->member != nullptr) + k_ = t; // Increment that follows will make it t->member. + else + { + // Otherwise assume it is a normal group. + // + g_ = resolve_members (r_->a_, search (r_->t_, *i_)); + + if (g_.members == nullptr) // Members are not know. + { + g_.count = 0; + return false; + } + + if (g_.count != 0) // Group is not empty. + j_ = 0; // Account for the increment that will follow. + } + + return true; + } + + template <typename T> + inline void prerequisite_members_range<T>::iterator:: + leave_group () + { + if (k_ != nullptr) + { + // Skip until the last element (next increment will reach the end). + // + for (; k_->member != nullptr; k_ = k_->member) ; + } + else + { + // Pretend we are on the last member of a normal group. + // + j_ = 0; + g_.count = 1; + g_.members = nullptr; // Ugly "special case signal" for operator++. + } + } + + template <typename T> + inline bool prerequisite_members_range<T>::iterator:: + group () const + { + return + k_ != nullptr ? k_->member != nullptr : /* ad hoc */ + g_.count != 0 ? g_.members != nullptr && j_ < g_.count : /* explicit */ + false; + } + + inline auto + prerequisite_members (action a, const target& t, members_mode m) + { + return prerequisite_members (a, t, t.prerequisites (), m); + } + + inline auto + reverse_prerequisite_members (action a, const target& t, members_mode m) + { + return prerequisite_members (a, t, reverse_iterate (t.prerequisites ()), m); + } + + // mtime_target + // + inline void mtime_target:: + mtime (timestamp mt) const + { + mtime_.store (mt.time_since_epoch ().count (), memory_order_release); + } + + inline timestamp mtime_target:: + load_mtime (const path& p) const + { + assert (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 (); + mtime_.store (r, memory_order_release); + } + + return timestamp (duration (r)); + } + + inline bool mtime_target:: + newer (timestamp mt) const + { + assert (phase == run_phase::execute); + + timestamp mp (mtime ()); + + // What do we do if timestamps are equal? This can happen, for example, + // on filesystems that don't have subsecond resolution. There is not + // much we can do here except detect the case where the target was + // changed on this run. + // + return mt < mp || (mt == mp && + executed_state_impl (action () /* inner */) == + target_state::changed); + } + + // path_target + // + inline const path& path_target:: + path () const + { + return path_state_.load (memory_order_acquire) == 2 ? path_ : empty_path; + } + + inline const path& path_target:: + path (path_type p) const + { + uint8_t e (0); + if (path_state_.compare_exchange_strong ( + e, + 1, + memory_order_acq_rel, + memory_order_acquire)) + { + path_ = move (p); + path_state_.fetch_add (1, memory_order_release); + } + else + { + // Spin the transition out. + // + for (; e == 1; e = path_state_.load (memory_order_acquire)) + /*this_thread::yield ()*/ ; + + assert (path_ == p); + } + + return path_; + } + + inline timestamp path_target:: + load_mtime () const + { + return mtime_target::load_mtime (path ()); + } +} |