aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/target.ixx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/target.ixx')
-rw-r--r--libbuild2/target.ixx295
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::