diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2019-10-22 22:49:19 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2019-10-31 17:33:32 +0300 |
commit | dad48d98b1a57706179c34853950588ec75a8467 (patch) | |
tree | 307cf55b44e88b2f6cc154c58cc74683040000ca | |
parent | 6bbf6390d95941cd5ead6eba649edc2a7fec9d21 (diff) |
Add support for package version constraint in pkg-build command arguments
Also document tests, examples, and benchmarks package manifest values.
-rw-r--r-- | bpkg/manifest-utility.cxx | 75 | ||||
-rw-r--r-- | bpkg/manifest-utility.hxx | 11 | ||||
-rw-r--r-- | bpkg/package.cxx | 34 | ||||
-rw-r--r-- | bpkg/package.hxx | 33 | ||||
-rw-r--r-- | bpkg/pkg-build.cli | 43 | ||||
-rw-r--r-- | bpkg/pkg-build.cxx | 384 | ||||
-rw-r--r-- | bpkg/pkg-status.cxx | 10 | ||||
-rw-r--r-- | bpkg/satisfaction.cxx | 6 | ||||
-rw-r--r-- | bpkg/satisfaction.hxx | 12 | ||||
-rw-r--r-- | bpkg/satisfaction.test.cxx | 62 | ||||
-rw-r--r-- | doc/manual.cli | 170 | ||||
-rw-r--r-- | tests/pkg-build.testscript | 158 |
12 files changed, 685 insertions, 313 deletions
diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx index 29025d3..0771df8 100644 --- a/bpkg/manifest-utility.cxx +++ b/bpkg/manifest-utility.cxx @@ -4,6 +4,8 @@ #include <bpkg/manifest-utility.hxx> +#include <cstring> // strcspn() + #include <libbutl/b.mxx> #include <libbutl/url.mxx> #include <libbutl/sha256.mxx> @@ -40,8 +42,6 @@ namespace bpkg package_name parse_package_name (const char* s, bool allow_version) { - using traits = string::traits_type; - if (!allow_version) try { @@ -52,10 +52,11 @@ namespace bpkg fail << "invalid package name '" << s << "': " << e; } - size_t n (traits::length (s)); - - if (const char* p = traits::find (s, n, '/')) - n = static_cast<size_t> (p - s); + // Calculate the package name length as a length of the prefix that + // doesn't contain spaces, slashes and the version constraint starting + // characters. Note that none of them are valid package name characters. + // + size_t n (strcspn (s, " /=<>([~^")); try { @@ -104,6 +105,68 @@ namespace bpkg return version (); } + optional<version_constraint> + parse_package_version_constraint (const char* s, + bool allow_wildcard, + bool fold_zero_revision, + bool version_only) + { + // Calculate the version specification position as a length of the prefix + // that doesn't contain slashes and the version constraint starting + // characters. + // + size_t n (strcspn (s, "/=<>([~^")); + + if (s[n] == '\0') // No version (constraint) is specified? + return nullopt; + + const char* v (s + n); // Constraint or version including '/'. + + // If only the version is allowed or the package name is followed by '/' + // then fallback to the version parsing. + // + if (version_only || v[0] == '/') + try + { + return version_constraint ( + parse_package_version (s, allow_wildcard, fold_zero_revision)); + } + catch (const invalid_argument& e) + { + fail << "invalid package version '" << v + 1 << "' in '" << s << "': " + << e; + } + + try + { + version_constraint r (v); + + if (!r.complete ()) + throw invalid_argument ("incomplete"); + + // There doesn't seem to be any good reason to allow specifying a stub + // version in the version constraint. Note that the constraint having + // both endpoints set to the wildcard version (which is a stub) denotes + // the system package wildcard version and may result only from the '/*' + // string representation. + // + auto stub = [] (const optional<version>& v) + { + return v && v->compare (wildcard_version, true) == 0; + }; + + if (stub (r.min_version) || stub (r.max_version)) + throw invalid_argument ("endpoint is a stub"); + + return r; + } + catch (const invalid_argument& e) + { + fail << "invalid package version constraint '" << v << "' in '" << s + << "': " << e << endf; + } + } + repository_location parse_location (const string& s, optional<repository_type> ot) try diff --git a/bpkg/manifest-utility.hxx b/bpkg/manifest-utility.hxx index 9f690b4..16546bd 100644 --- a/bpkg/manifest-utility.hxx +++ b/bpkg/manifest-utility.hxx @@ -61,6 +61,17 @@ namespace bpkg fold_zero_revision); } + // Extract the package constraint from either <name>[/<version>] or + // <name><version-constraint> forms, unless version_only is true. For the + // former case return the `== <version>` constraint. Return nullopt if only + // the package name is specified. + // + optional<version_constraint> + parse_package_version_constraint (const char*, + bool allow_wildcard = false, + bool fold_zero_revision = true, + bool version_only = false); + // If the passed location is a relative local path, then assume this is a // relative path to the repository directory and complete it based on the // current working directory. Diagnose invalid locations and throw failed. diff --git a/bpkg/package.cxx b/bpkg/package.cxx index 563fc93..e45718e 100644 --- a/bpkg/package.cxx +++ b/bpkg/package.cxx @@ -262,6 +262,40 @@ namespace bpkg return system ? "sys:" + n.string () + vs : n.string () + vs; } + string + package_string (const package_name& name, + const optional<version_constraint>& constraint, + bool system) + { + // Fallback to the version type-based overload if the constraint is not + // specified. + // + if (!constraint) + return package_string (name, version (), system); + + // There are no scenarios where the version constrain is present but is + // empty (both endpoints are nullopt). + // + assert (!constraint->empty ()); + + // If the endpoint versions are equal then represent the constraint as the + // "<name>/<version>" string rather than "<name> == <version>", using the + // version type-based overload. + // + const optional<version>& min_ver (constraint->min_version); + bool eq (min_ver == constraint->max_version); + + if (eq) + return package_string (name, *min_ver, system); + + if (system) + return package_string (name, version (), system) + "/..."; + + // Quote the result as it contains the space character. + // + return "'" + name.string () + ' ' + constraint->string () + "'"; + } + // selected_package // string selected_package:: diff --git a/bpkg/package.hxx b/bpkg/package.hxx index 4f40939..a3a6bab 100644 --- a/bpkg/package.hxx +++ b/bpkg/package.hxx @@ -442,15 +442,15 @@ namespace bpkg // package" to refer to the "effective" dependency that has been resolved to // the actual package object. // - #pragma db value(dependency_constraint) definition + #pragma db value(version_constraint) definition #pragma db value(dependency) definition #pragma db member(dependency::constraint) column("") #pragma db value(dependency_alternatives) definition using dependencies = vector<dependency_alternatives>; - // Wildcard version. Satisfies any dependency constraint and is represented - // as 0+0 (which is also the "stub version"; since a real version is always + // Wildcard version. Satisfies any version constraint and is represented as + // 0+0 (which is also the "stub version"; since a real version is always // greater than the stub version, we reuse it to signify a special case). // extern const version wildcard_version; @@ -515,7 +515,7 @@ namespace bpkg mutable optional<version_type> system_version_; public: - // Note: dependency constraints must be complete. + // Note: version constraints must be complete. // available_package (package_manifest&& m) : id (move (m.name), m.version), @@ -722,15 +722,32 @@ namespace bpkg const version&, bool system = false); + // Return the package name in the [sys:]<name>[<version-constraint>] form. + // The version constraint component is represented with the "/<version>" + // string for the `== <version>` constraint, "/*" string for the wildcard + // version, and is omitted for nullopt. + // + // If the version constraint other than the equality operator is specified + // for a system package, return the "sys:<name>/..." string (with "..." + // literally). This, in particular, is used for issuing diagnostics that + // advises the user to configure a system package. Note that in this case + // the user can only specify a specific version/wildcard on the command + // line. + // + string + package_string (const package_name& name, + const optional<version_constraint>&, + bool system = false); + // A map of "effective" prerequisites (i.e., pointers to other selected - // packages) to optional dependency constraint. Note that because it is a + // 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(). // class selected_package; using package_prerequisites = std::map<lazy_shared_ptr<selected_package>, - optional<dependency_constraint>, + optional<version_constraint>, compare_lazy_ptr>; #pragma db object pointer(shared_ptr) session @@ -1048,7 +1065,7 @@ namespace bpkg string name; #pragma db column(pp.value) - optional<dependency_constraint> constraint; + optional<version_constraint> constraint; }; */ @@ -1064,7 +1081,7 @@ namespace bpkg package_name name; #pragma db column("pp.") - optional<dependency_constraint> constraint; + optional<version_constraint> constraint; }; // Return a count of repositories that contain this repository fragment. diff --git a/bpkg/pkg-build.cli b/bpkg/pkg-build.cli index e247f47..0ac8dcb 100644 --- a/bpkg/pkg-build.cli +++ b/bpkg/pkg-build.cli @@ -13,7 +13,8 @@ namespace bpkg { "<options> <cfg-var> - <pkg-spec> <flags> <scheme> <pkg> <ver> + <pkg-spec> <flags> <scheme> <pkg> <ver-spec> + <version> <version-constraint> <file> <dir> <rep-loc>", @@ -25,12 +26,13 @@ namespace bpkg \b{bpkg pkg-build}|\b{build} [<options>] \ \b{--upgrade}|\b{-u} | \b{--patch}|\b{-p}\n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [<cfg-var>... \b{--}]} - \c{<pkg-spec> = [<flags>](([<scheme>\b{:}]<pkg>[\b{/}<ver>])\b{,}...[\b{@}<rep-loc>] | \n - \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{@}]<rep-loc> \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n - \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ <file> \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n + \c{<pkg-spec> = [<flags>](([<scheme>\b{:}]<pkg>[<ver-spec>])\b{,}...[\b{@}<rep-loc>] | \n + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{@}]<rep-loc> \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ <file> \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ <dir>\b{/})\n - <flags>\ \ \ \ = \b{?}\n - <scheme> \ \ = \b{sys}} + <flags>\ \ \ \ \ \ = \b{?}\n + <scheme> \ \ \ \ = \b{sys}\n + <ver-spec>\ \ \ = \b{/}<version> | <version-constraint>} \h|DESCRIPTION| @@ -56,21 +58,25 @@ namespace bpkg or \cb{--patch}. Each package can be specified as just the name (<pkg>) with optional - package version (<ver>) in which case the source code for the package - will be automatically fetched from one of the configured + version specification (<ver-spec>), in which case the source code for the + package will be automatically fetched from one of the configured repositories. See the \l{bpkg-rep-add(1)} and \l{bpkg-rep-fetch(1)} - commands for more information on package repositories. If <ver> is not - specified, then the latest available version will be built. To downgrade, - the desired version must be specified explicitly. For example: + commands for more information on package repositories. The version + specification (<ver-spec>) can be either the exact version in the + \c{\b{/}\i{version}} form or the version constraint as described in + \l{bpkg#package-version-constraint Package Version Constraint}. If + <ver-spec> is not specified, then the latest available version will be + built. To downgrade, the desired version must be specified + explicitly. For example: \ - bpkg build foo libfoo/1.2.3 + bpkg build foo libfoo/1.2.3 \"bar < 2.0.0\" \ Alternatively, the package repository location (<rep-loc>) can be - specified as part of the build command. In this case, if <ver> is not - specified, then the latest available from this repository version will be - built. For example: + specified as part of the build command. In this case, if <ver-spec> is + not specified, then the latest available from this repository version + will be built. For example: \ bpkg build foo,libfoo/1.2.3@https://git.example.org/foo.git#master @@ -91,8 +97,9 @@ namespace bpkg (<scheme>). Currently the only recognized scheme is \cb{sys} which instructs \cb{pkg-build} to configure the package as available from the system rather than building it from source. If the system package version - (<ver>) is not specified or is '\cb{*}', then it is considered to be - unknown but satisfying any dependency constraint. If the version is not + (<ver-spec>) is not specified or is '\cb{/*}', then it is considered to + be unknown but satisfying any version constraint. If specified, + <ver-spec> may not be a version constraint. If the version is not explicitly specified, then at least a stub package must be available from one of the repositories. @@ -125,7 +132,7 @@ namespace bpkg available for build as a dependency. Packages (both built to hold and as dependencies) that are specified with - an explicit package version (<ver>) or as an archive or directory, + an explicit package version (<ver-spec>) or as an archive or directory, will have their versions held, that is, they will not be automatically upgraded. diff --git a/bpkg/pkg-build.cxx b/bpkg/pkg-build.cxx index 5a5eaa1..6c218d5 100644 --- a/bpkg/pkg-build.cxx +++ b/bpkg/pkg-build.cxx @@ -53,7 +53,7 @@ namespace bpkg static odb::result<available_package> query_available (database& db, const package_name& name, - const optional<dependency_constraint>& c) + const optional<version_constraint>& c) { using query = query<available_package>; @@ -164,7 +164,7 @@ namespace bpkg vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> find_available (database& db, const package_name& name, - const optional<dependency_constraint>& c) + const optional<version_constraint>& c) { vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> r; @@ -208,7 +208,7 @@ namespace bpkg vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> find_available (database& db, const package_name& name, - const optional<dependency_constraint>& c, + const optional<version_constraint>& c, const vector<shared_ptr<repository_fragment>>& rfs, bool prereq = true) { @@ -239,7 +239,7 @@ namespace bpkg static pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> find_available_one (database& db, const package_name& name, - const optional<dependency_constraint>& c, + const optional<version_constraint>& c, const shared_ptr<repository_fragment>& rf, bool prereq = true) { @@ -260,7 +260,7 @@ namespace bpkg static pair<shared_ptr<available_package>, shared_ptr<repository_fragment>> find_available_one (database& db, const package_name& name, - const optional<dependency_constraint>& c, + const optional<version_constraint>& c, const vector<shared_ptr<repository_fragment>>& rfs, bool prereq = true) { @@ -328,6 +328,19 @@ namespace bpkg return make_pair (make_shared<available_package> (move (m)), move (af)); } + // Return true if the version constraint represents the wildcard version. + // + static 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; + } + // A "dependency-ordered" list of packages and their prerequisites. // That is, every package on the list only possibly depending on the // ones after it. In a nutshell, the usage is as follows: we first @@ -344,7 +357,7 @@ namespace bpkg // // During the satisfaction phase, we collect all the packages, their // prerequisites (and so on, recursively) in a map trying to satisfy - // any dependency constraints. Specifically, during this step, we may + // any version constraints. Specifically, during this step, we may // "upgrade" or "downgrade" a package that is already in a map as a // result of another package depending on it and, for example, requiring // a different version. One notable side-effect of this process is that @@ -415,10 +428,10 @@ namespace bpkg struct constraint_type { string dependent; - dependency_constraint value; + version_constraint value; constraint_type () = default; - constraint_type (string d, dependency_constraint v) + constraint_type (string d, version_constraint v) : dependent (move (d)), value (move (v)) {} }; @@ -868,9 +881,9 @@ namespace bpkg bool system (false); bool dep_optional (false); - // If the user specified the desired dependency version, then we will - // use it to overwrite the constraint imposed by the dependent - // package, checking that it is still satisfied. + // If the user specified the desired dependency version constraint, + // then we will use it to overwrite the constraint imposed by the + // dependent package, checking that it is still satisfied. // // Note that we can't just rely on the execution plan refinement that // will pick up the proper dependency version at the end of the day. @@ -880,17 +893,10 @@ namespace bpkg // pkg-build/dependency/apply-constraints/resolve-conflict{1,2} // tests). - // Points to the version constraint created from the desired - // dependency version, if specified. Is NULL otherwise. Can be used as - // boolean flag. + // Points to the desired dependency version constraint, if specified, + // and is NULL otherwise. Can be used as boolean flag. // - const dependency_constraint* dep_constr (nullptr); - - auto dep_version = [&dep_constr] () -> const version& - { - assert (dep_constr && dep_constr->min_version); - return *dep_constr->min_version; - }; + const version_constraint* dep_constr (nullptr); auto i (map_.find (dn)); if (i != map_.end ()) @@ -900,19 +906,23 @@ namespace bpkg dep_optional = !bp.action; // Is pre-entered. if (dep_optional && - bp.hold_version && *bp.hold_version) // The version is specified, + // + // The version constraint is specified, + // + bp.hold_version && *bp.hold_version) { assert (bp.constraints.size () == 1); const build_package::constraint_type& c (bp.constraints[0]); - dep_constr = &c.value; // Assign before dep_version() usage. + dep_constr = &c.value; system = bp.system; // If the user-specified dependency constraint is the wildcard // version, then it satisfies any dependency constraint. // - if (!satisfies (dep_version (), dp.constraint)) + if (!wildcard (*dep_constr) && + !satisfies (*dep_constr, dp.constraint)) fail << "unable to satisfy constraints on package " << dn << info << name << " depends on (" << dn << " " << *dp.constraint << ")" << @@ -928,9 +938,9 @@ namespace bpkg : dependency {dn, *dep_constr}); // First see if this package is already selected. If we already have - // it in the configuraion and it satisfies our dependency constraint, - // then we don't want to be forcing its upgrade (or, worse, - // downgrade). + // it in the configuraion and it satisfies our dependency version + // constraint, then we don't want to be forcing its upgrade (or, + // worse, downgrade). // shared_ptr<selected_package> dsp (db.find<selected_package> (dn)); @@ -968,10 +978,10 @@ namespace bpkg ? find_available_one (db, dn, nullopt, root) : find_available_one (db, dn, - dependency_constraint (dsp->version), + version_constraint (dsp->version), root); - // A stub satisfies any dependency constraint so we weed them out + // A stub satisfies any version constraint so we weed them out // (returning stub as an available package feels wrong). // if (dap == nullptr || dap->stub ()) @@ -1013,17 +1023,18 @@ namespace bpkg // Note that this logic (naturally) does not apply if the package is // already selected by the user (see above). // - // Also note that for the user-specified dependency version we rely - // on its presence in repositories of the first dependent met. As - // a result, we may fail too early if the version doesn't belong to - // its repositories, but belongs to the ones of some dependent that + // Also note that for the user-specified dependency version + // constraint we rely on the satisfying package version be present + // in repositories of the first dependent met. As a result, we may + // fail too early if such package version doesn't belong to its + // repositories, but belongs to the ones of some dependent that // we haven't met yet. Can we just search all repositories for an - // available package of this version and just take it, if present? - // We could, but then which repository should we pick? The wrong - // choice can introduce some unwanted repositories and package - // versions into play. So instead, we will postpone collecting the - // problematic dependent, expecting that some other one will find - // the version in its repositories. + // available package of the appropriate version and just take it, + // if present? We could, but then which repository should we pick? + // The wrong choice can introduce some unwanted repositories and + // package versions into play. So instead, we will postpone + // collecting the problematic dependent, expecting that some other + // one will find the appropriate version in its repositories. // // For a system package we pick the latest version just to make sure // the package is recognized. An unrecognized package means the @@ -1048,8 +1059,7 @@ namespace bpkg // We need to be careful not to print the wildcard-based // constraint. // - if (d.constraint && - (!dep_constr || dep_version () != wildcard_version)) + if (d.constraint && (!dep_constr || !wildcard (*dep_constr))) dr << ' ' << *d.constraint; dr << " of package " << name; @@ -1075,8 +1085,8 @@ namespace bpkg { // Note that the constraint can safely be printed as it can't // be a wildcard (produced from the user-specified dependency - // version). If it were, then the system version wouldn't be NULL - // and would satisfy itself. + // version constraint). If it were, then the system version + // wouldn't be NULL and would satisfy itself. // if (dap->system_version () == nullptr) fail << "dependency " << d << " of package " << name << " is " @@ -1087,7 +1097,9 @@ namespace bpkg if (!satisfies (*dap->system_version (), d.constraint)) fail << "dependency " << d << " of package " << name << " is " << "not available in source" << - info << package_string (dn, *dap->system_version (), true) + info << package_string (dn, + *dap->system_version (), + true /* system */) << " does not satisfy the constrains"; system = true; @@ -1403,7 +1415,7 @@ namespace bpkg if (check) { const version& av (p.available_version ()); - const dependency_constraint& c (*pd.constraint); + const version_constraint& c (*pd.constraint); if (!satisfies (av, c)) { @@ -1756,7 +1768,7 @@ namespace bpkg // selected package minor version reached the limit (see // standard-version.cxx for details). // - static optional<dependency_constraint> + static optional<version_constraint> patch_constraint (const shared_ptr<selected_package>& sp, bool quiet = false) { const package_name& nm (sp->name); @@ -1779,7 +1791,7 @@ namespace bpkg try { - return dependency_constraint ("~" + vs); + return version_constraint ("~" + vs); } // Note that the only possible reason for invalid_argument exception to // be thrown is that minor version reached the 99999 limit (see @@ -1800,12 +1812,12 @@ namespace bpkg struct dependency_package { package_name name; - bpkg::version version; // Empty if unspecified. - shared_ptr<selected_package> selected; // NULL if not present. + optional<version_constraint> constraint; // nullopt if unspecified. + shared_ptr<selected_package> selected; // NULL if not present. bool system; - bool patch; // Only for an empty version. + bool patch; // Only for an empty version. bool keep_out; - strings config_vars; // Only if not system. + strings config_vars; // Only if not system. }; using dependency_packages = vector<dependency_package>; @@ -1817,12 +1829,12 @@ namespace bpkg // upgrade/downgrade to as well as the repository fragment it must come // from, and the system flag. // - // If the explicitly specified dependency version can not be found in the - // dependents repositories, then return the "no changes are necessary" - // result if ignore_unsatisfiable argument is true and fail otherwise. The - // common approach is to pass true for this argument until the execution - // plan is finalized, assuming that the problematic dependency might be - // dropped. + // If the package version that satisfies explicitly specified dependency + // version constraint can not be found in the dependents repositories, then + // return the "no changes are necessary" result if ignore_unsatisfiable + // argument is true and fail otherwise. The common approach is to pass true + // for this argument until the execution plan is finalized, assuming that + // the problematic dependency might be dropped. // struct evaluate_result { @@ -1833,12 +1845,12 @@ namespace bpkg }; using package_dependents = vector<pair<shared_ptr<selected_package>, - optional<dependency_constraint>>>; + optional<version_constraint>>>; static optional<evaluate_result> evaluate_dependency (database&, const shared_ptr<selected_package>&, - const version& desired, + const optional<version_constraint>& desired, bool desired_sys, bool patch, bool explicitly, @@ -1883,18 +1895,20 @@ namespace bpkg if (i == deps.end ()) return nullopt; - // If the user expectation is exactly what the selected package is then - // no package change is required. + // If the selected package matches the user expectations then no package + // change is required. // const version& sv (sp->version); bool ssys (sp->system ()); - // The requested dependency version and system flag. + // The requested dependency version constraint and system flag. // - const version& dv (i->version); // May be empty. + const optional<version_constraint>& dvc (i->constraint); // May be nullopt. bool dsys (i->system); - if (dv == sv && ssys == dsys) + if (ssys == dsys && + dvc && + (ssys ? sv == *dvc->min_version : satisfies (sv, dvc))) { l5 ([&]{trace << *sp << ": unchanged";}); @@ -1932,7 +1946,7 @@ namespace bpkg return evaluate_dependency (db, sp, - dv, + dvc, dsys, i->patch, true /* explicitly */, @@ -1944,7 +1958,7 @@ namespace bpkg static optional<evaluate_result> evaluate_dependency (database& db, const shared_ptr<selected_package>& sp, - const version& dv, + const optional<version_constraint>& dvc, bool dsys, bool patch, bool explicitly, @@ -1971,9 +1985,9 @@ namespace bpkg // upgrading. For a system package we also put no constraints just to make // sure that the package is recognized. // - optional<dependency_constraint> c; + optional<version_constraint> c; - if (dv.empty ()) + if (!dvc) { assert (!dsys); // The version can't be empty for the system package. @@ -1989,7 +2003,7 @@ namespace bpkg } } else if (!dsys) - c = dependency_constraint (dv); + c = dvc; vector<pair<shared_ptr<available_package>, shared_ptr<repository_fragment>>> afs ( @@ -2034,7 +2048,7 @@ namespace bpkg // // Note that we also handle a package stub here. // - if (dv.empty () && av < sv) + if (!dvc && av < sv) { assert (!dsys); // Version can't be empty for the system package. @@ -2115,7 +2129,7 @@ namespace bpkg // is the only thing that we can get, and so returning the "no change" // result, unless we need to upgrade a package configured as system. // - if (dv.empty () && !ssys) + if (!dvc && !ssys) { assert (!dsys); // Version cannot be empty for the system package. @@ -2123,13 +2137,14 @@ namespace bpkg return no_change (); } - // If the desired dependency version is unavailable or unsatisfiable for - // some dependents then we fail, unless requested not to do so. In the - // later case we return the "no change" result. + // If the version satisfying the desired dependency version constraint is + // unavailable or unsatisfiable for some dependents then we fail, unless + // requested not to do so. In the later case we return the "no change" + // result. // if (ignore_unsatisfiable) { - l5 ([&]{trace << package_string (nm, dv, dsys) + l5 ([&]{trace << package_string (nm, dvc, dsys) << (unsatisfiable.empty () ? ": no source" : ": unsatisfiable");}); @@ -2144,7 +2159,7 @@ namespace bpkg { diag_record dr (fail); - if (dv.empty () && patch) + if (!dvc && patch) { assert (ssys); // Otherwise, we would bail out earlier (see above). @@ -2157,16 +2172,16 @@ namespace bpkg << "from its dependents' repositories"; } else if (!stub) - fail << package_string (nm, dsys ? version () : dv) + fail << package_string (nm, dsys ? nullopt : dvc) << " is not available from its dependents' repositories"; else // The only available package is a stub. { // Note that we don't advise to "build" the package as a system one as // it is already as such (see above). // - assert (dv.empty () && !dsys && ssys); + assert (!dvc && !dsys && ssys); - fail << package_string (nm, dv) << " is not available in source " + fail << package_string (nm, dvc) << " is not available in source " << "from its dependents' repositories"; } } @@ -2344,7 +2359,7 @@ namespace bpkg optional<evaluate_result> r ( evaluate_dependency (db, sp, - version () /* desired */, + nullopt /* desired */, false /*desired_sys */, !*upgrade /* patch */, false /* explicitly */, @@ -2706,8 +2721,8 @@ namespace bpkg } // Expand the package specs into individual package args, parsing them - // into the package scheme, name, and version components, and also saving - // associated options and configuration variables. + // into the package scheme, name, and version constraint components, and + // also saving associated options and configuration variables. // // Note that the package specs that have no scheme and location cannot be // unambiguously distinguished from the package archive and directory @@ -2716,30 +2731,37 @@ namespace bpkg // struct pkg_arg { - package_scheme scheme; - package_name name; - bpkg::version version; - string value; - pkg_options options; - strings config_vars; + package_scheme scheme; + package_name name; + optional<version_constraint> constraint; + string value; + pkg_options options; + strings config_vars; }; // Create the parsed package argument. // auto arg_package = [] (package_scheme sc, package_name nm, - version vr, + optional<version_constraint> vc, pkg_options os, strings vs) -> pkg_arg { - pkg_arg r {sc, move (nm), move (vr), string (), move (os), move (vs)}; + assert (!vc || !vc->empty ()); // May not be empty if present. + + pkg_arg r {sc, move (nm), move (vc), string (), move (os), move (vs)}; switch (sc) { case package_scheme::sys: { - if (r.version.empty ()) - r.version = wildcard_version; + if (!r.constraint) + r.constraint = version_constraint (wildcard_version); + + // The system package may only have an exact/wildcard version + // specified. + // + assert (r.constraint->min_version == r.constraint->max_version); const system_package* sp (system_repository.find (r.name)); @@ -2747,7 +2769,7 @@ namespace bpkg // if (sp == nullptr || !sp->authoritative) system_repository.insert (r.name, - r.version, + *r.constraint->min_version, true /* authoritative */); break; @@ -2764,7 +2786,7 @@ namespace bpkg { return pkg_arg {package_scheme::none, package_name (), - version (), + nullopt /* constraint */, move (v), move (os), move (vs)}; @@ -2785,10 +2807,11 @@ namespace bpkg string r (options && a.options.dependency () ? "?" : string ()); - r += package_string ( - a.name, - a.version != wildcard_version ? a.version : version (), - arg_sys (a)); + r += package_string (a.name, + (a.constraint && !wildcard (*a.constraint) + ? a.constraint + : nullopt), + arg_sys (a)); if (options) { @@ -2837,6 +2860,20 @@ namespace bpkg return r; }; + // The system package may only be constrained with an exact/wildcard + // version. + // + auto version_only = [] (package_scheme sc) + { + bool r (false); + switch (sc) + { + case package_scheme::none: r = false; break; + case package_scheme::sys: r = true; break; + } + return r; + }; + for (pkg_spec& ps: specs) { if (ps.location.empty ()) @@ -2852,18 +2889,21 @@ namespace bpkg bool sys (sc == package_scheme::sys); package_name n (parse_package_name (s)); - version v (parse_package_version (s, sys, fold_zero_rev (sc))); + + optional<version_constraint> vc ( + parse_package_version_constraint ( + s, sys, fold_zero_rev (sc), version_only (sc))); // For system packages not associated with a specific repository // location add the stub package to the imaginary system // repository (see above for details). // - if (sys && !v.empty ()) + if (sys && vc) stubs.push_back (make_shared<available_package> (n)); pkg_args.push_back (arg_package (sc, move (n), - move (v), + move (vc), move (ps.options), move (ps.config_vars))); } @@ -2940,7 +2980,7 @@ namespace bpkg continue; } - optional<dependency_constraint> c (patch_constraint (sp)); + optional<version_constraint> c (patch_constraint (sp)); // Skip the non-patchable selected package. Note that the // warning have already been issued in this case. @@ -2971,7 +3011,7 @@ namespace bpkg else pkg_args.push_back (arg_package (package_scheme::none, pv.first, - move (pv.second), + version_constraint (pv.second), ps.options, ps.config_vars)); } @@ -2993,7 +3033,10 @@ namespace bpkg bool sys (sc == package_scheme::sys); package_name n (parse_package_name (s)); - version v (parse_package_version (s, sys, fold_zero_rev (sc))); + + optional<version_constraint> vc ( + parse_package_version_constraint ( + s, sys, fold_zero_rev (sc), version_only (sc))); // Check if the package is present in the repository and its // complements, recursively. If the version is not specified then @@ -3019,12 +3062,12 @@ namespace bpkg rfs.push_back (move (fr)); } - optional<dependency_constraint> c; + optional<version_constraint> c; shared_ptr<selected_package> sp; if (!sys) { - if (v.empty ()) + if (!vc) { if (ps.options.patch () && (sp = db.find<selected_package> (n)) != nullptr) @@ -3039,7 +3082,7 @@ namespace bpkg } } else - c = dependency_constraint (v); + c = vc; } shared_ptr<available_package> ap ( @@ -3067,21 +3110,22 @@ namespace bpkg dr << " or its complements"; if (sp == nullptr && ap != nullptr) // Is a stub. - info << "specify sys:" << pkg << " if it is available " - << "from the system"; + dr << info << "specify " + << package_string (n, vc, true /* system */) + << " if it is available from the system"; } // Note that for a system package the wildcard version will be set // (see arg_package() for details). // - if (v.empty () && !sys) - v = ap->version; + if (!vc && !sys) + vc = version_constraint (ap->version); // Don't move options and variables as they may be reused. // pkg_args.push_back (arg_package (sc, move (n), - move (v), + move (vc), ps.options, ps.config_vars)); } @@ -3119,10 +3163,15 @@ namespace bpkg // Note that the variable order may matter. // + // @@ Later we may relax this and replace one package argument with + // another if they only differ with the version constraint and one + // constraint satisfies the other. We will also need to carefully + // maintain the above *_pkgs lists. + // if (!r.second && - (a.scheme != pa.scheme || - a.name != pa.name || - a.version != pa.version || + (a.scheme != pa.scheme || + a.name != pa.name || + a.constraint != pa.constraint || !compare_options (a.options, pa.options) || a.config_vars != pa.config_vars)) fail << "duplicate package " << pa.name << @@ -3198,7 +3247,7 @@ namespace bpkg pa = arg_package (package_scheme::none, m.name, - m.version, + version_constraint (m.version), move (pa.options), move (pa.config_vars)); @@ -3284,7 +3333,7 @@ namespace bpkg pa = arg_package (package_scheme::none, m.name, - m.version, + version_constraint (m.version), move (pa.options), move (pa.config_vars)); @@ -3334,14 +3383,15 @@ namespace bpkg // Don't fold the zero revision so that we build the exact X+0 // package revision, if it is specified. // - version v ( - parse_package_version (package, - false /* allow_wildcard */, - false /* fold_zero_revision */)); + optional<version_constraint> vc ( + parse_package_version_constraint ( + package, + false /* allow_wildcard */, + false /* fold_zero_revision */)); pa = arg_package (package_scheme::none, move (n), - move (v), + move (vc), move (pa.options), move (pa.config_vars)); } @@ -3354,9 +3404,9 @@ namespace bpkg // for a source code package. For a system package we pick the // latest one just to make sure the package is recognized. // - optional<dependency_constraint> c; + optional<version_constraint> c; - if (pa.version.empty ()) + if (!pa.constraint) { assert (!arg_sys (pa)); @@ -3378,7 +3428,7 @@ namespace bpkg } } else if (!arg_sys (pa)) - c = dependency_constraint (pa.version); + c = pa.constraint; auto rp (find_available_one (db, pa.name, c, root)); ap = move (rp.first); @@ -3433,11 +3483,9 @@ namespace bpkg // Make sure that the package is known. // - auto apr (pa.version.empty () || sys + auto apr (!pa.constraint || sys ? find_available (db, pa.name, nullopt) - : find_available (db, - pa.name, - dependency_constraint (pa.version))); + : find_available (db, pa.name, *pa.constraint)); if (apr.empty ()) { @@ -3452,7 +3500,7 @@ namespace bpkg sp = db.find<selected_package> (pa.name); dep_pkgs.push_back (dependency_package {move (pa.name), - move (pa.version), + move (pa.constraint), move (sp), sys, pa.options.patch (), @@ -3496,7 +3544,7 @@ namespace bpkg // if (ap == nullptr) { - if (!pa.version.empty () && + if (pa.constraint && find_available_one (db, pa.name, nullopt, @@ -3509,10 +3557,10 @@ namespace bpkg ap = nullptr; } - // If the user asked for a specific version, then that's what we - // ought to be building. + // If the user constrained the version, then that's what we ought to + // be building. // - if (!pa.version.empty ()) + if (pa.constraint) { for (;;) { @@ -3520,9 +3568,11 @@ namespace bpkg break; // Otherwise, our only chance is that the already selected object - // is that exact version. + // satisfies the version constraint. // - if (sp != nullptr && !sp->system () && sp->version == pa.version) + if (sp != nullptr && + !sp->system () && + satisfies (sp->version, pa.constraint)) break; // Derive ap from sp below. found = false; @@ -3584,9 +3634,12 @@ namespace bpkg assert (!arg_sys (pa)); dr << arg_string (pa, false /* options */) - << " is not available in source" << - info << "specify sys:" << arg_string (pa, false /* options */) - << " if it is available from the system"; + << " is not available in source"; + + pa.scheme = package_scheme::sys; + + dr << info << "specify " << arg_string (pa, false /* options */) + << " if it is available from the system"; } } @@ -3618,27 +3671,25 @@ namespace bpkg move (sp), move (ap), move (af), - true, // Hold package. - !pa.version.empty (), // Hold version. - {}, // Constraints. + true, // Hold package. + pa.constraint.has_value (), // Hold version. + {}, // Constraints. arg_sys (pa), keep_out, move (pa.config_vars), - {package_name ()}, // Required by (command line). - 0}; // Adjustments. + {package_name ()}, // Required by (command line). + 0}; // Adjustments. l4 ([&]{trace << "stashing held package " << p.available_name_version ();}); - // "Fix" the version the user asked for by adding the '==' constraint. + // "Fix" the version the user asked for by adding the constraint. // // Note: for a system package this must always be present (so that // this build_package instance is never replaced). // - if (!pa.version.empty ()) - p.constraints.emplace_back ( - "command line", - dependency_constraint (pa.version)); + if (pa.constraint) + p.constraints.emplace_back ("command line", move (*pa.constraint)); hold_pkgs.push_back (move (p)); } @@ -3664,7 +3715,7 @@ namespace bpkg const package_name& name (sp->name); - optional<dependency_constraint> pc; + optional<version_constraint> pc; if (o.patch ()) { @@ -3825,23 +3876,21 @@ namespace bpkg for (const dependency_package& p: dep_pkgs) { build_package bp { - nullopt, // Action. - nullptr, // Selected package. - nullptr, // Available package/repository fragment. + nullopt, // Action. + nullptr, // Selected package. + nullptr, // Available package/repository frag. nullptr, - false, // Hold package. - !p.version.empty (), // Hold version. - {}, // Constraints. + false, // Hold package. + p.constraint.has_value (), // Hold version. + {}, // Constraints. p.system, p.keep_out, p.config_vars, - {package_name ()}, // Required by (command line). - 0}; // Adjustments. + {package_name ()}, // Required by (command line). + 0}; // Adjustments. - if (!p.version.empty ()) - bp.constraints.emplace_back ( - "command line", - dependency_constraint (p.version)); + if (p.constraint) + bp.constraints.emplace_back ("command line", *p.constraint); pkgs.enter (p.name, move (bp)); } @@ -4066,11 +4115,12 @@ namespace bpkg if (!scratch) { // First, we check if the refinement is required, ignoring the - // unsatisfiable dependency versions. If we end up refining the - // execution plan, such dependencies might be dropped, and then - // there will be nothing to complain about. When no more refinements - // are necessary we will run the diagnostics check, to make sure - // that the unsatisfiable dependency, if left, is reported. + // unsatisfiable dependency version constraints. If we end up + // refining the execution plan, such dependencies might be dropped, + // and then there will be nothing to complain about. When no more + // refinements are necessary we will run the diagnostics check, to + // make sure that the unsatisfiable dependency, if left, is + // reported. // auto need_refinement = [&eval_dep, &deps, rec_pkgs, &db, &o] ( bool diag = false) -> bool diff --git a/bpkg/pkg-status.cxx b/bpkg/pkg-status.cxx index bfba21f..b4b8de6 100644 --- a/bpkg/pkg-status.cxx +++ b/bpkg/pkg-status.cxx @@ -19,10 +19,10 @@ namespace bpkg { struct package { - package_name name; - bpkg::version version; // Empty if unspecified. - shared_ptr<selected_package> selected; // NULL if none selected. - optional<dependency_constraint> constraint; // Version constraint, if any. + package_name name; + bpkg::version version; // Empty if unspecified. + shared_ptr<selected_package> selected; // NULL if none selected. + optional<version_constraint> constraint; // Version constraint, if any. }; using packages = vector<package>; @@ -236,7 +236,7 @@ namespace bpkg for (const auto& pair: s->prerequisites) { shared_ptr<selected_package> d (pair.first.load ()); - const optional<dependency_constraint>& c (pair.second); + const optional<version_constraint>& c (pair.second); dpkgs.push_back (package {d->name, version (), move (d), c}); } } diff --git a/bpkg/satisfaction.cxx b/bpkg/satisfaction.cxx index 53fd3b8..c3c4995 100644 --- a/bpkg/satisfaction.cxx +++ b/bpkg/satisfaction.cxx @@ -15,7 +15,7 @@ using namespace butl; namespace bpkg { bool - satisfies (const version& v, const dependency_constraint& c) + satisfies (const version& v, const version_constraint& c) { assert (!c.empty () && c.complete ()); @@ -25,7 +25,7 @@ namespace bpkg bool s (true); // Here an absent revision means zero revision and version X must satisfy - // the [X+0 ...) dependency constraint. Note that technically X < X+0. + // the [X+0 ...) version constraint. Note that technically X < X+0. // version ev (v.epoch, v.upstream, @@ -52,7 +52,7 @@ namespace bpkg } bool - satisfies (const dependency_constraint& l, const dependency_constraint& r) + satisfies (const version_constraint& l, const version_constraint& r) { assert (!l.empty () && l.complete () && !r.empty () && r.complete ()); diff --git a/bpkg/satisfaction.hxx b/bpkg/satisfaction.hxx index 31a3e49..5ab32a1 100644 --- a/bpkg/satisfaction.hxx +++ b/bpkg/satisfaction.hxx @@ -13,16 +13,16 @@ namespace bpkg { - // Note: all of the following functions expect the package dependency + // Note: all of the following functions expect the package version // constraints to be complete. // Return true if version satisfies the constraint. // bool - satisfies (const version&, const dependency_constraint&); + satisfies (const version&, const version_constraint&); inline bool - satisfies (const version& v, const optional<dependency_constraint>& c) + satisfies (const version& v, const optional<version_constraint>& c) { return !c || satisfies (v, *c); } @@ -32,11 +32,11 @@ namespace bpkg // l is a subset of r. // bool - satisfies (const dependency_constraint& l, const dependency_constraint& r); + satisfies (const version_constraint& l, const version_constraint& r); inline bool - satisfies (const optional<dependency_constraint>& l, - const optional<dependency_constraint>& r) + satisfies (const optional<version_constraint>& l, + const optional<version_constraint>& r) { return l ? (!r || satisfies (*l, *r)) : !r; } diff --git a/bpkg/satisfaction.test.cxx b/bpkg/satisfaction.test.cxx index fef30e1..4857e0d 100644 --- a/bpkg/satisfaction.test.cxx +++ b/bpkg/satisfaction.test.cxx @@ -9,43 +9,43 @@ namespace bpkg static int main (int, char*[]) { - using dc = dependency_constraint; + using vc = version_constraint; - assert ( satisfies (dc ("[1.0 2.0]"), dc ("[1.0+0 2.0]"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("[1.0+1 2.0]"))); - assert ( satisfies (dc ("[1.0+0 2.0]"), dc ("[1.0 2.0]"))); - assert ( satisfies (dc ("[1.0+1 2.0]"), dc ("[1.0 2.0]"))); + assert ( satisfies (vc ("[1.0 2.0]"), vc ("[1.0+0 2.0]"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("[1.0+1 2.0]"))); + assert ( satisfies (vc ("[1.0+0 2.0]"), vc ("[1.0 2.0]"))); + assert ( satisfies (vc ("[1.0+1 2.0]"), vc ("[1.0 2.0]"))); - assert (!satisfies (dc ("[1.0+0 2.0]"), dc ("(1.0 2.0]"))); - assert (!satisfies (dc ("[1.0+1 2.0]"), dc ("(1.0 2.0]"))); - assert (!satisfies (dc ("(1.0+0 2.0]"), dc ("(1.0 2.0]"))); - assert (!satisfies (dc ("(1.0+1 2.0]"), dc ("(1.0 2.0]"))); - assert ( satisfies (dc ("(1.0+0 2.0]"), dc ("[1.0 2.0]"))); - assert ( satisfies (dc ("(1.0+1 2.0]"), dc ("[1.0 2.0]"))); + assert (!satisfies (vc ("[1.0+0 2.0]"), vc ("(1.0 2.0]"))); + assert (!satisfies (vc ("[1.0+1 2.0]"), vc ("(1.0 2.0]"))); + assert (!satisfies (vc ("(1.0+0 2.0]"), vc ("(1.0 2.0]"))); + assert (!satisfies (vc ("(1.0+1 2.0]"), vc ("(1.0 2.0]"))); + assert ( satisfies (vc ("(1.0+0 2.0]"), vc ("[1.0 2.0]"))); + assert ( satisfies (vc ("(1.0+1 2.0]"), vc ("[1.0 2.0]"))); - assert (!satisfies (dc ("[1.0 2.0+0]"), dc ("[1.0 2.0)"))); - assert (!satisfies (dc ("[1.0 2.0+1]"), dc ("[1.0 2.0)"))); - assert ( satisfies (dc ("[1.0 2.0+0)"), dc ("[1.0 2.0)"))); - assert (!satisfies (dc ("[1.0 2.0+1)"), dc ("[1.0 2.0)"))); + assert (!satisfies (vc ("[1.0 2.0+0]"), vc ("[1.0 2.0)"))); + assert (!satisfies (vc ("[1.0 2.0+1]"), vc ("[1.0 2.0)"))); + assert ( satisfies (vc ("[1.0 2.0+0)"), vc ("[1.0 2.0)"))); + assert (!satisfies (vc ("[1.0 2.0+1)"), vc ("[1.0 2.0)"))); // Swap the above constraints. // - assert (!satisfies (dc ("[1.0 2.0]"), dc ("[1.0 2.0+0]"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("[1.0 2.0+1]"))); - assert ( satisfies (dc ("[1.0 2.0+0]"), dc ("[1.0 2.0]"))); - assert ( satisfies (dc ("[1.0 2.0+1]"), dc ("[1.0 2.0]"))); - - assert ( satisfies (dc ("(1.0 2.0]"), dc ("[1.0+0 2.0]"))); - assert ( satisfies (dc ("(1.0 2.0]"), dc ("[1.0+1 2.0]"))); - assert ( satisfies (dc ("(1.0 2.0]"), dc ("(1.0+0 2.0]"))); - assert ( satisfies (dc ("(1.0 2.0]"), dc ("(1.0+1 2.0]"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("(1.0+0 2.0]"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("(1.0+1 2.0]"))); - - assert ( satisfies (dc ("[1.0 2.0)"), dc ("[1.0 2.0+0)"))); - assert ( satisfies (dc ("[1.0 2.0)"), dc ("[1.0 2.0+1)"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("[1.0 2.0+0)"))); - assert (!satisfies (dc ("[1.0 2.0]"), dc ("[1.0 2.0+1)"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("[1.0 2.0+0]"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("[1.0 2.0+1]"))); + assert ( satisfies (vc ("[1.0 2.0+0]"), vc ("[1.0 2.0]"))); + assert ( satisfies (vc ("[1.0 2.0+1]"), vc ("[1.0 2.0]"))); + + assert ( satisfies (vc ("(1.0 2.0]"), vc ("[1.0+0 2.0]"))); + assert ( satisfies (vc ("(1.0 2.0]"), vc ("[1.0+1 2.0]"))); + assert ( satisfies (vc ("(1.0 2.0]"), vc ("(1.0+0 2.0]"))); + assert ( satisfies (vc ("(1.0 2.0]"), vc ("(1.0+1 2.0]"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("(1.0+0 2.0]"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("(1.0+1 2.0]"))); + + assert ( satisfies (vc ("[1.0 2.0)"), vc ("[1.0 2.0+0)"))); + assert ( satisfies (vc ("[1.0 2.0)"), vc ("[1.0 2.0+1)"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("[1.0 2.0+0)"))); + assert (!satisfies (vc ("[1.0 2.0]"), vc ("[1.0 2.0+1)"))); return 0; } diff --git a/doc/manual.cli b/doc/manual.cli index 7f0f3f9..3581880 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -237,6 +237,68 @@ model, we have a version key as just {\i{epoch}, \i{upstream}, \i{prerel}} but also store the package revision and iteration so that it can be shown it to the user, etc.| + +\h1#package-version-constraint|Package Version Constraint| + +The \c{bpkg} package version constraint may follow the package name in certain +contexts, such as the manifest values and \c{bpkg} command line, to restrict +the allowed package version set. It can be specified using comparison +operators, shortcut (to range) operators, or ranges and has the following +form: + +\ +<version-constraint> := <comparison> | <shortcut> | <range> +<comparison> := ('==' | '>' | '<' | '>=' | '<=') <version> +<shortcut> := ('^' | '~') <version> +<range> := ('(' | '[') <version> <version> (')' | ']') +\ + +The shortcut operators can only be used with \l{b#module-version standard +versions} (a semantic version without the pre-release part is a standard +version). They are equivalent to the following ranges. \N{The \c{X.Y.Z-} version +signifies the earliest pre-release in the \c{X.Y.Z} series; see +\l{#package-version Package Version} for details}. + +\ +~X.Y.Z [X.Y.Z X.Y+1.0-) + +^X.Y.Z [X.Y.Z X+1.0.0-) if X > 0 +^0.Y.Z [0.Y.Z 0.Y+1.0-) if X == 0 +\ + +That is, the tilde (\c{~}) constraint allows upgrades to any further patch +version while the caret (\c{^}) constraint \- also to any further minor +version. + +\N|Zero major version component is customarily used during early development +where the minor version effectively becomes major. As a result, the tilde +constraint has special semantics for this case.| + +Note that the shortuct operators can only be used with the complete, +three-component versions (\c{X.Y.Z} with the optional pre-release part per the +standard version). Specifically, there is no support for special \c{^X.Y} or +\c{~X} semantics offered by some package manager \- if desired, such +functionality can be easily achieved with ranges. Also, the \c{0.0.Z} version +is not considered special except as having zero major component for the tilde +semantics discussed above. + +Note also that pre-releases do not required any special considerations when +used with the shortcut operators. For example, if package \c{libfoo} is +usable starting with the second beta of the \c{2.0.0} release, then our +constraint could be expressed as: + +\ +libfoo ^2.0.0-b.2 +\ + +\N|Internally shortucts and comparisons can be represented as ranges (that is, +\c{[v, v]} for \c{==}, \c{(v, inf)} for \c{>}, etc). However, for display and +serialization such representations should be converted back to simple +operators. While it is possible that the original manifest specified equality +or shortucts as full ranges, it is acceptable to display/serialize them as +simpler operators.| + + \h1#manifests|Manifests| This chapter describes the general manifest file format as well as the @@ -548,6 +610,10 @@ license: <licenses> [; <comment>] [depends]: [?][*] <alternatives> [; <comment>] [requires]: [?] [<alternatives>] [; <comment>] +[tests]: <name> [<version-constraint>] +[examples]: <name> [<version-constraint>] +[benchmarks]: <name> [<version-constraint>] + [builds]: <class-expr> [; <comment>] [build-include]: <config>[/<target>] [; <comment>] [build-exclude]: <config>[/<target>] [; <comment>] @@ -890,11 +956,7 @@ build error notifications are sent to this email. [depends]: [?][*] <alternatives> [; <comment>] <alternatives> := <dependency> [ '|' <dependency>]* -<dependency> := <name> [<constraint>] -<constraint> := <comparison> | <shortcut> | <range> -<comparison> := ('==' | '>' | '<' | '>=' | '<=') <version> -<shortcut> := ('^' | '~') <version> -<range> := ('(' | '[') <version> <version> (')' | ']') +<dependency> := <name> [<version-constraint>] \ The prerequisite packages. If the \c{depends} value start with \c{*}, then @@ -935,60 +997,14 @@ depends: ? libqtcore >= 5.0.0 ; Only if GUI is enabled. It is recommended that you specify unconditional dependencies first with simple (no alternatives) dependencies leading each set. -The optional version constraint can be specified using comparison operators, -shortcut (to range) operators, and ranges. - -The shortcut operators can only be used with \l{b#module-version standard -versions} (a semantic version without the pre-release part is a standard -version). They are equivalent to the following ranges. \N{The \c{X.Y.Z-} version -signifies the earliest pre-release in the \c{X.Y.Z} series; see -\l{#package-version Package Version} for details}. - -\ -~X.Y.Z [X.Y.Z X.Y+1.0-) - -^X.Y.Z [X.Y.Z X+1.0.0-) if X > 0 -^0.Y.Z [0.Y.Z 0.Y+1.0-) if X == 0 -\ - -That is, the tilde (\c{~}) constraint allows upgrades to any further patch -version while the caret (\c{^}) constraint \- also to any further minor -version. - -\N|Zero major version component is customarily used during early development -where the minor version effectively becomes major. As a result, the tilde -constraint has special semantics for this case.| - -Note that the shortuct operators can only be used with the complete, -three-component versions (\c{X.Y.Z} with the optional pre-release part per the -standard version). Specifically, there is no support for special \c{^X.Y} or -\c{~X} semantics offered by some package manager \- if desired, such -functionality can be easily achieved with ranges. Also, the \c{0.0.Z} version -is not considered special except as having zero major component for the tilde -semantics discussed above. - -Note also that pre-releases do not required any special considerations when -used with the shortcut operators. For example, if package \c{libfoo} is -usable starting with the second beta of the \c{2.0.0} release, then our -constraint could be expressed as: - -\ -depends: libfoo ^2.0.0-b.2 -\ - -\N|Internally shortucts and comparisons can be represented as ranges (that is, -\c{[v, v]} for \c{==}, \c{(v, inf)} for \c{>}, etc). However, for display and -serialization such representations should be converted back to simple -operators. While it is possible that the original manifest specified equality -or shortucts as full ranges, it is acceptable to display/serialize them as -simpler operators.| - -Instead of a specific version, the constraint can be specified in terms of the -dependent package's version (that is, its \l{#manifest-package-version -\c{version}} value) using the special \c{$} value. A \c{depends} value that -contains \c{$} is called incomplete. This mechanism is primarily useful when -developing related packages that should track each other's versions exactly or -closely. For example: +See \l{#package-version-constraint Package Version Constraint} for the format +and semantics of the optional version constraint. Instead of a concrete +value, it can also be specified in terms of the dependent package's version +(that is, its \l{#manifest-package-version \c{version}} value) using the +special \c{$} value. A \c{depends} value that contains \c{$} is called +incomplete. This mechanism is primarily useful when developing related +packages that should track each other's versions exactly or closely. For +example: \ name: sqlite3 @@ -1135,6 +1151,38 @@ msvc[_NU] ; For example: msvc_14, msvc_15u3 \ +\h2#manifest-package-tests-examples-benchmarks|\c{tests, examples, benchmarks}| + +\ +[tests]: <name> [<version-constraint>] +[examples]: <name> [<version-constraint>] +[benchmarks]: <name> [<version-constraint>] +\ + +Separate tests, examples, and benchmarks packages. These packages are built +and tested by automated build bots together with the dependent package (see +the \c{bbot} documentation for details). This, in particular, implies that +these packages must be available from the dependent package's repository or +its complement repositories, recursively. The recommended naming convention +for these packages is the dependent package name followed with \c{-tests}, +\c{-examples}, or \c{-benchmarks}, respectively. For example: + +\ +name: hello +tests : hello-tests +examples: hello-examples +\ + +See \l{#package-version-constraint Package Version Constraint} for the format +and semantics of the optional version constraint. Instead of a concrete value, +it can also be specified in terms of the dependent package's version (see the +\l{#manifest-package-depends \c{depends}} value for details), for example: + +\ +tests: hello-tests ~$ +\ + + \h2#manifest-package-builds|\c{builds}| \ @@ -1213,10 +1261,10 @@ display the reason for the build configuration exclusion.| After evaluating all the \c{builds} values, the final configuration set can be further fine-tuned using the \l{#manifest-package-include-exclude -\c{build-{include,exclude\}}} patterns. +\c{build-{include, exclude\}}} patterns. -\h2#manifest-package-include-exclude|\c{build-{include,exclude\}}| +\h2#manifest-package-include-exclude|\c{build-{include, exclude\}}| \ [build-include]: <config>[/<target>] [; <comment>] diff --git a/tests/pkg-build.testscript b/tests/pkg-build.testscript index 48fe5a9..2f2fec8 100644 --- a/tests/pkg-build.testscript +++ b/tests/pkg-build.testscript @@ -196,12 +196,26 @@ test.options += --no-progress error: invalid package version '1.0.0-' in 'libfoo/1.0.0-': earliest version EOE + : earliest-constraint + : + $clone_root_cfg; + $* -- 'libfoo == 1.0.0-' 2>>EOE != 0 + error: invalid package version constraint '== 1.0.0-' in 'libfoo == 1.0.0-': invalid version: equal version endpoints are earliest + EOE + : stub : $clone_root_cfg; $* libfoo/0+1 2>>EOE != 0 error: invalid package version '0+1' in 'libfoo/0+1': stub version EOE + + : stub-constraint + : + $clone_root_cfg; + $* -- 'libfoo [0 1]' 2>>EOE != 0 + error: invalid package version constraint '[0 1]' in 'libfoo [0 1]': endpoint is a stub + EOE } : unknown-package @@ -222,6 +236,15 @@ test.options += --no-progress info: use 'bpkg rep-add' to add a repository EOE + : unknown-package-constraint + : + $clone_root_cfg; + $* 'libfoo>1.0.0' 2>>/EOE != 0 + error: unknown package libfoo + info: configuration cfg/ has no repositories + info: use 'bpkg rep-add' to add a repository + EOE + : archive : $clone_root_cfg; @@ -237,9 +260,10 @@ test.options += --no-progress { $clone_root_cfg && $pkg_unpack -e $src/libfoo-1.1.0; - $* libfoo >'update libfoo/1.1.0'; - $* libfoo/1.1.0 >'update libfoo/1.1.0'; - $* libfoo libfoo >'update libfoo/1.1.0'; + $* libfoo >'update libfoo/1.1.0'; + $* libfoo/1.1.0 >'update libfoo/1.1.0'; + $* -- 'libfoo == 1.1.0' >'update libfoo/1.1.0'; + $* libfoo libfoo >'update libfoo/1.1.0'; $* libfoo libfoo/1.1.0 2>>EOE != 0; error: duplicate package libfoo @@ -285,6 +309,22 @@ test.options += --no-progress $pkg_purge libfoo 2>'purged libfoo/1.1.0' } + : downgrade-constraint + : + { + $clone_cfg && $pkg_unpack -e $src/libfoo-1.1.0; + + $* libfoo >'update libfoo/1.1.0'; + $* 'libfoo<1.1.0' >'downgrade libfoo/1.0.0'; + + $* 'libfoo<1.0.0' 2>>EOE != 0; + error: 'libfoo < 1.0.0' is not available in source + info: specify sys:libfoo/... if it is available from the system + EOE + + $pkg_purge libfoo 2>'purged libfoo/1.1.0' + } + : upgrade : { @@ -1384,9 +1424,58 @@ test.options += --no-progress $clone_root_cfg; $rep_fetch $rep/t0c; - $* '?libbux' 2>'error: unknown package libbux' != 0; - $* '?sys:libbux' 2>'error: unknown package sys:libbux' != 0; - $* '?libbar/1.3' 2>'error: unknown package libbar/1.3' != 0 + $* '?libbux' 2>'error: unknown package libbux' != 0; + $* '?sys:libbux' 2>'error: unknown package sys:libbux' != 0; + $* '?libbar/1.3' 2>'error: unknown package libbar/1.3' != 0; + $* '?libbar[5 7]' 2>"error: unknown package 'libbar [5 7]'" != 0 + } + + : constraint + : + { + $clone_root_cfg; + $rep_fetch $rep/t0c; + + # Constraint the dependency version. + # + $* libbox '?libbaz < 0.1.0' 2>>~%EOE%; + fetched libbaz/0.0.4 + unpacked libbaz/0.0.4 + configured libbaz/0.0.4 + fetched libbox/0.0.1 + unpacked libbox/0.0.1 + configured libbox/0.0.1 + %info: .+ is up to date%{2} + updated libbaz/0.0.4 + updated libbox/0.0.1 + EOE + + $pkg_status libbaz >'libbaz configured !0.0.4 available 0.1.0'; + + # The selected dependency libbaz/0.0.4 satisfies the constraint, thus it + # is not upgraded. + # + $* '?libbaz < 1.0.0'; + + $pkg_status libbaz >'libbaz configured !0.0.4 available 0.1.0'; + + # Upgrade the dependency. + # + $* '?libbaz > 0.0.4' --yes 2>>~%EOE%; + disfigured libbox/0.0.1 + disfigured libbaz/0.0.4 + fetched libbaz/0.1.0 + unpacked libbaz/0.1.0 + configured libbaz/0.1.0 + configured libbox/0.0.1 + %info: .+ is up to date%{2} + updated libbaz/0.1.0 + updated libbox/0.0.1 + EOE + + $pkg_status libbaz >'libbaz configured !0.1.0'; + + $pkg_drop libbox } : system-no-repo @@ -1598,13 +1687,21 @@ test.options += --no-progress $clone_root_cfg; $rep_fetch $rep/t0a $rep/t0b; - $* libbar/0.0.1 ?libbaz/0.0.2 2>>EOE != 0 + $* libbar/0.0.1 ?libbaz/0.0.2 2>>EOE != 0; error: unable to satisfy constraints on package libbaz info: libbar depends on (libbaz == 0.0.1) info: command line depends on (libbaz == 0.0.2) info: specify libbaz version to satisfy libbar constraint info: while satisfying libbar/0.0.1 EOE + + $* -- libbar/0.0.1 '?libbaz>=0.0.2' 2>>EOE != 0 + error: unable to satisfy constraints on package libbaz + info: libbar depends on (libbaz == 0.0.1) + info: command line depends on (libbaz >= 0.0.2) + info: specify libbaz version to satisfy libbar constraint + info: while satisfying libbar/0.0.1 + EOE } : resolve-conflict @@ -1613,7 +1710,7 @@ test.options += --no-progress : satisfy-dependents : : Test resolving a conflict when libfix and libbiz have selected such - : versions of their dependency libbaz. that do not satisfy each other + : versions of their dependency libbaz, that do not satisfy each other : constraints. We resolve the conflict explicitly specifying : ?libbaz/0.0.3 on the command line, which satisfies both constraints. : @@ -1750,6 +1847,20 @@ test.options += --no-progress $pkg_drop libbar } + : same-constraint + : + { + $clone_cfg; + + $* libbar/0.0.1 2>!; + $* libbar/0.0.2 '?libbaz<0.0.2' 2>!; + + $pkg_status libbaz >'libbaz configured !0.0.1 available 0.1.0 0.0.4 0.0.3 0.0.2'; + $pkg_status libfox >'libfox configured 0.0.1'; + + $pkg_drop libbar + } + : src-to-sys : { @@ -1971,6 +2082,33 @@ test.options += --no-progress $pkg_drop libbar } + : version-to-constraint + : + { + $clone_cfg; + + $* libbar/0.0.1 '?sys:libbaz/0.0.1' 2>>EOE; + configured sys:libbaz/0.0.1 + fetched libbar/0.0.1 + unpacked libbar/0.0.1 + configured libbar/0.0.1 + EOE + + $* '?libbaz [0.0.1 0.0.2]' 2>>EOE; + disfigured libbar/0.0.1 + purged libbaz/0.0.1 + fetched libfox/0.0.1 + unpacked libfox/0.0.1 + configured libfox/0.0.1 + fetched libbaz/0.0.1 + unpacked libbaz/0.0.1 + configured libbaz/0.0.1 + configured libbar/0.0.1 + EOE + + $pkg_drop libbar + } + : src-to-wildcard : { @@ -2048,6 +2186,10 @@ test.options += --no-progress error: libfoo/0.0.1 is not available from its dependents' repositories EOE + $* '?libfoo < 0.0.2' 2>>EOE != 0; + error: 'libfoo < 0.0.2' is not available from its dependents' repositories + EOE + $pkg_drop libbar } |