diff options
Diffstat (limited to 'libbuild2/target.hxx')
-rw-r--r-- | libbuild2/target.hxx | 434 |
1 files changed, 362 insertions, 72 deletions
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index f745f59..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 @@ -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_; @@ -85,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;} @@ -107,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; @@ -166,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. // @@ -186,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, @@ -233,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. // @@ -268,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()). @@ -366,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 @@ -401,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). @@ -431,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; @@ -458,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; @@ -560,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; @@ -619,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 @@ -632,22 +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. 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. // @@ -657,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. // @@ -710,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 @@ -725,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; @@ -749,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 @@ -781,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, @@ -816,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; @@ -834,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 @@ -864,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. @@ -880,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; @@ -1015,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). // @@ -1046,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. @@ -1163,7 +1382,7 @@ namespace build2 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 */), + vars (*this, false /* shared */), state (c) { dynamic_type = &static_type; @@ -1437,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 @@ -1615,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 @@ -1870,6 +2086,10 @@ namespace build2 mutable shared_mutex mutex_; map_type map_; + +#if 0 + size_t buckets_ = 0; +#endif }; // Modification time-based target. @@ -1883,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 @@ -1916,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; @@ -1978,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. @@ -2131,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". // @@ -2257,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 @@ -2319,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 { @@ -2407,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> |