diff options
Diffstat (limited to 'libbuild2/target.hxx')
-rw-r--r-- | libbuild2/target.hxx | 1095 |
1 files changed, 895 insertions, 200 deletions
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 4ce871b..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_; @@ -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). @@ -266,6 +548,10 @@ namespace build2 // - Ad hoc group cannot have sub-groups (of any kind) though an ad hoc // 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). 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 // which can be an ad hoc group. For example, lib{} contains libs{} which @@ -274,6 +560,20 @@ 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 @@ -299,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; @@ -332,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 @@ -340,7 +650,10 @@ 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. @@ -366,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 @@ -379,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; @@ -438,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 @@ -451,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. // @@ -476,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. // @@ -516,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 @@ -531,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 @@ -556,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; @@ -581,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, @@ -616,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; @@ -632,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 @@ -644,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; @@ -664,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; @@ -680,110 +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); - clear_data (); - 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); - clear_data (); - 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. // @@ -795,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. // @@ -835,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; @@ -850,8 +1397,6 @@ namespace build2 virtual ~target (); - - friend class target_set; }; // All targets are from the targets set below. @@ -888,13 +1433,15 @@ namespace build2 // Helper for dealing with the prerequisite inclusion/exclusion (see // var_include in context.hxx). // + // 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). + // // 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 @@ -1077,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; @@ -1107,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 @@ -1144,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()). }; @@ -1185,7 +1739,7 @@ namespace build2 { if (r_->mode_ != members_mode::never && i_ != r_->e_ && - i_->type.see_through) + i_->type.see_through ()) switch_mode (); } @@ -1200,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 (); @@ -1212,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 (); ) // { @@ -1277,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 @@ -1395,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; } @@ -1409,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&, @@ -1418,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, @@ -1427,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), @@ -1435,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. @@ -1449,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), @@ -1457,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> @@ -1466,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> @@ -1476,18 +2051,23 @@ 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 () {return map_.begin ();} + iterator end () {return map_.end ();} - iterator begin () const {return map_.begin ();} - iterator end () const {return map_.end ();} + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} size_t size () const {return map_.size ();} @@ -1506,6 +2086,10 @@ namespace build2 mutable shared_mutex mutex_; map_type map_; + +#if 0 + size_t buckets_ = 0; +#endif }; // Modification time-based target. @@ -1513,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 @@ -1548,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; @@ -1600,13 +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. 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. @@ -1749,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) @@ -1762,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 @@ -1776,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> @@ -1809,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 @@ -1823,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; @@ -1851,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_; @@ -1860,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. @@ -1872,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). @@ -1884,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 @@ -1923,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 @@ -1953,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 @@ -2002,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> |