Skip to content

Conversation

tanishiking
Copy link
Contributor

@tanishiking 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. https://docs.npmjs.com/misc/semver#prerelease-tags
    • 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.

Wildcard

  • 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.

Examples

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

@eed3si9n
Copy link
Member

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 https://github.com/sbt/librarymanagement/blob/v1.1.4/core/src/main/scala/sbt/librarymanagement/VersionNumber.scala#L98. The newly added satisfies should be compatible with the tests in here - https://github.com/sbt/librarymanagement/blob/v1.1.4/ivy/src/test/scala/VersionNumberSpec.scala

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 vendor.map(_ + "@").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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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?

- 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.

https://semver.org/#spec-item-11
VersionNumber#matchesSemVer receive SemanticSelector instead of String
case object Gte extends SemSelOperator
case object Gt extends SemSelOperator
case object Eq extends SemSelOperator
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@tanishiking
Copy link
Contributor Author

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 https://semver.org/#spec-item-11 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 June 1, 2018 18:37
@eed3si9n
Copy link
Member

I think we are good to go on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants