From 9fb791e9fad6c63fc1dac49f4d05ae63b8a3db9b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 5 Jan 2016 11:55:15 +0200 Subject: Rename build directory/namespace to build2 --- build2/operation | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 build2/operation (limited to 'build2/operation') diff --git a/build2/operation b/build2/operation new file mode 100644 index 0000000..a843c9d --- /dev/null +++ b/build2/operation @@ -0,0 +1,359 @@ +// file : build2/operation -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_OPERATION +#define BUILD2_OPERATION + +#include +#include +#include +#include +#include // reference_wrapper + +#include + +#include + +namespace build2 +{ + 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 and pre-defined meta/operations. + // + const meta_operation_id perform_id = 1; + const meta_operation_id configure_id = 2; + const meta_operation_id disfigure_id = 3; + const meta_operation_id dist_id = 4; + + // 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; // Shall be first. + const operation_id update_id = 2; + const operation_id clean_id = 3; + const operation_id test_id = 4; + const operation_id install_id = 5; + + const action_id perform_update_id = (perform_id << 4) | update_id; + const action_id perform_clean_id = (perform_id << 4) | clean_id; + const action_id perform_test_id = (perform_id << 4) | test_id; + const action_id perform_install_id = (perform_id << 4) | install_id; + + const action_id configure_update_id = (configure_id << 4) | update_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&, bool quiet); + + 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 (unless quiet). + // + void + execute (action, const action_targets&, bool quiet); + + 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); + }; + + // Built-in operations. + // + extern operation_info default_; + extern operation_info update; + extern operation_info clean; + + // Global meta/operation tables. Each registered meta/operation + // is assigned an id which is used as an index in the per-project + // registered meta/operation lists. + // + // We have three types of meta/operations: built-in (e.g., perform, + // update), pre-defined (e.g., configure, test), and dynamically- + // defined. For built-in ones, both the id and implementation are + // part of the build2 core. For pre-defined, the id is registered + // as part of the core but the implementation is loaded as part of + // a module. The idea with pre-defined operations is that they have + // common, well-established semantics but could still be optional. + // Another aspect of pre-defined operations is that often rules + // across multiple modules need to know their ids. Finally, + // dynamically-defined meta/operations have their ids registered + // as part of a module load. In this case, the meta/operation is + // normally (but not necessarily) fully implemented by this module. + // + // Note also that the name of a meta/operation in a sense defines + // its semantics. It would be strange to have an operation called + // test that does two very different things in different projects. + // + extern butl::string_table meta_operation_table; + extern butl::string_table operation_table; + + // These are "sparse" in the sense that we may have "holes" that + // are represented as NULL pointers. Also, lookup out of bounds + // is treated as a hole. + // + template + struct sparse_vector + { + using base_type = std::vector; + using size_type = typename base_type::size_type; + + void + insert (size_type i, T& x) + { + size_type n (v_.size ()); + + if (i < n) + v_[i] = &x; + else + { + if (n != i) + v_.resize (i, nullptr); // Add holes. + v_.push_back (&x); + } + } + + T* + operator[] (size_type i) const + { + return i < v_.size () ? v_[i] : nullptr; + } + + bool + empty () const {return v_.empty ();} + + // Note that this is more of a "max index" rather than size. + // + size_type + size () const {return v_.size ();} + + private: + base_type v_; + }; + + using meta_operations = sparse_vector; + using operations = sparse_vector; +} + +#endif // BUILD2_OPERATION -- cgit v1.1