// file : build/operation -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD_OPERATION #define BUILD_OPERATION #include #include #include #include #include // reference_wrapper #include #include namespace build { class location; class scope; class target_key; // While we are using uint8_t for the meta/operation ids, we assume // that each is limited to 4 bits (max 128 entries) so that we can // store the combined action id in uint8_t as well. This makes our // life easier when it comes to defining switch labels for action // ids (no need to mess with endian-ness). // // Note that 0 is not a valid meta/operation/action id. // using meta_operation_id = std::uint8_t; using operation_id = std::uint8_t; using action_id = std::uint8_t; // Meta-operations and operations are not the end of the story. We // also have operation nesting (currently only one level deep) which // is used to implement pre/post operations (currently, but may be // useful for other things). Here is the idea: the test operation // needs to make sure that the targets that it needs to test are // up-to-date. So it runs update as its pre-operation. It is almost // like an ordinary update except that it has test as its outer // operation (the meta-operations are always the same). This way a // rule can recognize that this is "update for test" and do something // differently. For example, if an executable is not a test, then // there is no use updating it. At the same time, most rules will // ignore the fact that this is a nested update and for them it is // "update as usual". // struct action { action (): inner_id (0), outer_id (0) {} // Invalid action. bool valid () const {return inner_id != 0;} // If this is not a nested operation, then outer should be 0. // action (meta_operation_id m, operation_id inner, operation_id outer = 0) : inner_id ((m << 4) | inner), outer_id (outer == 0 ? 0 : (m << 4) | outer) {} meta_operation_id meta_operation () const {return inner_id >> 4;} operation_id operation () const {return inner_id & 0xF;} operation_id outer_operation () const {return outer_id & 0xF;} // Implicit conversion operator to action_id for the switch() // statement, etc. Most places will only care about the inner // operation. // operator action_id () const {return inner_id;} action_id inner_id; action_id outer_id; }; // This is an "overrides" comparison, i.e., it returns true // if the recipe for x overrides recipe for y. The idea is // that for the same inner operation, action with an outer // operation is "weaker" than the one without. // inline bool operator> (action x, action y) { return x.inner_id != y.inner_id || (x.outer_id != y.outer_id && y.outer_id != 0); } // Note that these ignore the outer operation. // inline bool operator== (action x, action y) {return x.inner_id == y.inner_id;} inline bool operator!= (action x, action y) {return !(x == y);} std::ostream& operator<< (std::ostream&, action); // Id constants for build-in operations. // const meta_operation_id perform_id = 1; // The default operation is a special marker that can be used to // indicate that no operation was explicitly specified by the user. // const operation_id default_id = 1; const operation_id update_id = 2; const operation_id clean_id = 3; const action_id perform_update_id = (perform_id << 4) | update_id; const action_id perform_clean_id = (perform_id << 4) | clean_id; // Recipe execution mode. // // When a target is a prerequisite of another target, its recipe can be // executed before the dependent's recipe (the normal case) or after. // We will call these "front" and "back" execution modes, respectively // (think "the prerequisite is 'front-running' the dependent"). // // There could also be several dependent targets and the prerequisite's // recipe can be execute as part of the first dependent (the normal // case) or last (or for all/some of them; see the recipe execution // protocol in ). We will call these "first" and "last" // execution modes, respectively. // // Now you may be having a hard time imagining where a mode other than // the normal one (first/front) could be useful. An the answer is, // compensating or inverse operations such as clean, uninstall, etc. // If we use the last/back mode for, say, clean, then we will remove // targets in the order inverse to the way they were updated. While // this sounds like an elegant idea, are there any practical benefits // of doing it this way. As it turns out there is (at least) one: when // we are removing a directory (see fsdir{}), we want to do it after // all the targets that depend on it (such as files, sub-directories) // were removed. If we do it before, then the directory won't be empty // yet. // // It appears that this execution mode is dictated by the essence of // the operation. Constructive operations (those that "do") seem to // naturally use the first/front mode. That is, we need to "do" the // prerequisite first before we can "do" the dependent. While the // destructive ones (those that "undo") seem to need last/back. That // is, we need to "undo" all the dependents before we can "undo" the // prerequisite (say, we need to remove all the files before we can // remove their directory). // // If you noticed the parallel with the way C++ construction and // destruction works for base/derived object then you earned a gold // star! // // Note that the front/back mode is realized in the dependen's recipe // (which is another indication that it is a property of the operation). // enum class execution_mode {first, last}; // Meta-operation info. // // Normally a list of resolved and matched targets to execute. But // can be something else, depending on the meta-operation. // typedef std::vector action_targets; struct meta_operation_info { const std::string name; // Name derivatives for diagnostics. If empty, then the meta- // operation need not be mentioned. // const std::string name_do; // E.g., [to] 'configure'. const std::string name_doing; // E.g., [while] 'configuring'. const std::string name_done; // E.g., 'is configured'. // If operation_pre() is not NULL, then it may translate default_id // (and only default_id) to some other operation. If not translated, // then default_id is used. If, however, operation_pre() is NULL, // then default_id is translated to update_id. // void (*meta_operation_pre) (); // Start of meta-operation batch. operation_id (*operation_pre) (operation_id); // Start of operation batch. // Meta-operation-specific logic to load the buildfile, search and match // the targets, and execute the action on the targets. // void (*load) (const path& buildfile, scope& root, const dir_path& out_base, const dir_path& src_base, const location&); void (*search) (scope& root, const target_key&, const location&, action_targets&); void (*match) (action, action_targets&); void (*execute) (action, const action_targets&); void (*operation_post) (operation_id); // End of operation batch. void (*meta_operation_post) (); // End of meta-operation batch. }; // Built-in meta-operations. // // perform // // Load the buildfile. This is the default implementation that first // calls root_pre(), then creates the scope for out_base, and, finally, // loads the buildfile unless it has already been loaded for the root // scope. // void load (const path& buildfile, scope& root, const dir_path& out_base, const dir_path& src_base, const location&); // Search and match the target. This is the default implementation // that does just that and adds a pointer to the target to the list. // void search (scope&, const target_key&, const location&, action_targets&); void match (action, action_targets&); // Execute the action on the list of targets. This is the default // implementation that does just that while issuing appropriate // diagnostics. // void execute (action, const action_targets&); extern meta_operation_info perform; // Operation info. // struct operation_info { const std::string name; // Name derivatives for diagnostics. Note that unlike meta-operations, // these can only be empty for the default operation (id 1), And // meta-operations that make use of the default operation shall not // have empty derivatives (failed which only target name will be // printed). // const std::string name_do; // E.g., [to] 'update'. const std::string name_doing; // E.g., [while] 'updating'. const std::string name_done; // E.g., 'is up to date'. const execution_mode mode; // If the returned operation_id's are not 0, then they are injected // as pre/post operations for this operation. Can be NULL if unused. // The returned operation_id shall not be default_id. // operation_id (*pre) (meta_operation_id); operation_id (*post) (meta_operation_id); }; // Build-in operations. // extern operation_info default_; extern operation_info update; extern operation_info clean; // Meta/operation tables. // using meta_operation_table = butl::string_table< meta_operation_id, std::reference_wrapper>; using operation_table = butl::string_table< operation_id, std::reference_wrapper>; } namespace butl { template <> struct string_table_traits< std::reference_wrapper> { static const std::string& key (const build::meta_operation_info& x) {return x.name;} }; template <> struct string_table_traits< std::reference_wrapper> { static const std::string& key (const build::operation_info& x) {return x.name;} }; } #endif // BUILD_OPERATION