aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/target.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/target.hxx')
-rw-r--r--libbuild2/target.hxx379
1 files changed, 328 insertions, 51 deletions
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx
index 26c7208..20cd32d 100644
--- a/libbuild2/target.hxx
+++ b/libbuild2/target.hxx
@@ -4,8 +4,9 @@
#ifndef LIBBUILD2_TARGET_HXX
#define LIBBUILD2_TARGET_HXX
+#include <cstddef> // max_align_t
#include <iterator> // tags, etc.
-#include <type_traits> // aligned_storage
+#include <type_traits> // is_*
#include <unordered_map>
#include <libbutl/multi-index.hxx> // map_iterator_adapter
@@ -88,9 +89,15 @@ namespace build2
prerequisite_target (const target_type* t, bool a = false, uintptr_t d = 0)
: target (t), include (a ? include_adhoc : 0), data (d) {}
+ prerequisite_target (const target_type& t, bool a = false, uintptr_t d = 0)
+ : prerequisite_target (&t, a, d) {}
+
prerequisite_target (const target_type* t, include_type a, uintptr_t d = 0)
: prerequisite_target (t, a == include_type::adhoc, d) {}
+ prerequisite_target (const target_type& t, include_type a, uintptr_t d = 0)
+ : prerequisite_target (&t, a, d) {}
+
const target_type* target;
operator const target_type*& () {return target;}
@@ -110,8 +117,15 @@ namespace build2
// prerequisites that are updated during match should have this bit set
// (see dyndep_rule::*_existing_file() for details).
//
- static const uintptr_t include_adhoc = 0x01;
- static const uintptr_t include_udm = 0x02;
+ // target
+ //
+ // The data member contains the target pointer that has been "blanked
+ // out" for some reason (updated during match, unmatched, etc). See
+ // dyndep_rule::updated_during_match() for details.
+ //
+ static const uintptr_t include_adhoc = 0x01;
+ static const uintptr_t include_udm = 0x02;
+ static const uintptr_t include_target = 0x80;
uintptr_t include;
@@ -169,7 +183,112 @@ namespace build2
//
struct match_extra
{
- bool fallback; // True if matching a fallback rule (see match_rule()).
+ bool locked; // Normally true (see adhoc_rule::match() for background).
+ bool fallback; // True if matching a fallback rule (see match_rule_impl()).
+
+ // When matching a rule, the caller may wish to request a subset of the
+ // full functionality of performing the operation on the target. This is
+ // achieved with match options.
+ //
+ // Since the match caller normally has no control over which rule will be
+ // matched, the options are not specific to a particular rule. Rather,
+ // options are defined for performing a specific operation on a specific
+ // target type and would normally be part of the target type semantics.
+ // To put it another way, when a rule matches a target of certain type for
+ // certain operation, there is an expectation of certain semantics, some
+ // parts of which could be made optional.
+ //
+ // As a concrete example, consider installing libs{}, which traditionally
+ // has two parts: runtime (normally just the versioned shared library) and
+ // build-time (non-versioned symlinks, pkg-config files, headers, etc).
+ // The option to install only the runtime files is part of the bin::libs{}
+ // semantics, not of, say, cc::install_rule.
+ //
+ // The match options are specified as a uint64_t mask, which means there
+ // can be a maximum of 64 options per operation/target type. Options are
+ // opt-out rather than opt-in. That is, by default, all the options are
+ // enabled unless the match caller explicitly opted out of some
+ // functionality. Even if the caller opted out, there is no guarantee that
+ // the matching rule will honor this request (for example, because it is a
+ // user-provided ad hoc recipe). To put it another way, support for
+ // options is a quality of implementation matter.
+ //
+ // From the rule implementation's point view, match options are handled as
+ // follows: On initial match()/apply(), cur_options is initialized to ~0
+ // (all options enabled) and the matching rule is expected to override it
+ // with new_options in apply() (note that match() should no base any
+ // decisions on new_options since they may change between match() and
+ // apply()). This way a rule that does not support any match options does
+ // not need to do anything. Subsequent match calls may add new options
+ // which causes a rematch that manifests in the rule's reapply() call. In
+ // reapply(), cur_options are the currently enabled options and
+ // new_options are the newly requested options. Here the rule is expected
+ // to factor new_options to cur_options as appropriate. Note also that on
+ // rematch, if current options already include new options, then no call
+ // to reapply() is made. This, in particular, means that a rule that does
+ // not adjust cur_options in match() will never get a reapply() call
+ // (because all the options are enabled from the start). Note that
+ // cur_options should only be modfied in apply() or reapply().
+ //
+ // If a rematch is triggered after the rule has already been executed, an
+ // error is issued. This means that match options are not usable for
+ // operation/target types that could plausibly be executed during
+ // match. In particular, using match options for update and clean
+ // operations is a bad idea (update of pretty much any target can happen
+ // during match as a result of a tool update while clean might have to be
+ // performed during match to provide the mirror semantics).
+ //
+ // Note also that with rematches the assumption that in the match phase
+ // after matching the target we can MT-safely examine its state (such as
+ // its prerequisite_targets) no longer holds since such state could be
+ // modified during a rematch. As a result, if the target type specifies
+ // options for a certain operation, then you should not rely on this
+ // assumption for targets of this type during this operation.
+ //
+ // A rule that supports match options must also be prepared to handle the
+ // apply() call with new_options set to 0, for example, by using a
+ // minimally supported set of options instead. While 0 usually won't be
+ // passed by the match caller, this value is passed in the following
+ // circumstances:
+ //
+ // - match to resolve group (resolve_group())
+ // - match to resolve members (resolve_members())
+ // - match of ad hoc group via one of its ad hoc members
+ //
+ // Note that the 0 cur_options value is illegal.
+ //
+ // When it comes to match options specified for group members, the
+ // semantics differs between explicit and ad hoc groups. For explicit
+ // groups, the standard semantics described above applies and the group's
+ // reapply() function will be called both for the group itself as well as
+ // for its members and its the responsibility of the rule to decide what
+ // to do with the two sets of options (e.g., factor member's options into
+ // group's options, etc). For ad hoc groups, members are not matched to a
+ // rule but to the group_recipe directly (so there cannot be a call to
+ // reapply()). Currently, ad hoc group members cannot have options (more
+ // precisely, their options should always be ~0). An alternative semantics
+ // where the group rule is called to translate member options to group
+ // options may be implemented in the future (see match_impl_impl() for
+ // details).
+ //
+ // Note: match options are currently not exposed in Buildscript ad hoc
+ // recipes/rules (but are in C++).
+ //
+ static constexpr uint64_t all_options = ~uint64_t (0);
+
+ uint64_t cur_options;
+ uint64_t new_options;
+
+ atomic<uint64_t> cur_options_; // Implementation detail (see lock_impl()).
+
+ // The list of post hoc prerequisite targets for this target. Only not
+ // NULL in rule::apply_posthoc() and rule::reapply() functions and only if
+ // there are post hoc prerequisites. Primarily useful for adjusting match
+ // options for post hoc prerequisites (but can also be used to blank some
+ // of them out).
+ //
+ vector<context::posthoc_target::prerequisite_target>*
+ posthoc_prerequisite_targets;
// Auxiliary data storage.
//
@@ -189,7 +308,7 @@ namespace build2
? sizeof (string)
: sizeof (void*) * 4);
- std::aligned_storage<data_size>::type data_;
+ alignas (std::max_align_t) unsigned char data_[data_size];
void (*data_dtor_) (void*) = nullptr;
template <typename R,
@@ -236,9 +355,17 @@ namespace build2
// Implementation details.
//
+ // NOTE: see match_rule_impl() in algorithms.cxx if changing anything here.
+ //
public:
+ explicit
+ match_extra (bool l = true, bool f = false)
+ : locked (l), fallback (f),
+ cur_options (all_options), new_options (0),
+ posthoc_prerequisite_targets (nullptr) {}
+
void
- init (bool fallback);
+ reinit (bool fallback);
// Force freeing of the dynamically-allocated memory.
//
@@ -271,6 +398,10 @@ namespace build2
// fuzzy: they feel more `real` than `implied`. Maybe introduce
// `synthesized` in-between?
//
+ // @@ There are also now dynamically-discovered targets (ad hoc group
+ // members; see depdb-dyndep --dyn-target) which currently end up
+ // with prereq_new.
+ //
enum class target_decl: uint8_t
{
prereq_new = 1, // Created from prerequisite (create_new_target()).
@@ -369,9 +500,12 @@ namespace build2
//
// Note that the group-member link-up can happen anywhere between the
// member creation and rule matching so reading the group before the
- // member has been matched can be racy.
+ // member has been matched can be racy. However, once the member is linked
+ // up to the group, this relationship is immutable. As a result, one can
+ // atomically query the current value to see if already linked up (can be
+ // used as an optimization, to avoid deadlocks, etc).
//
- const target* group = nullptr;
+ relaxed_atomic<const target*> group = nullptr;
// What has been described above is an "explicit" group. That is, there is
// a dedicated target type that explicitly serves as a group and there is
@@ -404,7 +538,7 @@ namespace build2
// usually needed is to derive its path.
//
// - Unless declared, members are discovered lazily, they are only known
- // after the group's rule's apply() call.
+ // after the matching rule's apply() call.
//
// - Only declared members can be used as prerequisites but all can be
// used as targets (e.g., to set variables, etc).
@@ -434,7 +568,11 @@ namespace build2
// target for the ad hoc members (with a special target type that rules
// like install could recognize). See also the variable lookup semantics.
// We could also probably support see_through via an attribute or some
- // such.
+ // such. Or perhaps such cases should be handled through explicit groups
+ // and the ad hoc semantics is left to the non-see_through "primary
+ // targets with a bunch of subordinates" cases. In other words, if the
+ // members are "equal/symmetrical", then perhaps an explicit group is the
+ // correct approach.
//
const_ptr<target> adhoc_member = nullptr;
@@ -461,7 +599,8 @@ namespace build2
public:
// Normally you should not call this function directly and rather use
- // resolve_members() from <libbuild2/algorithm.hxx>.
+ // resolve_members() from <libbuild2/algorithm.hxx>. Note that action
+ // is always inner.
//
virtual group_view
group_members (action) const;
@@ -563,7 +702,8 @@ namespace build2
prerequisites () const;
// Swap-in a list of prerequisites. Return false if unsuccessful (i.e.,
- // someone beat us to it). Note that it can be called on const target.
+ // someone beat us to it), in which case the passed prerequisites are
+ // not moved. Note that it can be called on const target.
//
bool
prerequisites (prerequisites_type&&) const;
@@ -648,12 +788,14 @@ namespace build2
// If target_only is true, then only look in target and its target group
// without continuing in scopes. As an optimization, the caller can also
- // pass the base scope of the target, if already known.
+ // pass the base scope of the target, if already known. If locked is true,
+ // assume the targets mutex is locked.
//
pair<lookup_type, size_t>
lookup_original (const variable&,
bool target_only = false,
- const scope* bs = nullptr) const;
+ const scope* bs = nullptr,
+ bool locked = false) const;
// Return a value suitable for assignment. See scope for details.
//
@@ -663,11 +805,41 @@ namespace build2
value&
assign (const variable* var) {return vars.assign (var);} // For cached.
+ // Note: variable must already be entered.
+ //
+ value&
+ assign (const string& name)
+ {
+ return vars.assign (base_scope ().var_pool ().find (name));
+ }
+
// Return a value suitable for appending. See scope for details.
//
value&
- append (const variable&);
+ append (const variable&, const scope* bs = nullptr);
+ // Note: variable must already be entered.
+ //
+ value&
+ append (const string& name)
+ {
+ const scope& bs (base_scope ());
+ return append (*bs.var_pool ().find (name), &bs);
+ }
+
+ // As above but assume the targets mutex is locked.
+ //
+ value&
+ append_locked (const variable&, const scope* bs = nullptr);
+
+ // Note: variable must already be entered.
+ //
+ value&
+ append_locked (const string& name)
+ {
+ const scope& bs (base_scope ());
+ return append_locked (*bs.var_pool ().find (name), &bs);
+ }
// Rule hints.
//
@@ -737,7 +909,11 @@ namespace build2
//
mutable atomic_count dependents {0};
- // Match state storage between the match() and apply() calls.
+ // Match state storage between the match() and apply() calls with only
+ // the *_options members extended to reapply().
+ //
+ // Note: in reality, cur_options are used beyong (re)apply() as an
+ // implementation detail.
//
build2::match_extra match_extra;
@@ -761,6 +937,12 @@ namespace build2
//
target_state state;
+ // Set to true (only for the inner action) if this target has been
+ // matched but not executed as a result of the resolve_members() call.
+ // See also context::resolve_count.
+ //
+ bool resolve_counted;
+
// Rule-specific variables.
//
// The rule (for this action) has to be matched before these variables
@@ -843,8 +1025,11 @@ namespace build2
// Return true if the target has been matched for the specified action.
// This function can only be called during the match or execute phases.
//
+ // If you need to observe something in the matched target (e.g., the
+ // matched rule or recipe), use memory_order_acquire.
+ //
bool
- matched (action) const;
+ matched (action, memory_order mo = memory_order_relaxed) const;
// This function can only be called during match if we have observed
// (synchronization-wise) that this target has been matched (i.e., the
@@ -873,6 +1058,12 @@ namespace build2
target_state
executed_state (action, bool fail = true) const;
+ // Return true if the state comes from the group. Target must be at least
+ // matched except for ad hoc group members during the execute phase.
+ //
+ bool
+ group_state (action) const;
+
protected:
// Version that should be used during match after the target has been
// matched for this action.
@@ -889,24 +1080,28 @@ namespace build2
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 (action) const;
-
public:
// 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).
//
- // Note also that it is possible the target can vary from action to
- // action, just like recipes. We don't need to keep track of the action
- // here since the targets will be updated if the recipe is updated,
- // normally as part of rule::apply().
- //
- // Note that the recipe may modify this list.
+ // A rule should make sure that the target's prerequisite_targets are in
+ // the "canonical" form (that is, all the prerequisites that need to be
+ // executed are present with prerequisite_target::target pointing to the
+ // corresponding target). This is relied upon in a number of places,
+ // including in dump and to be able to pretend-execute the operation on
+ // this target without actually calling the recipe (see perform_execute(),
+ // resolve_members_impl() for background). Note that a rule should not
+ // store targets that are semantically prerequisites in an ad hoc manner
+ // (e.g., in match data) with a few well-known execeptions (see
+ // group_recipe and inner_recipe).
+ //
+ // Note that the recipe may modify this list during execute. Normally this
+ // would be just blanking out of ad hoc prerequisites, in which case check
+ // for ad hoc first and for not NULL second if accessing prerequisites of
+ // targets that you did not execute (see the library metadata protocol in
+ // cc for an example).
//
mutable action_state<build2::prerequisite_targets> prerequisite_targets;
@@ -1024,13 +1219,28 @@ namespace build2
}
template <typename T>
- typename std::enable_if<!data_invocable<T>::value, T&>::type&
+ typename std::enable_if<!data_invocable<T>::value, T&>::type
data (action a) const
{
using V = typename std::remove_cv<T>::type;
return state[a].recipe.target<data_wrapper<V>> ()->d;
}
+ // Return NULL if there is no data or the data is of a different type.
+ //
+ template <typename T>
+ typename std::enable_if<!data_invocable<T>::value, T*>::type
+ try_data (action a) const
+ {
+ using V = typename std::remove_cv<T>::type;
+
+ if (auto& r = state[a].recipe)
+ if (auto* t = r.target<data_wrapper<V>> ())
+ return &t->d;
+
+ return nullptr;
+ }
+
// Note that in this case we don't strip const (the expectation is that we
// move the recipe in/out of data).
//
@@ -1055,18 +1265,18 @@ namespace build2
}
template <typename T>
- typename std::enable_if<data_invocable<T>::value, T&>::type&
+ typename std::enable_if<data_invocable<T>::value, T&>::type
data (action a) const
{
return *state[a].recipe.target<T> ();
}
- void
- clear_data (action a) const
+ template <typename T>
+ typename std::enable_if<data_invocable<T>::value, T*>::type
+ try_data (action a) const
{
- const opstate& s (state[a]);
- s.recipe = nullptr;
- s.recipe_keep = false;
+ auto& r = state[a].recipe;
+ return r ? r.target<T> () : nullptr;
}
// Target type info and casting.
@@ -1446,9 +1656,7 @@ namespace build2
}
include_type
- include (action, const target&,
- const prerequisite_member&,
- lookup* = nullptr);
+ include (action, const target&, const prerequisite_member&, lookup* = nullptr);
// A "range" that presents a sequence of prerequisites (e.g., from
// group_prerequisites()) as a sequence of prerequisite_member's. For each
@@ -1624,8 +1832,7 @@ namespace build2
group_view g_;
size_t j_; // 1-based index, to support enter_group().
const target* k_; // Current member of ad hoc group or NULL.
- mutable typename std::aligned_storage<sizeof (value_type),
- alignof (value_type)>::type m_;
+ alignas (value_type) mutable unsigned char m_[sizeof (value_type)];
};
iterator
@@ -1896,7 +2103,7 @@ namespace build2
dynamic_type = &static_type;
}
- // Modification time is an "atomic cash". That is, it can be set at any
+ // Modification time is an "atomic cache". That is, it can be set at any
// time (including on a const instance) and we assume everything will be
// ok regardless of the order in which racing updates happen because we do
// not modify the external state (which is the source of timestemps) while
@@ -1929,8 +2136,7 @@ namespace build2
// If the mtime is unknown, then load it from the filesystem also caching
// the result.
//
- // Note: can only be called during executing and must not be used if the
- // target state is group.
+ // Note: must not be used if the target state is group.
//
timestamp
load_mtime (const path&) const;
@@ -1991,7 +2197,7 @@ namespace build2
// Target path. Must be absolute and normalized.
//
- // Target path is an "atomic consistent cash". That is, it can be set at
+ // Target path is an "atomic consistent cache". That is, it can be set at
// any time (including on a const instance) but any subsequent updates
// must set the same path. Or, in other words, once the path is set, it
// never changes.
@@ -2144,6 +2350,54 @@ namespace build2
static const target_type static_type;
};
+ // Mtime-based group target.
+ //
+ // Used to support explicit groups in buildfiles: can be derived from,
+ // populated with static members using the group{foo}<...> syntax, and
+ // matched with an ad hoc recipe/rule, including dynamic member extraction.
+ // Note that it is not see-through but a derived group can be made see-
+ // through via the [see_through] attribute.
+ //
+ // Note also that you shouldn't use it as a base for a custom group defined
+ // in C++, instead deriving from mtime_target directly and using a custom
+ // members layout more appropriate for the group's semantics. To put it
+ // another way, a group-based target should only be matched by an ad hoc
+ // recipe/rule (see match_rule_impl() in algorithms.cxx for details).
+ //
+ class LIBBUILD2_SYMEXPORT group: public mtime_target
+ {
+ public:
+ vector<reference_wrapper<const target>> static_members;
+
+ // Note: we expect no NULL entries in members.
+ //
+ vector<const target*> members; // Layout compatible with group_view.
+ action members_action; // Action on which members were resolved.
+ size_t members_on = 0; // Operation number on which members were resolved.
+ size_t members_static; // Number of static ones in members (always first).
+
+ void
+ reset_members (action a)
+ {
+ members.clear ();
+ members_action = a;
+ members_on = ctx.current_on;
+ members_static = 0;
+ }
+
+ virtual group_view
+ group_members (action) const override;
+
+ group (context& c, dir_path d, dir_path o, string n)
+ : mtime_target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
+
+ public:
+ static const target_type static_type;
+ };
+
// Alias target. It represents a list of targets (its prerequisites)
// as a single "name".
//
@@ -2270,6 +2524,22 @@ namespace build2
static const target_type static_type;
};
+ // This target type is primarily used for files mentioned in the `recipe`
+ // directive.
+ //
+ class LIBBUILD2_SYMEXPORT buildscript: public file
+ {
+ public:
+ buildscript (context& c, dir_path d, dir_path o, string n)
+ : file (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
+
+ public:
+ static const target_type static_type;
+ };
+
// Common documentation file target.
//
class LIBBUILD2_SYMEXPORT doc: public file
@@ -2332,7 +2602,7 @@ namespace build2
// in the generic install rule. @@ This is still a TODO.
//
// Note that handling subsections with man1..9{} is easy, we
- // simply specify the extension explicitly, e.g., man{foo.1p}.
+ // simply specify the extension explicitly, e.g., man1{foo.1p}.
//
class LIBBUILD2_SYMEXPORT man: public doc
{
@@ -2420,7 +2690,7 @@ namespace build2
string&, optional<string>&, const location&,
bool);
- // Target print functions.
+ // Target print functions (target_type::print).
//
// Target type uses the extension but it is fixed and there is no use
@@ -2435,17 +2705,24 @@ namespace build2
LIBBUILD2_SYMEXPORT bool
target_print_1_ext_verb (ostream&, const target_key&, bool);
+ // Target search functions (target_type::search).
+ //
+
// The default behavior, that is, look for an existing target in the
// prerequisite's directory scope.
//
+ // Note that this implementation assumes a target can only be found in the
+ // out tree (targets that can be in the src tree would normally use
+ // file_search() below).
+ //
LIBBUILD2_SYMEXPORT const target*
- target_search (const target&, const prerequisite_key&);
+ target_search (context&, const target*, const prerequisite_key&);
- // First look for an existing target as above. If not found, then look
- // for an existing file in the target-type-specific list of paths.
+ // First look for an existing target both in out and src. If not found, then
+ // look for an existing file in src.
//
LIBBUILD2_SYMEXPORT const target*
- file_search (const target&, const prerequisite_key&);
+ file_search (context&, const target*, const prerequisite_key&);
}
#include <libbuild2/target.ixx>