aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-10-22 19:49:19 (GMT)
committerKaren Arutyunov <karen@codesynthesis.com>2019-10-31 14:33:32 (GMT)
commitdad48d98b1a57706179c34853950588ec75a8467 (patch)
tree307cf55b44e88b2f6cc154c58cc74683040000ca
parent6bbf6390d95941cd5ead6eba649edc2a7fec9d21 (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.cxx75
-rw-r--r--bpkg/manifest-utility.hxx11
-rw-r--r--bpkg/package.cxx34
-rw-r--r--bpkg/package.hxx33
-rw-r--r--bpkg/pkg-build.cli43
-rw-r--r--bpkg/pkg-build.cxx384
-rw-r--r--bpkg/pkg-status.cxx10
-rw-r--r--bpkg/satisfaction.cxx6
-rw-r--r--bpkg/satisfaction.hxx12
-rw-r--r--bpkg/satisfaction.test.cxx62
-rw-r--r--doc/manual.cli170
-rw-r--r--tests/pkg-build.testscript158
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
}