aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/target.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/target.hxx')
-rw-r--r--libbuild2/target.hxx1129
1 files changed, 922 insertions, 207 deletions
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx
index 01f01e1..20cd32d 100644
--- a/libbuild2/target.hxx
+++ b/libbuild2/target.hxx
@@ -4,11 +4,12 @@
#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.mxx> // map_iterator_adapter
+#include <libbutl/multi-index.hxx> // map_iterator_adapter
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
@@ -38,16 +39,19 @@ namespace build2
// Prerequisite inclusion/exclusion (see include() function below).
//
+ // Note that posthoc is handled internally and should normally be treated by
+ // the rules the same as excluded.
+ //
class include_type
{
public:
- enum value {excluded, adhoc, normal};
+ enum value {excluded, posthoc, adhoc, normal};
include_type (value v): v_ (v) {}
include_type (bool v): v_ (v ? normal : excluded) {}
operator value () const {return v_;}
- explicit operator bool () const {return v_ != excluded;}
+ explicit operator bool () const {return v_ == normal || v_ == adhoc;}
private:
value v_;
@@ -70,50 +74,308 @@ namespace build2
};
// List of prerequisites resolved to targets. Unless additional storage is
- // needed, it can be used as just vector<const target*> (which is what we
+ // needed, it can be treated as just vector<const target*> (which is what we
// used to have initially).
//
+ // The include member normally just indicates (in the first bit) whether
+ // this prerequisite is ad hoc. But it can also carry additional information
+ // (for example, from operation-specific override) in other bits (see below
+ // for details).
+ //
struct prerequisite_target
{
using target_type = build2::target;
prerequisite_target (const target_type* t, bool a = false, uintptr_t d = 0)
- : target (t), adhoc (a), data (d) {}
+ : 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;}
operator const target_type* () const {return target;}
const target_type* operator-> () const {return target;}
- const target_type* target;
- bool adhoc; // True if include=adhoc.
- uintptr_t data;
+ // The first 8 bits are reserved with the first two having the following
+ // semantics:
+ //
+ // adhoc
+ //
+ // This prerequisite is ad hoc.
+ //
+ // udm
+ //
+ // This prerequisite is updated during match. Note that only static
+ // prerequisites that are updated during match should have this bit set
+ // (see dyndep_rule::*_existing_file() for details).
+ //
+ // 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;
+
+ bool adhoc () const {return (include & include_adhoc) != 0;}
+
+ // Auxiliary data.
+ //
+ uintptr_t data;
};
using prerequisite_targets = vector<prerequisite_target>;
- // A rule match is an element of hint_rule_map.
+ // A rule match is an element of name_rule_map.
//
using rule_match = pair<const string, reference_wrapper<const rule>>;
+ // A map of target type plus operation ids to rule hints (see name_rule_map
+ // for details on rule names and hints). The default_id serves as a fallback
+ // for update and clean operations.
+ //
+ // Note that for now hints are tried in the order specified and the first
+ // that matches, used.
+ //
+ struct rule_hints
+ {
+ // Return empty string if not found.
+ //
+ const string&
+ find (const target_type&, operation_id, bool untyped) const;
+
+ bool
+ empty () const {return map.empty ();}
+
+ // Note that insertion of an existing entry overrides the old value.
+ //
+ void
+ insert (const target_type*, operation_id, string);
+
+ struct value_type
+ {
+ const target_type* type;
+ operation_id operation;
+ string hint;
+ };
+
+ vector<value_type> map;
+ };
+
// Additional information about a rule match (see rule.hxx for details).
//
+ // Note that passing this information to a base rule's match() as-is may or
+ // may not be correct. If some changes must be made (for example, the
+ // fallback flag must be cleared), then that should be done by modifying
+ // (and restoring, if necessary) the passed instance rather than making a
+ // copy (which would not survive to apply()).
+ //
struct match_extra
{
- bool fallback; // True if matching a fallback rule.
- string buffer; // Auxiliary buffer that's reused during match/apply.
+ 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.
+ //
+ // A rule (whether matches or not) may use this pad to pass data between
+ // its match and apply functions (but not the recipe). The rule should
+ // static assert that the size of the pad is sufficient for its needs.
+ //
+ // This facility is complementary to the auxiliary data storage in target:
+ // it can store slightly more/extra data without dynamic memory allocation
+ // but can only be used during match/apply.
+ //
+ // Note also that a rule that delegates to another rule may not be able to
+ // use this mechanism fully since the delegated-to rule may also need the
+ // data storage.
+ //
+ static constexpr size_t data_size = (sizeof (string) > sizeof (void*) * 4
+ ? sizeof (string)
+ : sizeof (void*) * 4);
+
+ alignas (std::max_align_t) unsigned char data_[data_size];
+ void (*data_dtor_) (void*) = nullptr;
+
+ template <typename R,
+ typename T = typename std::remove_cv<
+ typename std::remove_reference<R>::type>::type>
+ typename std::enable_if<std::is_trivially_destructible<T>::value,T&>::type
+ data (R&& d)
+ {
+ assert (sizeof (T) <= data_size);
+ clear_data ();
+ return *new (&data_) T (forward<R> (d));
+ }
+
+ template <typename R,
+ typename T = typename std::remove_cv<
+ typename std::remove_reference<R>::type>::type>
+ typename std::enable_if<!std::is_trivially_destructible<T>::value,T&>::type
+ data (R&& d)
+ {
+ assert (sizeof (T) <= data_size);
+ clear_data ();
+ T& r (*new (&data_) T (forward<R> (d)));
+ data_dtor_ = [] (void* p) {static_cast<T*> (p)->~T ();};
+ return r;
+ }
+
+ template <typename T>
+ T&
+ data () {return *reinterpret_cast<T*> (&data_);}
+
+ template <typename T>
+ const T&
+ data () const {return *reinterpret_cast<const T*> (&data_);}
+
+ void
+ clear_data ()
+ {
+ if (data_dtor_ != nullptr)
+ {
+ data_dtor_ (&data_);
+ data_dtor_ = nullptr;
+ }
+ }
// 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.
//
void
free ();
+
+ ~match_extra ()
+ {
+ clear_data ();
+ }
};
// Target.
@@ -126,17 +388,34 @@ namespace build2
// Note that the order of the enumerators is arranged so that their
// integral values indicate whether one "overrides" the other.
//
+ // We refer to the targets other than real and implied as
+ // dynamically-created or just dynamic.
+ //
// @@ We have cases (like pkg-config extraction) where it should probably be
// prereq_file rather than implied (also audit targets.insert<> calls).
//
+ // @@ Also, synthesized dependency declarations (e.g., in cc::link_rule) are
+ // 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, // Created from prerequisite (create_new_target()).
- prereq_file, // Created from prerequisite/file (search_existing_file ()).
- implied, // Target-spec variable assignment, implicitly-entered, etc.
- real // Real dependency declaration.
+ prereq_new = 1, // Created from prerequisite (create_new_target()).
+ prereq_file, // Created from prerequisite/file (search_existing_file()).
+ implied, // Target-spec variable assignment, implicitly-entered, etc.
+ real // Real dependency declaration.
};
+ inline bool
+ operator>= (target_decl l, target_decl r)
+ {
+ return static_cast<uint8_t> (l) >= static_cast<uint8_t> (r);
+ }
+
class LIBBUILD2_SYMEXPORT target
{
public:
@@ -201,15 +480,15 @@ namespace build2
// obj{}).
//
// In an all-group, when a group is updated, normally all its members are
- // updates (and usually with a single command), though there could be some
+ // updated (and usually with a single command), though there could be some
// members that are omitted, depending on the configuration (e.g., an
// inline file not/being generated). When an all-group is mentioned as a
// prerequisite, the rule is usually interested in the individual members
- // rather than the whole group. For example, a C++ compile rule would like
- // to "see" the ?xx{} members when it gets a cli.cxx{} group.
+ // rather than the group target. For example, a C++ compile rule would
+ // like to "see" the ?xx{} members when it gets a cli.cxx{} group.
//
// Which brings us to the group iteration mode. The target type contains a
- // member called see_through that indicates whether the default iteration
+ // flag called see_through that indicates whether the default iteration
// mode for the group should be "see through"; that is, whether we see the
// members or the group itself. For the iteration support itself, see the
// *_prerequisite_members() machinery below.
@@ -221,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
@@ -256,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).
@@ -267,7 +549,8 @@ namespace build2
// group can be a sub-group of an explicit group.
//
// - Member variable lookup skips the ad hoc group (since the group is the
- // first member, this is normally what we want).
+ // first member, this is normally what we want). But special semantics
+ // could be arranged; see var_backlink, for example.
//
// Note that ad hoc groups can be part of explicit groups. In a sense, we
// have a two-level grouping: an explicit group with its members each of
@@ -277,8 +560,25 @@ namespace build2
// Use add_adhoc_member(), find_adhoc_member() from algorithms to manage
// ad hoc members.
//
+ // One conceptual issue we have with our ad hoc group implementation is
+ // that the behavior could be sensitive to the order in which the members
+ // are specified (due to the primary member logic). For example, unless we
+ // specify the header in the header/source group first, it will not be
+ // installed. Perhaps the solution is to synthesize a separate group
+ // 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. 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;
+ // Return true if this target is an ad hoc group (that is, its primary
+ // member).
+ //
bool
adhoc_group () const
{
@@ -288,6 +588,9 @@ namespace build2
(group == nullptr || group->adhoc_member == nullptr);
}
+ // Return true if this target is an ad hoc group member (that is, its
+ // secondary member).
+ //
bool
adhoc_group_member () const
{
@@ -296,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;
@@ -312,6 +616,11 @@ namespace build2
target_key
key_locked () const;
+ // Note that the returned name is guaranteed to be "stable" (e.g., for
+ // hashing) only if the target has the extension assigned. This happens,
+ // for example, when a path is derived for a path-based target (which
+ // normally happens when such a target is matched for update).
+ //
names
as_name () const;
@@ -324,7 +633,16 @@ namespace build2
// Most qualified scope that contains this target.
//
const scope&
- base_scope () const;
+ base_scope () const
+ {
+ if (ctx.phase != run_phase::load)
+ {
+ if (const scope* s = base_scope_.load (memory_order_consume))
+ return *s;
+ }
+
+ return base_scope_impl ();
+ }
// Root scope of a project that contains this target. Note that
// a target can be out of any (known) project root in which case
@@ -332,7 +650,16 @@ namespace build2
// then use base_scope().root_scope() expression instead.
//
const scope&
- root_scope () const;
+ root_scope () const
+ {
+ return *base_scope ().root_scope ();
+ }
+
+ // Root scope of a bundle amalgamation that contains this target. The
+ // same notes as to root_scope() apply.
+ //
+ const scope&
+ bundle_scope () const {return *root_scope ().bundle_scope ();}
// Root scope of a strong amalgamation that contains this target.
// The same notes as to root_scope() apply.
@@ -352,6 +679,16 @@ namespace build2
return out_dir ().sub (s.out_path ());
}
+ // Implementation details (use above functions instead).
+ //
+ // Base scope cached value. Invalidated every time we switch to the load
+ // phase (which is the only phase where we may insert new scopes).
+ //
+ mutable atomic<const scope*> base_scope_ {nullptr};
+
+ const scope&
+ base_scope_impl () const;
+
// Prerequisites.
//
// We use an atomic-empty semantics that allows one to "swap in" a set of
@@ -365,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;
@@ -424,8 +762,9 @@ namespace build2
lookup_type
operator[] (const string& name) const
{
- const variable* var (ctx.var_pool.find (name));
- return var != nullptr ? operator[] (*var) : lookup_type ();
+ const scope& bs (base_scope ());
+ const variable* var (bs.var_pool ().find (name));
+ return var != nullptr ? lookup (*var, &bs).first : lookup_type ();
}
// As above but also return the depth at which the value is found. The
@@ -437,19 +776,26 @@ namespace build2
// earlier. If no value is found, then the depth is set to ~0.
//
pair<lookup_type, size_t>
- lookup (const variable& var) const
+ lookup (const variable& var, const scope* bs = nullptr) const
{
- auto p (lookup_original (var));
+ auto p (lookup_original (var, false, bs));
return var.overrides == nullptr
? p
- : base_scope ().lookup_override (var, move (p), true);
+ : (bs != nullptr
+ ? *bs
+ : base_scope ()).lookup_override (var, move (p), true);
}
// If target_only is true, then only look in target and its target group
- // without continuing in scopes.
+ // without continuing in scopes. As an optimization, the caller can also
+ // 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;
+ lookup_original (const variable&,
+ bool target_only = false,
+ const scope* bs = nullptr,
+ bool locked = false) const;
// Return a value suitable for assignment. See scope for details.
//
@@ -459,10 +805,53 @@ 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.
+ //
+ public:
+ build2::rule_hints rule_hints;
+
+ // Find the rule hint for the specified operation taking into account the
+ // target type/group. Note: racy with regards to the group link-up and
+ // should only be called when safe.
+ //
+ const string&
+ find_hint (operation_id) const;
// Ad hoc recipes.
//
@@ -499,6 +888,12 @@ namespace build2
static const size_t offset_executed = 5; // Recipe has been executed.
static const size_t offset_busy = 6; // Match/execute in progress.
+ // @@ PERF There is a lot of data below that is only needed for "output"
+ // as opposed to "source" targets (data pads, prerequisite_targets,
+ // etc). Maybe we should move this stuff to an optional extra (like we
+ // have for the root scope). Maybe we could even allocate it as part of
+ // the target's memory block or some such?
+
// Inner/outer operation state. See <libbuild2/action.hxx> for details.
//
class LIBBUILD2_SYMEXPORT opstate
@@ -514,24 +909,40 @@ 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;
- // Matched rule (pointer to hint_rule_map element). Note that in case of
+ // Matched rule (pointer to name_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;
+ // Note: also used as the auxiliary data storage during match, which is
+ // why mutable (see the target::data() API below for details). The
+ // default recipe_keep value is set by clear_target().
+ //
+ mutable build2::recipe recipe;
+ mutable bool recipe_keep; // Keep after execution.
+ bool recipe_group_action; // Recipe is group_action.
// Target state for this operation. Note that it is undetermined until
// a rule is matched and recipe applied (see set_recipe()).
//
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
@@ -539,8 +950,8 @@ namespace build2
// no iffy modifications of the group's variables by member's rules).
//
// They are also automatically cleared before another rule is matched,
- // similar to the data pad. In other words, rule-specific variables are
- // only valid for this match-execute phase.
+ // similar to the auxiliary data storage. In other words, rule-specific
+ // variables are only valid for this match-execute phase.
//
variable_map vars;
@@ -564,13 +975,6 @@ namespace build2
return operator[] (*var);
}
- lookup_type
- operator[] (const string& name) const
- {
- const variable* var (target_->ctx.var_pool.find (name));
- return var != nullptr ? operator[] (*var) : lookup_type ();
- }
-
// As above but also return the depth at which the value is found. The
// depth is calculated by adding 1 for each test performed. So a value
// that is from the rule will have depth 1. That from the target - 2,
@@ -599,14 +1003,18 @@ namespace build2
value&
assign (const variable* var) {return vars.assign (var);} // For cached.
+ // Implementation details.
+ //
public:
explicit
- opstate (context& c): vars (c, false /* global */) {}
+ opstate (context& c): vars (variable_map::owner::target, &c) {}
private:
friend class target_set;
- const target* target_ = nullptr; // Back-pointer, set by target_set.
+ // Back-pointer, set by target_set along with vars.target_.
+ //
+ const target* target_ = nullptr;
};
action_state<opstate> state;
@@ -615,10 +1023,13 @@ namespace build2
const opstate& operator[] (action a) const {return state[a];}
// Return true if the target has been matched for the specified action.
- // This function can only be called during execution.
+ // 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
@@ -627,7 +1038,7 @@ namespace build2
target_state
matched_state (action, bool fail = true) const;
- // See try_match().
+ // See try_match_sync().
//
pair<bool, target_state>
try_matched_state (action, bool fail = true) const;
@@ -647,12 +1058,18 @@ 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.
//
// Indicate whether there is a rule match with the first half of the
- // result (see try_match()).
+ // result (see try_match_sync()).
//
pair<bool, target_state>
matched_state_impl (action) const;
@@ -663,108 +1080,246 @@ 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;
- // Auxilary data storage.
+ // Auxiliary data storage.
//
// A rule that matches (i.e., returns true from its match() function) may
- // use this pad to pass data between its match and apply functions as well
- // as the recipe. After the recipe is executed, the data is destroyed by
- // calling data_dtor (if not NULL). The rule should static assert that the
- // size of the pad is sufficient for its needs.
- //
- // Note also that normally at least 2 extra pointers may be stored without
- // a dynamic allocation in the returned recipe (small object optimization
- // in std::function). So if you need to pass data only between apply() and
- // the recipe, then this might be a more convenient way.
- //
- // Note also that a rule that delegates to another rule may not be able to
- // use this mechanism fully since the delegated-to rule may also need the
- // data pad.
- //
- // Currenly the data is not destroyed until the next match.
+ // use this facility to pass data between its match and apply functions as
+ // well as the recipe. Specifically, between match() and apply() the data
+ // is stored in the recipe member (which is std::move_only_function-like).
+ // If the data needs to be passed on to the recipe, then it must become
+ // the recipe itself. Here is a typical arrangement:
+ //
+ // class compile_rule
+ // {
+ // struct match_data
+ // {
+ // ... // Data.
+ //
+ // const compile_rule& rule;
+ //
+ // target_state
+ // operator() (action a, const target& t)
+ // {
+ // return rule.perform_update (a, t, this);
+ // }
+ // };
+ //
+ // virtual bool
+ // match (action a, const target& t)
+ // {
+ // ... // Determine if matching.
+ //
+ // t.data (a, match_data {..., *this});
+ // return true;
+ // }
+ //
+ // virtual bool
+ // apply (action a, target& t)
+ // {
+ // match_data& md (t.data (a));
+ //
+ // ... // Match prerequisites, etc.
+ //
+ // return move (md); // Data becomes the recipe.
+ // }
+ //
+ // target_state
+ // perform_update (action a, const target& t, match_data& md) const
+ // {
+ // ... // Access data (also available as t.data<match_data> (a)).
+ // }
+ // };
+ //
+ // Note: see also similar facility in match_extra.
+ //
+ // After the recipe is executed, the recipe/data is destroyed, unless
+ // explicitly requested not to (see below). The rule may static assert
+ // that the small size of the storage (which doesn't require dynamic
+ // memory allocation) is sufficient for its needs.
+ //
+ // Note also that a rule that delegates to another rule may need to store
+ // the base rule's data/recipe in its own data/recipe.
+
+ // Provide the small object optimization size for the common compilers
+ // (see recipe.hxx for details) in case a rule wants to make sure its data
+ // won't require a dynamic memory allocation. Note that using a minimum
+ // generally available (2 pointers) is not always possible because the
+ // data size may depend on sizes of other compiler-specific types (e.g.,
+ // std::string).
+ //
+ static constexpr size_t small_data_size =
+#if defined(__GLIBCXX__)
+ sizeof (void*) * 2
+#elif defined(_LIBCPP_VERSION)
+ sizeof (void*) * 3
+#elif defined(_MSC_VER)
+ sizeof (void*) * 6
+#else
+ sizeof (void*) * 2 // Assume at least 2 pointers.
+#endif
+ ;
+
+ template <typename T>
+ struct data_wrapper
+ {
+ T d;
+
+ target_state
+ operator() (action, const target&) const // Never called.
+ {
+ return target_state::unknown;
+ }
+ };
+
+ // Avoid wrapping the data if it is already a recipe.
//
- // Note that the recipe may modify the data. Currently reserved for the
- // inner part of the action.
+ // Note that this techniques requires a fix for LWG issue 2132 (which all
+ // our minimum supported compiler versions appear to have).
//
- static constexpr size_t data_size = sizeof (string) * 16;
- mutable std::aligned_storage<data_size>::type data_pad;
+ template <typename T>
+ struct data_invocable: std::is_constructible<
+ std::function<recipe_function>,
+ std::reference_wrapper<typename std::remove_reference<T>::type>> {};
- mutable void (*data_dtor) (void*) = nullptr;
+ template <typename T>
+ typename std::enable_if<!data_invocable<T>::value, void>::type
+ data (action a, T&& d) const
+ {
+ using V = typename std::remove_cv<
+ typename std::remove_reference<T>::type>::type;
- template <typename R,
- typename T = typename std::remove_cv<
- typename std::remove_reference<R>::type>::type>
- typename std::enable_if<std::is_trivially_destructible<T>::value,T&>::type
- data (R&& d) const
+ const opstate& s (state[a]);
+ s.recipe = data_wrapper<V> {forward<T> (d)};
+ s.recipe_keep = false; // Can't keep non-recipe data.
+ }
+
+ template <typename T>
+ typename std::enable_if<!data_invocable<T>::value, T&>::type
+ data (action a) const
{
- assert (sizeof (T) <= data_size && data_dtor == nullptr);
- return *new (&data_pad) T (forward<R> (d));
+ using V = typename std::remove_cv<T>::type;
+ return state[a].recipe.target<data_wrapper<V>> ()->d;
}
- template <typename R,
- typename T = typename std::remove_cv<
- typename std::remove_reference<R>::type>::type>
- typename std::enable_if<!std::is_trivially_destructible<T>::value,T&>::type
- data (R&& d) const
+ // 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
{
- assert (sizeof (T) <= data_size && data_dtor == nullptr);
- T& r (*new (&data_pad) T (forward<R> (d)));
- data_dtor = [] (void* p) {static_cast<T*> (p)->~T ();};
- return r;
+ 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).
+ //
+ // If keep is true, then keep the recipe as data after execution. In
+ // particular, this can be used to communicate between inner/outer rules
+ // (see cc::install_rule for an example).
+ //
+ //
template <typename T>
- T&
- data () const {return *reinterpret_cast<T*> (&data_pad);}
+ typename std::enable_if<data_invocable<T>::value, void>::type
+ data (action a, T&& d, bool keep = false) const
+ {
+ const opstate& s (state[a]);
+ s.recipe = forward<T> (d);
+ s.recipe_keep = keep;
+ }
void
- clear_data () const
+ keep_data (action a, bool keep = true) const
{
- if (data_dtor != nullptr)
- {
- data_dtor (&data_pad);
- data_dtor = nullptr;
- }
+ state[a].recipe_keep = keep;
+ }
+
+ template <typename T>
+ typename std::enable_if<data_invocable<T>::value, T&>::type
+ data (action a) const
+ {
+ return *state[a].recipe.target<T> ();
+ }
+
+ template <typename T>
+ typename std::enable_if<data_invocable<T>::value, T*>::type
+ try_data (action a) const
+ {
+ auto& r = state[a].recipe;
+ return r ? r.target<T> () : nullptr;
}
// Target type info and casting.
//
public:
const target*
- is_a (const target_type& tt) const {
- return type ().is_a (tt) ? this : nullptr;}
+ is_a (const target_type& tt) const
+ {
+ return type ().is_a (tt) ? this : nullptr;
+ }
template <typename T>
T*
- is_a () {return dynamic_cast<T*> (this);}
+ is_a ()
+ {
+ // At least with GCC we see slightly better and more consistent
+ // performance with our own type information.
+ //
+#if 0
+ return dynamic_cast<T*> (this);
+#else
+ // We can skip dynamically-derived type here (derived_type).
+ //
+ return dynamic_type->is_a<T> () ? static_cast<T*> (this) : nullptr;
+#endif
+ }
template <typename T>
const T*
- is_a () const {return dynamic_cast<const T*> (this);}
+ is_a () const
+ {
+#if 0
+ return dynamic_cast<const T*> (this);
+#else
+ return dynamic_type->is_a<T> () ? static_cast<const T*> (this) : nullptr;
+#endif
+ }
const target*
- is_a (const char* n) const {
- return type ().is_a (n) ? this : nullptr;}
+ is_a (const char* n) const
+ {
+ return type ().is_a (n) ? this : nullptr;
+ }
// Unchecked cast.
//
@@ -776,18 +1331,23 @@ namespace build2
const T&
as () const {return static_cast<const T&> (*this);}
- // Dynamic derivation to support define.
+ // Target type information.
+ //
+ // A derived target is expected to set dynamic_type to its static_type in
+ // its constructor body.
+ //
+ // We also have dynamic "derivation" support (e.g., via define in
+ // buildfile).
//
- const target_type* derived_type = nullptr;
-
const target_type&
type () const
{
- return derived_type != nullptr ? *derived_type : dynamic_type ();
+ return derived_type != nullptr ? *derived_type : *dynamic_type;
}
static const target_type static_type;
- virtual const target_type& dynamic_type () const = 0;
+ const target_type* dynamic_type;
+ const target_type* derived_type = nullptr;
// RW access.
//
@@ -816,13 +1376,19 @@ namespace build2
// Targets should be created via the targets set below.
//
- public:
+ protected:
+ friend class target_set;
+
target (context& c, dir_path d, dir_path o, string n)
: ctx (c),
dir (move (d)), out (move (o)), name (move (n)),
- vars (c, false /* global */),
- state (c) {}
+ vars (*this, false /* shared */),
+ state (c)
+ {
+ dynamic_type = &static_type;
+ }
+ public:
target (target&&) = delete;
target& operator= (target&&) = delete;
@@ -831,8 +1397,6 @@ namespace build2
virtual
~target ();
-
- friend class target_set;
};
// All targets are from the targets set below.
@@ -866,20 +1430,18 @@ namespace build2
uint8_t
unmark (const target*&);
- // Helper for dealing with the prerequisite inclusion/exclusion (the
- // 'include' buildfile variable, see var_include in context.hxx).
+ // Helper for dealing with the prerequisite inclusion/exclusion (see
+ // var_include in context.hxx).
//
- // Note that the include(prerequisite_member) overload is also provided.
+ // If the lookup argument is not NULL, then it will be set to the operation-
+ // specific override, if present. Note that in this case the caller is
+ // expected to validate that the override value is valid (note: use the same
+ // diagnostics as in include() for consistency).
//
- // @@ Maybe this filtering should be incorporated into *_prerequisites() and
- // *_prerequisite_members() logic? Could make normal > adhoc > excluded and
- // then pass the "threshold".
+ // Note that the include(prerequisite_member) overload is also provided.
//
include_type
- include (action,
- const target&,
- const prerequisite&,
- const target* = nullptr);
+ include (action, const target&, const prerequisite&, lookup* = nullptr);
// A "range" that presents the prerequisites of a group and one of
// its members as one continuous sequence, or, in other words, as
@@ -1062,7 +1624,8 @@ namespace build2
return member != nullptr ? member : prerequisite.target.load (mo);
}
- // Return as a new prerequisite instance.
+ // Return as a new prerequisite instance. Note that it includes a copy
+ // of prerequisite-specific variables.
//
prerequisite_type
as_prerequisite () const;
@@ -1092,11 +1655,8 @@ namespace build2
return os << pm.key ();
}
- inline include_type
- include (action a, const target& t, const prerequisite_member& pm)
- {
- return include (a, t, pm.prerequisite, pm.member);
- }
+ include_type
+ 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
@@ -1129,10 +1689,19 @@ namespace build2
// See-through group members iteration mode. Ad hoc members must always
// be entered explicitly.
//
+ // Note that if the group is empty, then we see the group itself (rather
+ // than nothing). Failed that, an empty group would never be executed (e.g.,
+ // during clean) since there is no member to trigger the group execution.
+ // Other than that, it feels like seeing the group in this cases should be
+ // harmless (i.e., rules are generally prepared to see prerequisites they
+ // don't recognize).
+ //
enum class members_mode
{
- always, // Iterate over members, assert if not resolvable.
- maybe, // Iterate over members if resolvable, group otherwise.
+ always, // Iterate over members if not empty, group if empty, assert if
+ // not resolvable.
+ maybe, // Iterate over members if resolvable and not empty, group
+ // otherwise.
never // Iterate over group (can still use enter_group()).
};
@@ -1170,7 +1739,7 @@ namespace build2
{
if (r_->mode_ != members_mode::never &&
i_ != r_->e_ &&
- i_->type.see_through)
+ i_->type.see_through ())
switch_mode ();
}
@@ -1185,9 +1754,10 @@ namespace build2
leave_group ();
// Iterate over this group's members. Return false if the member
- // information is not available. Similar to leave_group(), you should
- // increment the iterator after calling this function (provided it
- // returned true).
+ // information is not available (note: return true if the group is
+ // empty). Similar to leave_group(), you should increment the iterator
+ // after calling this function provided group() returns true (see
+ // below).
//
bool
enter_group ();
@@ -1197,7 +1767,7 @@ namespace build2
//
// for (...; ++i)
// {
- // if (i->prerequisite.type.see_through)
+ // if (i->prerequisite.type.see_through ())
// {
// for (i.enter_group (); i.group (); )
// {
@@ -1262,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
@@ -1380,7 +1949,7 @@ namespace build2
const dir_path& out,
const string& name) const
{
- slock l (mutex_);
+ slock l (mutex_, defer_lock); if (ctx.phase != run_phase::load) l.lock ();
auto i (map_.find (target_key {&type, &dir, &out, &name, nullopt}));
return i != map_.end () ? i->second.get () : nullptr;
}
@@ -1394,7 +1963,17 @@ namespace build2
// If the target was inserted, keep the map exclusive-locked and return
// the lock. In this case, the target is effectively still being created
- // since nobody can see it until the lock is released.
+ // since nobody can see it until the lock is released. Note that there
+ // is normally quite a bit of contention around this map so make sure to
+ // not hold the lock longer than absolutely necessary.
+ //
+ // If skip_find is true, then don't first try to find an existing target
+ // with a shared lock, instead going directly for the unique lock and
+ // insert. It's a good idea to pass true as this argument if you know the
+ // target is unlikely to be there.
+ //
+ // If need_lock is false, then release the lock (the target insertion is
+ // indicated by the presence of the associated mutex).
//
pair<target&, ulock>
insert_locked (const target_type&,
@@ -1403,8 +1982,13 @@ namespace build2
string name,
optional<string> ext,
target_decl,
- tracer&);
+ tracer&,
+ bool skip_find = false,
+ bool need_lock = true);
+ // As above but instead of the lock return an indication of whether the
+ // target was inserted.
+ //
pair<target&, bool>
insert (const target_type& tt,
dir_path dir,
@@ -1412,7 +1996,8 @@ namespace build2
string name,
optional<string> ext,
target_decl decl,
- tracer& t)
+ tracer& t,
+ bool skip_find = false)
{
auto p (insert_locked (tt,
move (dir),
@@ -1420,9 +2005,11 @@ namespace build2
move (name),
move (ext),
decl,
- t));
+ t,
+ skip_find,
+ false));
- return pair<target&, bool> (p.first, p.second.owns_lock ()); // Clang 3.7
+ return pair<target&, bool> (p.first, p.second.mutex () != nullptr);
}
// Note that the following versions always enter implied targets.
@@ -1434,7 +2021,8 @@ namespace build2
dir_path out,
string name,
optional<string> ext,
- tracer& t)
+ tracer& t,
+ bool skip_find = false)
{
return insert (tt,
move (dir),
@@ -1442,7 +2030,8 @@ namespace build2
move (name),
move (ext),
target_decl::implied,
- t).first.template as<T> ();
+ t,
+ skip_find).first.template as<T> ();
}
template <typename T>
@@ -1451,9 +2040,10 @@ namespace build2
const dir_path& out,
const string& name,
const optional<string>& ext,
- tracer& t)
+ tracer& t,
+ bool skip_find = false)
{
- return insert<T> (T::static_type, dir, out, name, ext, t);
+ return insert<T> (T::static_type, dir, out, name, ext, t, skip_find);
}
template <typename T>
@@ -1461,18 +2051,26 @@ namespace build2
insert (const dir_path& dir,
const dir_path& out,
const string& name,
- tracer& t)
+ tracer& t,
+ bool skip_find = false)
{
- return insert<T> (dir, out, name, nullopt, t);
+ return insert<T> (dir, out, name, nullopt, t, skip_find);
}
// Note: not MT-safe so can only be used during serial execution.
//
public:
- using iterator = butl::map_iterator_adapter<map_type::const_iterator>;
+ using iterator = butl::map_iterator_adapter<map_type::iterator>;
+ using const_iterator = butl::map_iterator_adapter<map_type::const_iterator>;
- iterator begin () const {return map_.begin ();}
- iterator end () const {return map_.end ();}
+ iterator begin () {return map_.begin ();}
+ iterator end () {return map_.end ();}
+
+ const_iterator begin () const {return map_.begin ();}
+ const_iterator end () const {return map_.end ();}
+
+ size_t
+ size () const {return map_.size ();}
void
clear () {map_.clear ();}
@@ -1488,6 +2086,10 @@ namespace build2
mutable shared_mutex mutex_;
map_type map_;
+
+#if 0
+ size_t buckets_ = 0;
+#endif
};
// Modification time-based target.
@@ -1495,9 +2097,13 @@ namespace build2
class LIBBUILD2_SYMEXPORT mtime_target: public target
{
public:
- using target::target;
+ mtime_target (context& c, dir_path d, dir_path o, string n)
+ : target (c, move (d), move (o), move (n))
+ {
+ 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
@@ -1530,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;
@@ -1582,11 +2187,17 @@ namespace build2
class LIBBUILD2_SYMEXPORT path_target: public mtime_target
{
public:
- using mtime_target::mtime_target;
+ path_target (context& c, dir_path d, dir_path o, string n)
+ : mtime_target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
using path_type = build2::path;
- // Target path is an "atomic consistent cash". That is, it can be set at
+ // Target path. Must be absolute and normalized.
+ //
+ // 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.
@@ -1615,7 +2226,7 @@ namespace build2
// the path_mtime() function to do it in the correct order.
//
const path_type&
- path () const;
+ path (memory_order = memory_order_acquire) const;
const path_type&
path (path_type) const;
@@ -1729,11 +2340,62 @@ namespace build2
class LIBBUILD2_SYMEXPORT file: public path_target
{
public:
- using path_target::path_target;
+ file (context& c, dir_path d, dir_path o, string n)
+ : path_target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
+
+ public:
+ 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;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Alias target. It represents a list of targets (its prerequisites)
@@ -1742,11 +2404,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT alias: public target
{
public:
- using target::target;
+ alias (context& c, dir_path d, dir_path o, string n)
+ : target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Directory target. Note that this is not a filesystem directory
@@ -1756,11 +2421,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT dir: public alias
{
public:
- using alias::alias;
+ dir (context& c, dir_path d, dir_path o, string n)
+ : alias (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
public:
template <typename K>
@@ -1789,11 +2457,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT fsdir: public target
{
public:
- using target::target;
+ fsdir (context& c, dir_path d, dir_path o, string n)
+ : target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Executable file (not necessarily binary, though we do fallback to the
@@ -1803,7 +2474,11 @@ namespace build2
class LIBBUILD2_SYMEXPORT exe: public file
{
public:
- using file::file;
+ exe (context& c, dir_path d, dir_path o, string n)
+ : file (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
using process_path_type = build2::process_path;
@@ -1831,7 +2506,6 @@ namespace build2
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
private:
process_path_type process_path_;
@@ -1840,11 +2514,30 @@ namespace build2
class LIBBUILD2_SYMEXPORT buildfile: public file
{
public:
- using file::file;
+ buildfile (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;
+ };
+
+ // 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;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Common documentation file target.
@@ -1852,11 +2545,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT doc: public file
{
public:
- using file::file;
+ doc (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;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Legal files (LICENSE, AUTHORS, COPYRIGHT, etc).
@@ -1864,11 +2560,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT legal: public doc
{
public:
- using doc::doc;
+ legal (context& c, dir_path d, dir_path o, string n)
+ : doc (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// The problem with man pages is this: different platforms have
@@ -1903,26 +2602,32 @@ 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
{
public:
- using doc::doc;
+ man (context& c, dir_path d, dir_path o, string n)
+ : doc (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
class LIBBUILD2_SYMEXPORT man1: public man
{
public:
- using man::man;
+ man1 (context& c, dir_path d, dir_path o, string n)
+ : man (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// We derive manifest from doc rather than file so that it get automatically
@@ -1933,11 +2638,14 @@ namespace build2
class LIBBUILD2_SYMEXPORT manifest: public doc
{
public:
- using doc::doc;
+ manifest (context& c, dir_path d, dir_path o, string n)
+ : doc (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
- virtual const target_type& dynamic_type () const {return static_type;}
};
// Common implementation of the target factory, extension, and search
@@ -1982,32 +2690,39 @@ 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
// printing it (e.g., man1{}).
//
- LIBBUILD2_SYMEXPORT void
- target_print_0_ext_verb (ostream&, const target_key&);
+ LIBBUILD2_SYMEXPORT bool
+ target_print_0_ext_verb (ostream&, const target_key&, bool);
// Target type uses the extension and there is normally no default so it
// should be printed (e.g., file{}).
//
- LIBBUILD2_SYMEXPORT void
- target_print_1_ext_verb (ostream&, const target_key&);
+ 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>