mirror of https://github.com/sbt/sbt.git
Automatic eviction warning
This extracts info.versionScheme from POM and uses that to guide the eviction report instead of taking a stab in the dark that all Scala libraries are using pvp.
This commit is contained in:
parent
50ca3902c3
commit
9d87715100
|
|
@ -13,6 +13,7 @@ public class SbtPomExtraProperties {
|
|||
public static final String POM_SCALA_VERSION = "scalaVersion";
|
||||
public static final String POM_SBT_VERSION = "sbtVersion";
|
||||
public static final String POM_API_KEY = "info.apiURL";
|
||||
public static final String VERSION_SCHEME_KEY = "info.versionScheme";
|
||||
|
||||
public static final String LICENSE_COUNT_KEY = "license.count";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
package librarymanagement
|
||||
|
||||
import sbt.internal.librarymanagement.mavenint.SbtPomExtraProperties
|
||||
import sbt.librarymanagement.ModuleID
|
||||
|
||||
// See APIMappings.scala
|
||||
private[sbt] object VersionSchemes {
|
||||
final val EarlySemVer = "early-semver"
|
||||
final val SemVerSpec = "semver-spec"
|
||||
final val PackVer = "pvp"
|
||||
|
||||
def validateScheme(value: String): Unit =
|
||||
value match {
|
||||
case EarlySemVer | SemVerSpec | PackVer => ()
|
||||
case "semver" =>
|
||||
sys.error(
|
||||
s"""'semver' is ambiguous.
|
||||
|Based on the Semantic Versioning 2.0.0, 0.y.z updates are all initial development and thus
|
||||
|0.6.0 and 0.6.1 would NOT maintain any compatibility, but in Scala ecosystem it is
|
||||
|common to start adopting binary compatibility even in 0.y.z releases.
|
||||
|
|
||||
|Specify 'early-semver' for the early variant.
|
||||
|Specify 'semver-spec' for the spec-correct SemVer.""".stripMargin
|
||||
)
|
||||
case x => sys.error(s"unknown version scheme: $x")
|
||||
}
|
||||
|
||||
/** info.versionScheme property will be included into POM after sbt 1.4.0.
|
||||
*/
|
||||
def extractFromId(mid: ModuleID): Option[String] = extractFromExtraAttributes(mid.extraAttributes)
|
||||
|
||||
def extractFromExtraAttributes(extraAttributes: Map[String, String]): Option[String] =
|
||||
extraAttributes.get(SbtPomExtraProperties.VERSION_SCHEME_KEY)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package sbt.librarymanagement
|
|||
import collection.mutable
|
||||
import Configurations.Compile
|
||||
import ScalaArtifacts.{ LibraryID, CompilerID }
|
||||
import sbt.internal.librarymanagement.VersionSchemes
|
||||
import sbt.util.Logger
|
||||
import sbt.util.ShowLines
|
||||
|
||||
|
|
@ -136,6 +137,17 @@ object EvictionWarningOptions {
|
|||
}
|
||||
}
|
||||
|
||||
lazy val guessEarlySemVer
|
||||
: PartialFunction[(ModuleID, Option[ModuleID], Option[ScalaModuleInfo]), Boolean] = {
|
||||
case (m1, Some(m2), _) =>
|
||||
(m1.revision, m2.revision) match {
|
||||
case (VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2)) =>
|
||||
VersionNumber.EarlySemVer
|
||||
.isCompatible(VersionNumber(ns1, ts1, es1), VersionNumber(ns2, ts2, es2))
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
lazy val guessFalse
|
||||
: PartialFunction[(ModuleID, Option[ModuleID], Option[ScalaModuleInfo]), Boolean] = {
|
||||
case (_, _, _) => false
|
||||
|
|
@ -290,9 +302,25 @@ object EvictionWarning {
|
|||
var binaryIncompatibleEvictionExists = false
|
||||
def guessCompatible(p: EvictionPair): Boolean =
|
||||
p.evicteds forall { r =>
|
||||
options.guessCompatible(
|
||||
(r.module, p.winner map { _.module }, module.scalaModuleInfo)
|
||||
)
|
||||
val winnerOpt = p.winner map { _.module }
|
||||
val extraAttributes = (p.winner match {
|
||||
case Some(r) => r.extraAttributes
|
||||
case _ => Map.empty
|
||||
}) ++ (winnerOpt match {
|
||||
case Some(w) => w.extraAttributes
|
||||
case _ => Map.empty
|
||||
})
|
||||
val schemeOpt = VersionSchemes.extractFromExtraAttributes(extraAttributes)
|
||||
val f = (winnerOpt, schemeOpt) match {
|
||||
case (Some(_), Some(VersionSchemes.EarlySemVer)) =>
|
||||
EvictionWarningOptions.guessEarlySemVer
|
||||
case (Some(_), Some(VersionSchemes.SemVerSpec)) =>
|
||||
EvictionWarningOptions.guessSemVer
|
||||
case (Some(_), Some(VersionSchemes.PackVer)) =>
|
||||
EvictionWarningOptions.guessSecondSegment
|
||||
case _ => options.guessCompatible(_)
|
||||
}
|
||||
f((r.module, winnerOpt, module.scalaModuleInfo))
|
||||
}
|
||||
pairs foreach {
|
||||
case p if isScalaArtifact(module, p.organization, p.name) =>
|
||||
|
|
|
|||
|
|
@ -167,9 +167,19 @@ object VersionNumber {
|
|||
* Also API compatibility is expected even when the first segment is zero.
|
||||
*/
|
||||
object SecondSegment extends VersionNumberCompatibility {
|
||||
def name: String = "Second Segment Variant"
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
PackVer.isCompatible(v1, v2)
|
||||
}
|
||||
|
||||
/** A variant of SemVar that seems to be common among the Scala libraries.
|
||||
* The second segment (y in x.y.z) increments breaks the binary compatibility even when x > 0.
|
||||
* Also API compatibility is expected even when the first segment is zero.
|
||||
*/
|
||||
object PackVer extends VersionNumberCompatibility {
|
||||
import SemVer._
|
||||
|
||||
def name: String = "Second Segment Variant"
|
||||
def name: String = "Package Versioning Policy"
|
||||
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
doIsCompat(dropBuildMetadata(v1), dropBuildMetadata(v2))
|
||||
|
|
@ -182,6 +192,69 @@ object VersionNumber {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A variant of SemVar that enforces API compatibility when the first segment is zero.
|
||||
*/
|
||||
object EarlySemVer extends VersionNumberCompatibility {
|
||||
import SemVer._
|
||||
|
||||
def name: String = "Early Semantic Versioning"
|
||||
|
||||
/* Quotes of parts of the rules in the SemVer Spec relevant to compatibility checking:
|
||||
*
|
||||
* Rule 2:
|
||||
* > A normal version number MUST take the form X.Y.Z
|
||||
*
|
||||
* Rule 6:
|
||||
* > Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced.
|
||||
*
|
||||
* Rule 7:
|
||||
* > Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced.
|
||||
*
|
||||
* Rule 8:
|
||||
* > Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced.
|
||||
*
|
||||
* Rule 9:
|
||||
* > A pre-release version MAY be denoted by appending a hyphen and a series of
|
||||
* > dot separated identifiers immediately following the patch version.
|
||||
* > Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||
* > Identifiers MUST NOT be empty.
|
||||
* > Numeric identifiers MUST NOT include leading zeroes.
|
||||
* > Pre-release versions have a lower precedence than the associated normal version.
|
||||
* > A pre-release version indicates that the version is unstable and might not satisfy the
|
||||
* > intended compatibility requirements as denoted by its associated normal version.
|
||||
* > Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
|
||||
*
|
||||
* Rule 10:
|
||||
* > Build metadata MAY be denoted by appending a plus sign and a series of
|
||||
* > dot separated identifiers immediately following the patch or pre-release version.
|
||||
* > Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||
* > Identifiers MUST NOT be empty.
|
||||
* > Build metadata SHOULD be ignored when determining version precedence.
|
||||
* > Thus two versions that differ only in the build metadata, have the same precedence.
|
||||
* > Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
|
||||
*
|
||||
* Rule 10 means that build metadata is never considered for compatibility
|
||||
* we'll enforce this immediately by dropping them from both versions
|
||||
* Rule 2 we enforce with custom extractors.
|
||||
* Rule 6, 7 & 8 means version compatibility is determined by comparing the two X values
|
||||
* Rule 9..
|
||||
* Dale thinks means pre-release versions are fully equals checked..
|
||||
* Eugene thinks means pre-releases before 1.0.0 are not compatible, if not they are..
|
||||
* Rule 4 is modified in this variant.
|
||||
*/
|
||||
def isCompatible(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
doIsCompat(dropBuildMetadata(v1), dropBuildMetadata(v2))
|
||||
|
||||
private[this] def doIsCompat(v1: VersionNumber, v2: VersionNumber): Boolean =
|
||||
(v1, v2) match {
|
||||
case (NormalVersion(0, _, 0), NormalVersion(0, _, 0)) => v1 == v2
|
||||
case (NormalVersion(0, y1, _), NormalVersion(0, y2, _)) => y1 == y2
|
||||
case (NormalVersion(_, 0, 0), NormalVersion(_, 0, 0)) => v1 == v2 // R9 maybe?
|
||||
case (NormalVersion(x1, _, _), NormalVersion(x2, _, _)) => x1 == x2 // R6, R7 & R8
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait VersionNumberCompatibility {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import org.scalatest.{ FreeSpec, Inside, Matchers }
|
|||
|
||||
// This is a specification to check VersionNumber and VersionNumberCompatibility.
|
||||
class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
||||
import VersionNumber.{ SemVer, SecondSegment }
|
||||
import VersionNumber.{ EarlySemVer, SemVer, PackVer }
|
||||
|
||||
version("1") { v =>
|
||||
assertParsesTo(v, Seq(1), Seq(), Seq())
|
||||
|
|
@ -28,10 +28,15 @@ class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
|||
assertIsNotCompatibleWith(v, "2.0.0", SemVer)
|
||||
assertIsNotCompatibleWith(v, "1.0.0-M1", SemVer)
|
||||
|
||||
assertIsCompatibleWith(v, "1.0.1", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "1.1.1", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "2.0.0", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "1.0.0-M1", SecondSegment)
|
||||
assertIsCompatibleWith(v, "1.0.1", EarlySemVer)
|
||||
assertIsCompatibleWith(v, "1.1.1", EarlySemVer)
|
||||
assertIsNotCompatibleWith(v, "2.0.0", EarlySemVer)
|
||||
assertIsNotCompatibleWith(v, "1.0.0-M1", EarlySemVer)
|
||||
|
||||
assertIsCompatibleWith(v, "1.0.1", PackVer)
|
||||
assertIsNotCompatibleWith(v, "1.1.1", PackVer)
|
||||
assertIsNotCompatibleWith(v, "2.0.0", PackVer)
|
||||
assertIsNotCompatibleWith(v, "1.0.0-M1", PackVer)
|
||||
}
|
||||
|
||||
version("1.0.0.0") { v =>
|
||||
|
|
@ -49,9 +54,13 @@ class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
|||
assertIsNotCompatibleWith(v, "0.12.1", SemVer)
|
||||
assertIsNotCompatibleWith(v, "0.12.1-M1", SemVer)
|
||||
|
||||
assertIsNotCompatibleWith(v, "0.12.0-RC1", SecondSegment)
|
||||
assertIsCompatibleWith(v, "0.12.1", SecondSegment)
|
||||
assertIsCompatibleWith(v, "0.12.1-M1", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "0.12.0-RC1", EarlySemVer)
|
||||
assertIsCompatibleWith(v, "0.12.1", EarlySemVer)
|
||||
assertIsCompatibleWith(v, "0.12.1-M1", EarlySemVer)
|
||||
|
||||
assertIsNotCompatibleWith(v, "0.12.0-RC1", PackVer)
|
||||
assertIsCompatibleWith(v, "0.12.1", PackVer)
|
||||
assertIsCompatibleWith(v, "0.12.1-M1", PackVer)
|
||||
}
|
||||
|
||||
version("0.1.0-SNAPSHOT") { v =>
|
||||
|
|
@ -62,9 +71,13 @@ class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
|||
assertIsNotCompatibleWith(v, "0.1.0", SemVer)
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT+001", SemVer)
|
||||
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "0.1.0", SecondSegment)
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT+001", SecondSegment)
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT", EarlySemVer)
|
||||
assertIsNotCompatibleWith(v, "0.1.0", EarlySemVer)
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT+001", EarlySemVer)
|
||||
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT", PackVer)
|
||||
assertIsNotCompatibleWith(v, "0.1.0", PackVer)
|
||||
assertIsCompatibleWith(v, "0.1.0-SNAPSHOT+001", PackVer)
|
||||
}
|
||||
|
||||
version("0.1.0-M1") { v =>
|
||||
|
|
@ -86,7 +99,7 @@ class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
|||
assertParsesTo(v, Seq(2, 10, 4), Seq("20140115", "000117", "b3a", "sources"), Seq())
|
||||
assertCascadesTo(v, Seq("2.10.4-20140115-000117-b3a-sources", "2.10.4", "2.10"))
|
||||
assertIsCompatibleWith(v, "2.0.0", SemVer)
|
||||
assertIsNotCompatibleWith(v, "2.0.0", SecondSegment)
|
||||
assertIsNotCompatibleWith(v, "2.0.0", PackVer)
|
||||
}
|
||||
|
||||
version("20140115000117-b3a-sources") { v =>
|
||||
|
|
@ -187,9 +200,10 @@ class VersionNumberSpec extends FreeSpec with Matchers with Inside {
|
|||
) = {
|
||||
val prefix = if (expectOutcome) "should" else "should NOT"
|
||||
val compatibilityStrategy = vnc match {
|
||||
case SemVer => "SemVer"
|
||||
case SecondSegment => "SecondSegment"
|
||||
case _ => val s = vnc.name; if (s contains " ") s""""$s"""" else s
|
||||
case SemVer => "SemVer"
|
||||
case PackVer => "PackVer"
|
||||
case EarlySemVer => "EarlySemVer"
|
||||
case _ => val s = vnc.name; if (s contains " ") s""""$s"""" else s
|
||||
}
|
||||
s"$prefix be $compatibilityStrategy compatible with $v2" in {
|
||||
vnc.isCompatible(VersionNumber(v1.value), VersionNumber(v2)) shouldBe expectOutcome
|
||||
|
|
|
|||
Loading…
Reference in New Issue