// file : build/target -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD_TARGET #define BUILD_TARGET #include #include #include #include // unique_ptr #include // size_t #include // uint8_t #include // reference_wrapper #include #include #include // move(), forward(), declval() #include #include #include // reverse_iterate() #include // map_iterator_adapter #include #include #include #include #include #include #include #include namespace build { class scope; class target; target& search (prerequisite&); // From . // Target state. // enum class target_state: std::uint8_t { // The order of the enumerators is arranged so that their integral // values indicate whether one "overrides" the other in the merge // operator (see below). // unknown, unchanged, postponed, changed, failed, group // Target's state is the group's state. }; std::ostream& operator<< (std::ostream&, target_state); inline target_state& operator |= (target_state& l, target_state r) { if (static_cast (r) > static_cast (l)) l = r; return l; } // Recipe. // // The returned target state should be changed, unchanged, or // postponed, though you shouldn't be returning postponed // directly. If there is an error, then the recipe should // throw rather than returning failed. // // The return value of the recipe is used to update the target // state except if the state was manually set by the recipe to // target_state::group. Note that in this case the returned by // the recipe value is still used as the resulting target state // so it should match the group's state. // using recipe_function = target_state (action, target&); using recipe = std::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 , // for details. The group recipe call's the group's // recipe. // extern const recipe empty_recipe; extern const recipe noop_recipe; extern const recipe default_recipe; extern const recipe group_recipe; target_state noop_action (action, target&); // Defined in . target_state group_action (action, target&); // Defined in . // Prerequisite references as used in the target::prerequisites list // below. // struct prerequisite_ref: std::reference_wrapper { typedef std::reference_wrapper base; using base::base; // Return true if this reference belongs to the target's prerequisite // list. Note that this test only works if you use references to // the container elements and the container hasn't been resized // since such a reference was obtained. Normally this function is // used when iterating over a combined prerequisites range (see // group_prerequisites below). // bool belongs (const target&) const; }; // A view of target group members. // struct group_view { target* const* members; // NULL means not yet known. std::size_t count; }; // Target. // class target { public: typedef build::action action_type; virtual ~target () = default; target (const target&) = delete; target& operator= (const target&) = delete; target (dir_path d, std::string n, const std::string* e) : dir (std::move (d)), name (std::move (n)), ext (e) {} // Reset the target before matching a rule for it. The // default implementation clears prerequisite_targets. // virtual void reset (action_type); const dir_path dir; // Absolute and normalized. const std::string name; const std::string* ext; // Extension, NULL means unspecified, // empty means no extension. // 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. // // The semantics of the interaction between the group and its // members and what it means to, say, update the group, is // unspecified and 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: "alternatives" // and "combination". In an alternatives 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 the group as a whole. In this case we // say that the rule "semantically recognizes" the group and picks // some of its members. // // Updating an alternatives 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 a combination group, when a group is updated, normally all // 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 // a combination 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. // target* group {nullptr}; // You should not call this function directly; rather use // resolve_group_members() from . // virtual group_view group_members (action_type) const; target_key key () const {return target_key {&type (), &dir, &name, &ext};} // Scoping. // public: // Most qualified scope that contains this target. // 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. // scope& root_scope () const; // Root scope of a strong amalgamation that contains this target. // The same notes as to root_scope() apply. // 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. // scope& weak_scope () const {return *root_scope ().weak_scope ();} bool in (const scope& s) const { return (s.out_path_ != nullptr && dir.sub (*s.out_path_)) || (s.src_path_ != nullptr && dir.sub (*s.src_path_)); } // Prerequisites. // public: typedef std::vector prerequisites_type; prerequisites_type prerequisites; // Targets to which prerequisites resolve for this recipe. 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(). // typedef std::vector prerequisite_targets_type; prerequisite_targets_type prerequisite_targets; // Check if there are any prerequisites, taking into account // group prerequisites. // bool has_prerequisites () const { return !prerequisites.empty () || (group != nullptr && !group->prerequisites.empty ()); } // Target-specific variables. // 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. // lookup operator[] (const variable&) const; lookup operator[] (const std::string& name) const { return operator[] (var_pool.find (name)); } // Return a value suitable for assignment. See class scope for // details. // value& assign (const variable& var) {return vars.assign (var).first;} value& assign (const std::string& name) {return vars.assign (name).first;} // Return a value suitable for appending. See class scope for // details. // value& append (const variable&); value& append (const std::string& name) { return append (var_pool.find (name)); } public: target_state raw_state; target_state state () const { return raw_state != target_state::group ? raw_state : group->raw_state; } // Number of direct targets that depend on this target in the current // action. It is incremented during the match phase 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 ). // // Note that setting a new recipe (which happens when we match the rule // and which in turn is triggered by the first dependent) clears this // counter. However, if the previous action was the same as the current, // then the existing recipe is reused. In this case, however, the counter // should have been decremented to 0 naturally, as part of the previous // action execution. // std::size_t dependents; public: action_type action; // Action this recipe is for. public: typedef build::recipe recipe_type; const recipe_type& recipe (action_type a) const {return a > action ? empty_recipe : recipe_;} void recipe (action_type, recipe_type); // Target type info. // public: template T* is_a () {return dynamic_cast (this);} template const T* is_a () const {return dynamic_cast (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; private: recipe_type recipe_; }; std::ostream& operator<< (std::ostream&, 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_ref& pr: group_prerequisites (t)) // // for (prerequisite_ref& pr: 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. // class group_prerequisites { public: typedef target::prerequisites_type prerequisites_type; explicit group_prerequisites (target& t): t_ (t) {} struct iterator { typedef prerequisites_type::iterator base_iterator; typedef base_iterator::value_type value_type; typedef base_iterator::pointer pointer; typedef base_iterator::reference reference; typedef base_iterator::difference_type difference_type; typedef std::bidirectional_iterator_tag iterator_category; iterator () {} iterator (target* t, prerequisites_type* c, base_iterator i) : t_ (t), c_ (c), i_ (i) {} iterator& operator++ () { if (++i_ == c_->end () && c_ != &t_->prerequisites) { c_ = &t_->prerequisites; i_ = c_->begin (); } return *this; } iterator operator++ (int) {iterator r (*this); operator++ (); return r;} iterator& operator-- () { if (i_ == c_->begin () && c_ == &t_->prerequisites) { c_ = &t_->group->prerequisites; i_ = c_->end (); } --i_; return *this; } 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.c_ == y.c_ && x.i_ == y.i_; } friend bool operator!= (const iterator& x, const iterator& y) {return !(x == y);} private: target* t_ {nullptr}; prerequisites_type* c_ {nullptr}; base_iterator i_; }; typedef std::reverse_iterator reverse_iterator; iterator begin () const { auto& c ((t_.group != nullptr && !t_.group->prerequisites.empty () ? *t_.group : t_).prerequisites); return iterator (&t_, &c, c.begin ()); } iterator end () const { auto& c (t_.prerequisites); return iterator (&t_, &c, c.end ()); } reverse_iterator rbegin () const {return reverse_iterator (end ());} reverse_iterator rend () const {return reverse_iterator (begin ());} std::size_t size () const { return t_.prerequisites.size () + (t_.group != nullptr ? t_.group->prerequisites.size () : 0); } private: target& t_; }; // A member of a prerequisite. If 'target' 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 { typedef build::target target_type; typedef build::prerequisite prerequisite_type; prerequisite_ref& prerequisite; target_type* target; template bool is_a () const { return target != nullptr ? target->is_a () != nullptr : prerequisite.get ().is_a (); } prerequisite_key key () const { return target != nullptr ? prerequisite_key {prerequisite.get ().proj, target->key (), nullptr} : prerequisite.get ().key (); } const build::target_type& type () const { return target != nullptr ? target->type () : prerequisite.get ().type; } const std::string& name () const { return target != nullptr ? target->name : prerequisite.get ().name; } const std::string* proj () const { // Target cannot be project-qualified. // return target != nullptr ? nullptr : prerequisite.get ().proj; } target_type& search () const { return target != nullptr ? *target : build::search (prerequisite); } prerequisite_type& as_prerequisite (tracer&) const; }; inline std::ostream& operator<< (std::ostream& os, const prerequisite_member& pm) { return os << pm.key (); } // 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. 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 class prerequisite_members_range; template inline prerequisite_members_range prerequisite_members (action a, T&& x, bool members = true) { return prerequisite_members_range (a, std::forward (x), members); } template class prerequisite_members_range { public: prerequisite_members_range (action a, T&& r, bool m) : a_ (a), members_ (m), r_ (std::forward (r)), e_ (r_.end ()) {} using base_iterator = decltype (std::declval ().begin ()); struct iterator { typedef prerequisite_member value_type; typedef const value_type* pointer; typedef const value_type& reference; typedef typename base_iterator::difference_type difference_type; typedef std::forward_iterator_tag iterator_category; iterator (): r_ (nullptr) {} iterator (const prerequisite_members_range* r, const base_iterator& i) : r_ (r), i_ (i), g_ {nullptr, 0} { if (r_->members_ && i_ != r_->e_ && i_->get ().type.see_through) { bool r (switch_members ()); assert (r); // Group could not be resolved. } } 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 () { // Pretend we are on the last member of some group. // j_ = 0; g_.count = 1; } // 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 () { bool r (switch_members ()); if (r) --j_; // Compensate for the increment that will follow. return r; } value_type operator* () const { return value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; } pointer operator-> () const { static_assert ( std::is_trivially_destructible::value, "prerequisite_member is not trivially destructible"); return new (&m_) value_type {*i_, g_.count != 0 ? g_.members[j_ - 1] : nullptr}; } 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_); } friend bool operator!= (const iterator& x, const iterator& y) {return !(x == y);} private: bool switch_members (); private: const prerequisite_members_range* r_; base_iterator i_; group_view g_; std::size_t j_; // 1-based index, to support enter_group(). mutable std::aligned_storage::type m_; }; iterator begin () const {return iterator (this, r_.begin ());} iterator end () const {return iterator (this, e_);} private: action a_; bool members_; // Go into group members by default? T r_; base_iterator e_; }; // prerequisite_members(t.prerequisites) // inline auto prerequisite_members (action a, target& t, bool members = true) { return prerequisite_members (a, t.prerequisites, members); } // prerequisite_members(reverse_iterate(t.prerequisites)) // inline auto reverse_prerequisite_members (action a, target& t, bool members = true) { return prerequisite_members ( a, butl::reverse_iterate (t.prerequisites), members); } // prerequisite_members(group_prerequisites (t)) // inline auto group_prerequisite_members (action a, target& t, bool members = true) { return prerequisite_members (a, group_prerequisites (t), members); } // prerequisite_members(reverse_iterate (group_prerequisites (t))) // inline auto reverse_group_prerequisite_members (action a, target& t, bool members = true) { return prerequisite_members ( a, butl::reverse_iterate (group_prerequisites (t)), members); } // // struct target_set { typedef std::map> map; typedef butl::map_iterator_adapter iterator; iterator find (const target_key& k, tracer& trace) const; iterator find (const target_type& type, const dir_path& dir, const std::string& name, const std::string* ext, tracer& trace) const { return find (target_key {&type, &dir, &name, &ext}, trace); } // As above but ignore the extension and return the target or // nullptr instead of the iterator. // template T* find (const dir_path& dir, const std::string& name) const { const std::string* e (nullptr); auto i (map_.find (target_key {&T::static_type, &dir, &name, &e})); return i != map_.end () ? static_cast (i->second.get ()) : nullptr; } iterator begin () const {return map_.begin ();} iterator end () const {return map_.end ();} std::pair insert (const target_type&, dir_path dir, std::string name, const std::string* ext, tracer&); template T& insert (const dir_path& dir, const std::string& name, const std::string* ext, tracer& t) { return static_cast ( insert (T::static_type, dir, name, ext, t).first); } template T& insert (const dir_path& dir, const std::string& name, tracer& t) { return static_cast ( insert (T::static_type, dir, name, nullptr, t).first); } void clear () {map_.clear ();} private: map map_; }; extern target_set targets; // Modification time-based target. // class mtime_target: public target { public: using target::target; // Generally, modification time for a target can only be queried // after a rule has been matched since that's where the path is // normally gets assigned. Normally, however, it would make sense // to first execute the rule to get the "updated" timestamp. // // The rule for groups that utilize the group state 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. // timestamp mtime () const { const mtime_target* t (raw_state == target_state::group ? static_cast (group) : this); if (t->mtime_ == timestamp_unknown) t->mtime_ = t->load_mtime (); return t->mtime_; } void mtime (timestamp mt) { // While we can cache the mtime at any time, it may be ignored // if the target state is group (see the mtime() accessor). // mtime_ = mt; } protected: virtual timestamp load_mtime () const = 0; public: static const target_type static_type; private: mutable timestamp mtime_ {timestamp_unknown}; }; // Filesystem path-based target. // class path_target: public mtime_target { public: using mtime_target::mtime_target; typedef build::path path_type; const path_type& path () const {return path_;} void path (path_type p) {assert (path_.empty ()); path_ = std::move (p);} // Derive a path from target's dir, name, and, if specified, ext. // If ext is not specified, then use default_ext and 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.ext 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. // void derive_path (const char* default_ext = nullptr, const char* name_prefix = nullptr, const char* name_suffix = nullptr); public: static const target_type static_type; private: path_type path_; }; // File target. // class file: public path_target { public: using path_target::path_target; protected: // Note that it is final in order to be consistent with file_rule, // search_existing_file(). // virtual timestamp load_mtime () const final; 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 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 dir: public alias { public: using alias::alias; public: static const target_type static_type; virtual const target_type& dynamic_type () const {return static_type;} }; // 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 fsdir: public target { public: using target::target; public: static const target_type static_type; virtual const target_type& dynamic_type () const {return static_type;} }; class 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. // // @@ Maybe these should be in the built-in doc module? // class 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 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 man1: public man { public: using man::man; 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 target* target_factory (const target_type&, dir_path d, string n, const string* e) { return new T (move (d), move (n), e); } // Return fixed target extension. // template const std::string& target_extension_fix (const target_key&, scope&); // Get the extension from the variable or use the default if none set. // Issue diagnostics and fail if the default is NULL. // template const std::string& target_extension_var (const target_key&, scope&); // The default behavior, that is, look for an existing target in the // prerequisite's directory scope. // target* search_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. // target* search_file (const prerequisite_key&); } #include #include #endif // BUILD_TARGET