diff options
Diffstat (limited to 'bpkg/package.hxx')
-rw-r--r-- | bpkg/package.hxx | 546 |
1 files changed, 439 insertions, 107 deletions
diff --git a/bpkg/package.hxx b/bpkg/package.hxx index 13efcd4..400519a 100644 --- a/bpkg/package.hxx +++ b/bpkg/package.hxx @@ -11,9 +11,10 @@ #include <type_traits> // static_assert #include <odb/core.hxx> +#include <odb/section.hxx> #include <odb/nested-container.hxx> -#include <libbutl/timestamp.mxx> +#include <libbutl/timestamp.hxx> #include <libbpkg/package-name.hxx> @@ -25,9 +26,13 @@ // Used by the data migration entries. // -#define DB_SCHEMA_VERSION_BASE 6 +// NOTE: drop all the `#pragma db member(...) default(...)` pragmas when +// migration is no longer supported (i.e., the current and base schema +// versions are the same). +// +#define DB_SCHEMA_VERSION_BASE 12 -#pragma db model version(DB_SCHEMA_VERSION_BASE, 12, closed) +#pragma db model version(DB_SCHEMA_VERSION_BASE, 26, closed) namespace bpkg { @@ -73,7 +78,7 @@ namespace bpkg std::chrono::nanoseconds::period>::value, "The following timestamp ODB mapping is invalid"); - // As pointed out in libbutl/timestamp.mxx we will overflow in year 2262, but + // As pointed out in libbutl/timestamp.hxx we will overflow in year 2262, but // by that time some larger basic type will be available for mapping. // #pragma db map type(timestamp) as(uint64_t) \ @@ -99,7 +104,7 @@ namespace bpkg string upstream; optional<string> release; - // @@ TMP: work around MSVC 16.2 bug. + // Work around MSVC 16.2 bug. // _version () = default; _version (uint16_t e, @@ -343,7 +348,7 @@ namespace bpkg repository_url url; repository_type type; - // @@ TMP: work around MSVC 16.2 bug. + // Work around MSVC 16.2 bug. // _repository_location () = default; _repository_location (repository_url u, repository_type t) @@ -513,6 +518,10 @@ namespace bpkg operator size_t () const {return result;} }; + // language + // + #pragma db value(language) definition + // package_location // #pragma db value @@ -535,6 +544,7 @@ namespace bpkg #pragma db value(version_constraint) definition #pragma db value(dependency) definition #pragma db member(dependency::constraint) column("") + #pragma db value(dependency_alternative) definition #pragma db value(dependency_alternatives) definition // Extend dependency_alternatives to also represent the special test @@ -555,12 +565,15 @@ namespace bpkg dependency_alternatives_ex (dependency_alternatives da) : dependency_alternatives (move (da)) {} + // As above but built incrementally. + // + dependency_alternatives_ex (bool b, std::string c) + : dependency_alternatives (b, move (c)) {} + // Create the special test dependencies object (built incrementally). // dependency_alternatives_ex (test_dependency_type t, bool buildtime) - : dependency_alternatives (false /* conditional */, - buildtime, - "" /* comment */), + : dependency_alternatives (buildtime, "" /* comment */), type (t) {} }; @@ -576,9 +589,39 @@ namespace bpkg make_move_iterator (das.end ())); } + // Return true if this is a toolchain build-time dependency. If the package + // argument is specified and this is a toolchain build-time dependency then + // also verify its constraint and fail if it is unsatisfied. Note that the + // package argument is used for diagnostics only. + // + class common_options; + + bool + toolchain_buildtime_dependency (const common_options&, + const dependency_alternatives&, + const package_name*); + + // Return true if any dependency other than toolchain build-time + // dependencies is specified. Optionally, verify toolchain build-time + // dependencies specifying the package argument which will be used for + // diagnostics only. + // + bool + has_dependencies (const common_options&, + const dependencies&, + const package_name* = nullptr); + + // Return true if some clause that is a buildfile fragment is specified for + // any of the dependencies. + // + template <typename T> + bool + has_buildfile_clause (const vector<T>& dependencies); + // tests // #pragma db value(test_dependency) definition + #pragma db member(test_dependency::buildtime) default(false) using optional_test_dependency_type = optional<test_dependency_type>; @@ -600,6 +643,19 @@ namespace bpkg // extern const version wildcard_version; + // Return true if the version constraint represents the wildcard version. + // + inline bool + wildcard (const version_constraint& vc) + { + bool r (vc.min_version && *vc.min_version == wildcard_version); + + if (r) + assert (vc.max_version == vc.min_version); + + return r; + } + // package_name // #pragma db value(package_name) type("TEXT") options("COLLATE NOCASE") @@ -616,14 +672,31 @@ namespace bpkg available_package_id (package_name, const bpkg::version&); }; + // buildfile + // + #pragma db value(buildfile) definition + + // distribution_name_value + // + #pragma db value(distribution_name_value) definition + #pragma db object pointer(shared_ptr) session class available_package { public: using version_type = bpkg::version; + using upstream_version_type = bpkg::upstream_version; available_package_id id; - upstream_version version; + upstream_version_type version; + + optional<string> upstream_version; + optional<string> type; + + small_vector<language, 1> languages; + odb::section languages_section; + + optional<package_name> project; // List of repository fragments to which this package version belongs // (yes, in our world, it can be in multiple, unrelated repositories) @@ -643,8 +716,17 @@ namespace bpkg // Package manifest data and, potentially, the special test dependencies. // - // Note that there can be only one special test dependencies entry in the - // list and it's always the last one, if present. + // Note that there can only be one special test dependencies entry in the + // list. It can only be present for a test package and specifies all the + // main packages as the alternative dependencies. If present, it is + // located right after the last explicit depends clause which specifies a + // main package for this test package, if such a clause is present, and as + // the first entry otherwise. The idea here is to inject the special + // depends clause as early as possible, so that the other clauses could + // potentially refer to the reflection variables it may set. But not too + // early, so that the explicit main package dependencies are already + // resolved by the time of resolving the special clause to avoid the + // 'unable to select dependency alternative' error. // using dependencies_type = bpkg::dependencies; @@ -652,6 +734,18 @@ namespace bpkg small_vector<test_dependency, 1> tests; + // Note that while the bootstrap buildfile is always present for stub + // packages, we don't save buildfiles for stubs of any kind (can come from + // repository, be based on system selected package, etc), leaving *_build + // as nullopt and buildfiles empty. + // + optional<bool> alt_naming; + optional<string> bootstrap_build; + optional<string> root_build; + vector<buildfile> buildfiles; + + vector<distribution_name_value> distribution_values; + // Present for non-transient objects only (and only for certain repository // types). // @@ -662,14 +756,31 @@ namespace bpkg mutable optional<version_type> system_version_; public: - // Note: version constraints must be complete. + // Note: version constraints must be complete and the bootstrap build must + // be present, unless this is a stub. // available_package (package_manifest&& m) : id (move (m.name), m.version), version (move (m.version)), + upstream_version (move (m.upstream_version)), + type (move (m.type)), + languages (move (m.languages)), + project (move (m.project)), dependencies (convert (move (m.dependencies))), tests (move (m.tests)), - sha256sum (move (m.sha256sum)) {} + distribution_values (move (m.distribution_values)), + sha256sum (move (m.sha256sum)) + { + if (!stub ()) + { + assert (m.bootstrap_build.has_value () && m.alt_naming.has_value ()); + + alt_naming = m.alt_naming; + bootstrap_build = move (m.bootstrap_build); + root_build = move (m.root_build); + buildfiles = move (m.buildfiles); + } + } // Create available stub package. // @@ -689,6 +800,18 @@ namespace bpkg bool stub () const {return version.compare (wildcard_version, true) == 0;} + string + effective_type () const + { + return package_manifest::effective_type (type, id.name); + } + + small_vector<language, 1> + effective_languages () const + { + return package_manifest::effective_languages (languages, id.name); + } + // Return package system version if one has been discovered. Note that // we do not implicitly assume a wildcard version. // @@ -705,37 +828,111 @@ namespace bpkg // #pragma db member(id) id column("") #pragma db member(version) set(this.version.init (this.id.version, (?))) + + // languages + // + #pragma db member(languages) id_column("") value_column("language_") \ + section(languages_section) + + #pragma db member(languages_section) load(lazy) update(always) + + // locations + // #pragma db member(locations) id_column("") value_column("") \ unordered value_not_null // dependencies // - using _dependency_key = odb::nested_key<dependency_alternatives_ex>; - using _dependency_alternatives_ex_type = - std::map<_dependency_key, dependency>; - - #pragma db value(_dependency_key) - #pragma db member(_dependency_key::outer) column("dependency_index") - #pragma db member(_dependency_key::inner) column("index") + // Note that this is a 2-level nested container which is mapped to three + // container tables each containing data of each dimension. + // Container of the dependency_alternatives_ex values. + // #pragma db member(dependencies) id_column("") value_column("") - #pragma db member(dependency_alternatives_ex) \ - table("available_package_dependency_alternatives") \ - virtual(_dependency_alternatives_ex_type) \ + + // Container of the dependency_alternative values. + // + using _dependency_alternative_key = + odb::nested_key<dependency_alternatives_ex>; + + using _dependency_alternatives_type = + std::map<_dependency_alternative_key, dependency_alternative>; + + #pragma db value(_dependency_alternative_key) + #pragma db member(_dependency_alternative_key::outer) column("dependency_index") + #pragma db member(_dependency_alternative_key::inner) column("index") + + #pragma db member(dependency_alternatives) \ + virtual(_dependency_alternatives_type) \ after(dependencies) \ get(odb::nested_get (this.dependencies)) \ set(odb::nested_set (this.dependencies, std::move (?))) \ + id_column("") key_column("") value_column("") + + // Container of the dependency values. + // + using _dependency_key = odb::nested2_key<dependency_alternatives_ex>; + using _dependency_alternative_dependencies_type = + std::map<_dependency_key, dependency>; + + #pragma db value(_dependency_key) + #pragma db member(_dependency_key::outer) column("dependency_index") + #pragma db member(_dependency_key::middle) column("alternative_index") + #pragma db member(_dependency_key::inner) column("index") + + #pragma db member(dependency_alternative_dependencies) \ + virtual(_dependency_alternative_dependencies_type) \ + after(dependency_alternatives) \ + get(odb::nested2_get (this.dependencies)) \ + set(odb::nested2_set (this.dependencies, std::move (?))) \ id_column("") key_column("") value_column("dep_") // tests // #pragma db member(tests) id_column("") value_column("test_") + // distribution_values + // + #pragma db member(distribution_values) id_column("") value_column("dist_") + + // alt_naming + // + // Note that since no real packages with alternative buildfile naming use + // conditional dependencies yet, we can just set alt_naming to false + // during migration to the database schema version 20. Also we never rely + // on alt_naming to be nullopt for the stub packages, so let's not + // complicate things and set alt_naming to false for them either. + // + #pragma db member(alt_naming) default(false) + + // *_build + // + // Note that since no real packages use conditional dependencies yet, we + // can just set bootstrap_build to the empty string during migration to + // the database schema version 15. Also we never rely on bootstrap_build + // to be nullopt for the stub packages, so let's not complicate things and + // set bootstrap_build to the empty string for them either. + // + #pragma db member(bootstrap_build) default("") + + // buildfiles + // + #pragma db member(buildfiles) id_column("") value_column("") + private: friend class odb::access; available_package () = default; }; + // The available packages together with the repository fragments they belong + // to. + // + // Note that lazy_shared_ptr is used to also convey the databases the + // objects belong to. + // + using available_packages = vector<pair<shared_ptr<available_package>, + lazy_shared_ptr<repository_fragment>>>; + #pragma db view object(available_package) struct available_package_count { @@ -786,46 +983,6 @@ namespace bpkg shared_ptr<available_package> package; }; - // Query the available packages that optionally satisfy the specified - // version constraint and return them in the version descending order, by - // default. Note that a stub satisfies any constraint. - // - odb::result<available_package> - query_available (database&, - const package_name&, - const optional<version_constraint>&, - bool order = true); - - // Only return packages that are in the specified repository fragments, their - // complements or prerequisites (if prereq is true), recursively. While you - // could maybe come up with a (barely comprehensible) view/query to achieve - // this, doing it on the "client side" is definitely more straightforward. - // - vector<shared_ptr<available_package>> - filter (const shared_ptr<repository_fragment>&, - odb::result<available_package>&&, - bool prereq = true); - - pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> - filter_one (const shared_ptr<repository_fragment>&, - odb::result<available_package>&&, - bool prereq = true); - - shared_ptr<repository_fragment> - filter (const shared_ptr<repository_fragment>&, - const shared_ptr<available_package>&, - bool prereq = true); - - vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> - filter (const vector<shared_ptr<repository_fragment>>&, - odb::result<available_package>&&, - bool prereq = true); - - pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> - filter_one (const vector<shared_ptr<repository_fragment>>&, - odb::result<available_package>&&, - bool prereq = true); - // Check if there are packages available in the specified configurations. If // that's not the case then print the info message into the diag record or, // if it is NULL, print the error message and fail. @@ -920,21 +1077,35 @@ namespace bpkg } // A map of "effective" prerequisites (i.e., pointers to other selected - // packages) to optional version constraint. Note that because it is a - // single constraint, we don't support multiple dependencies on the same - // package (e.g., two ranges of versions). See pkg_configure(). + // packages) to optional version constraint (plus some other info). Note + // that because it is a single constraint, we don't support multiple + // dependencies on the same package (e.g., two ranges of versions). See + // pkg_configure(). // // Note also that the pointer can refer to a selected package in another // database. // class selected_package; + #pragma db value + struct prerequisite_info + { + // The "tightest" version constraint among all dependencies resolved to + // this prerequisite. + // + optional<version_constraint> constraint; + + // Database mapping. + // + #pragma db member(constraint) column("") + }; + // Note that the keys for this map need to be created with the database // passed to their constructor, which is required for persisting them (see // _selected_package_ref() implementation for details). // using package_prerequisites = std::map<lazy_shared_ptr<selected_package>, - optional<version_constraint>, + prerequisite_info, compare_lazy_ptr>; // Database mapping for lazy_shared_ptr<selected_package> to configuration @@ -964,6 +1135,30 @@ namespace bpkg to(bpkg::_selected_package_ref (?)) \ from(std::move (?).to_ptr (*db)) + enum class config_source + { + user, // User configuration specified on command line. + dependent, // Dependent-imposed configuration from prefer/require clauses. + reflect // Package-reflected configuration from reflect clause. + }; + + string + to_string (config_source); + + config_source + to_config_source (const string&); // May throw std::invalid_argument. + + #pragma db map type(config_source) as(string) \ + to(to_string (?)) \ + from(bpkg::to_config_source (?)) + + #pragma db value + struct config_variable + { + string name; + config_source source; + }; + #pragma db object pointer(shared_ptr) session class selected_package { @@ -1016,9 +1211,9 @@ namespace bpkg // package version revision increment. In particular, new subprojects // should trigger the package reconfiguration. // - // Must be present if the source directory is present, unless the object - // is created/updated during the package build simulation (see pkg-build - // for details). Note that during the simulation the manifest may not be + // Only present for external packages, unless the objects are + // created/updated during the package build simulation (see pkg-build for + // details). Note that during the simulation the manifest may not be // available. // // @@ Currently we don't consider subprojects recursively (would most @@ -1030,18 +1225,46 @@ namespace bpkg // optional<std::string> manifest_checksum; - // Path to the output directory of this package, if any. It is - // always relative to the configuration directory, and is <name> - // for external packages and <name>-<version> for others. It is - // only set once the package is configured and its main purse is - // to keep track of what needs to be cleaned by the user before - // a broken package can be purged. Note that it could be the - // same as src_root. + // Only present for external packages which have buildfile clauses in the + // dependencies, unless the objects are created/updated during the package + // build simulation (see pkg-build for details). + // + // Note that the checksum is always calculated over the files rather than + // the *-build manifest values. This is "parallel" to the package skeleton + // logic. + // + optional<std::string> buildfiles_checksum; + + // Path to the output directory of this package, if any. It is always + // relative to the configuration directory, and is <name> for external + // packages and <name>-<version> for others. It is only set once the + // package is configured and its main purpose is to keep track of what + // needs to be cleaned by the user before a broken package can be + // purged. Note that it could be the same as src_root. // optional<dir_path> out_root; package_prerequisites prerequisites; + // 1-based indexes of the selected dependency alternatives which the + // prerequisite packages are resolved from. Parallel to the dependencies + // member of the respective available package. Entries which don't + // correspond to a selected alternative (toolchain build-time dependency, + // not enabled alternatives, etc) are set to 0. + // + using indexes_type = vector<size_t>; // Make sure ODB maps it portably. + indexes_type dependency_alternatives; + odb::section dependency_alternatives_section; + + // Project configuration variable names and their sources. + // + vector<config_variable> config_variables; + + // SHA256 checksum of variables (names and values) referred to by the + // config_variables member. + // + std::string config_checksum; + public: bool system () const @@ -1065,7 +1288,10 @@ namespace bpkg // pkg-unpack --existing <dir> // - (repository_fragment.empty () && !archive); + // Note that the system package can have no repository associated (see + // imaginary system repository in pkg-build.cxx for details). + // + (repository_fragment.empty () && !archive && !system ()); } // Represent the wildcard version with the "*" string. Represent naturally @@ -1083,6 +1309,18 @@ namespace bpkg std::string string (database&) const; + // Return the relative archive path completed using the configuration + // directory. Return the absolute archive path as is. + // + path + effective_archive (const dir_path& configuration) const + { + // Cast for compiling with ODB (see above). + // + assert (static_cast<bool> (archive)); + return archive->absolute () ? *archive : configuration / *archive; + } + // Return the relative source directory completed using the configuration // directory. Return the absolute source directory as is. // @@ -1095,8 +1333,7 @@ namespace bpkg return src_root->absolute () ? *src_root : configuration / *src_root; } - // Return the output directory using the configuration directory. Note - // that the output directory is always relative. + // Return the output directory using the configuration directory. // dir_path effective_out_root (const dir_path& configuration) const @@ -1104,6 +1341,9 @@ namespace bpkg // Cast for compiling with ODB (see above). // assert (static_cast<bool> (out_root)); + + // Note that out_root is always relative. + // return configuration / *out_root; } @@ -1114,6 +1354,21 @@ namespace bpkg #pragma db member(prerequisites) id_column("package") \ key_column("") value_column("") + #pragma db member(dependency_alternatives) id_column("package") \ + value_column("position") section(dependency_alternatives_section) + + #pragma db member(dependency_alternatives_section) load(lazy) update(always) + + #pragma db member(config_variables) id_column("package") value_column("") + + // For the sake of simplicity let's not calculate the checksum during + // migration. It seems that the only drawback of this approach is a + // (single) spurious reconfiguration of a dependency of a dependent with + // configuration clause previously configured by bpkg with the database + // schema version prior to 24. + // + #pragma db member(config_checksum) default("") + // Explicit aggregate initialization for C++20 (private default ctor). // selected_package (package_name n, @@ -1128,6 +1383,7 @@ namespace bpkg optional<dir_path> sr, bool ps, optional<std::string> mc, + optional<std::string> bc, optional<dir_path> o, package_prerequisites pps) : name (move (n)), @@ -1142,6 +1398,7 @@ namespace bpkg src_root (move (sr)), purge_src (ps), manifest_checksum (move (mc)), + buildfiles_checksum (move (bc)), out_root (move (o)), prerequisites (move (pps)) {} @@ -1156,6 +1413,16 @@ namespace bpkg return os << p.string (); } + // Create a transient (or fake, if you prefer) available_package object + // corresponding to the specified selected object, which is expected to not + // be in the broken state. Note that the package locations list is left + // empty. + // + shared_ptr<available_package> + make_available (const common_options&, + database&, + const shared_ptr<selected_package>&); + // Try to find a dependency in the dependency configurations (see // database::dependency_configs() for details). Return pointers to the found // package and the configuration it belongs to. Return a pair of NULLs if no @@ -1171,21 +1438,24 @@ namespace bpkg // // Pass the build2 project info for the package, if available, to speed up // the call and NULL otherwise (in which case it will be queried by the - // implementation). + // implementation). In the former case it is assumed that the package info + // has been retrieved with the b_info_flags::subprojects flag. // // Notes: // // - The package directory is considered an iteration of the package if this // upstream version and revision is already present (selected) in the - // configuration and has a source directory. If that's the case, then the - // specified directory path and the checksum of the manifest file it - // contains are compared to the ones of the package present in the - // configuration. If both match, then the present package version - // (including its iteration, if any) is returned. Otherwise (the package - // has moved and/or the packaging information has changed), the present - // package version with the incremented iteration number is returned. Note - // that the directory path is matched only for the external selected - // packages. + // configuration and has a source directory. If that's the case and if the + // present version is not external (the package is being switched to a + // local potentially amended version), then the present package version + // with the incremented iteration number is returned. Otherwise (the + // present package is external), the specified directory path and the + // package checksum (see package_checksum() for details) are compared to + // the ones of the package present in the configuration. If both match, + // then the present package version (including its iteration, if any) is + // returned. Otherwise (the package has moved and/or the package + // information has changed), the present package version with the + // incremented iteration number is returned. // // - Only a single package iteration is valid per version in the // configuration. This, in particular, means that a package of the @@ -1199,8 +1469,6 @@ namespace bpkg // - The manifest file located in the specified directory is not parsed, and // so is not checked to match the specified package name and version. // - class common_options; - // Note: loads selected packages. // optional<version> @@ -1348,40 +1616,104 @@ namespace bpkg // not detached during such map lifetimes. Considers both package name and // database for objects comparison. // - struct config_package + struct package_key { - database& db; - package_name name; + reference_wrapper<database> db; + package_name name; - config_package (database& d, package_name n): db (d), name (move (n)) {} + package_key (database& d, package_name n): db (d), name (move (n)) {} + + bool + operator== (const package_key& v) const + { + // See operator==(database, database). + // + return name == v.name && &db.get () == &v.db.get (); + } + + bool + operator!= (const package_key& v) const + { + return !(*this == v); + } + + bool + operator< (const package_key&) const; + + // Return the package string representation in the form: + // + // <name>[ <config-dir>] + // + std::string + string () const; + }; + + inline ostream& + operator<< (ostream& os, const package_key& p) + { + return os << p.string (); + } + + // Database, package name, and package version. + // + // It is normally used as a key for maps containing data for package + // versions across multiple linked configurations. Assumes that the + // respective databases are not detached during such map lifetimes. + // Considers all package name, package version, and database for objects + // comparison. + // + // The package name can be a pseudo-package (command line as a dependent, + // etc), in which case the version is absent. The version can also be empty, + // denoting a package of an unknown version. + // + struct package_version_key + { + reference_wrapper<database> db; + package_name name; + optional<bpkg::version> version; + + package_version_key (database& d, package_name n, bpkg::version v) + : db (d), name (move (n)), version (move (v)) {} // Create a pseudo-package (command line as a dependent, etc). // - config_package (database& d, string n) + package_version_key (database& d, string n) : db (d), - name (n.empty () ? package_name () : package_name (move (n))) {} + name (move (n), package_name::raw_string) {} bool - operator== (const config_package& v) const + operator== (const package_version_key& v) const { // See operator==(database, database). // - return name == v.name && &db == &v.db; + return name == v.name && + version == v.version && + &db.get () == &v.db.get (); } bool - operator< (const config_package& v) const + operator!= (const package_version_key& v) const { - // See operator==(database, database). - // - int r (name.compare (v.name)); - return r != 0 ? (r < 0) : (&db < &v.db); + return !(*this == v); } + bool + operator< (const package_version_key&) const; + + // Return the package string representation in the form: + // + // <name>[/<version>] [ <config-dir>] + // std::string - string () const; + string (bool ignore_version = false) const; }; + inline ostream& + operator<< (ostream& os, const package_version_key& p) + { + return os << p.string (); + } + // Return a count of repositories that contain this repository fragment. // #pragma db view table("main.repository_fragments") |