// 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 #include #include #include #include #include #include #include #include #include #include #include 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 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 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 find_override (const variable&, pair 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 value& assign (string name) { return vars.assign (variable_pool::instance.insert (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>; 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 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> 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, bool> derive_target_type (const string& name, const target_type& base); template pair, 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 pre; function post; }; using operation_callback_map = std::multimap; 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_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 T* lookup_module (const string& name) const { return root_extra->modules.lookup (name); } public: // RW access. // scope& rw () const { assert (phase == run_phase::load); return const_cast (*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 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 ().representation (); // 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; 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 (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 (p)); } // RW access. // public: scope_map& rw () const { assert (phase == run_phase::load); return const_cast (*this); } scope_map& rw (scope&) const {return const_cast (*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 #endif // LIBBUILD2_SCOPE_HXX