diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2019-06-24 12:01:19 +0200 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2019-07-01 18:13:55 +0300 |
commit | 977d07a3ae47ef204665d1eda2d642e5064724f3 (patch) | |
tree | 525a3d6421f61ce789b690191d3c30fc09be3517 /libbuild2/target.hxx | |
parent | 7161b24963dd9da4d218f92c736b77c35c328a2d (diff) |
Split build system into library and driver
Diffstat (limited to 'libbuild2/target.hxx')
-rw-r--r-- | libbuild2/target.hxx | 1817 |
1 files changed, 1817 insertions, 0 deletions
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx new file mode 100644 index 0000000..cfbd9bc --- /dev/null +++ b/libbuild2/target.hxx @@ -0,0 +1,1817 @@ +// file : libbuild2/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_TARGET_HXX +#define LIBBUILD2_TARGET_HXX + +#include <iterator> // tags, etc. +#include <type_traits> // aligned_storage +#include <unordered_map> + +#include <libbutl/multi-index.mxx> // map_iterator_adapter + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/scope.hxx> +#include <libbuild2/action.hxx> +#include <libbuild2/variable.hxx> +#include <libbuild2/target-key.hxx> +#include <libbuild2/target-type.hxx> +#include <libbuild2/target-state.hxx> +#include <libbuild2/prerequisite.hxx> + +#include <libbuild2/export.hxx> + +namespace build2 +{ + class rule; + class scope; + class target; + + // From <libbuild2/context.hxx>. + // + LIBBUILD2_SYMEXPORT extern size_t current_on; + + // From <libbuild2/algorithm.hxx>. + // + const target& search (const target&, const prerequisite&); + const target* search_existing (const prerequisite&); + + // Recipe. + // + // The returned target state is normally changed or unchanged. If there is + // an error, then the recipe should throw failed rather than returning (this + // is the only exception that a recipe can throw). + // + // The return value of the recipe is used to update the target state. If it + // is target_state::group then the target's state is the group's state. + // + // The recipe may also return postponed in which case the target state is + // assumed to be unchanged (normally this means a prerequisite was postponed + // and while the prerequisite will be re-examined via another dependency, + // this target is done). + // + // Note that max size for the "small capture optimization" in std::function + // ranges (in pointer sizes) from 0 (GCC prior to 5) to 2 (GCC 5) to 6 (VC + // 14.2). With the size ranging (in bytes for 64-bit target) from 32 (GCC) + // to 64 (VC). + // + using recipe_function = target_state (action, const target&); + using recipe = function<recipe_function>; + + // Commonly-used recipes. The default recipe executes the action on + // all the prerequisites in a loop, skipping ignored. Specifically, + // for actions with the "first" execution mode, it calls + // execute_prerequisites() while for those with the "last" mode -- + // reverse_execute_prerequisites(); see <libbuild2/operation.hxx>, + // <libbuild2/algorithm.hxx> for details. The group recipe call's the + // group's recipe. + // + LIBBUILD2_SYMEXPORT extern const recipe empty_recipe; + LIBBUILD2_SYMEXPORT extern const recipe noop_recipe; + LIBBUILD2_SYMEXPORT extern const recipe default_recipe; + LIBBUILD2_SYMEXPORT extern const recipe group_recipe; + + // Defined in <libbuild2/algorithm.hxx>. + // + LIBBUILD2_SYMEXPORT target_state + noop_action (action, const target&); + + // Defined in <libbuild2/algorithm.hxx>. + // + LIBBUILD2_SYMEXPORT target_state + group_action (action, const target&); + + // A view of target group members. + // + struct group_view + { + const target* const* members; // NULL means not yet known. + size_t count; + }; + + // List of prerequisites resolved to targets. Unless additional storage is + // needed, it can be used as just vector<const target*> (which is what we + // used to have initially). + // + 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) {} + + prerequisite_target (const target_type* t, include_type a, uintptr_t d = 0) + : prerequisite_target (t, a == include_type::adhoc, d) {} + + 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; + }; + using prerequisite_targets = vector<prerequisite_target>; + + // A rule match is an element of hint_rule_map. + // + using rule_match = pair<const string, reference_wrapper<const rule>>; + + // Target. + // + class LIBBUILD2_SYMEXPORT target + { + optional<string>* ext_; // Reference to value in target_key. + + public: + // For targets that are in the src tree of a project we also keep the + // corresponding out directory. As a result we may end up with multiple + // targets for the same file if we are building multiple configurations of + // the same project at once. We do it this way because, in a sense, a + // target's out directory is its "configuration" (in terms of variables). + // As an example, consider installing the same README file (src) but for + // two different project configurations at once. Which installation + // directory should we use? The answer depends on which configuration you + // ask. + // + // Empty out directory indicates this target is in the out tree (including + // when src == out). We also treat out of project targets as being in the + // out tree. + // + const dir_path dir; // Absolute and normalized. + const dir_path out; // Empty or absolute and normalized. + const string name; + + const string* ext () const; // Return NULL if not specified. + const string& ext (string); + + const dir_path& + out_dir () const {return out.empty () ? dir : out;} + + // A target that is not (yet) entered as part of a real dependency + // declaration (for example, that is entered as part of a target-specific + // variable assignment, dependency extraction, etc) is called implied. + // + // The implied flag should only be cleared during the load phase via the + // MT-safe target_set::insert(). + // + bool implied; + + // Target group to which this target belongs, if any. Note that we assume + // that the group and all its members are in the same scope (for example, + // in variable lookup). We also don't support nested groups (with an + // exception for ad hoc groups; see below). + // + // The semantics of the interaction between the group and its members and + // what it means to, say, update the group, is unspecified and is + // determined by the group's type. In particular, a group can be created + // out of member types that have no idea they are part of this group + // (e.g., cli.cxx{}). + // + // Normally, however, there are two kinds of groups: "all" and "choice". + // In a choice-group, normally one of the members is selected when the + // group is mentioned as a prerequisite with, perhaps, an exception for + // special rules, like aliases, where it makes more sense to treat such + // group prerequisites as a whole. In this case we say that the rule + // "semantically recognizes" the group and picks some of its members. + // + // Updating a choice-group as a whole can mean updating some subset of its + // members (e.g., lib{}). Or the group may not support this at all (e.g., + // 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 + // 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. + // + // Which brings us to the group iteration mode. The target type contains a + // member 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. + // + // In an all-group we usually want the state (and timestamp; see mtime()) + // for members to come from the group. This is achieved with the special + // target_state::group state. You would normally also use the group_recipe + // for group members. + // + // 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. + // + const target* group = nullptr; + + // What has been described above is a "explicit" group. That is, there is + // a dedicated target type that explicitly serves as a group and there is + // an explicit mechanism for discovering the group's members. + // + // However, sometimes, we may want to create a group on the fly out of a + // normal target type. For example, we have the libs{} target type. But + // on Windows a shared library consist of (at least) two files: the import + // library and the DLL itself. So we somehow need to be able to capture + // that. One approach would be to imply the presence of the second file. + // However, that means that a lot of generic rules (e.g., clean, install, + // etc) will need to know about this special semantics on Windows. Also, + // there would be no convenient way to customize things like extensions, + // etc (for which we use target-specific variables). In other words, it + // would be much easier and more consistent to make these extra files + // proper targets. + // + // So to support this requirement we have "ad hoc" groups. The idea is + // that any target can be turned either by a user's declaration in a + // buildfile or by the rule that matches it into an ad hoc group by + // chaining several targets together. + // + // Ad hoc groups have a more restricted semantics compared to the normal + // groups. In particular: + // + // - The ad hoc group itself is in a sense its first/primary target. + // + // - Group member's recipes, if set, should be group_recipe. Normally, a + // rule-managed member isn't matched by the rule since all that's + // 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. + // + // - Only declared members can be used as prerequisites but all can be + // used as targets (e.g., to set variables, etc). + // + // - Members don't have prerequisites. + // + // - 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). + // + // 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 + // may have an import stub as its ad hoc member. + // + // Use add_adhoc_member(), find_adhoc_member() from algorithms to manage + // ad hoc members. + // + const_ptr<target> member = nullptr; + + bool + adhoc_group () const + { + // An ad hoc group can be a member of a normal group. + // + return member != nullptr && + (group == nullptr || group->member == nullptr); + } + + bool + adhoc_member () const + { + return group != nullptr && group->member != nullptr; + } + + public: + // Normally you should not call this function directly and rather use + // resolve_members() from algorithm.hxx. + // + virtual group_view + group_members (action) const; + + // Note that the returned key "tracks" the target (except for the + // extension). + // + target_key + key () const; + + // Scoping. + // + public: + // Most qualified scope that contains this target. + // + const scope& + base_scope () const; + + // Root scope of a project that contains this target. Note that + // a target can be out of any (known) project root in which case + // this function asserts. If you need to detect this situation, + // then use base_scope().root_scope() expression instead. + // + const scope& + root_scope () const; + + // Root scope of a strong amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + const scope& + strong_scope () const {return *root_scope ().strong_scope ();} + + // Root scope of the outermost amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + const scope& + weak_scope () const {return *root_scope ().weak_scope ();} + + bool + in (const scope& s) const + { + return out_dir ().sub (s.out_path ()); + } + + // Prerequisites. + // + // We use an atomic-empty semantics that allows one to "swap in" a set of + // prerequisites if none were specified. This is used to implement + // "synthesized" dependencies. + // + public: + using prerequisites_type = build2::prerequisites; + + const prerequisites_type& + 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. + // + bool + prerequisites (prerequisites_type&&) const; + + // Check if there are any prerequisites. Note that the group version may + // be racy (see target::group). + // + bool + has_prerequisites () const; + + bool + has_group_prerequisites () const; + + private: + friend class parser; + + // Note that the state is also used to synchronize the prerequisites + // value so we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + atomic<uint8_t> prerequisites_state_ {0}; + prerequisites_type prerequisites_; + + static const prerequisites_type empty_prerequisites_; + + // Target-specific variables. + // + // See also rule-specific variables below. + // + public: + variable_map vars; + + // Lookup, including in groups to which this target belongs and then in + // outer scopes (including target type/pattern-specific variables). If you + // only want to lookup in this target, do it on the variable map directly + // (and note that there will be no overrides). + // + lookup + operator[] (const variable& var) const + { + return find (var).first; + } + + lookup + operator[] (const variable* var) const // For cached variables. + { + assert (var != nullptr); + return operator[] (*var); + } + + lookup + operator[] (const string& name) const + { + const variable* var (var_pool.find (name)); + return var != nullptr ? operator[] (*var) : lookup (); + } + + // 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 target will have depth 1. That from the group -- 2. + // From the innermost scope's target type/patter-specific variables -- + // 3. From the innermost scope's variables -- 4. And so on. The idea is + // that given two lookups from the same target, we can say which one came + // earlier. If no value is found, then the depth is set to ~0. + // + pair<lookup, size_t> + find (const variable& var) const + { + auto p (find_original (var)); + return var.overrides == nullptr + ? p + : base_scope ().find_override (var, move (p), true); + } + + // If target_only is true, then only look in target and its target group + // without continuing in scopes. + // + pair<lookup, size_t> + find_original (const variable&, bool target_only = false) const; + + // Return a value suitable for assignment. See scope for details. + // + value& + assign (const variable& var) {return vars.assign (var);} + + value& + assign (const variable* var) {return vars.assign (var);} // For cached. + + // Return a value suitable for appending. See scope for details. + // + value& + append (const variable&); + + // Target operation state. + // + public: + // Atomic task count that is used during match and execution to track the + // target's "meta-state" as well as the number of its sub-tasks (e.g., + // busy+1, busy+2, and so on, for instance, number of prerequisites + // being matched or executed). + // + // For each operation in a meta-operation batch (current_on) we have a + // "band" of counts, [touched, executed], that represent the target + // meta-state. Once the next operation is started, this band "moves" thus + // automatically resetting the target to "not yet touched" state for this + // operation. + // + // The target is said to be synchronized (in this thread) if we have + // either observed the task count to reach applied or executed or we have + // successfully changed it (via compare_exchange) to locked or busy. If + // the target is synchronized, then we can access and modify (second case) + // its state etc. + // + static const size_t offset_touched = 1; // Target has been locked. + static const size_t offset_tried = 2; // Rule match has been tried. + static const size_t offset_matched = 3; // Rule has been matched. + static const size_t offset_applied = 4; // Rule has been applied. + static const size_t offset_executed = 5; // Recipe has been executed. + static const size_t offset_busy = 6; // Match/execute in progress. + + static size_t count_base () {return 5 * (current_on - 1);} + + static size_t count_touched () {return offset_touched + count_base ();} + static size_t count_tried () {return offset_tried + count_base ();} + static size_t count_matched () {return offset_matched + count_base ();} + static size_t count_applied () {return offset_applied + count_base ();} + static size_t count_executed () {return offset_executed + count_base ();} + static size_t count_busy () {return offset_busy + count_base ();} + + // Inner/outer operation state. See operation.hxx for details. + // + class LIBBUILD2_SYMEXPORT opstate + { + public: + mutable atomic_count task_count {0}; // Start offset_touched - 1. + + // Number of direct targets that depend on this target in the current + // operation. It is incremented during match and then decremented during + // execution, before running the recipe. As a result, the recipe can + // detect the last chance (i.e., last dependent) to execute the command + // (see also the first/last execution modes in <operation.hxx>). + // + mutable atomic_count dependents {0}; + + // Matched rule (pointer to hint_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; + + // Target state for this operation. Note that it is undetermined until + // a rule is matched and recipe applied (see set_recipe()). + // + target_state state; + + // Rule-specific variables. + // + // The rule (for this action) has to be matched before these variables + // can be accessed and only the rule being matched can modify them (so + // 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. + // + variable_map vars; + + // Lookup, continuing in the target-specific variables, etc. Note that + // the group's rule-specific variables are not included. If you only + // want to lookup in this target, do it on the variable map directly + // (and note that there will be no overrides). + // + lookup + operator[] (const variable& var) const + { + return find (var).first; + } + + lookup + operator[] (const variable* var) const // For cached variables. + { + assert (var != nullptr); + return operator[] (*var); + } + + lookup + operator[] (const string& name) const + { + const variable* var (var_pool.find (name)); + return var != nullptr ? operator[] (*var) : lookup (); + } + + // 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, + // and so on, similar to target-specific variables. + // + pair<lookup, size_t> + find (const variable& var) const + { + auto p (find_original (var)); + return var.overrides == nullptr + ? p + : target_->base_scope ().find_override (var, move (p), true, true); + } + + // If target_only is true, then only look in target and its target group + // without continuing in scopes. + // + pair<lookup, size_t> + find_original (const variable&, bool target_only = false) const; + + // Return a value suitable for assignment. See target for details. + // + value& + assign (const variable& var) {return vars.assign (var);} + + value& + assign (const variable* var) {return vars.assign (var);} // For cached. + + public: + opstate (): vars (false /* global */) {} + + private: + friend class target_set; + + const target* target_ = nullptr; // Back-pointer, set by target_set. + }; + + action_state<opstate> state; + + opstate& operator[] (action a) {return state[a];} + const opstate& operator[] (action a) const {return state[a];} + + // This function should only be called during match if we have observed + // (synchronization-wise) that this target has been matched (i.e., the + // rule has been applied) for this action. + // + target_state + matched_state (action, bool fail = true) const; + + // See try_match(). + // + pair<bool, target_state> + try_matched_state (action, bool fail = true) const; + + // After the target has been matched and synchronized, check if the target + // is known to be unchanged. Used for optimizations during search & match. + // + bool + unchanged (action) const; + + // This function should only be called during execution if we have + // observed (synchronization-wise) that this target has been executed. + // + target_state + executed_state (action, bool fail = true) 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()). + // + pair<bool, target_state> + matched_state_impl (action) const; + + // Return fail-untranslated (but group-translated) state assuming the + // target is executed and synchronized. + // + 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. + // + mutable action_state<build2::prerequisite_targets> prerequisite_targets; + + // Auxilary 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. + // + // Note that the recipe may modify the data. Currently reserved for the + // inner part of the action. + // + static constexpr size_t data_size = sizeof (string) * 16; + mutable std::aligned_storage<data_size>::type data_pad; + + mutable 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) const + { + assert (sizeof (T) <= data_size && data_dtor == nullptr); + return *new (&data_pad) 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) 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; + } + + template <typename T> + T& + data () const {return *reinterpret_cast<T*> (&data_pad);} + + void + clear_data () const + { + if (data_dtor != nullptr) + { + data_dtor (&data_pad); + data_dtor = nullptr; + } + } + + // Target type info and casting. + // + public: + const target* + 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);} + + template <typename T> + const T* + is_a () const {return dynamic_cast<const T*> (this);} + + // Unchecked cast. + // + template <typename T> + T& + as () {return static_cast<T&> (*this);} + + template <typename T> + const T& + as () const {return static_cast<const T&> (*this);} + + // Dynamic derivation to support define. + // + const target_type* derived_type = nullptr; + + const target_type& + type () const + { + return derived_type != nullptr ? *derived_type : dynamic_type (); + } + + virtual const target_type& dynamic_type () const = 0; + static const target_type static_type; + + public: + // Split the name leaf into target name (in place) and extension + // (returned). + // + static optional<string> + split_name (string&, const location&); + + // Combine the target name and extension into the name leaf. + // + // If the target type has the default extension, then "escape" the + // existing extension if any. + // + static void + combine_name (string&, const optional<string>&, bool default_extension); + + // Targets should be created via the targets set below. + // + public: + target (dir_path d, dir_path o, string n) + : dir (move (d)), out (move (o)), name (move (n)), + vars (false /* global */) {} + + target (target&&) = delete; + target& operator= (target&&) = delete; + + target (const target&) = delete; + target& operator= (const target&) = delete; + + virtual + ~target (); + + friend class target_set; + }; + + // All targets are from the targets set below. + // + inline bool + operator== (const target& x, const target& y) {return &x == &y;} + + inline bool + operator!= (const target& x, const target& y) {return !(x == y);} + + ostream& + operator<< (ostream&, const target&); + + // Sometimes it is handy to "mark" a pointer to a target (for example, in + // prerequisite_targets). We use the last 2 bits in a pointer for that (aka + // the "bit stealing" technique). Note that the pointer needs to be unmarked + // before it can be usable so care must be taken in the face of exceptions, + // etc. + // + void + mark (const target*&, uint8_t = 1); + + uint8_t + marked (const target*); // Can be used as a predicate or to get the mark. + + uint8_t + unmark (const target*&); + + // A "range" that presents the prerequisites of a group and one of + // its members as one continuous sequence, or, in other words, as + // if they were in a single container. The group's prerequisites + // come first followed by the member's. If you need to see them + // in the other direction, iterate in reverse, for example: + // + // for (prerequisite& p: group_prerequisites (t)) + // + // for (prerequisite& p: reverse_iterate (group_prerequisites (t)) + // + // Note that in this case the individual elements of each list will + // also be traversed in reverse, but that's what you usually want, + // anyway. + // + // Note that you either should be iterating over a locked target (e.g., in + // rule's match() or apply()) or you should call resolve_group(). + // + class group_prerequisites + { + public: + explicit + group_prerequisites (const target& t); + + group_prerequisites (const target& t, const target* g); + + using prerequisites_type = target::prerequisites_type; + using base_iterator = prerequisites_type::const_iterator; + + struct iterator + { + using value_type = base_iterator::value_type; + using pointer = base_iterator::pointer; + using reference = base_iterator::reference; + using difference_type = base_iterator::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + + iterator () {} + iterator (const target* t, + const target* g, + const prerequisites_type* c, + base_iterator i): t_ (t), g_ (g), c_ (c), i_ (i) {} + + iterator& + operator++ (); + + iterator + operator++ (int) {iterator r (*this); operator++ (); return r;} + + iterator& + operator-- (); + + iterator + operator-- (int) {iterator r (*this); operator-- (); return r;} + + reference operator* () const {return *i_;} + pointer operator-> () const {return i_.operator -> ();} + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.t_ == y.t_ && x.g_ == y.g_ && x.c_ == y.c_ && x.i_ == y.i_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + const target* t_ = nullptr; + const target* g_ = nullptr; + const prerequisites_type* c_ = nullptr; + base_iterator i_; + }; + + using reverse_iterator = std::reverse_iterator<iterator>; + + iterator + begin () const; + + iterator + end () const; + + reverse_iterator + rbegin () const {return reverse_iterator (end ());} + + reverse_iterator + rend () const {return reverse_iterator (begin ());} + + size_t + size () const; + + private: + const target& t_; + const target* g_; + }; + + // A member of a prerequisite. If 'member' is NULL, then this is the + // prerequisite itself. Otherwise, it is its member. In this case + // 'prerequisite' still refers to the prerequisite. + // + struct prerequisite_member + { + using scope_type = build2::scope; + using target_type = build2::target; + using prerequisite_type = build2::prerequisite; + using target_type_type = build2::target_type; + + const prerequisite_type& prerequisite; + const target_type* member; + + template <typename T> + bool + is_a () const + { + return member != nullptr + ? member->is_a<T> () != nullptr + : prerequisite.is_a<T> (); + } + + bool + is_a (const target_type_type& tt) const + { + return member != nullptr + ? member->is_a (tt) != nullptr + : prerequisite.is_a (tt); + } + + prerequisite_key + key () const; + + const target_type_type& + type () const + { + return member != nullptr ? member->type () : prerequisite.type; + } + + const string& + name () const + { + return member != nullptr ? member->name : prerequisite.name; + } + + const dir_path& + dir () const + { + return member != nullptr ? member->dir : prerequisite.dir; + } + + const optional<project_name>& + proj () const + { + // Member cannot be project-qualified. + // + return member != nullptr ? nullopt_project_name : prerequisite.proj; + } + + const scope_type& + scope () const + { + return member != nullptr ? member->base_scope () : prerequisite.scope; + } + + const target_type& + search (const target_type& t) const + { + return member != nullptr ? *member : build2::search (t, prerequisite); + } + + const target_type* + search_existing () const + { + return member != nullptr + ? member + : build2::search_existing (prerequisite); + } + + const target_type* + load (memory_order mo = memory_order_consume) + { + return member != nullptr ? member : prerequisite.target.load (mo); + } + + // Return as a new prerequisite instance. + // + prerequisite_type + as_prerequisite () const; + }; + + // It is often stored as the target's auxiliary data so make sure there is + // no destructor overhead. + // + static_assert (std::is_trivially_destructible<prerequisite_member>::value, + "prerequisite_member is not trivially destructible"); + + inline ostream& + operator<< (ostream& os, const prerequisite_member& pm) + { + 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); + } + + // A "range" that presents a sequence of prerequisites (e.g., from + // group_prerequisites()) as a sequence of prerequisite_member's. For each + // group prerequisite you will "see" either the prerequisite itself or all + // its members, depending on the default iteration mode of the target group + // type (ad hoc groups are never implicitly see through since one can only + // safely access members after a synchronous match). You can skip the + // rest of the group members with leave_group() and you can force iteration + // over the members with enter_group(). Usage: + // + // for (prerequisite_member pm: prerequisite_members (a, ...)) + // + // Where ... can be: + // + // t.prerequisites + // reverse_iterate(t.prerequisites) + // group_prerequisites (t) + // reverse_iterate (group_prerequisites (t)) + // + // But use shortcuts instead: + // + // prerequisite_members (a, t) + // reverse_prerequisite_members (a, t) + // group_prerequisite_members (a, t) + // reverse_group_prerequisite_members (a, t) + // + template <typename R> + class prerequisite_members_range; + + // See-through group members iteration mode. Ad hoc members must always + // be entered explicitly. + // + enum class members_mode + { + always, // Iterate over members, assert if not resolvable. + maybe, // Iterate over members if resolvable, group otherwise. + never // Iterate over group (can still use enter_group()). + }; + + template <typename R> + inline prerequisite_members_range<R> + prerequisite_members (action a, const target& t, + R&& r, + members_mode m = members_mode::always) + { + return prerequisite_members_range<R> (a, t, forward<R> (r), m); + } + + template <typename R> + class prerequisite_members_range + { + public: + prerequisite_members_range (action a, const target& t, + R&& r, + members_mode m) + : a_ (a), t_ (t), mode_ (m), r_ (forward<R> (r)), e_ (r_.end ()) {} + + using base_iterator = decltype (declval<R> ().begin ()); + + struct iterator + { + using value_type = prerequisite_member; + using pointer = const value_type*; + using reference = const value_type&; + using difference_type = typename base_iterator::difference_type; + using iterator_category = std::forward_iterator_tag; + + iterator (): r_ (nullptr) {} + iterator (const prerequisite_members_range* r, const base_iterator& i) + : r_ (r), i_ (i), g_ {nullptr, 0}, k_ (nullptr) + { + if (r_->mode_ != members_mode::never && + i_ != r_->e_ && + i_->type.see_through) + switch_mode (); + } + + iterator& operator++ (); + iterator operator++ (int) {iterator r (*this); operator++ (); return r;} + + // Skip iterating over the rest of this group's members, if any. Note + // that the only valid operation after this call is to increment the + // iterator. + // + void + 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). + // + bool + enter_group (); + + // Return true if the next element is this group's members. Normally + // used to iterate over group members only, for example: + // + // for (...; ++i) + // { + // if (i->prerequisite.type.see_through) + // { + // for (i.enter_group (); i.group (); ) + // { + // ++i; + // ... + // } + // } + // } + // + bool + group () const; + + value_type operator* () const + { + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return value_type {*i_, t}; + } + + pointer operator-> () const + { + static_assert ( + std::is_trivially_destructible<value_type>::value, + "prerequisite_member is not trivially destructible"); + + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return new (&m_) value_type {*i_, t}; + } + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.i_ == y.i_ && + x.g_.count == y.g_.count && + (x.g_.count == 0 || x.j_ == y.j_) && + x.k_ == y.k_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + // What we have here is a state for three nested iteration modes (and + // no, I am not proud of it). The innermost mode is iteration over an ad + // hoc group (k_). Then we have iteration over a normal group (g_ and + // j_). Finally, at the outer level, we have the range itself (i_). + // + // Also, the enter/leave group support is full of ugly, special cases. + // + private: + void + switch_mode (); + + private: + const prerequisite_members_range* r_; + base_iterator i_; + 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_; + }; + + iterator + begin () const {return iterator (this, r_.begin ());} + + iterator + end () const {return iterator (this, e_);} + + private: + action a_; + const target& t_; + members_mode mode_; + R r_; + base_iterator e_; + }; + + // prerequisite_members(t.prerequisites ()) + // + auto + prerequisite_members (action a, const target& t, + members_mode m = members_mode::always); + + // prerequisite_members(reverse_iterate(t.prerequisites ())) + // + auto + reverse_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always); + + // prerequisite_members(group_prerequisites (t)) + // + inline auto + group_prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, group_prerequisites (t), m); + } + + inline auto + group_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, group_prerequisites (t), m); + } + + // prerequisite_members(reverse_iterate (group_prerequisites (t))) + // + inline auto + reverse_group_prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members ( + a, t, reverse_iterate (group_prerequisites (t)), m); + } + + inline auto + reverse_group_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members ( + a, t, reverse_iterate (group_prerequisites (t)), m); + } + + // A target with an unspecified extension is considered equal to the one + // with the specified one. And when we find a target with an unspecified + // extension via a key with the specified one, we update the extension, + // essentially modifying the map's key. To make this work we use a hash + // map. The key's hash ignores the extension, so the hash will stay stable + // across extension updates. + // + // Note also that once the extension is specified, it becomes immutable. + // + class LIBBUILD2_SYMEXPORT target_set + { + public: + using map_type = std::unordered_map<target_key, unique_ptr<target>>; + + // Return existing target or NULL. + // + const target* + find (const target_key& k, tracer& trace) const; + + const target* + find (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const optional<string>& ext, + tracer& trace) const + { + return find (target_key {&type, &dir, &out, &name, ext}, trace); + } + + template <typename T> + const T* + find (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const optional<string>& ext, + tracer& trace) const + { + return static_cast<const T*> (find (type, dir, out, name, ext, trace)); + } + + // As above but ignore the extension. + // + const target* + find (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name) const + { + slock l (mutex_); + auto i (map_.find (target_key {&type, &dir, &out, &name, nullopt})); + return i != map_.end () ? i->second.get () : nullptr; + } + + template <typename T> + const T* + find (const dir_path& dir, const dir_path& out, const string& name) const + { + return static_cast<const T*> (find (T::static_type, dir, out, name)); + } + + // 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. + // + pair<target&, ulock> + insert_locked (const target_type&, + dir_path dir, + dir_path out, + string name, + optional<string> ext, + bool implied, + tracer&); + + pair<target&, bool> + insert (const target_type& tt, + dir_path dir, + dir_path out, + string name, + optional<string> ext, + bool implied, + tracer& t) + { + auto p (insert_locked (tt, + move (dir), + move (out), + move (name), + move (ext), + implied, + t)); + + return pair<target&, bool> (p.first, p.second.owns_lock ()); + } + + // Note that the following versions always enter implied targets. + // + template <typename T> + T& + insert (const target_type& tt, + dir_path dir, + dir_path out, + string name, + optional<string> ext, + tracer& t) + { + return insert (tt, + move (dir), + move (out), + move (name), + move (ext), + true, + t).first.template as<T> (); + } + + template <typename T> + T& + insert (const dir_path& dir, + const dir_path& out, + const string& name, + const optional<string>& ext, + tracer& t) + { + return insert<T> (T::static_type, dir, out, name, ext, t); + } + + template <typename T> + T& + insert (const dir_path& dir, + const dir_path& out, + const string& name, + tracer& t) + { + return insert<T> (dir, out, name, nullopt, t); + } + + // Note: not MT-safe so can only be used during serial execution. + // + public: + using iterator = butl::map_iterator_adapter<map_type::const_iterator>; + + iterator begin () const {return map_.begin ();} + iterator end () const {return map_.end ();} + + void + clear () {map_.clear ();} + + private: + friend class target; // Access to mutex. + + mutable shared_mutex mutex_; + map_type map_; + }; + + LIBBUILD2_SYMEXPORT extern target_set targets; + + // Modification time-based target. + // + class LIBBUILD2_SYMEXPORT mtime_target: public target + { + public: + using target::target; + + // Modification time is an "atomic cash". 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 + // updating the internal. + // + // The modification time is reserved for the inner operation thus there is + // no action argument. + // + // The rule for groups that utilize target_state::group is as follows: if + // it has any members that are mtime_targets, then the group should be + // mtime_target and the members get the mtime from it. During match and + // execute the target should be synchronized. + // + // Note that this function can be called before the target is matched in + // which case the value always comes from the target itself. In other + // words, that group logic only kicks in once the target is matched. + // + timestamp + mtime () const; + + // Note also that while we can cache the mtime, it may be ignored if the + // target state is set to group (see above). + // + void + mtime (timestamp) const; + + // 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. + // + timestamp + load_mtime (const path&) const; + + // Return true if this target is newer than the specified timestamp. + // + // Note: can only be called during execute on a synchronized target. + // + bool + newer (timestamp) const; + + public: + static const target_type static_type; + + protected: + + // Complain if timestamp is not lock-free unless we were told non-lock- + // free is ok. + // +#ifndef LIBBUILD2_ATOMIC_NON_LOCK_FREE + // C++17: + // + // static_assert (atomic<timestamp::rep>::is_always_lock_free, + // "timestamp is not lock-free on this architecture"); + // +#if !defined(ATOMIC_LLONG_LOCK_FREE) || ATOMIC_LLONG_LOCK_FREE != 2 +# error timestamp is not lock-free on this architecture +#endif +#endif + + // Note that the value is not used to synchronize any other state so we + // use the release-consume ordering (i.e., we are only interested in the + // mtime value being synchronized). + // + // Store it as an underlying representation (normally int64_t) since + // timestamp is not usable with atomic (non-noexcept default ctor). + // + mutable atomic<timestamp::rep> mtime_ {timestamp_unknown_rep}; + }; + + // Filesystem path-based target. + // + class LIBBUILD2_SYMEXPORT path_target: public mtime_target + { + public: + using mtime_target::mtime_target; + + typedef build2::path path_type; + + // Target path is an "atomic consistent cash". 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. + // + // An empty path may signify special unknown/undetermined/unreal location + // (for example, a binless library or an installed import library -- we + // know the DLL is there, just not exactly where). In this case you would + // also normally set its mtime. + // + // We used to return a pointer to properly distinguish between not set and + // empty but that proved too tedious to work with. So now we return empty + // path both when not set (which will be empty_path so you can distinguish + // the two case if you really want to) and when set to empty. Note that + // this means there could be a race between path and mtime (unless you + // lock the target in some other way; see file_rule) so in this case it + // makes sense to set the timestamp first. + // + const path_type& + path () const; + + const path_type& + path (path_type) const; + + timestamp + load_mtime () const; + + // Derive a path from target's dir, name, and, if set, ext. If ext is not + // set, try to derive it using the target type extension function and + // fallback to default_ext, if specified. In both cases also update the + // target's extension (this becomes important if later we need to reliably + // determine whether this file has an extension; think hxx{foo.bar.} and + // hxx{*}:extension is empty). + // + // If name_prefix is not NULL, add it before the name part and after the + // directory. Similarly, if name_suffix is not NULL, add it after the name + // part and before the extension. + // + // Finally, if the path was already assigned to this target, then this + // function verifies that the two are the same. + // + const path_type& + derive_path (const char* default_ext = nullptr, + const char* name_prefix = nullptr, + const char* name_suffix = nullptr); + + // This version can be used to derive the path from another target's path + // by adding another extension. + // + const path_type& + derive_path (path_type base, const char* default_ext = nullptr); + + // As above but only derives (and returns) the extension (empty means no + // extension used). + // + const string& + derive_extension (const char* default_ext = nullptr) + { + return *derive_extension (false, default_ext); + } + + // As above but if search is true then look for the extension as if it was + // a prerequisite, not a target. In this case, if no extension can be + // derived, return NULL instead of failing (like search_existing_file()). + // + const string* + derive_extension (bool search, const char* default_ext = nullptr); + + // Const versions of the above that can be used on unlocked targets. Note + // that here we don't allow providing any defaults since you probably + // should only use this version if everything comes from the target itself + // (and is therefore atomic). + // + const path_type& + derive_path () const + { + return const_cast<path_target*> (this)->derive_path (); // MT-aware. + } + + const string& + derive_extension () const + { + return const_cast<path_target*> (this)->derive_extension (); // MT-aware. + } + + public: + static const target_type static_type; + + private: + // Note that the state is also used to synchronize the path value so + // we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + mutable atomic<uint8_t> path_state_ {0}; + mutable path_type path_; + }; + + // File target. + // + class LIBBUILD2_SYMEXPORT file: public path_target + { + public: + using path_target::path_target; + + 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) + // as a single "name". + // + class LIBBUILD2_SYMEXPORT alias: public target + { + public: + using target::target; + + 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 + // but rather an alias target with the directory name. For actual + // filesystem directory (creation), see fsdir. + // + class LIBBUILD2_SYMEXPORT dir: public alias + { + public: + using alias::alias; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + + public: + template <typename K> + static const target* + search_implied (const scope&, const K&, tracer&); + + // Return true if the implied buildfile is plausible for the specified + // subdirectory of a project with the specified root scope. That is, there + // is a buildfile in at least one of its subdirectories. Note that the + // directory must exist. + // + static bool + check_implied (const scope& root, const dir_path&); + + private: + static prerequisites_type + collect_implied (const scope&); + }; + + // While a filesystem directory is mtime-based, the semantics is not very + // useful in our case. In particular, if another target depends on fsdir{}, + // then all that's desired is the creation of the directory if it doesn't + // already exist. In particular, we don't want to update the target just + // because some unrelated entry was created in that directory. + // + class LIBBUILD2_SYMEXPORT fsdir: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Executable file. + // + class LIBBUILD2_SYMEXPORT exe: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class LIBBUILD2_SYMEXPORT buildfile: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Common documentation file targets. + // + class LIBBUILD2_SYMEXPORT doc: public file + { + public: + using file::file; + + 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 + // different sets of sections. What seems to be the "sane" set + // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps + // 8 to 1M (system administration). The section determines two + // things: the directory where the page is installed (e.g., + // /usr/share/man/man1) as well as the extension of the file + // (e.g., test.1). Note also that there could be sub-sections, + // e.g., 1p (for POSIX). Such a page would still go into man1 + // but will have the .1p extension (at least that's what happens + // on Linux). The challenge is to somehow handle this in a + // portable manner. So here is the plan: + // + // First of all, we have the man{} target type which can be used + // for a custom man page. That is, you can have any extension and + // install it anywhere you please: + // + // man{foo.X}: install = man/manX + // + // Then we have man1..9{} target types which model the "sane" + // section set and that would be automatically installed into + // correct locations on other platforms. In other words, the + // idea is that you should be able to have the foo.8 file, + // write man8{foo} and have it installed as man1m/foo.1m on + // some SysV host. + // + // Re-mapping the installation directory is easy: to help with + // that we have assigned install.man1..9 directory names. The + // messy part is to change the extension. It seems the only + // way to do that would be to have special logic for man pages + // 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}. + // + class LIBBUILD2_SYMEXPORT man: public doc + { + public: + using doc::doc; + + 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; + + 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 + // installed into the same place where the rest of the documentation goes. + // If you think about it, it's kind of a documentation, similar to (but + // better than) the version file that many projects come with. + // + class LIBBUILD2_SYMEXPORT manifest: public doc + { + public: + using doc::doc; + + 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 + // functions. + // + template <typename T> + target* + target_factory (const target_type&, dir_path d, dir_path o, string n) + { + return new T (move (d), move (o), move (n)); + } + + // Return fixed target extension unless one was specified. + // + template <const char* ext> + const char* + target_extension_fix (const target_key&, const scope*); + + template <const char* ext> + bool + target_pattern_fix (const target_type&, const scope&, + string&, optional<string>&, const location&, + bool); + + // Get the extension from the variable or use the default if none set. If + // the default is NULL, then return NULL. + // + template <const char* var, const char* def> + optional<string> + target_extension_var (const target_key&, const scope&, const char*, bool); + + template <const char* var, const char* def> + bool + target_pattern_var (const target_type&, const scope&, + string&, optional<string>&, const location&, + bool); + + // Target print functions. + // + + // 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&); + + // 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&); + + // The default behavior, that is, look for an existing target in the + // prerequisite's directory scope. + // + LIBBUILD2_SYMEXPORT const target* + target_search (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. + // + LIBBUILD2_SYMEXPORT const target* + file_search (const target&, const prerequisite_key&); +} + +#include <libbuild2/target.ixx> +#include <libbuild2/target.txx> + +#endif // LIBBUILD2_TARGET_HXX |