// file : build/target -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file #ifndef BUILD_TARGET #define BUILD_TARGET #include #include #include #include // unique_ptr #include // size_t #include // function, reference_wrapper #include #include #include // move() #include #include // map_iterator_adapter #include #include #include #include #include #include #include // compare_*, extension_pool namespace build { class scope; class target; // Target state. // enum class target_state {unknown, postponed, unchanged, changed, failed}; // Recipe. // // The returned target state should be changed, unchanged, or // postponed. If there is an error, then the recipe should throw // rather than returning failed. // // The recipe execution protocol is as follows: before executing // the recipe, the caller sets the target's state to failed. If // the recipe returns normally and the target's state is still // failed, then the caller sets it to the returned value. This // means that the recipe can set the target's state manually to // some other value. For example, setting it to unknown will // result in the recipe to be executed again if this target is a // prerequisite of another target. Note that in this case the // returned by the recipe value is still used (by the caller) as // the resulting target state for this execution of the recipe. // Returning postponed from the last call to the recipe means // that the action could not be executed at this time (see fsdir // clean for an example). // 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. Specially, // for actions with the "first" execution mode, it calls // execute_prerequisites() while for those with the "last" mode -- // reverse_execute_prerequisites(); see , // for details. // extern const recipe empty_recipe; extern const recipe noop_recipe; extern const recipe default_recipe; target_state noop_recipe_function (action, target&); // Prerequisite target. It consist of a reference to the prerequisite // plus a pointer to target to which it resolves in this context. // struct prerequisite_target { typedef build::target target_type; prerequisite* prereq; // Must not be NULL but can be reset. operator prerequisite& () const {return *prereq;} // Target to which this prerequisite resolves in this context. // Note that unlike prerequisite::target, this can be a group // member target. Depending on the stage, NULL means either // the target is not yet resolved or it should be skipped. // // 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 target will be updates // if the recipe is updated, as part of rule::apply(). // target_type* target {nullptr}; operator target_type* () const {return target;} explicit prerequisite_target (prerequisite& p): prereq (&p) {} prerequisite_target (prerequisite& p, target_type& t) : prereq (&p), target (&t) {} }; // Target. // class target { public: 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) {} 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 {nullptr}; // 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 // (see, for example, variable lookup). 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 // NULL is returned. // scope* root_scope () const; // Prerequisites. // public: typedef std::vector prerequisites_type; prerequisites_type prerequisites; // Target-specific variables. // public: variable_map vars; // Lookup, including in groups to which this target belongs and // then in outer scopes. If you only want to lookup in this target, // do it on the the variables map directly. // value_proxy operator[] (const variable&) const; value_proxy operator[] (const std::string& name) const { return operator[] (variable_pool.find (name)); } // Return a value_proxy suitable for assignment. See class scope // for details. // value_proxy assign (const variable& var) { return vars.assign (var); } value_proxy assign (const std::string& name) { return assign (variable_pool.find (name)); } // Return a value_proxy suitable for appending. See class scope // for details. // value_proxy append (const variable&); value_proxy append (const std::string& name) { return append (variable_pool.find (name)); } public: target_state 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: typedef build::recipe recipe_type; const recipe_type& recipe (action_id a) const {return action_ == a ? recipe_ : empty_recipe;} void recipe (action_id a, recipe_type r) { assert (action_ != a || !recipe_); action_ = a; recipe_ = std::move (r); // Also reset the target state. If this is a noop recipe, then // mark the target unchanged so that we don't waste time executing // the recipe. // recipe_function** f (recipe_.target ()); state = (f == nullptr || *f != &noop_recipe_function) ? target_state::unknown : target_state::unchanged; dependents = 0; } // Target type info. // public: template T* is_a () {return dynamic_cast (this);} template const T* is_a () const {return dynamic_cast (this);} virtual const target_type& type () const = 0; static const target_type static_type; private: action_id action_ {0}; // Action id of this recipe. recipe_type recipe_; }; std::ostream& operator<< (std::ostream&, const target&); struct target_set { typedef std::map> map; typedef 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 { const std::string* e (ext); return find (target_key {&type, &dir, &name, &e}, trace); } // As above but ignore the extension and return the target or // nullptr instead of the iterator. // target* find (const target_type& type, const dir_path& dir, const std::string& name) const { const std::string* e (nullptr); auto i (map_.find (target_key {&type, &dir, &name, &e})); return i != map_.end () ? 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&); void clear () {map_.clear ();} private: map map_; }; extern target_set targets; class target_type_map: public std::map< const char*, std::reference_wrapper, compare_c_string> { public: typedef std::map, compare_c_string> base; void insert (const target_type& tt) {emplace (tt.name, tt);} using base::find; // Given a name, figure out its type, taking into account extensions, // special names (e.g., '.' and '..'), or anything else that might be // relevant. Also process the name (in place) by extracting the // extension, adjusting dir/value, etc (note that the dir is not // necessarily normalized). Return NULL if not found. // const target_type* find (name&, const std::string*& ext) const; }; extern target_type_map target_types; template target* target_factory (dir_path d, std::string n, const std::string* e) { return new T (std::move (d), std::move (n), e); } // Default implementation for a target that is a member of a // target group. Besides creating the target as above this // version also tries to "link up" with the group. // template target* member_target_factory (dir_path d, std::string n, const std::string* e) { target* g (targets.find (G::static_type, d, n)); target* t (new T (std::move (d), std::move (n), e)); t->group = g; return t; } // Modification time-based target. // class mtime_target: public target { public: using target::target; timestamp mtime () const { if (mtime_ == timestamp_unknown) mtime_ = load_mtime (); return mtime_; } void mtime (timestamp mt) {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);} // Return a path derived from target's dir, name, and, if specified, // ext. If ext is not specified, then use default_ext. If name_prefix // if not NULL, add it before the name part and after the directory. // Similarly, if name_suffix if not NULL, add it after the name part // and before the extension. // path_type derived_path (const char* default_ext = nullptr, const char* name_prefix = nullptr, const char* name_suffix = nullptr); protected: virtual timestamp load_mtime () const; public: static const target_type static_type; private: path_type path_; }; // File target. // class file: public path_target { public: using path_target::path_target; public: virtual const target_type& type () const {return static_type;} static const target_type static_type; }; // Directory alias/action target. Note that it is not mtime-based. // Rather it is meant to represent a group of targets. For actual // filesystem directory (creation), see fsdir. // class dir: public target { public: using target::target; public: virtual const target_type& type () const {return static_type;} static const target_type 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: virtual const target_type& type () const {return static_type;} static const target_type static_type; }; } #endif // BUILD_TARGET