diff options
Diffstat (limited to 'libbuild2/scope.hxx')
-rw-r--r-- | libbuild2/scope.hxx | 416 |
1 files changed, 316 insertions, 100 deletions
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index d441267..09d61e9 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -4,9 +4,6 @@ #ifndef LIBBUILD2_SCOPE_HXX #define LIBBUILD2_SCOPE_HXX -#include <map> -#include <unordered_set> - #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> #include <libbuild2/utility.hxx> @@ -27,10 +24,14 @@ namespace build2 { class dir; - using subprojects = std::map<project_name, dir_path>; + using subprojects = map<project_name, dir_path>; + // Print as name@dir sequence. + // + // Note: trailing slash is not printed for the directory path. + // LIBBUILD2_SYMEXPORT ostream& - operator<< (ostream&, const subprojects&); // Print as name@dir sequence. + operator<< (ostream&, const subprojects&); class LIBBUILD2_SYMEXPORT scope { @@ -44,8 +45,10 @@ namespace build2 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). + bool out_eq_src () const {return out_path_ == src_path_;} + + // These are pointers to the keys in scope_map. The second can be NULL + // during bootstrap until initialized. // const dir_path* out_path_ = nullptr; const dir_path* src_path_ = nullptr; @@ -68,6 +71,15 @@ namespace build2 const scope* root_scope () const; // Root scope of the outermost "strong" (source-based) amalgamation of + // this scope that has a project name or NULL if this scope is not (yet) + // in any (known) project. If there is no bundle amalgamation, then this + // function returns the root scope of the project (in other words, in this + // case a project is treated as its own bundle, even if it's unnamed). + // + scope* bundle_scope (); + const scope* bundle_scope () const; + + // Root scope of the outermost "strong" (source-based) 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 @@ -89,8 +101,8 @@ namespace build2 scope& global_scope () {return const_cast<scope&> (ctx.global_scope);} const scope& global_scope () const {return ctx.global_scope;} - // Return true if the specified root scope is a sub-scope of this root - // scope. Note that both scopes must be root. + // Return true if the specified root scope is a sub-scope of (but not the + // same as) this root scope. Note that both scopes must be root. // bool sub_root (const scope&) const; @@ -122,7 +134,7 @@ namespace build2 lookup_type operator[] (const string& name) const { - const variable* var (ctx.var_pool.find (name)); + const variable* var (var_pool ().find (name)); return var != nullptr ? operator[] (*var) : lookup_type (); } @@ -131,21 +143,46 @@ namespace build2 lookup_type lookup (const variable& var, const target_key& tk) const { - return lookup (var, tk.type, tk.name).first; + return lookup (var, &tk).first; + } + + lookup_type + lookup (const variable& var, + const target_key& tk, + const target_key& gk) const + { + return lookup (var, &tk, &gk).first; } + // Note for dir{} and fsdir{} target name is the directory leaf (without + // the trailing slash). Also, if extension is to be matched (for this + // target type), then it should be included in the name. + // lookup_type lookup (const variable& var, const target_type& tt, const string& tn) const { - return lookup (var, &tt, &tn).first; + return lookup (var, target_key {&tt, nullptr, nullptr, &tn, nullopt}); } + lookup_type + lookup (const variable& var, + const target_type& tt, const string& tn, + const target_type& gt, const string& gn) const + { + return lookup (var, + target_key {&tt, nullptr, nullptr, &tn, nullopt}, + target_key {>, nullptr, nullptr, &gn, nullopt}); + } + + // Note that target keys may be incomplete (only type and name must be + // present plus dir for dir{} and fsdir{} targets if name is empty). + // pair<lookup_type, size_t> lookup (const variable& var, - const target_type* tt = nullptr, - const string* tn = nullptr) const + const target_key* tk = nullptr, + const target_key* gk = nullptr) const { - auto p (lookup_original (var, tt, tn)); + auto p (lookup_original (var, tk, gk)); return var.overrides == nullptr ? p : lookup_override (var, move (p)); } @@ -153,11 +190,11 @@ namespace build2 // can be used to skip a number of initial lookups. // pair<lookup_type, size_t> - lookup_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; + lookup_original (const variable&, + const target_key* tk = nullptr, + const target_key* g1k = nullptr, + const target_key* g2k = nullptr, + size_t start_depth = 1) const; pair<lookup_type, size_t> lookup_override (const variable& var, @@ -263,13 +300,6 @@ namespace build2 // variable_type_map target_vars; - // 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. // // Note that target types are project-wide (even if the module that @@ -290,7 +320,7 @@ namespace build2 const target_type& insert_target_type (const target_type& tt) { - return root_extra->target_types.insert (tt); + return root_extra->target_types.insert (tt).first; } template <typename T> @@ -313,10 +343,13 @@ namespace build2 // 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. + // not necessarily normalized). If the target type is already resolved, + // then it can be passed as the last argument. Return NULL if not found. // pair<const target_type*, optional<string>> - find_target_type (name&, const location&) const; + find_target_type (name&, + const location&, + const target_type* = nullptr) const; // As above but process the potentially out-qualified target name further // by completing (relative to this scope) and normalizing the directories @@ -325,40 +358,56 @@ namespace build2 // the out directory. // pair<const target_type&, optional<string>> - find_target_type (name&, name&, const location&) const; + find_target_type (name&, name&, + const location&, + const target_type* = nullptr) const; // As above, but return the result as a target key (with its members // shallow-pointing to processed parts in the two names). // target_key - find_target_key (name&, name&, const location&) const; + find_target_key (name&, name&, + const location&, + const target_type* = nullptr) const; // As above, but the names are passed as a vector. Issue appropriate // diagnostics if the wrong number of names is passed. // target_key - find_target_key (names&, const location&) const; + find_target_key (names&, + const location&, + const target_type* = nullptr) const; // Similar to the find_target_type() but does not complete relative // directories. // pair<const target_type&, optional<string>> - find_prerequisite_type (name&, name&, const location&) const; + find_prerequisite_type (name&, name&, + const location&, + const target_type* = nullptr) const; // As above, but return a prerequisite key. // prerequisite_key - find_prerequisite_key (name&, name&, const location&) const; + find_prerequisite_key (name&, name&, + const location&, + const target_type* = nullptr) const; prerequisite_key - find_prerequisite_key (names&, const location&) const; + find_prerequisite_key (names&, + const location&, + const target_type* = nullptr) 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. // + // Note: the flags are OR'ed to the base's flags. + // pair<reference_wrapper<const target_type>, bool> - derive_target_type (const string& name, const target_type& base); + derive_target_type (const string& name, + const target_type& base, + target_type::flag flags = target_type::flag::none); template <typename T> pair<reference_wrapper<const target_type>, bool> @@ -376,22 +425,24 @@ namespace build2 // public: rule_map rules; + vector<unique_ptr<adhoc_rule_pattern>> adhoc_rules; template <typename T> void - insert_rule (action_id a, const char* hint, const rule& r) + insert_rule (action_id a, string name, const rule& r) { - rules.insert<T> (a, hint, r); + rules.insert<T> (a, move (name), r); } + // 0 meta-operation id is treated as an (emulated) wildcard. + // + // Emulated means that we just iterate over all the meta-operations known + // to this project (and they should all be known at this point) and + // register the rule for each of them. + // template <typename T> void - insert_rule (meta_operation_id mid, operation_id oid, - const char* hint, - const rule& r) - { - rules.insert<T> (mid, oid, hint, r); - } + insert_rule (meta_operation_id, operation_id, string name, const rule&); // Operation callbacks. // @@ -416,8 +467,7 @@ namespace build2 function<callback> post; }; - using operation_callback_map = std::multimap<action_id, - operation_callback>; + using operation_callback_map = multimap<action_id, operation_callback>; operation_callback_map operation_callbacks; @@ -443,9 +493,10 @@ namespace build2 // is not yet determined (happens at the end of bootstrap_src()). NULL // means there are no subprojects. // - optional<const build2::subprojects*> subprojects; + optional<build2::subprojects*> subprojects; - bool altn; // True if using alternative build file/directory naming. + bool altn; // True if using alternative build file/directory naming. + bool loaded; // True if already loaded (load_root()). // Build file/directory naming scheme used by this project. // @@ -464,14 +515,40 @@ namespace build2 const path& src_root_file; // build[2]/bootstrap/src-root.build[2] const path& out_root_file; // build[2]/bootstrap/src-root.build[2] + // Project-private variable pool. + // + // Note: see scope::var_pool_ and use scope::var_pool(). + // + variable_pool var_pool; + // Meta/operations supported by this project. // build2::meta_operations meta_operations; build2::operations operations; - // Modules loaded by this project. + // Modules imported/loaded by this project. // - module_map modules; + module_import_map imported_modules; + module_state_map loaded_modules; + + // Buildfiles already loaded for this project. + // + // We don't expect too many of them per project so let's use vector + // with linear search. + // + paths buildfiles; + + bool + insert_buildfile (const path& f) + { + bool r (find (buildfiles.begin (), + buildfiles.end (), + f) == buildfiles.end ()); + if (r) + buildfiles.push_back (f); + + return r; + } // Variable override cache. // @@ -480,33 +557,72 @@ namespace build2 // Target types. // target_type_map target_types; + + // Environment variable overrides. + // + // These overrides should be applied to the environment when running + // tools (e.g., compilers) or querying environment variables from the + // buildfiles and by the build system itself. Populated by the config + // module and is not available during bootstrap (more precisely, not + // available until before_first modules have been initialized). The list + // is either empty of NULL-terminated. + // + // See also auto_project_env below. + // + vector<const char*> environment; + + // A checksum of the above environment variables (empty if there are + // none). This can be used to take into account project environment + // when, for example, caching environment-sensitive information. + // + string environment_checksum; + + root_extra_type (scope&, bool altn); // file.cxx }; unique_ptr<root_extra_type> root_extra; + // The last argument is the operation variable (see var_include) or NULL + // if not used. + // void - insert_operation (operation_id id, const operation_info& in) + insert_operation (operation_id id, + const operation_info& in, + const variable* ovar) { - root_extra->operations.insert (id, in); + // The operation variable should have prerequisite or target visibility. + // + assert (ovar == nullptr || + (ovar->visibility == variable_visibility::prereq || + ovar->visibility == variable_visibility::target)); + + root_extra->operations.insert (id, project_operation_info {&in, ovar}); } void insert_meta_operation (meta_operation_id id, const meta_operation_info& in) { - root_extra->meta_operations.insert (id, in); + root_extra->meta_operations.insert (id, &in); } bool find_module (const string& name) const { - return root_extra->modules.find_module<module> (name) != nullptr; + return root_extra->loaded_modules.find_module<module> (name) != nullptr; } template <typename T> T* + find_module (const string& name) + { + return root_extra->loaded_modules.find_module<T> (name); + } + + template <typename T> + const T* find_module (const string& name) const { - return root_extra->modules.find_module<T> (name); + return root_extra->loaded_modules.find_module<T> (name); } public: @@ -519,10 +635,29 @@ namespace build2 return const_cast<scope&> (*this); } + // Return the project-private variable pool (which is chained to the + // public pool) unless pub is true, in which case return the public pool. + // + // You would normally go for the public pool directly as an optimization + // (for example, in the module's init()) if you know all your variables + // are qualified and thus public. + // variable_pool& - var_pool () + var_pool (bool pub = false) + { + return (pub ? ctx.var_pool : + var_pool_ != nullptr ? *var_pool_ : + root_ != nullptr ? *root_->var_pool_ : + ctx.var_pool).rw (*this); + } + + const variable_pool& + var_pool (bool pub = false) const { - return ctx.var_pool.rw (*this); + return (pub ? ctx.var_pool : + var_pool_ != nullptr ? *var_pool_ : + root_ != nullptr ? *root_->var_pool_ : + ctx.var_pool); } private: @@ -530,14 +665,14 @@ namespace build2 friend class scope_map; friend class temp_scope; - // These two from <libbuild2/file.hxx> set strong_. + // These from <libbuild2/file.hxx> set strong_. // - friend LIBBUILD2_SYMEXPORT void create_bootstrap_outer (scope&); + friend LIBBUILD2_SYMEXPORT void create_bootstrap_outer (scope&, bool); friend LIBBUILD2_SYMEXPORT scope& create_bootstrap_inner (scope&, const dir_path&); - scope (context& c, bool global) - : ctx (c), vars (c, global), target_vars (c, global) {} + scope (context&, bool shared); + ~scope (); // Return true if this root scope can be amalgamated. // @@ -551,6 +686,8 @@ namespace build2 scope* root_; scope* strong_ = nullptr; // Only set on root scopes. // NULL means no strong amalgamtion. + + variable_pool* var_pool_ = nullptr; // For temp_scope override. }; inline bool @@ -567,6 +704,23 @@ namespace build2 return to_stream (os, s.out_path (), true /* representation */); } + // Automatic project environment setup/cleanup. + // + struct auto_project_env: auto_thread_env + { + auto_project_env () = default; + + explicit + auto_project_env (nullptr_t p) // Clear current environment. + : auto_thread_env (p) {} + + explicit + auto_project_env (const scope& rs) + : auto_thread_env (rs.root_extra->environment.empty () + ? nullptr + : rs.root_extra->environment.data ()) {} + }; + // Return the src/out directory corresponding to the given out/src. The // passed directory should be a sub-directory of out/src_root. // @@ -601,66 +755,127 @@ namespace build2 // 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). + // not entered in to the scope map and its parent is the global scope. 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. // class temp_scope: public scope { public: - temp_scope (scope& p) - : scope (p.ctx, false /* global */) + temp_scope (scope& gs) + : scope (gs.ctx, false /* shared */), + var_pool_ (nullptr /* shared */, &gs.ctx.var_pool.rw (gs), nullptr) { - 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. + // Note that making this scope its own root is a bad idea. + // + root_ = nullptr; + parent_ = &gs; + out_path_ = gs.out_path_; + scope::var_pool_ = &var_pool_; } + + private: + variable_pool var_pool_; }; - // Scope map. + // Scope map. Protected by the phase mutex. // - // Protected by the phase mutex. Note that the scope map is only for paths - // from the out tree. + // While it contains both out and src paths, the latter is not available + // during bootstrap (see setup_root() and setup_base() for details). // - using scope_map_base = dir_path_map<scope>; - - class scope_map: public scope_map_base + // Note also that the same src path can be naturally associated with + // multiple out paths/scopes (and one of them may be the same as src). + // + class scope_map { public: + // The first element, if not NULL, is for the "owning" out path. The rest + // of the elements are for the src path shallow references. + // + // Note that the global scope is in the first element. + // + struct scopes: small_vector<scope*, 3> + { + scopes () = default; + ~scopes () {if (!empty ()) delete front ();} + + scopes (scopes&&) = default; // For GCC 4.9 + scopes (const scopes&) = delete; + scopes& operator= (scopes&&) = delete; + scopes& operator= (const scopes&) = delete; + }; + + using map_type = dir_path_map<scopes>; + + using iterator = map_type::iterator; + using const_iterator = map_type::const_iterator; + + // Insert a scope given its out path. + // // 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); + insert_out (const dir_path& our_path, bool root = false); - // Find the most qualified scope that encompasses this path. + // Insert a shallow reference to the scope for its src path. // - const scope& - find (const dir_path& d) const - { - return const_cast<scope_map*> (this)->find (d); - } + LIBBUILD2_SYMEXPORT iterator + insert_src (scope&, const dir_path& src_path); + // Find the most qualified scope that encompasses this out path. + // const scope& - find (const path& p) const + find_out (const dir_path& d) 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)); + return const_cast<scope_map*> (this)->find_out (d); } + // Find all the scopes that encompass this path (out or src). + // + // If skip_null_out is false, then the first element always corresponds to + // the out scope and is NULL if there is none (see struct scopes above for + // details). + // + // Note that the returned range will never be empty (there is always the + // global scope). + // + // If the path is in src, then we may end up with multiple scopes. For + // example, if two configurations of the same project are being built in a + // single invocation. How can we pick the scope that is "ours", for some + // definition of "ours"? + // + // The current thinking is that a project can be "associated" with other + // projects: its sub-projects and imported projects (it doesn't feel like + // its super-projects should be in this set, but maybe). And "ours" could + // mean belonging to one of the associated projects. This feels correct + // since a project shouldn't really be reaching into unrelated projects. + // And a project can only import one instance of any given project. + // + // We could implement this by keeping track (in scope::root_extra) of all + // the imported projects. The potential problem is performance: we would + // need to traverse the imported projects set recursively (potentially + // re-traversing the same projects multiple times). + // + // An alternative idea is to tag associated scopes with some marker so + // that all the scopes that "know" about each other have the same tag, + // essentially partitioning the scope set into connected subsets. One + // issue here (other than the complexity of implementing something like + // this) is that there could potentially be multiple source scopes with + // the same tag (e.g., two projects that don't know anything about each + // other could each import a different configuration of some common + // project and in turn be both imported by yet another project thus all + // acquiring the same tag). BTW, this could also be related to that + // "island append" restriction we have on loading additional buildfile. + // + LIBBUILD2_SYMEXPORT pair<scopes::const_iterator, scopes::const_iterator> + find (const dir_path&, bool skip_null_out = true) const; + + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + const_iterator find_exact (const dir_path& d) const {return map_.find (d);} + // RW access. // public: @@ -681,10 +896,11 @@ namespace build2 scope_map (context& c): ctx (c) {} LIBBUILD2_SYMEXPORT scope& - find (const dir_path&); + find_out (const dir_path&); private: context& ctx; + map_type map_; }; } |