New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add semantic version selector API #239

merged 4 commits into from Jun 14, 2018


None yet
2 participants

tanishiking commented May 8, 2018

This PR addresses sbt/sbt#3710.

As disscussed in #218, it is unnatural to implement an Ordering[VersionNumber] for satisfying the use case presented in #3710. So this PR implements an API that is based on semantic version selector intead of implementing an Ordering[VersionNumber]. Some behaviors are revised to satisfy #3710 refering npm's semver implementation.

Logical operators

  • As semantic version selector's draft or npm says, we can combine comparators by spaces to an and clause. (>1.0.0 <2.0.0 means (greater than 1.0.0) AND (less than 2.0.0))
  • And clauses can be combined by || to or clause. (>1.0.0 <2.0.0 || >3.0.0 means ((greater than 1.0.0) AND (less than 2.0.0)) OR (greater than 3.0.0)).

Basic operators

  • This PR implements these operators(<=, <, >=, >, =). If no operator is specified, = is assumed.
  • VersionNumber's pre-release version and metadata are ignored. So VersionNumber("1.0.0-alpha+meta").satisfies("=1.0.0") would be true.
    • The npm implementation just exclude the prerelease versions like 1.0.0-alpha from range matching semantics for avoiding auto upgrade libraries to their prerelease versions.
    • I think it would be convenient to match prerelease versions in our context (just comparing the versions, no updating libraries).
  • If minor or patch versions are not specified, some numbers are assumed.
    • <=1.0 is equivalent to <1.1.0.
    • <1.0 is equivalent to <1.0.0.
    • >=1.0 is equivalent to >=1.0.0.
    • >1.0 is equivalent to >=1.1.0.
    • =1.0 is equivalent to >=1.0 <=1.0 (so >=1.0.0 <1.1.0).

Range operator

  • We can specify the version range by A.B.C - D.E.F. This is equivalent to >=A.B.C <=D.E.F.
  • Both side of comparators around - are required and they can not have any operators.


  • We can use wildcard selectors *, x, X. (For example 1.2.x.)
  • Actually, using wildcard in minor or patch version selector is equivalent to just not specifying the field. For example, =1.x is equivalent to =1, 1.2.x is equivalent to 1.2.
    • So this wildcard selectors are optional.

If invalid form of selector string is passed to SemanticSelector(), IllegalArgumentException wii be thrown.


scala> import sbt.librarymanagement.{ VersionNumber, SemanticSelector }
import sbt.librarymanagement.{VersionNumber, SemanticSelector}

scala> VersionNumber("2.12.5").matchesSemVer(SemanticSelector(">=2.12"))
res1: Boolean = true

scala> VersionNumber("2.12.5").matchesSemVer(SemanticSelector("<2.12"))
res2: Boolean = false

scala> VersionNumber("2.13.0-M4").matchesSemVer(SemanticSelector("2.13"))
res3: Boolean = false

scala> VersionNumber("2.12.5").matchesSemVer(SemanticSelector("2.12.1 - 2.12.6"))
res4: Boolean = true

scala> VersionNumber("2.12.5").matchesSemVer(SemanticSelector("2.12.x"))
res5: Boolean = true

scala> VersionNumber("2.12.5").matchesSemVer(SemanticSelector("2.11.x || 2.12.x"))
res6: Boolean = true

This comment has been minimized.


eed3si9n commented May 8, 2018

@tanishiking Thanks for the PR!

VersionNumber's pre-release version and metadata are ignored.

Doesn't that contradict SemVer? We don't have < operators, but we implement def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean in The newly added satisfies should be compatible with the tests in here -

* Semantic version selector API to check if the VersionNumber satisfies
* conditions described by semantic version selector.
sealed abstract case class SemanticSelector(

This comment has been minimized.


eed3si9n May 8, 2018


For binary compatibility reasons, let's avoid case classes.
I think we should use Contraband here. I have a PR on sbt/sbt that adds JavaVersion for example:

type JavaVersion {
  numbers: [Long]
  vendor: String

  #x def numberStr: String = numbers.mkString(".")
  #xtostring + "@").getOrElse("") + numberStr

  #xcompanion def apply(version: String): JavaVersion = sbt.internal.CrossJava.parseJavaVersion(version)
@@ -27,6 +27,10 @@ final class VersionNumber private[sbt] (
case _ => false
def satisfies(selector: String): Boolean = {

This comment has been minimized.


eed3si9n May 8, 2018


"satisfies" might be too strong since Ivy/Coursier/Maven have their own implementations, and also many of Scala or Java versions are not SemVer. A better name might be matchesSemVer, and let it accept selector: SemanticSelector?

tanishiking added some commits May 10, 2018

Make semantic selector honor semver ordering around pre-release tags.
- When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version. Example: 1.0.0-alpha < 1.0.0.
- Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each <del>dot</del> hyphen separated identifier from left to right until a difference is found as follows
- identifiers consisting of only digits are compared numerically and identifiers with letters or hyphens are compared lexically in ASCII sort order.
- Numeric identifiers always have lower precedence than non-numeric identifiers.
- A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
- Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
Rename VersionNumber#satisfies to VersionNumber#matchesSemVer
VersionNumber#matchesSemVer receive SemanticSelector instead of String
case object Gte extends SemSelOperator
case object Gt extends SemSelOperator
case object Eq extends SemSelOperator

This comment has been minimized.


tanishiking May 12, 2018


I implemented SemSelOperator without contraband, because contraband's enum doesn't seem to have any ways to implement custom toString method?


This comment has been minimized.


tanishiking commented May 12, 2018

@eed3si9n Thank you for the reviewing!
I added some commits that reflect your feedbacks. I'll rebase them before merging.

Doesn't that contradict SemVer?

Hmm, yes you are right. It was'nt approprite to just ignore pre-release tags...
I added a commit f8efdb1 that make SemanticSelector API satisfy the and enable SemanticSelector API to specifiy pre-release tags.
Now, I think newly modified satisfies (matchesSemVer) is compatible with the tests of VersionNumber.SemVer.isCompatible.

For binary compatibility reasons, let's avoid case classes. I think we should use Contraband here.

I undefstantood, 4e8b6dc changes the SemanticSelector to use contraband instead of defining them by case calsses.

A better name might be matchesSemVer, and let it accept selector: SemanticSelector?

I agree with you! d5f5cbb renames satisfies to matchesSemVer and change the argument to receive SemanticSelector.
I think it would be convenient for sbt users to add an additinal method matchesSemVer(semsel: String): Boolean = { SemanticSelecotr(semsel).matches(this) } (short-hand of matchesSemVer(semsel: SemanticSelector)), let's add a such method?

@dwijnand dwijnand requested a review from eed3si9n Jun 1, 2018


This comment has been minimized.


eed3si9n commented Jun 14, 2018

I think we are good to go on this.

@eed3si9n eed3si9n merged commit 69b9d61 into sbt:1.x Jun 14, 2018

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment