Hello, everyone.

Following my earlier threads, I'd like to propose a first complete
solution for new version restrictions for package dependencies. I
honestly doubt it's going to be approved since it's a major change.
Nevertheless, I think it's an interesting topic for consideration.

What is included:

- conjunctive version ranges,
- revision-free and revision-oriented comparisons,
- full set of (blocker-free) logical operators.

What isn't included:

- disjunctive version ranges,
- complete lower bound problem solution,
- extensions for prefix matching,
- some convenience shortcuts like Ruby's ~> op.


Backwards compatibility [recommended]
=====================================

For backwards compatibility, package dependency specifications using
old-style restrictions will still be accepted. Those specifications
will retain the old behavior, and have no new features.


New package dependency syntax
=============================

New-style package dependencies use the following syntax:

  <cat> "/" <pkg> [":" <slot>] ["[" <vers> "]"] ["[" <usedep> "]"]

with <vers> now using the following sub-syntax:

  <op> <version> ["," <op> <version>...]

The version restriction operator is removed from the front and all
package dependency specifications start with the category and package
name, followed by optional package slot. This can be followed by
optional version restrictions and USE flag restrictions.

The version constraints (if present) must *always* be placed inside
square brackets, even for a single constraint. Each constraint starts
with an operator followed by a version string. Multiple constraints are
separated using "," character, and are conjunctive (AND-ed).

The operators are not valid for beginning of a USE dependency string,
therefore the version constraint can be clearly distinguished from USE
contraints.

The version and USE flag constraints are always disjoint. If both are
present, they must be surrounded by separate pairs of brackets.

Examples:

  dev-foo/bar:13[foo]          # slot + USE
  dev-foo/bar[>=3]             # single version constraint
  dev-foo/bar:4[>=4.11,<4.20]  # slot + version range
  dev-foo/bar[>=3][foo]        # version + USE


Version restrictions
====================

Each version restriction consists of an operator followed by a version
string.

The following revision-free version comparison operators are provided:

 ==   exact version match, or prefix match (with *)
 !=   exact version non-match, or prefix non-match (with *)
 <    version less than match
 <=   version less or equal to match
 >    version greater than match
 >=   version greater or equal to match

All those operators compare on versions ignoring the revision part.
They must be followed by a valid version with no revision part.
Additionally, the == and != operators can accept a version followed by
* to indicate prefix match.

The following revision-oriented version comparison operators are
provided:

 ===  exact version+revision match
 !==  exact version+revision non-match
 <==  version+revision less or equal to match
 >==  version+revision greater or equal to match

Those operators include both version and revision in the comparison.
They must be followed by a valid version with an optional revision
part. No revision is equal to -r0. Prefix match is not allowed.

Examples:

 [==1.3.3]             version 1.3.3, any revision
 [>1.3.3]              version >1.3.3 (e.g. 1.3.3.1 or 1.3.4...)
 [<=1.3.3]             version <=1.3.3 (incl. any revision of 1.3.3)
 [===1.3.3]            1.3.3-r0
 [>==1.3.3-r2]         1.3.3-r2 or newer
 [>=1.2,!=1.3.3]       version >=1.2 but not 1.3.3 (any revision)
 [>=1.2,<1.4]          version >=1.2 but <1.4
 [==1.2*]              any version starting with 1.2 prefix
 [>=1.2,<1.8,!=1.6*]   version >=1.2 but <1.8, also excluding 1.6*


Mapping from existing dependency syntax
=======================================

It should be noted that whenever revision match is desired, one of *==
operators need to be used. They do not include '<' or '>' variants, so
the revision needs to be decreased or increased appropriately for <==
or >==.

The behavior of current '~' operator is now equal to '==', so the
former is removed.

 =foo-1.2.3         ===1.2.3
 =foo-1.2.3-r3      ===1.2.3-r3
 =foo-1.2.3*        ==1.2.3*
 ~foo-1.2.3         ==1.2.3
 >foo-1.2.3         >==1.2.3-r1
 >foo-1.2.3-r9999   >1.2.3
 >=foo-1.2.3        >=1.2.3 or >==1.2.3
 >=foo-1.2.3-r3     >==1.2.3-r3
 <foo-1.2.3         <1.2.3
 <foo-1.2.3-r4      <==1.2.3-r3
 <=foo-1.2.3        <==1.2.3
 <=foo-1.2.3-r3     <==1.2.3-r3
 <=foo-1.2.3-r9999  <=1.2.3


Solutions to other problems
===========================

The provided operators make it possible to quite conveniently express
common types of dependencies. The remaining kinds can be constructed
using conjunctive ranges combined with existing operators.
In particular, for this specific reason the != and !== operators are
provided.

Disjunctive version ranges were considered needed rarely. If specific
versions needs to be excluded from the base version range, the !=
and !== operators (optionally in the prefix matching mode) can be used
to do so.

  [>=1.2,<1.6,!=1.4*,!=1.5*]

While I agree that this is not perfect and can become quite verbose at
times, the use cases for it are rather limited.

Revision ranges can be easily constructed using version ranges:

 [>==1.3-r3,<==1.3-r7]

Not that I see any real use for them.

Pre-release version ranges can be achieved using the relatively safe
_alpha_alpha or _p_p suffixes, or just predicting the upstream version
use.

The convenience Ruby ~> operator needs to be expanded to the verbose
range:

 [>=1.3.4,<1.4] or [>=1.3.4,==1.3*]


Rationale
=========

The key goal behind this concept is to optimize for upstream version
specifications, and provide the minimal reasonable, clear, symmetric
set of tools needed to achieve the correct dependencies.

The version syntax changes are necessary to be able to clearly express
version ranges, and also to distinguish old and new operators.
Furthermore, they increase the readability and usefulness of package
dependency specifications. The square braces and ordering are based
after Exherbo but can be changed if necessary.

The "," separator for versions is copied from USE dependencies which
are conjunctive as well. The disjunctive variant was not included since
our research has shown that it is used very rarely (i.e. only once
in the few base Exherbo repositories we've checked). Any more complex
logic would only make the dependencies less readable for unlikely
benefit.

The default behavior for new operators is meant to accommodate
the common necessity of expressing upstream version restrictions
in ebuilds. Its major advantage is that all the operators behave
symmetrically now (i.e. you don't have to add -r9999 to some of them to
match upstream constraints).

The additional ===, !==, >==, <== operators are provided to accommodate
Gentoo-specific revision constraints, and distinguish them from plain
upstream version constraints. No variant for '<' and '>' is provided
since the resulting syntax would be colliding or confusing, and all
possible revisions can be already expressed clearly using existing
operators.

Negations were added for the == and === equality operators to help
constructing version ranges. They also provide major readability
(and behavior) benefit over the current necessity of disallowing
single versions via blockers.

The prefix matching behavior was retained since it has its use cases.
Furthermore, it becomes useful with conjunctive version ranger to
disallow single versions matching a generic range.

-- 
Best regards,
Michał Górny
<http://dev.gentoo.org/~mgorny/>

Attachment: pgpU44w8AUMn4.pgp
Description: OpenPGP digital signature

Reply via email to