From dad48d98b1a57706179c34853950588ec75a8467 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 22 Oct 2019 22:49:19 +0300 Subject: Add support for package version constraint in pkg-build command arguments Also document tests, examples, and benchmarks package manifest values. --- bpkg/manifest-utility.cxx | 75 ++++++++- bpkg/manifest-utility.hxx | 11 ++ bpkg/package.cxx | 34 ++++ bpkg/package.hxx | 33 +++- bpkg/pkg-build.cli | 43 ++--- bpkg/pkg-build.cxx | 384 +++++++++++++++++++++++++-------------------- bpkg/pkg-status.cxx | 10 +- bpkg/satisfaction.cxx | 6 +- bpkg/satisfaction.hxx | 12 +- bpkg/satisfaction.test.cxx | 62 ++++---- 10 files changed, 426 insertions(+), 244 deletions(-) (limited to 'bpkg') 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 +#include // strcspn() + #include #include #include @@ -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 (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 + 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& 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 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 [/] or + // forms, unless version_only is true. For the + // former case return the `== ` constraint. Return nullopt if only + // the package name is specified. + // + optional + 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& 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 + // "/" string rather than " == ", using the + // version type-based overload. + // + const optional& 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; - // 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 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:][] form. + // The version constraint component is represented with the "/" + // string for the `== ` 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:/..." 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&, + 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, - optional, + optional, compare_lazy_ptr>; #pragma db object pointer(shared_ptr) session @@ -1048,7 +1065,7 @@ namespace bpkg string name; #pragma db column(pp.value) - optional constraint; + optional constraint; }; */ @@ -1064,7 +1081,7 @@ namespace bpkg package_name name; #pragma db column("pp.") - optional constraint; + optional 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 { " - + + ", @@ -25,12 +26,13 @@ namespace bpkg \b{bpkg pkg-build}|\b{build} [] \ \b{--upgrade}|\b{-u} | \b{--patch}|\b{-p}\n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [... \b{--}]} - \c{ = [](([\b{:}][\b{/}])\b{,}...[\b{@}] | \n - \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{@}] \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n - \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n + \c{ = [](([\b{:}][])\b{,}...[\b{@}] | \n + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [\b{@}] \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n + \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \n \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \b{/})\n - \ \ \ \ = \b{?}\n - \ \ = \b{sys}} + \ \ \ \ \ \ = \b{?}\n + \ \ \ \ = \b{sys}\n + \ \ \ = \b{/} | } \h|DESCRIPTION| @@ -56,21 +58,25 @@ namespace bpkg or \cb{--patch}. Each package can be specified as just the name () with optional - package version () in which case the source code for the package - will be automatically fetched from one of the configured + version specification (), 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 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 () 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 + 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 () can be - specified as part of the build command. In this case, if 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 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 (). 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 - () is not specified or is '\cb{*}', then it is considered to be - unknown but satisfying any dependency constraint. If the version is not + () is not specified or is '\cb{/*}', then it is considered to + be unknown but satisfying any version constraint. If specified, + 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 () or as an archive or directory, + an explicit package version () 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 query_available (database& db, const package_name& name, - const optional& c) + const optional& c) { using query = query; @@ -164,7 +164,7 @@ namespace bpkg vector, shared_ptr>> find_available (database& db, const package_name& name, - const optional& c) + const optional& c) { vector, shared_ptr>> r; @@ -208,7 +208,7 @@ namespace bpkg vector, shared_ptr>> find_available (database& db, const package_name& name, - const optional& c, + const optional& c, const vector>& rfs, bool prereq = true) { @@ -239,7 +239,7 @@ namespace bpkg static pair, shared_ptr> find_available_one (database& db, const package_name& name, - const optional& c, + const optional& c, const shared_ptr& rf, bool prereq = true) { @@ -260,7 +260,7 @@ namespace bpkg static pair, shared_ptr> find_available_one (database& db, const package_name& name, - const optional& c, + const optional& c, const vector>& rfs, bool prereq = true) { @@ -328,6 +328,19 @@ namespace bpkg return make_pair (make_shared (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 dsp (db.find (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 + static optional patch_constraint (const shared_ptr& 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; // NULL if not present. + optional constraint; // nullopt if unspecified. + shared_ptr 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; @@ -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, - optional>>; + optional>>; static optional evaluate_dependency (database&, const shared_ptr&, - const version& desired, + const optional& 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& 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_dependency (database& db, const shared_ptr& sp, - const version& dv, + const optional& 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 c; + optional 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, shared_ptr>> 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 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 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 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 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 (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 c (patch_constraint (sp)); + optional 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 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 c; + optional c; shared_ptr sp; if (!sys) { - if (v.empty ()) + if (!vc) { if (ps.options.patch () && (sp = db.find (n)) != nullptr) @@ -3039,7 +3082,7 @@ namespace bpkg } } else - c = dependency_constraint (v); + c = vc; } shared_ptr 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 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 c; + optional 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 (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 pc; + optional 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; // NULL if none selected. - optional constraint; // Version constraint, if any. + package_name name; + bpkg::version version; // Empty if unspecified. + shared_ptr selected; // NULL if none selected. + optional constraint; // Version constraint, if any. }; using packages = vector; @@ -236,7 +236,7 @@ namespace bpkg for (const auto& pair: s->prerequisites) { shared_ptr d (pair.first.load ()); - const optional& c (pair.second); + const optional& 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& c) + satisfies (const version& v, const optional& 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& l, - const optional& r) + satisfies (const optional& l, + const optional& 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; } -- cgit v1.1