diff options
Diffstat (limited to 'libbuild2/scope.hxx')
-rw-r--r-- | libbuild2/scope.hxx | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx new file mode 100644 index 0000000..7b4fec5 --- /dev/null +++ b/libbuild2/scope.hxx @@ -0,0 +1,471 @@ +// file : libbuild2/scope.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_SCOPE_HXX +#define LIBBUILD2_SCOPE_HXX + +#include <map> +#include <unordered_set> + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/module.hxx> +#include <libbuild2/variable.hxx> +#include <libbuild2/target-key.hxx> +#include <libbuild2/target-type.hxx> +#include <libbuild2/target-state.hxx> +#include <libbuild2/rule-map.hxx> +#include <libbuild2/operation.hxx> + +#include <libbuild2/export.hxx> + +namespace build2 +{ + class dir; + + class LIBBUILD2_SYMEXPORT scope + { + public: + // Absolute and normalized. + // + const dir_path& out_path () const {return *out_path_;} + const dir_path& src_path () const {return *src_path_;} + + // The first is a pointer to the key in scope_map. The second is a pointer + // to the src_root/base variable value, if any (i.e., it can be NULL). + // + const dir_path* out_path_ = nullptr; + const dir_path* src_path_ = nullptr; + + bool + root () const {return root_ == this;} + + scope* parent_scope () {return parent_;} + const scope* parent_scope () const {return parent_;} + + // Root scope of this scope or NULL if this scope is not (yet) + // in any (known) project. Note that if the scope itself is + // root, then this function return this. To get to the outer + // root, query the root scope of the parent. + // + scope* root_scope () {return root_;} + const scope* root_scope () const {return root_;} + + // Root scope of a strong amalgamation of this scope or NULL if + // this scope is not (yet) in any (known) project. If there is + // no strong amalgamation, then this function returns the root + // scope of the project (in other words, in this case a project + // is treated as its own strong amalgamation). + // + scope* strong_scope (); + const scope* strong_scope () const; + + // Root scope of the outermost amalgamation or NULL if this scope is not + // (yet) in any (known) project. If there is no amalgamation, then this + // function returns the root scope of the project (in other words, in this + // case a project is treated as its own amalgamation). + // + scope* weak_scope (); + const scope* weak_scope () const; + + // Return true if the specified root scope is a sub-scope of this root + // scope. Note that both scopes must be root. + // + bool + sub_root (const scope&) const; + + // Variables. + // + public: + variable_map vars; + + // Lookup, including in outer scopes. If you only want to lookup in this + // scope, do it on the the variables 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 include target type/pattern-specific variables. + // + lookup + find (const variable& var, const target_key& tk) const + { + return find (var, tk.type, tk.name).first; + } + + lookup + find (const variable& var, const target_type& tt, const string& tn) const + { + return find (var, &tt, &tn).first; + } + + pair<lookup, size_t> + find (const variable& var, + const target_type* tt = nullptr, + const string* tn = nullptr) const + { + auto p (find_original (var, tt, tn)); + return var.overrides == nullptr ? p : find_override (var, move (p)); + } + + // Implementation details (used by scope target lookup). The start_depth + // can be used to skip a number of initial lookups. + // + pair<lookup, size_t> + find_original ( + const variable&, + const target_type* tt = nullptr, const string* tn = nullptr, + const target_type* gt = nullptr, const string* gn = nullptr, + size_t start_depth = 1) const; + + pair<lookup, size_t> + find_override (const variable&, + pair<lookup, size_t> original, + bool target = false, + bool rule = false) const; + + // Return a value suitable for assignment (or append if you only want to + // append to the value from this scope). If the value does not exist in + // this scope's map, then a new one with the NULL value is added and + // returned. Otherwise the existing value is returned. + // + value& + assign (const variable& var) {return vars.assign (var);} + + value& + assign (const variable* var) {return vars.assign (var);} // For cached. + + value& + assign (string name) + { + return assign (variable_pool::instance.insert (move (name))); + } + + // Assign a typed non-overridable variable with normal visibility. + // + template <typename T> + value& + assign (string name) + { + return vars.assign (variable_pool::instance.insert<T> (move (name))); + } + + // Return a value suitable for appending. If the variable does not + // exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this + // function proceeds as assign(). + // + value& + append (const variable&); + + // Target type/pattern-specific variables. + // + variable_type_map target_vars; + + // Variable override caches. Only on project roots (in root_extra) plus a + // global one for the global scope. + // + // The key is the variable plus the innermost (scope-wise) variable map to + // which this override applies. See find_override() for details. + // + // Note: since it can be modified on any lookup (including during the + // execute phase), the cache is protected by its own mutex shard. + // + using variable_override_cache = variable_cache<pair<const variable*, + const variable_map*>>; + + static variable_override_cache global_override_cache; + + // Set of buildfiles already loaded for this scope. The included + // buildfiles are checked against the project's root scope while + // imported -- against the global scope (global_scope). + // + public: + std::unordered_set<path> buildfiles; + + // Target types. + // + public: + target_type_map target_types; + + const target_type* + find_target_type (const string&, const scope** = nullptr) const; + + // Given a target name, figure out its type, taking into account + // extensions, special names (e.g., '.' and '..'), or anything else that + // might be relevant. Process the name (in place) by extracting (and + // returning) extension, adjusting dir/leaf, etc., (note that the dir is + // not necessarily normalized). Return NULL if not found. + // + pair<const target_type*, optional<string>> + find_target_type (name&, const location&) const; + + // Dynamically derive a new target type from an existing one. Return the + // reference to the target type and an indicator of whether it was + // actually created. + // + pair<reference_wrapper<const target_type>, bool> + derive_target_type (const string& name, const target_type& base); + + template <typename T> + pair<reference_wrapper<const target_type>, bool> + derive_target_type (const string& name) + { + return derive_target_type (name, T::static_type); + } + + // Rules. + // + public: + rule_map rules; + + // Operation callbacks. + // + // An entity (module, core) can register a function that will be called + // when an action is executed on the dir{} target that corresponds to this + // scope. The pre callback is called just before the recipe and the post + // -- immediately after. The callbacks are only called if the recipe + // (including noop recipe) is executed for the corresponding target. The + // callbacks should only be registered during the load phase. + // + // It only makes sense for callbacks to return target_state changed or + // unchanged and to throw failed in case of an error. These pre/post + // states will be merged with the recipe state and become the target + // state. See execute_recipe() for details. + // + public: + struct operation_callback + { + using callback = target_state (action, const scope&, const dir&); + + function<callback> pre; + function<callback> post; + }; + + using operation_callback_map = std::multimap<action_id, + operation_callback>; + + operation_callback_map operation_callbacks; + + // Extra root scope-only data. + // + public: + struct root_data + { + bool altn; // True if using alternative build file/directory naming. + + // Build file/directory naming scheme used by this project. + // + const string& build_ext; // build or build2 (no dot) + const dir_path& build_dir; // build/ or build2/ + const path& buildfile_file; // buildfile or build2file + const path& buildignore_file; // buildignore or build2ignore + + const dir_path& root_dir; // build[2]/root/ + const dir_path& bootstrap_dir; // build[2]/bootstrap/ + + const path& bootstrap_file; // build[2]/bootstrap.build[2] + const path& root_file; // build[2]/root.build[2] + const path& export_file; // build[2]/export.build[2] + const path& src_root_file; // build[2]/bootstrap/src-root.build[2] + const path& out_root_file; // build[2]/bootstrap/src-root.build[2] + + // Meta/operations supported by this project. + // + build2::meta_operations meta_operations; + build2::operations operations; + + // Modules. + // + loaded_module_map modules; + + // Variable override cache (see above). + // + mutable variable_override_cache override_cache; + }; + + unique_ptr<root_data> root_extra; + + void + insert_operation (operation_id id, const operation_info& in) + { + root_extra->operations.insert (id, in); + } + + void + insert_meta_operation (meta_operation_id id, const meta_operation_info& in) + { + root_extra->meta_operations.insert (id, in); + } + + template <typename T> + T* + lookup_module (const string& name) const + { + return root_extra->modules.lookup<T> (name); + } + + public: + // RW access. + // + scope& + rw () const + { + assert (phase == run_phase::load); + return const_cast<scope&> (*this); + } + + // RW access to global scope (RO via global global_scope below). + // + scope& + global () {return *global_;} + + public: + static scope* global_; // Normally not accessed directly. + + private: + friend class parser; + friend class scope_map; + friend class temp_scope; + + // These two from <libbuild2/file.hxx> set strong_. + // + friend LIBBUILD2_SYMEXPORT void create_bootstrap_outer (scope&); + friend LIBBUILD2_SYMEXPORT scope& create_bootstrap_inner (scope&, + const dir_path&); + + explicit + scope (bool global): vars (global), target_vars (global) {} + + scope* parent_; + scope* root_; + scope* strong_ = nullptr; // Only set on root scopes. + // NULL means no strong amalgamtion. + }; + + inline ostream& + operator<< (ostream& os, const scope& s) + { + return os << s.out_path ().string (); // Always absolute. + } + + // Temporary scope. The idea is to be able to create a temporary scope in + // order not to change the variables in the current scope. Such a scope is + // not entered in to the scope map. As a result it can only be used as a + // temporary set of variables. In particular, defining targets directly in + // such a scope will surely end up badly. Defining any nested scopes will be + // as if defining such a scope in the parent (since path() returns parent's + // path). + // + class temp_scope: public scope + { + public: + temp_scope (scope& p) + : scope (false) // Not global. + { + out_path_ = p.out_path_; + src_path_ = p.src_path_; + parent_ = &p; + root_ = p.root_; + // No need to copy strong_ since we are never root scope. + } + }; + + // Scope map. + // + // Protected by the phase mutex. Note that the scope map is only for paths + // from the out tree. + // + using scope_map_base = dir_path_map<scope>; + + class scope_map: public scope_map_base + { + public: + // Note that we assume the first insertion into the map is always the + // global scope with empty key. + // + LIBBUILD2_SYMEXPORT iterator + insert (const dir_path&, bool root = false); + + // Find the most qualified scope that encompasses this path. + // + const scope& + find (const dir_path& d) const + { + return const_cast<scope_map*> (this)->find (d); + } + + const scope& + find (const path& p) const + { + // Natural thing to do here would be to call find (p.directory ()). + // However, there could be a situation where the passed path is a + // directory (i.e., the calling code does not know what it is dealing + // with), so let's use the whole path. + // + // In fact, ideally, we should have used path_map instead of + // dir_path_map to be able to search for both paths without any casting + // (and copies). But currently we have too much stuff pointing to the + // key. + // + return find (path_cast<dir_path> (p)); + } + + // RW access. + // + public: + scope_map& + rw () const + { + assert (phase == run_phase::load); + return const_cast<scope_map&> (*this); + } + + scope_map& + rw (scope&) const {return const_cast<scope_map&> (*this);} + + private: + LIBBUILD2_SYMEXPORT static scope_map instance; + + // Entities that can access bypassing the lock proof. + // + friend int main (int, char*[]); + friend LIBBUILD2_SYMEXPORT variable_overrides reset (const strings&); + + LIBBUILD2_SYMEXPORT scope& + find (const dir_path&); + + public: + // For var_pool initialization. + // + LIBBUILD2_SYMEXPORT static const scope_map& cinstance; + }; + + LIBBUILD2_SYMEXPORT extern const scope_map& scopes; + LIBBUILD2_SYMEXPORT extern const scope* global_scope; +} + +#include <libbuild2/scope.ixx> + +#endif // LIBBUILD2_SCOPE_HXX |