[2.x] fix: Fixes evicted warning for version intervals (#8719)

When a dependency declares a version range (e.g. Ivy [1.3.1,2.3] or comma-separated "1.3.1,2.3" as used by Coursier), and the resolver picks a version inside that range, sbt was still reporting an eviction warning. The chosen version satisfies the range, so it should not be reported as an eviction.

Example (from #6244): oauth2-oidc-sdk and nimbus-jose-jwt both depend on net.minidev:json-smart with range [1.3.1,2.3]. Resolution selects 2.3, which is within the range. Before this fix, sbt reported an eviction for json-smart even though 2.3 satisfies [1.3.1,2.3].
This commit is contained in:
PandaMan 2026-02-09 02:27:28 -05:00 committed by GitHub
parent 3e3cde19b9
commit f47afb5b49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 3 deletions

View File

@ -12,7 +12,9 @@ object VersionRange {
(revision.contains("[")) ||
(revision.contains("]")) ||
(revision.contains("(")) ||
(revision.contains(")"))
(revision.contains(")")) ||
// Comma-separated range e.g. "1.3.1,2.3" (fixes #6244 when Coursier passes range without brackets)
(revision.contains(",") && revision.exists(_.isDigit))
}
/**
@ -25,6 +27,15 @@ object VersionRange {
if (!isVersionRange(range)) {
// Not a range, just compare directly
version == range
} else if (range.contains(",") && !hasMavenVersionRange(range)) {
// Comma-separated range without brackets e.g. "1.3.1,2.3" (fixes #6244)
val parts = range.split(",", 2)
if (parts.length == 2) {
val lower = parts(0).trim
val upper = parts(1).trim
lower.nonEmpty && upper.nonEmpty &&
compareVersions(version, lower) >= 0 && compareVersions(version, upper) <= 0
} else false
} else if (range.endsWith("+")) {
// Handle plus ranges like "1.0+" meaning >= 1.0
val base = range.dropRight(1)

View File

@ -337,15 +337,18 @@ object EvictionWarning {
def guessCompatible(p: EvictionPair): Boolean =
p.evicteds forall { r =>
val winnerOpt = p.winner map { _.module }
val evictedRev = r.module.revision
// Same version: no eviction (fixes #6244 when resolution picks version within requested range)
val sameVersion: Boolean = winnerOpt.exists(_.revision == evictedRev)
// Check if the evicted module's revision is a version range and if the winner satisfies it
// This handles cases like [4.1.0,5) where 4.2.1 would be within range (fixes #3978)
val evictedRev = r.module.revision
// and [1.3.1,2.3] where 2.3 is valid (fixes #6244)
val winnerSatisfiesRange: Boolean = winnerOpt match {
case Some(winner) if VersionRange.isVersionRange(evictedRev) =>
VersionRange.versionSatisfiesRange(winner.revision, evictedRev)
case _ => false
}
if (winnerSatisfiesRange) {
if (sameVersion || winnerSatisfiesRange) {
true
} else {
val extraAttributes = ((p.winner match {

View File

@ -98,4 +98,19 @@ class VersionRangeSpec extends UnitSpec {
assert(VersionRange.versionSatisfiesRange("1.0.0", "[1.0]") == true)
assert(VersionRange.versionSatisfiesRange("1.1", "[1.0]") == false)
}
// Exact reproduction case from issue #6244 (net.minidev:json-smart, [1.3.1,2.3] -> 2.3 selected)
it should "not treat 2.3 as evicted when range is [1.3.1,2.3] (fixes #6244)" in {
assert(VersionRange.isVersionRange("[1.3.1,2.3]") == true)
assert(VersionRange.versionSatisfiesRange("2.3", "[1.3.1,2.3]") == true)
assert(VersionRange.versionSatisfiesRange("1.3.1", "[1.3.1,2.3]") == true)
assert(VersionRange.versionSatisfiesRange("2.4", "[1.3.1,2.3]") == false)
}
it should "handle comma-separated range without brackets (fixes #6244)" in {
assert(VersionRange.isVersionRange("1.3.1,2.3") == true)
assert(VersionRange.versionSatisfiesRange("2.3", "1.3.1,2.3") == true)
assert(VersionRange.versionSatisfiesRange("1.3.1", "1.3.1,2.3") == true)
assert(VersionRange.versionSatisfiesRange("2.4", "1.3.1,2.3") == false)
}
}

View File

@ -364,6 +364,28 @@ object EvictionWarningSpec extends BaseIvySpecification {
def javaLibDirectDeps = Vector(commonsIo14, commonsIo24)
def javaLibTransitiveDeps = Vector(unfilteredUploads080, bnfparser10)
def scalaLibTransitiveDeps = Vector(scala2104, bananaSesame04, akkaRemote234)
// #6244: oauth2-oidc-sdk and nimbus-jose-jwt both depend on net.minidev:json-smart [1.3.1,2.3];
// resolution selects 2.3, which is within the range, so we must not report it as eviction.
def oauth2OidcSdk822 =
ModuleID("com.nimbusds", "oauth2-oidc-sdk", "8.22").withConfigurations(Some("compile"))
def nimbusJoseJwt8201 =
ModuleID("com.nimbusds", "nimbus-jose-jwt", "8.20.1").withConfigurations(Some("compile"))
def versionIntervalDeps6244 = Vector(oauth2OidcSdk822, nimbusJoseJwt8201)
test(
"When winner satisfies version range [1.3.1,2.3], should not report eviction for json-smart (fixes #6244)"
) {
val m = module(defaultModuleId, versionIntervalDeps6244, Some("2.12.12"))
val report = ivyUpdate(m)
val warning = EvictionWarning(m, fullOptions, report)
val jsonSmartEviction =
warning.reportedEvictions.find(p => p.organization == "net.minidev" && p.name == "json-smart")
assert(
jsonSmartEviction.isEmpty,
s"#6244: json-smart 2.3 is within range [1.3.1,2.3], should not be reported as eviction. Got:\n${warning.lines.mkString("\n")}"
)
}
def dummyScalaModuleInfo(v: String): ScalaModuleInfo =
ScalaModuleInfo(
scalaFullVersion = v,