From 2821443c80582f22adddebc73eb1283efffef94e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 4 May 2023 21:04:29 +0200 Subject: [PATCH 1/4] Simplify processEvictions by inlining calculateCompatible --- .../sbt/librarymanagement/EvictionError.scala | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala index 6d0d0c274..52b056e6d 100644 --- a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala +++ b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala @@ -83,47 +83,43 @@ object EvictionError { } }: _*) - def calculateCompatible(p: EvictionPair): (Boolean, String, Boolean, String) = { - val winnerOpt = p.winner map { _.module } - val extraAttributes = ((p.winner match { - case Some(r) => r.extraAttributes.toMap - case _ => Map.empty - }): collection.immutable.Map[String, String]) ++ (winnerOpt match { - case Some(w) => w.extraAttributes.toMap - case _ => Map.empty - }) - // prioritize user-defined version scheme to allow overriding the real scheme - val schemeOpt = userDefinedSchemes - .get((p.organization, p.name)) - .orElse(userDefinedSchemes.get((p.organization, "*"))) - .orElse(VersionSchemes.extractFromExtraAttributes(extraAttributes)) - .orElse(userDefinedSchemes.get(("*", "*"))) - val f = (winnerOpt, schemeOpt) match { - case (Some(_), Some(scheme)) => VersionSchemes.evalFunc(scheme) - case _ => EvictionWarningOptions.guessTrue - } - val scheme = - if (isNameScalaSuffixed(p.name)) assumedVersionScheme - else assumedVersionSchemeJava - val guess = VersionSchemes.evalFunc(scheme) - (p.evicteds forall { r => - f((r.module, winnerOpt, module.scalaModuleInfo)) - }, schemeOpt.getOrElse("?"), p.evicteds forall { r => - guess((r.module, winnerOpt, module.scalaModuleInfo)) - }, scheme) - } pairs foreach { // don't report on a transitive eviction that does not have a winner // https://github.com/sbt/sbt/issues/4946 case p if p.winner.isDefined => - val r = calculateCompatible(p) - if (!r._1) { - incompatibleEvictions += (p -> r._2) - } else if (!r._3) { - assumedIncompatEvictions += (p -> r._4) + val winner = p.winner.get + def lookupUserDefined(org: String = "*", mod: String = "*") = + userDefinedSchemes.get((org, mod)) + def fromWinnerPom = VersionSchemes.extractFromExtraAttributes( + winner.extraAttributes.toMap ++ winner.module.extraAttributes + ) + // prioritize user-defined version scheme to allow overriding the real scheme + val userDefinedSchemeOrFromPom = + lookupUserDefined(p.organization, p.name) + .orElse(lookupUserDefined(p.organization)) + .orElse(fromWinnerPom) + .orElse(lookupUserDefined()) + + val assumedScheme = + if (isNameScalaSuffixed(p.name)) assumedVersionScheme + else assumedVersionSchemeJava + + def hasIncompatibleVersionForScheme(scheme: Option[String]) = { + val isCompat = + scheme.map(VersionSchemes.evalFunc).getOrElse(EvictionWarningOptions.guessTrue) + p.evicteds.exists { r => + !isCompat((r.module, Some(winner.module), module.scalaModuleInfo)) + } } + + if (hasIncompatibleVersionForScheme(userDefinedSchemeOrFromPom)) + incompatibleEvictions += (p -> userDefinedSchemeOrFromPom.getOrElse("?")) + else if (hasIncompatibleVersionForScheme(Some(assumedScheme))) + assumedIncompatEvictions += (p -> assumedScheme) + case _ => () } + new EvictionError( incompatibleEvictions.toList, assumedIncompatEvictions.toList, From 87f8089ba8b647e76b2102658959d32fbcbcb8f9 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 4 May 2023 21:04:29 +0200 Subject: [PATCH 2/4] More readability for processEvictions --- .../sbt/librarymanagement/EvictionError.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala index 52b056e6d..9711a4d6f 100644 --- a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala +++ b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala @@ -88,33 +88,34 @@ object EvictionError { // https://github.com/sbt/sbt/issues/4946 case p if p.winner.isDefined => val winner = p.winner.get - def lookupUserDefined(org: String = "*", mod: String = "*") = + def fromLibraryDependencySchemes(org: String = "*", mod: String = "*") = userDefinedSchemes.get((org, mod)) def fromWinnerPom = VersionSchemes.extractFromExtraAttributes( winner.extraAttributes.toMap ++ winner.module.extraAttributes ) // prioritize user-defined version scheme to allow overriding the real scheme val userDefinedSchemeOrFromPom = - lookupUserDefined(p.organization, p.name) - .orElse(lookupUserDefined(p.organization)) + fromLibraryDependencySchemes(p.organization, p.name) + .orElse(fromLibraryDependencySchemes(p.organization)) .orElse(fromWinnerPom) - .orElse(lookupUserDefined()) + .orElse(fromLibraryDependencySchemes()) val assumedScheme = if (isNameScalaSuffixed(p.name)) assumedVersionScheme else assumedVersionSchemeJava - def hasIncompatibleVersionForScheme(scheme: Option[String]) = { + def hasIncompatibleVersionForScheme(scheme: String) = { val isCompat = - scheme.map(VersionSchemes.evalFunc).getOrElse(EvictionWarningOptions.guessTrue) + VersionSchemes.evalFunc(scheme) p.evicteds.exists { r => !isCompat((r.module, Some(winner.module), module.scalaModuleInfo)) } } - if (hasIncompatibleVersionForScheme(userDefinedSchemeOrFromPom)) + val userDefinedSchemeOrAlways = userDefinedSchemeOrFromPom.getOrElse(VersionSchemes.Always) + if (hasIncompatibleVersionForScheme(userDefinedSchemeOrAlways)) incompatibleEvictions += (p -> userDefinedSchemeOrFromPom.getOrElse("?")) - else if (hasIncompatibleVersionForScheme(Some(assumedScheme))) + else if (hasIncompatibleVersionForScheme(assumedScheme)) assumedIncompatEvictions += (p -> assumedScheme) case _ => () From 998df8b692415bdfad5bb6af2a4853dcb45e0967 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 4 May 2023 21:04:29 +0200 Subject: [PATCH 3/4] Allow user to suppress eviction errors for a specific library ... even if they would result in an incompatible eviction based on the assumed version scheme. --- .../sbt/librarymanagement/EvictionError.scala | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala index 9711a4d6f..27333fb2f 100644 --- a/core/src/main/scala/sbt/librarymanagement/EvictionError.scala +++ b/core/src/main/scala/sbt/librarymanagement/EvictionError.scala @@ -88,35 +88,43 @@ object EvictionError { // https://github.com/sbt/sbt/issues/4946 case p if p.winner.isDefined => val winner = p.winner.get - def fromLibraryDependencySchemes(org: String = "*", mod: String = "*") = - userDefinedSchemes.get((org, mod)) - def fromWinnerPom = VersionSchemes.extractFromExtraAttributes( - winner.extraAttributes.toMap ++ winner.module.extraAttributes - ) - // prioritize user-defined version scheme to allow overriding the real scheme - val userDefinedSchemeOrFromPom = - fromLibraryDependencySchemes(p.organization, p.name) - .orElse(fromLibraryDependencySchemes(p.organization)) - .orElse(fromWinnerPom) - .orElse(fromLibraryDependencySchemes()) - - val assumedScheme = - if (isNameScalaSuffixed(p.name)) assumedVersionScheme - else assumedVersionSchemeJava def hasIncompatibleVersionForScheme(scheme: String) = { - val isCompat = - VersionSchemes.evalFunc(scheme) + val isCompat = VersionSchemes.evalFunc(scheme) p.evicteds.exists { r => !isCompat((r.module, Some(winner.module), module.scalaModuleInfo)) } } - val userDefinedSchemeOrAlways = userDefinedSchemeOrFromPom.getOrElse(VersionSchemes.Always) - if (hasIncompatibleVersionForScheme(userDefinedSchemeOrAlways)) - incompatibleEvictions += (p -> userDefinedSchemeOrFromPom.getOrElse("?")) - else if (hasIncompatibleVersionForScheme(assumedScheme)) - assumedIncompatEvictions += (p -> assumedScheme) + // from libraryDependencyScheme or defined in the pom using the `info.versionScheme` attribute + val userDefinedSchemeOrFromPom = { + def fromLibraryDependencySchemes(org: String = "*", mod: String = "*") = + userDefinedSchemes.get((org, mod)) + def fromWinnerPom = VersionSchemes.extractFromExtraAttributes( + winner.extraAttributes.toMap ++ winner.module.extraAttributes + ) + + fromLibraryDependencySchemes(p.organization, p.name) // by org and name + .orElse(fromLibraryDependencySchemes(p.organization)) // for whole org + .orElse(fromWinnerPom) // from pom + .orElse(fromLibraryDependencySchemes()) // global + } + + // We want the user to be able to suppress eviction errors for a specific library, + // which would result in an incompatible eviction based on the assumed version scheme. + // So, only fall back to the assumed scheme if there is no given scheme by the user or the pom. + userDefinedSchemeOrFromPom match { + case Some(givenScheme) => + if (hasIncompatibleVersionForScheme(givenScheme)) + incompatibleEvictions += (p -> givenScheme) + case None => + val assumedScheme = + if (isNameScalaSuffixed(p.name)) assumedVersionScheme + else assumedVersionSchemeJava + + if (hasIncompatibleVersionForScheme(assumedScheme)) + assumedIncompatEvictions += (p -> assumedScheme) + } case _ => () } From d4296d3e919709ac3bc53b5291275648c1d8ad04 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 6 May 2023 22:52:59 -0400 Subject: [PATCH 4/4] Reproduce sbt/sbt#6745 --- .../librarymanagement/EvictionErrorSpec.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala b/ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala index a6b97c99e..f4b2c53e2 100644 --- a/ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala +++ b/ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala @@ -97,6 +97,23 @@ object EvictionErrorSpec extends BaseIvySpecification { assert(EvictionError(report, m, overrideRules).incompatibleEvictions.isEmpty) } + test("it should selectively allow opt-out from the error despite assumed scheme") { + val deps = Vector(`scala2.12.17`, `akkaActor2.6.0`, `swagger-akka-http1.4.0`) + val m = module(defaultModuleId, deps, Some("2.12.17")) + val report = ivyUpdate(m) + val overrideRules = List("org.scala-lang.modules" %% "scala-java8-compat" % "always") + assert( + EvictionError( + report = report, + module = m, + schemes = overrideRules, + assumedVersionScheme = "early-semver", + assumedVersionSchemeJava = "always", + assumedEvictionErrorLevel = Level.Error, + ).assumedIncompatibleEvictions.isEmpty + ) + } + // older Akka was on pvp def oldAkkaPvp = List("com.typesafe.akka" % "*" % "pvp") @@ -104,8 +121,12 @@ object EvictionErrorSpec extends BaseIvySpecification { ModuleID("com.typesafe.akka", "akka-actor", "2.1.4").withConfigurations(Some("compile")) cross CrossVersion.binary lazy val `akkaActor2.3.0` = ModuleID("com.typesafe.akka", "akka-actor", "2.3.0").withConfigurations(Some("compile")) cross CrossVersion.binary + lazy val `akkaActor2.6.0` = + ModuleID("com.typesafe.akka", "akka-actor", "2.6.0").withConfigurations(Some("compile")) cross CrossVersion.binary lazy val `scala2.10.4` = ModuleID("org.scala-lang", "scala-library", "2.10.4").withConfigurations(Some("compile")) + lazy val `scala2.12.17` = + ModuleID("org.scala-lang", "scala-library", "2.12.17").withConfigurations(Some("compile")) lazy val `scala2.13.3` = ModuleID("org.scala-lang", "scala-library", "2.13.3").withConfigurations(Some("compile")) lazy val `bananaSesame0.4` = @@ -122,6 +143,9 @@ object EvictionErrorSpec extends BaseIvySpecification { ("org.typelevel" %% "cats-parse" % "0.1.0").withConfigurations(Some("compile")) lazy val `cats-parse0.2.0` = ("org.typelevel" %% "cats-parse" % "0.2.0").withConfigurations(Some("compile")) + lazy val `swagger-akka-http1.4.0` = + ("com.github.swagger-akka-http" %% "swagger-akka-http" % "1.4.0") + .withConfigurations(Some("compile")) def dummyScalaModuleInfo(v: String): ScalaModuleInfo = ScalaModuleInfo(