aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/scope.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/scope.hxx')
-rw-r--r--libbuild2/scope.hxx416
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 {&gt, 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_;
};
}