From def2c2dfaf5374f139b310c4f05b0614cb99359e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 28 Mar 2022 15:04:35 +0200 Subject: Implement dependency configuration negotiation For the detailed history see the dep-config and dep-config-neg branches. --- bpkg/package-skeleton.hxx | 275 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 249 insertions(+), 26 deletions(-) (limited to 'bpkg/package-skeleton.hxx') diff --git a/bpkg/package-skeleton.hxx b/bpkg/package-skeleton.hxx index aaa9b8c..7224d1d 100644 --- a/bpkg/package-skeleton.hxx +++ b/bpkg/package-skeleton.hxx @@ -4,12 +4,13 @@ #ifndef BPKG_PACKAGE_SKELETON_HXX #define BPKG_PACKAGE_SKELETON_HXX -#include // build2::context +#include #include #include #include +#include #include namespace bpkg @@ -20,6 +21,12 @@ namespace bpkg class package_skeleton { public: + // If the package is system, then its available package should be NULL if + // it doesn't match the system package version "close enough" to be usable + // as the source of its configuration information (types, defaults). If it + // is NULL, then the skeleton can only be used to print and collect the + // configuration information. + // // If the package is external, then the existing package source root // directory needs to be specified (as absolute and normalized). In this // case, if output root is specified (as absolute and normalized; normally @@ -29,50 +36,152 @@ namespace bpkg // If the package is not external, then none of the root directories // should be specified. // - // Note that the options, database, and available_package are expected to + // The disfigure argument should indicate whether the package is being + // reconfigured from scratch (--disfigure). + // + // The config_vars argument contains configuration variables specified by + // the user in this bpkg execution. Optional config_srcs is used to + // extract (from config.build or equivalent) configuration variables + // specified by the user in previous bpkg executions. It should be NULL if + // this is the first build of the package. The extracted variables are + // merged with config_vars and the combined result is returned by + // collect_config() below. + // + // @@ TODO: speaking of the "config.build or equivalent" part, the + // equivalent is likely to be extracted configuration (probably saved + // to file in tmp somewhere) that we will load with config.config.load. + // It doesn't seem like a good idea to pass it as part of config_vars + // (because sometimes we may need to omit it) so most likely it will be + // passed as a separate arguments (likely a file path). + // + // Note that the options, database, and config_srcs are expected to // outlive this object. // // Note also that this creates an "unloaded" skeleton and is therefore // relatively cheap. // package_skeleton (const common_options& co, - database&, - const available_package&, + package_key, + bool system, + shared_ptr, strings config_vars, + bool disfigure, + const vector* config_srcs, optional src_root, optional out_root); - // For the following evaluate_*() functions assume that the clause belongs - // to the specified (by index) depends value (used to print its location - // on failure for an external package). + + package_key package; + bool system; + shared_ptr available; + + // The following functions should be called in the following sequence + // (* -- zero or more, ? -- zero or one): + // + // * reload_defaults() | verify_sensible() + // ? dependent_config() + // * evaluate_*() + // * empty() | print_config() + // collect_config() + // + // Note that a copy of the skeleton is expected to continue with the + // sequence rather than starting from scratch, unless reset() is called. + // + public: + // Reload the default values and type information for configuration + // variables using the values with the buildfile origin as a "tentative" + // dependent configuration. + // + void + reload_defaults (package_configuration&); + + // Load overrides for a system package without skeleton info. Note that + // this is done in an ad hoc manner and only to support evaluate_require() + // semantics (see the implementation for details). + // + void + load_overrides (package_configuration&); + + // Verify the specified "tentative" dependent configuration is sensible, + // that is, acceptable to the dependency itself. If it is not, then the + // second half of the result contains the diagnostics. + // + pair + verify_sensible (const package_configuration&); + + // Incorporate the "final" dependent configuration into subsequent + // evaluations. Dependent configuration variables are expected not to + // clash with user. // + void + dependent_config (const package_configuration&); + + // For the following evaluate_*() functions assume that the clause belongs + // to the dependency alternative specified as a pair of indexes (depends + // value index and alternative index). + // Evaluate the enable clause. // bool - evaluate_enable (const string&, size_t depends_index); + evaluate_enable (const string&, pair); // Evaluate the reflect clause. // void - evaluate_reflect (const string&, size_t depends_index); + evaluate_reflect (const string&, pair); + + // Evaluate the prefer/accept or require clauses on the specified + // dependency configurations (serves as both input and output). + // + // Return true is acceptable and false otherwise. If acceptable, the + // passed configuration is updated with new values, if any. + // + using dependency_configurations = + small_vector, 1>; + + bool + evaluate_prefer_accept (const dependency_configurations&, + const string&, const string&, pair); + + bool + evaluate_require (const dependency_configurations&, + const string&, pair); + + // Reset the skeleton to the start of the call sequence. + // + // Note that this function cannot be called after collect_config(). + // + void + reset (); - // Return the accumulated reflect values together with their sources (the - // resulting vectors are parallel). + // Return true if there are no accumulated *project* configuration + // variables that will be printed by print_config(). // - // Note that the result is merged with config_vars and should be used - // instead rather than in addition to config_vars. The source of - // configuration variables which are not the project variables is absent. + bool + empty_print (); + + // Print the accumulated *project* configuration variables as command line + // overrides one per line with the specified indentation. + // + void + print_config (ostream&, const char* indent); + + // Return the accumulated configuration variables (first) and project + // configuration variable sources (second). Note that the arrays are not + // necessarily parallel (config_vars may contain non-project variables). + // + // Note that the dependent and reflect variables are merged with + // config_vars/config_srcs and should be used instead rather than in + // addition to config_vars. // // Note also that this should be the final call on this object. // - pair>> + pair> collect_config () &&; - const package_name& - name () const {return available_->id.name;} - // Implementation details. // + public: // We have to define these because context is forward-declared. Also, copy // constructor has some special logic. // @@ -84,32 +193,146 @@ namespace bpkg package_skeleton& operator= (const package_skeleton&) = delete; private: - // Create the skeleton if necessary and (re)load the build system state. + // Load old user configuration variables from config.build (or equivalent) + // and merge them into config_vars_. Also verify new user configuration + // already in config_vars_ makes sense. + // + // This should be done before any attempt to load the configuration with + // config.config.disfigure and, if this did not happen, inside + // collect_config() (since the package will be reconfigured with + // config.config.disfigure). + // + void + load_old_config (); + + // (Re)load the build system state. // // Call this function before evaluating every clause. // + // If dependency configurations are specified, then typify the variables + // and set their values. If defaults is false, then only typify the + // variables and set overrides without setting the default/buildfile + // values. Note that buildfile values have value::extra set to 2. While + // at it, also remove from dependency_var_prefixes_ and add to + // dependency_var_prefixes variable prefixes (config.) for + // the passed dependencies. + // build2::scope& - load (); + load (const dependency_configurations& = {}, + strings* dependency_var_prefixes = nullptr, + bool defaults = true); - private: + // Merge command line variable overrides into one list (normally to be + // passed to bootstrap()). + // + // If cache is true, then assume the result can be reused on subsequent + // calls. + // + const strings& + merge_cmd_vars (const strings& dependent_vars, + const strings& dependency_vars = {}, + bool cache = false); + + // Implementation details (public for bootstrap()). + // + public: // NOTE: remember to update move/copy constructors! // const common_options* co_; database* db_; - const available_package* available_; + + string var_prefix_; // config. + strings config_vars_; + bool disfigure_; + const vector* config_srcs_; // NULL if nothing to do or + // already done. dir_path src_root_; // Must be absolute and normalized. dir_path out_root_; // If empty, the same as src_root_. bool created_ = false; + bool verified_ = false; + bool loaded_old_config_; + bool develop_ = true; // Package has config.*.develop. + unique_ptr ctx_; build2::scope* rs_ = nullptr; - strings cmd_vars_; // Storage for merged build2_cmd_vars and config_vars_. - strings reflect_names_; // Reflect configuration variable names. - strings reflect_vars_; // Reflect configuration variable overrides. - string reflect_frag_; // Reflect configuration variable fragment. + // Storage for merged build2_cmd_vars and config_vars_ and extra overrides + // (like config.config.disfigure). If cache is true, then the existing + // content can be reused. + // + strings cmd_vars_; + bool cmd_vars_cache_ = false; + + strings dependent_vars_; // Dependent variable overrides. + vector dependent_orgs_; // Dependent originators (parallel). + + // Reflect variable value storage. Used for both real reflect and + // dependency reflect. + // + struct reflect_variable_value + { + string name; + build2::config::variable_origin origin; + optional type; + optional value; + }; + + class reflect_variable_values: public vector + { + public: + const reflect_variable_value* + find (const string& name) + { + auto i (find_if (begin (), end (), + [&name] (const reflect_variable_value& v) + { + return v.name == name; + })); + return i != end () ? &*i : nullptr; + } + }; + + reflect_variable_values reflect_; // Reflect variables. + + // Dependency configuration variables set by the prefer/require clauses + // and that should be reflected in subsequent clauses. + // + // The same prefer/require clause could be re-evaluated multiple times in + // which case the previous dependency reflect values from this clause (but + // not from any previous clauses) should be dropped. This is achieved by + // keeping track of the depends_index for the most recently evaluated + // prefer/require clause along with the position of the first element that + // was added by this clause. Note also that this logic does the right + // thing if we move to a different dependency alternative withing the same + // depends value. + // + reflect_variable_values dependency_reflect_; + size_t dependency_reflect_index_ = 0; + size_t dependency_reflect_pending_ = 0; + + // List of variable prefixes (config.) of all known dependencies. + // + // This information is used to detect and diagnose references to undefined + // dependency configuration variables (for example, those that were not + // set and therefore not reflected). The pending index is used to ignore + // the entries added by the last evaluate_prefer_accept() in the following + // reflect clause (see prefer_accept_ below for details). + // + strings dependency_var_prefixes_; + size_t dependency_var_prefixes_pending_ = 0; + + // Position of the last successfully evaluated prefer/accept clauses. + // + // This information is used to make all (as opposed to only those set by + // the prefer clause) dependency configuration variables available to the + // reflect clause but only at the same position. This allows for some more + // advanced configuration techniques, such as, using a feature if enabled + // by someone else but not having any preferences ourselves. + // + optional> prefer_accept_; }; } -- cgit v1.1