diff options
Diffstat (limited to 'libbuild2/context.hxx')
-rw-r--r-- | libbuild2/context.hxx | 387 |
1 files changed, 317 insertions, 70 deletions
diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index c4d85c9..33fc892 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -21,13 +21,14 @@ namespace build2 { class file_cache; - class loaded_modules_lock; + class module_libraries_lock; class LIBBUILD2_SYMEXPORT run_phase_mutex { public: // Acquire a phase lock potentially blocking (unless already in the // desired phase) until switching to the desired phase is possible. + // Return false on failure. // bool lock (run_phase); @@ -38,11 +39,22 @@ namespace build2 void unlock (run_phase); - // Switch from one phase to another. + // Switch from one phase to another. Return nullopt on failure (so can be + // used as bool), true if switched from a different phase, and false if + // joined/switched to the same phase (this, for example, can be used to + // decide if a phase switching housekeeping is really necessary). Note: + // currently only implemented for the load phase (always returns true + // for the others). // - bool + optional<bool> relock (run_phase unlock, run_phase lock); + // Statistics. + // + public: + size_t contention = 0; // # of contentious phase (re)locks. + size_t contention_load = 0; // # of contentious load phase locks. + private: friend class context; @@ -61,7 +73,7 @@ namespace build2 // is exclusive so we have a separate mutex to serialize it (think of it // as a second level locking). // - // When the mutex is unlocked (all three counters become zero, the phase + // When the mutex is unlocked (all three counters become zero), the phase // is always changed to load (this is also the initial state). // context& ctx_; @@ -94,8 +106,28 @@ namespace build2 explicit global_mutexes (size_t vc) - : variable_cache_size (vc), - variable_cache (new shared_mutex[variable_cache_size]) {} + { + init (vc); + } + + global_mutexes () = default; // Create uninitialized instance. + + void + init (size_t vc) + { + variable_cache_size = vc; + variable_cache.reset (new shared_mutex[vc]); + } + }; + + // Match-only level. + // + // See the --match-only and --load-only options for background. + // + enum class match_only_level + { + alias, // Match only alias{} targets. + all // Match all targets. }; // A build context encapsulates the state of a build. It is possible to have @@ -120,9 +152,9 @@ namespace build2 // instead go the multiple communicating schedulers route, a la the job // server). // - // The loaded_modules state (module.hxx) is shared among all the contexts + // The module_libraries state (module.hxx) is shared among all the contexts // (there is no way to have multiple shared library loading "contexts") and - // is protected by loaded_modules_lock. A nested context should normally + // is protected by module_libraries_lock. A nested context should normally // inherit this lock value from its outer context. // // Note also that any given thread should not participate in multiple @@ -138,17 +170,66 @@ namespace build2 // class LIBBUILD2_SYMEXPORT context { + public: + // In order to perform each operation the build system goes through the + // following phases: + // + // load - load the buildfiles + // match - search prerequisites and match rules + // execute - execute the matched rule + // + // The build system starts with a "serial load" phase and then continues + // with parallel match and execute. Match, however, can be interrupted + // both with load and execute. + // + // Match can be interrupted with "exclusive load" in order to load + // additional buildfiles. Similarly, it can be interrupted with (parallel) + // execute in order to build targetd required to complete the match (for + // example, generated source code or source code generators themselves). + // + // Such interruptions are performed by phase change that is protected by + // phase_mutex (which is also used to synchronize the state changes + // between phases). + // + // Serial load can perform arbitrary changes to the build state. Exclusive + // load, however, can only perform "island appends". That is, it can + // create new "nodes" (variables, scopes, etc) but not (semantically) + // change already existing nodes or invalidate any references to such (the + // idea here is that one should be able to load additional buildfiles as + // long as they don't interfere with the existing build state). The + // "islands" are identified by the load_generation number (1 for the + // initial/serial load). It is incremented in case of a phase switch and + // can be stored in various "nodes" to verify modifications are only done + // "within the islands". Another example of invalidation would be + // insertion of a new scope "under" an existing target thus changing its + // scope hierarchy (and potentially even its base scope). This would be + // bad because we may have made decisions based on the original hierarchy, + // for example, we may have queried a variable which in the new hierarchy + // would "see" a new value from the newly inserted scope. + // + // The special load_generation value 0 indicates initialization before + // anything has been loaded. Currently, it is changed to 1 at the end + // of the context constructor. + // + // Note must come (and thus initialized) before the data_ member. + // + run_phase phase = run_phase::load; + size_t load_generation = 0; + + private: struct data; unique_ptr<data> data_; public: - scheduler& sched; - global_mutexes& mutexes; - file_cache& fcache; + // These are only NULL for the "bare minimum" context (see below). + // + scheduler* sched; + global_mutexes* mutexes; + file_cache* fcache; - // Match only flag (see --match-only but also dist). + // Match only flag/level (see --{load,match}-only but also dist). // - bool match_only; + optional<match_only_level> match_only; // Skip booting external modules flag (see --no-external-modules). // @@ -189,6 +270,10 @@ namespace build2 bool dry_run = false; bool dry_run_option; + // Diagnostics buffering flag (--no-diag-buffer). + // + bool no_diag_buffer; + // Keep going flag. // // Note that setting it to false is not of much help unless we are running @@ -197,39 +282,13 @@ namespace build2 // bool keep_going; - // In order to perform each operation the build system goes through the - // following phases: - // - // load - load the buildfiles - // match - search prerequisites and match rules - // execute - execute the matched rule - // - // The build system starts with a "serial load" phase and then continues - // with parallel match and execute. Match, however, can be interrupted - // both with load and execute. - // - // Match can be interrupted with "exclusive load" in order to load - // additional buildfiles. Similarly, it can be interrupted with (parallel) - // execute in order to build targetd required to complete the match (for - // example, generated source code or source code generators themselves). - // - // Such interruptions are performed by phase change that is protected by - // phase_mutex (which is also used to synchronize the state changes - // between phases). + // Targets to trace (see the --trace-* options). // - // Serial load can perform arbitrary changes to the build state. Exclusive - // load, however, can only perform "island appends". That is, it can - // create new "nodes" (variables, scopes, etc) but not (semantically) - // change already existing nodes or invalidate any references to such (the - // idea here is that one should be able to load additional buildfiles as - // long as they don't interfere with the existing build state). The - // "islands" are identified by the load_generation number (0 for the - // initial/serial load). It is incremented in case of a phase switch and - // can be stored in various "nodes" to verify modifications are only done - // "within the islands". + // Note that these must be set after construction and must remain valid + // for the lifetime of the context instance. // - run_phase phase = run_phase::load; - size_t load_generation = 0; + const vector<name>* trace_match = nullptr; + const vector<name>* trace_execute = nullptr; // A "tri-mutex" that keeps all the threads in one of the three phases. // When a thread wants to switch a phase, it has to wait for all the other @@ -270,6 +329,7 @@ namespace build2 string current_oname; const meta_operation_info* current_mif; + const operation_info* current_inner_oif; const operation_info* current_outer_oif; @@ -291,6 +351,22 @@ namespace build2 (current_mname.empty () && current_oname == mo)); }; + // Meta/operation-specific context-global auxiliary data storage. + // + // Note: cleared by current_[meta_]operation() below. Normally set by + // meta/operation-specific callbacks from [mate_]operation_info. + // + // Note also: watch out for MT-safety in the data itself. + // + static void + null_current_data_deleter (void* p) { assert (p == nullptr); } + + using current_data_ptr = unique_ptr<void, void (*) (void*)>; + + current_data_ptr current_mdata = {nullptr, null_current_data_deleter}; + current_data_ptr current_inner_odata = {nullptr, null_current_data_deleter}; + current_data_ptr current_outer_odata = {nullptr, null_current_data_deleter}; + // Current operation number (1-based) in the meta-operation batch. // size_t current_on; @@ -329,20 +405,47 @@ namespace build2 // decremented after such recipe has been executed. If such a recipe has // skipped executing the operation, then it should increment the skip // count. These two counters are used for progress monitoring and - // diagnostics. + // diagnostics. The resolve count keeps track of the number of targets + // matched but not executed as a result of the resolve_members() calls + // (see also target::resolve_counted). // atomic_count dependency_count; atomic_count target_count; atomic_count skip_count; + atomic_count resolve_count; // Build state (scopes, targets, variables, etc). // const scope_map& scopes; target_set& targets; - const variable_pool& var_pool; + const variable_pool& var_pool; // Public variables pool. + const variable_patterns& var_patterns; // Public variables patterns. const variable_overrides& var_overrides; // Project and relative scope. function_map& functions; + // Current targets with post hoc prerequisites. + // + // Note that we don't expect many of these so a simple mutex should be + // sufficient. Note also that we may end up adding more entries as we + // match existing so use list for node and iterator stability. See + // match_poshoc() for details. + // + struct posthoc_target + { + struct prerequisite_target + { + const build2::target* target; + uint64_t match_options; + }; + + build2::action action; + reference_wrapper<const build2::target> target; + vector<prerequisite_target> prerequisite_targets; + }; + + list<posthoc_target> current_posthoc_targets; + mutex current_posthoc_targets_mutex; + // Global scope. // const scope& global_scope; @@ -350,6 +453,10 @@ namespace build2 variable_override_cache& global_override_cache; const strings& global_var_overrides; + // Cached values (from global scope). + // + const target_triplet* build_host; // build.host + // Cached variables. // @@ -379,8 +486,8 @@ namespace build2 const variable* var_import_build2; const variable* var_import_target; - // The import.metadata variable and the --build2-metadata option are used - // to pass the metadata compatibility version. + // The import.metadata export stub variable and the --build2-metadata + // executable option are used to pass the metadata compatibility version. // // This serves both as an indication that the metadata is required (can be // useful, for example, in cases where it is expensive to calculate) as @@ -392,7 +499,8 @@ namespace build2 // The export.metadata value should start with the version followed by the // metadata variable prefix (for example, cli in cli.version). // - // The following metadata variable names have pre-defined meaning: + // The following metadata variable names have pre-defined meaning for + // executable targets (exe{}; see also process_path_ex): // // <var-prefix>.name = [string] # Stable name for diagnostics. // <var-prefix>.version = [string] # Version for diagnostics. @@ -402,7 +510,8 @@ namespace build2 // If the <var-prefix>.name variable is missing, it is set to the target // name as imported. // - // See also process_path_ex. + // Note that the same mechanism is used for library user metadata (see + // cc::pkgconfig_{load,save}() for details). // const variable* var_import_metadata; const variable* var_export_metadata; @@ -411,6 +520,21 @@ namespace build2 // const variable* var_extension; + // This variable can only be specified as prerequisite-specific (see the + // `include` variable for details). + // + // [string] prerequisite visibility + // + // Valid values are `true` and `false`. Additionally, some rules (and + // potentially only for certain types of prerequisites) may support the + // `unmatch` (match but do not update, if possible), `match` (update + // during match), and `execute` (update during execute, as is normally) + // values (the `execute` value may be useful if the rule has the `match` + // semantics by default). Note that if unmatch is impossible, then the + // prerequisite is treated as ad hoc. + // + const variable* var_update; + // Note that this variable can also be specified as prerequisite-specific // (see the `include` variable for details). // @@ -418,18 +542,51 @@ namespace build2 // const variable* var_clean; - // Forwarded configuration backlink mode. Valid values are: + // Forwarded configuration backlink mode. The value has two components + // in the form: + // + // <mode> [<print>] + // + // Valid <mode> values are: // // false - no link. // true - make a link using appropriate mechanism. // symbolic - make a symbolic link. // hard - make a hard link. // copy - make a copy. - // overwrite - copy over but don't remove on clean (committed gen code). + // overwrite - copy over but don't remove on clean. + // group - inherit the group mode (only valid for group members). // - // Note that it can be set by a matching rule as a rule-specific variable. + // While the <print> component should be either true or false and can be + // used to suppress printing of specific ad hoc group members at verbosity + // level 1. Note that it cannot be false for the primary member. // - // [string] target visibility + // Note that this value can be set by a matching rule as a rule-specific + // variable. + // + // Note also that the overwrite mode was originally meant for handling + // pregenerated source code. But in the end this did not pan out for + // the following reasons: + // + // 1. This would mean that the pregenerated and regenerated files end up + // in the same place (e.g., depending on the develop mode) and it's + // hard to make this work without resorting to a conditional graph. + // + // This could potentially be addressed by allowing backlink to specify + // a different location (similar to dist). + // + // 2. This support for pregenerated source code would be tied to forwarded + // configurations. + // + // Nevertheless, there may be a kernel of an idea here in that we may be + // able to provide a built-in "post-copy" mechanism which would allow one + // to have a pregenerated setup even when using non-ad hoc recipes + // (currently we just manually diff/copy stuff at the end of a recipe). + // (Or maybe we should stick to ad hoc recipes with post-diff/copy and + // just expose a mechanism to delegate to a different rule, which we + // already have). + // + // [names] target visibility // const variable* var_backlink; @@ -456,14 +613,19 @@ namespace build2 // Sometimes it may be desirable to apply exclusions only to specific // operations. The initial idea was to extend this value to allow // specifying the operation (e.g., clean@false). However, later we - // realized that we could reuse the "operation variables" (clean, install, - // test) with a more natural-looking result. Note that currently we only - // recognize the built-in clean variable (for other variables we will need - // some kind of registration in an operation-to-variable map, probably in - // root scope). See also install::file_rule::filter(). + // realized that we could reuse the "operation-specific variables" + // (update, clean, install, test; see project_operation_info) with a more + // natural-looking and composable result. Plus, this allows for + // operation-specific "modifiers", for example, "unmatch" and "update + // during match" logic for update (see var_update for details) or + // requiring explicit install=true to install exe{} prerequisites (see + // install::file_rule::filter()). // - // To query this value in rule implementations use the include() helpers - // from <libbuild2/prerequisites.hxx>. + // To query this value and its operation-specific override if any, the + // rule implementations use the include() helper. + // + // Note that there are also related (but quite different) for_<operation> + // variables for operations that act as outer (e.g., test, install). // // [string] prereq visibility // @@ -480,14 +642,34 @@ namespace build2 build2::meta_operation_table meta_operation_table; build2::operation_table operation_table; + // Import cache (see import_load()). + // + struct import_key + { + dir_path out_root; // Imported project's out root. + name target; // Imported target (unqualified). + uint64_t metadata; // Metadata version (0 if none). + + friend bool + operator< (const import_key& x, const import_key& y) + { + int r; + return ((r = x.out_root.compare (y.out_root)) != 0 ? r < 0 : + (r = x.target.compare (y.target)) != 0 ? r < 0 : + x.metadata < y.metadata); + } + }; + + map<import_key, pair<names, const scope&>> import_cache; + // The old/new src_root remapping for subprojects. // dir_path old_src_root; dir_path new_src_root; - // NULL if this context hasn't already locked the loaded_modules state. + // NULL if this context hasn't already locked the module_libraries state. // - const loaded_modules_lock* modules_lock; + const module_libraries_lock* modules_lock; // Nested context for updating build system modules and ad hoc recipes. // @@ -504,17 +686,76 @@ namespace build2 // properly setup context (including, normally, a self-reference in // modules_context). // - explicit + // The var_override_function callback can be used to parse ad hoc project- + // wide variable overrides (see parse_variable_override()). This has to + // happen at a specific point during context construction (see the + // implementation for details). + // + // Note: see also the trace_* data members that, if needed, must be set + // separately, after construction. + // + struct reserves + { + size_t targets; + size_t variables; + + reserves (): targets (0), variables (0) {} + reserves (size_t t, size_t v): targets (t), variables (v) {} + }; + + using var_override_function = void (context&, size_t&); + context (scheduler&, global_mutexes&, file_cache&, - bool match_only = false, + optional<match_only_level> match_only = nullopt, bool no_external_modules = false, bool dry_run = false, + bool no_diag_buffer = false, bool keep_going = true, const strings& cmd_vars = {}, + reserves = {0, 160}, optional<context*> module_context = nullptr, - const loaded_modules_lock* inherited_mudules_lock = nullptr); + const module_libraries_lock* inherited_modules_lock = nullptr, + const function<var_override_function>& = nullptr); + + // Special context with bare minimum of initializations. It is only + // guaranteed to be sufficiently initialized to call extract_variable(). + // + // Note that for this purpose you may omit calls to init_diag() and + // init(). + // + context (); + + // Reserve elements in containers to avoid re-allocation/re-hashing. Zero + // values are ignored (that is, the corresponding container reserve() + // function is not called). Can only be called in the load phase. + // + void + reserve (reserves); + + // Parse a variable override returning its type in the first half of the + // pair. Index is the variable index (used to derive unique name) and if + // buildspec is true then assume `--` is used as a separator between + // variables and buildscpec and issue appropriate diagnostics. + // + // Note: should only be called from the var_override_function constructor + // callback. + // + pair<char, variable_override> + parse_variable_override (const string& var, size_t index, bool buildspec); + + // Enter project-wide (as opposed to global) variable overrides. + // + // If the amalgamation scope is specified, then use it instead of + // rs.weak_scope() to set overrides with global visibility (make sure you + // understand the implications before doing this). + // + void + enter_project_overrides (scope& rs, + const dir_path& out_base, + const variable_overrides&, + scope* amalgamation = nullptr); // Set current meta-operation and operation. // @@ -608,14 +849,20 @@ namespace build2 // struct LIBBUILD2_SYMEXPORT phase_unlock { - phase_unlock (context&, bool unlock = true, bool delay = false); + explicit phase_unlock (context*, bool delay = false); + explicit phase_unlock (context& ctx, bool delay = false) + : phase_unlock (&ctx, delay) {} + ~phase_unlock () noexcept (false); void unlock (); + void + lock (); + context* ctx; - phase_lock* lock; + phase_lock* lock_; }; // Assuming we have a lock on the current phase, temporarily switch to a @@ -661,8 +908,8 @@ namespace build2 // Note: move-assignable to empty only. // - wait_guard (wait_guard&&); - wait_guard& operator= (wait_guard&&); + wait_guard (wait_guard&&) noexcept; + wait_guard& operator= (wait_guard&&) noexcept; wait_guard (const wait_guard&) = delete; wait_guard& operator= (const wait_guard&) = delete; |