From 03c371db5ec514bea888ec3c32c13c624015ad6f Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sun, 26 Apr 2026 13:37:00 -0400 Subject: [PATCH] [2.0.x] Add Test configuration to evictionWarningOptions (#9134) * Add Test configuration to evictionWarningOptions * Add Test configuration to default evictionWarningOptions * Deduplicate eviction errors based on callers. * Update eviction error test for semantic versioning. * Group evictions by configuration and update test. Co-authored-by: Zainab Ali --- build.sbt | 15 ++ .../sbt/librarymanagement/EvictionError.scala | 208 ++++++++++-------- .../librarymanagement/EvictionWarning.scala | 44 ++-- .../librarymanagement/EvictionErrorSpec.scala | 66 +++++- main/src/main/scala/sbt/Defaults.scala | 4 + .../sbt/internal/LibraryManagement.scala | 32 +-- .../evicted-test-config/build.sbt | 6 +- .../evicted-test-config/test | 8 + 8 files changed, 239 insertions(+), 144 deletions(-) diff --git a/build.sbt b/build.sbt index 2138733dc..0fbeeccb5 100644 --- a/build.sbt +++ b/build.sbt @@ -1185,6 +1185,21 @@ lazy val lmCore = (project in file("lm-core")) }, mimaSettings, mimaBinaryIssueFilters ++= Seq( + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "sbt.librarymanagement.EvictionError.apply" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sbt.librarymanagement.EvictionError.processEvictions*" + ), + ProblemFilters.exclude[IncompatibleMethTypeProblem]( + "sbt.librarymanagement.EvictionWarning.buildEvictions" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sbt.librarymanagement.EvictionError.$*" + ), + ProblemFilters.exclude[DirectMissingMethodProblem]( + "sbt.librarymanagement.EvictionError.configuration" + ) ), ) .dependsOn(utilLogging, utilPosition, utilCache) diff --git a/lm-core/src/main/scala/sbt/librarymanagement/EvictionError.scala b/lm-core/src/main/scala/sbt/librarymanagement/EvictionError.scala index caadad37c..161cc72ea 100644 --- a/lm-core/src/main/scala/sbt/librarymanagement/EvictionError.scala +++ b/lm-core/src/main/scala/sbt/librarymanagement/EvictionError.scala @@ -30,7 +30,7 @@ object EvictionError { assumedVersionScheme, assumedVersionSchemeJava, assumedEvictionErrorLevel, - Configurations.Compile, + EvictionWarningOptions.default.configurations, ) } @@ -41,48 +41,29 @@ object EvictionError { assumedVersionScheme: String, assumedVersionSchemeJava: String, assumedEvictionErrorLevel: Level.Value, - configuration: ConfigRef, + configurations: Seq[ConfigRef], ): EvictionError = { - val options = EvictionWarningOptions.full.withConfigurations(Vector(configuration)) - val evictions = EvictionWarning.buildEvictions(options, report) + val evictions = EvictionWarning + .buildEvictions(configurations, report) processEvictions( module, - options, evictions, schemes, assumedVersionScheme, assumedVersionSchemeJava, assumedEvictionErrorLevel, - configuration, ) } private[sbt] def processEvictions( module: ModuleDescriptor, - options: EvictionWarningOptions, - reports: Seq[OrganizationArtifactReport], + reports: Seq[(ConfigRef, OrganizationArtifactReport)], schemes: Seq[ModuleID], assumedVersionScheme: String, assumedVersionSchemeJava: String, assumedEvictionErrorLevel: Level.Value, - configuration: ConfigRef = Configurations.Compile, ): EvictionError = { val directDependencies = module.directDependencies - val pairs = reports map { detail => - val evicteds = detail.modules filter { _.evicted } - val winner = (detail.modules filterNot { _.evicted }).headOption - new EvictionPair( - detail.organization, - detail.name, - winner, - evicteds, - true, - options.showCallers - ) - } - val incompatibleEvictions: mutable.ListBuffer[(EvictionPair, String)] = mutable.ListBuffer() - val assumedIncompatibleEvictions: mutable.ListBuffer[(EvictionPair, String)] = - mutable.ListBuffer() val sbvOpt = module.scalaModuleInfo.map(_.scalaBinaryVersion) val userDefinedSchemes: Map[(String, String), String] = Map(schemes flatMap { s => val organization = s.organization @@ -105,69 +86,115 @@ object EvictionError { List((s.organization, s.name) -> versionScheme) } }*) + val pairs = reports + .flatMap { case (config, detail) => + val evicteds = detail.modules filter { _.evicted } + val winner = (detail.modules filterNot { _.evicted }).headOption + // don't report on a transitive eviction that does not have a winner + // https://github.com/sbt/sbt/issues/4946 + winner match { + case Some(winner) => + // 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 + ) - 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 winner = p.winner.get - - def hasIncompatibleVersionForScheme(scheme: String) = { - val isCompat = VersionSchemes.evalFunc(scheme) - p.evicteds.exists { r => - !isCompat((r.module, Some(winner.module), module.scalaModuleInfo)) - } - } - - // 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 => + fromLibraryDependencySchemes(detail.organization, detail.name) // by org and name + .orElse(fromLibraryDependencySchemes(detail.organization)) // for whole org + .orElse(fromWinnerPom) // from pom + .orElse(fromLibraryDependencySchemes()) // global + } val assumedScheme = - if (isNameScalaSuffixed(p.name)) assumedVersionScheme + if (isNameScalaSuffixed(detail.name)) assumedVersionScheme else assumedVersionSchemeJava - if (hasIncompatibleVersionForScheme(assumedScheme)) - assumedIncompatibleEvictions += (p -> assumedScheme) - } + // 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. + val (scheme, isAssumed) = userDefinedSchemeOrFromPom + .map(scheme => (scheme, false)) + .getOrElse((assumedScheme, true)) - case _ => () - } + val hasIncompatibleVersionForScheme = { + val isCompat = VersionSchemes.evalFunc(scheme) + evicteds.exists { r => + !isCompat((r.module, Some(winner.module), module.scalaModuleInfo)) + } + } + + if (hasIncompatibleVersionForScheme) + Some( + ( + EvictionErrorPair( + detail.name, + detail.organization, + winner.module, + evicteds.map(_.module), + callers(winner, evicteds), + scheme, + configurations = Vector.empty, + isAssumed, + ), + config + ) + ) + else None + case None => None + } + } + // Deduplicate eviction pairs by configuration. + .groupMap(_._1)(_._2) + .map { case (pair, configs) => + pair.copy(configurations = configs.toVector) + } + + val (assumedIncompatibleEvictions, incompatibleEvictions) = pairs.partition(_.isAssumed) new EvictionError( incompatibleEvictions.toList, assumedIncompatibleEvictions.toList, - configuration, ) } + private def callers( + winner: ModuleReport, + evicteds: Vector[ModuleReport], + ): List[(ModuleID, String)] = { + val seen: mutable.Set[ModuleID] = mutable.Set() + (evicteds.toList :+ winner).flatMap { r => + val rev = r.module.revision + r.callers.toList flatMap { caller => + if (seen(caller.caller)) Nil + else { + seen += caller.caller + List((caller.caller, rev)) + } + } + } + } + given evictionErrorLines: ShowLines[EvictionError] = ShowLines { (a: EvictionError) => a.toLines } } +private final case class EvictionErrorPair( + name: String, + organization: String, + winner: ModuleID, + evicted: Vector[ModuleID], + callers: List[(ModuleID, String)], + scheme: String, + configurations: Vector[ConfigRef], + isAssumed: Boolean +) + final class EvictionError private[sbt] ( - val incompatibleEvictions: Seq[(EvictionPair, String)], - val assumedIncompatibleEvictions: Seq[(EvictionPair, String)], - val configuration: ConfigRef = Configurations.Compile, + val incompatibleEvictions: Seq[EvictionErrorPair], + val assumedIncompatibleEvictions: Seq[EvictionErrorPair], ) { def run(): Unit = if (incompatibleEvictions.nonEmpty) { @@ -178,33 +205,30 @@ final class EvictionError private[sbt] ( def toAssumedLines: List[String] = toLines(assumedIncompatibleEvictions, true) - private def configurationLabel: String = - if (configuration.name == Configurations.Compile.name) "library dependencies" - else s"${configuration.name.capitalize} dependencies" - - def toLines(evictions: Seq[(EvictionPair, String)], assumed: Boolean): List[String] = { + def toLines( + evictions: Seq[EvictionErrorPair], + assumed: Boolean + ): List[String] = { val out: mutable.ListBuffer[String] = mutable.ListBuffer() - out += s"found version conflict(s) in $configurationLabel; some are suspected to be binary incompatible:" + out += "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:" out += "" - evictions.foreach({ (a, scheme) => - val seen: mutable.Set[ModuleID] = mutable.Set() - val callers: List[String] = (a.evicteds.toList ::: a.winner.toList) flatMap { r => - val rev = r.module.revision - r.callers.toList flatMap { caller => - if (seen(caller.caller)) Nil - else { - seen += caller.caller - List(f"\t +- ${caller}%-50s (depends on $rev)") - } - } + evictions.foreach({ case a => + val callers: List[String] = a.callers.map { case (caller, rev) => + f"\t +- ${caller}%-50s (depends on $rev)" } val que = if (assumed) "?" else "" - val winnerRev = a.winner match { - case Some(r) => s":${r.module.revision} ($scheme$que) is selected over ${a.evictedRevs}" - case _ => " is evicted for all versions" - } - val title = s"\t* ${a.organization}:${a.name}$winnerRev" - val lines = title :: (if (a.showCallers) callers.reverse else Nil) ::: List("") + val evictedRevs = a.evicted.map(_.revision) + val evictedRevsTitle = + if (evictedRevs.size <= 1) evictedRevs.mkString + else evictedRevs.mkString("{", ", ", "}") + + val winnerRev = + s":${a.winner.revision} (${a.scheme}$que) is selected over ${evictedRevsTitle}" + val configurationTitle = + if (a.configurations.size <= 1) a.configurations.mkString + else a.configurations.mkString("{", ", ", "}") + val title = s"\t* ${a.organization}:${a.name}$winnerRev for $configurationTitle" + val lines = title :: callers.reverse ::: List("") out ++= lines }) out.toList diff --git a/lm-core/src/main/scala/sbt/librarymanagement/EvictionWarning.scala b/lm-core/src/main/scala/sbt/librarymanagement/EvictionWarning.scala index 87c1c27ff..5f668165a 100644 --- a/lm-core/src/main/scala/sbt/librarymanagement/EvictionWarning.scala +++ b/lm-core/src/main/scala/sbt/librarymanagement/EvictionWarning.scala @@ -2,6 +2,7 @@ package sbt.librarymanagement import collection.mutable import Configurations.Compile +import Configurations.Test import ScalaArtifacts.{ LibraryID, CompilerID } import sbt.internal.librarymanagement.{ VersionSchemes, VersionRange } import sbt.util.Logger @@ -74,7 +75,7 @@ object EvictionWarningOptions { def default: EvictionWarningOptions = summary def full: EvictionWarningOptions = new EvictionWarningOptions( - Vector(Compile), + Vector(Compile, Test), warnScalaVersionEviction = true, warnDirectEvictions = true, warnTransitiveEvictions = true, @@ -85,7 +86,7 @@ object EvictionWarningOptions { ) def summary: EvictionWarningOptions = new EvictionWarningOptions( - Vector(Compile), + Vector(Compile, Test), warnScalaVersionEviction = false, warnDirectEvictions = false, warnTransitiveEvictions = false, @@ -268,31 +269,20 @@ object EvictionWarning { options: EvictionWarningOptions, report: UpdateReport ): EvictionWarning = { - val evictions = buildEvictions(options, report) + val evictions = buildEvictions(options.configurations, report) processEvictions(module, options, evictions) } private[sbt] def buildEvictions( - options: EvictionWarningOptions, + configurations: Seq[ConfigRef], report: UpdateReport - ): Seq[OrganizationArtifactReport] = { - val buffer: mutable.ListBuffer[OrganizationArtifactReport] = mutable.ListBuffer() + ): Seq[(ConfigRef, OrganizationArtifactReport)] = { val confs = report.configurations filter { x => - options.configurations.contains[ConfigRef](x.configuration) + configurations.contains[ConfigRef](x.configuration) } - confs flatMap { confReport => - confReport.details map { detail => - if ( - (detail.modules exists { _.evicted }) && - !(buffer exists { x => - (x.organization == detail.organization) && (x.name == detail.name) - }) - ) { - buffer += detail - } - } - } - buffer.toList.toVector + confs.flatMap { confReport => + confReport.details.map(report => (confReport.configuration, report)) + }.toVector } private[sbt] def isScalaArtifact( @@ -310,9 +300,21 @@ object EvictionWarning { private[sbt] def processEvictions( module: ModuleDescriptor, options: EvictionWarningOptions, - reports: Seq[OrganizationArtifactReport] + configsAndReports: Seq[(ConfigRef, OrganizationArtifactReport)] ): EvictionWarning = { val directDependencies = module.directDependencies + val buffer: mutable.ListBuffer[OrganizationArtifactReport] = mutable.ListBuffer() + configsAndReports.foreach { case (_, detail) => + if ( + (detail.modules exists { _.evicted }) && + !(buffer exists { x => + (x.organization == detail.organization) && (x.name == detail.name) + }) + ) { + buffer += detail + } + } + val reports = buffer.toList.toVector val pairs = reports map { detail => val evicteds = detail.modules filter { _.evicted } val winner = (detail.modules filterNot { _.evicted }).headOption diff --git a/lm-ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala b/lm-ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala index f8adb5c2f..b99cec489 100644 --- a/lm-ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala +++ b/lm-ivy/src/test/scala/sbt/internal/librarymanagement/EvictionErrorSpec.scala @@ -27,7 +27,7 @@ object EvictionErrorSpec extends BaseIvySpecification { List( "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:", "", - "\t* com.typesafe.akka:akka-actor_2.10:2.3.0 (pvp) is selected over 2.1.4", + "\t* com.typesafe.akka:akka-actor_2.10:2.3.0 (pvp) is selected over 2.1.4 for {compile, test}", "\t +- com.example:foo:0.1.0 (depends on 2.1.4)", "" ) @@ -43,7 +43,7 @@ object EvictionErrorSpec extends BaseIvySpecification { List( "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:", "", - "\t* com.typesafe.akka:akka-actor_2.10:2.3.4 (pvp) is selected over 2.1.4", + "\t* com.typesafe.akka:akka-actor_2.10:2.3.4 (pvp) is selected over 2.1.4 for {compile, test}", "\t +- com.typesafe.akka:akka-remote_2.10:2.3.4 (depends on 2.3.4)", "\t +- org.w3:banana-rdf_2.10:0.4 (depends on 2.1.4)", "\t +- org.w3:banana-sesame_2.10:0.4 (depends on 2.1.4)", @@ -61,7 +61,7 @@ object EvictionErrorSpec extends BaseIvySpecification { List( "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:", "", - "\t* com.typesafe.akka:akka-actor_2.10:2.3.4 (pvp?) is selected over 2.1.4", + "\t* com.typesafe.akka:akka-actor_2.10:2.3.4 (pvp?) is selected over 2.1.4 for {compile, test}", "\t +- com.typesafe.akka:akka-remote_2.10:2.3.4 (depends on 2.3.4)", "\t +- org.w3:banana-rdf_2.10:0.4 (depends on 2.1.4)", "\t +- org.w3:banana-sesame_2.10:0.4 (depends on 2.1.4)", @@ -79,13 +79,20 @@ object EvictionErrorSpec extends BaseIvySpecification { List( "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:", "", - "\t* org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.0.0, 2.2.0}", + "\t* org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.0.0, 2.2.0} for compile", "\t +- com.example:foo:0.1.0 (depends on 3.0.0-M4)", "\t +- co.fs2:fs2-core_2.13:2.4.5 (depends on 2.2.0)", "\t +- org.http4s:http4s-core_2.13:0.21.11 (depends on 2.2.0)", "\t +- io.chrisdavenport:vault_2.13:2.0.0 (depends on 2.0.0)", "\t +- io.chrisdavenport:unique_2.13:2.0.0 (depends on 2.0.0)", - "" + "", + "\t* org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.0.0, 2.2.0} for test", + "\t +- com.example:foo:0.1.0 (depends on 2.2.0)", + "\t +- co.fs2:fs2-core_2.13:2.4.5 (depends on 2.2.0)", + "\t +- org.http4s:http4s-core_2.13:0.21.11 (depends on 2.2.0)", + "\t +- io.chrisdavenport:vault_2.13:2.0.0 (depends on 2.0.0)", + "\t +- io.chrisdavenport:unique_2.13:2.0.0 (depends on 2.0.0)", + "", ) ) } @@ -115,6 +122,55 @@ object EvictionErrorSpec extends BaseIvySpecification { ) } + test("it should handle cross-platform dependencies with %%% operator") { + // Test that the userDefinedSchemes map correctly includes platform suffixes + // when using Binary CrossVersion with a suffix (as created by %%% operator) + val scalaVersion = "2.12.19" + val schemes = List( + ModuleID("com.lihaoyi", "geny", "always") + .cross(CrossVersion.binaryWith("", "_sjs1")) + ) + val scalaModuleInfo = dummyScalaModuleInfo(scalaVersion) + + // Simulate what happens in EvictionError.processEvictions + val sbvOpt = Some(scalaModuleInfo.scalaBinaryVersion) + val userDefinedSchemes: Map[(String, String), String] = Map(schemes flatMap { s => + val organization = s.organization + val versionScheme = s.revision + (s.crossVersion, sbvOpt) match { + case (b: Binary, Some(sbv)) => + List((s.organization, s"${s.name}${b.suffix}_$sbv") -> versionScheme) + case _ => + List((s.organization, s.name) -> versionScheme) + } + }*) + + // The key should include the platform suffix "_sjs1" before the Scala version + assert(userDefinedSchemes.contains(("com.lihaoyi", "geny_sjs1_2.12"))) + assert(userDefinedSchemes(("com.lihaoyi", "geny_sjs1_2.12")) == "always") + } + + test("it should detect evictions for Test configuration") { + val deps = + Vector(`scala2.13.3`, `http4s0.21.11`.withConfigurations(Some("test")), `cats-effect3.0.0-M4`) + val m = module(defaultModuleId, deps, Some("2.13.3")) + val report = ivyUpdate(m) + assert( + EvictionError(report, m, Nil).lines == + List( + "found version conflict(s) in library dependencies; some are suspected to be binary incompatible:", + "", + "\t* org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.0.0, 2.2.0} for test", + "\t +- com.example:foo:0.1.0 (depends on 2.2.0)", + "\t +- co.fs2:fs2-core_2.13:2.4.5 (depends on 2.2.0)", + "\t +- org.http4s:http4s-core_2.13:0.21.11 (depends on 2.2.0)", + "\t +- io.chrisdavenport:vault_2.13:2.0.0 (depends on 2.0.0)", + "\t +- io.chrisdavenport:unique_2.13:2.0.0 (depends on 2.0.0)", + "" + ) + ) + } + // older Akka was on pvp def oldAkkaPvp = List("com.typesafe.akka" % "*" % "pvp") diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1f6f9890d..b943ea5c4 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -3956,6 +3956,7 @@ object Classpaths { Seq[UpdateReport], UnresolvedWarningConfiguration, Level.Value, + EvictionWarningOptions, Seq[ModuleID], Level.Value, String, @@ -3986,6 +3987,7 @@ object Classpaths { transitiveUpdate.toTaskable, (update / unresolvedWarningConfiguration).toTaskable, evictionErrorLevel.toTaskable, + (update / evictionWarningOptions).toTaskable, libraryDependencySchemes.toTaskable, assumedEvictionErrorLevel.toTaskable, assumedVersionScheme.toTaskable, @@ -4016,6 +4018,7 @@ object Classpaths { tu, uwConfig, eel, + ewo, lds, aeel, avs, @@ -4086,6 +4089,7 @@ object Classpaths { transitiveUpdates = tu, uwConfig = uwConfig, evictionLevel = eel, + evictionWarningOptions = ewo, versionSchemeOverrides = lds, assumedEvictionErrorLevel = aeel, assumedVersionScheme = avs, diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index dd1e601a4..bf1f55b68 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -46,6 +46,7 @@ private[sbt] object LibraryManagement { transitiveUpdates: Seq[UpdateReport], uwConfig: UnresolvedWarningConfiguration, evictionLevel: Level.Value, + evictionWarningOptions: EvictionWarningOptions, versionSchemeOverrides: Seq[ModuleID], assumedEvictionErrorLevel: Level.Value, assumedVersionScheme: String, @@ -74,29 +75,20 @@ private[sbt] object LibraryManagement { val report1 = transform(report) // Warn of any eviction and compatibility warnings - val evictionErrorCompile = EvictionError( + val evictionError = EvictionError( report1, module, versionSchemeOverrides, assumedVersionScheme, assumedVersionSchemeJava, assumedEvictionErrorLevel, - Configurations.Compile, - ) - val evictionErrorTest = EvictionError( - report1, - module, - versionSchemeOverrides, - assumedVersionScheme, - assumedVersionSchemeJava, - assumedEvictionErrorLevel, - Configurations.Test, + evictionWarningOptions.configurations, ) def extraLines = List( "", "this can be overridden using libraryDependencySchemes or evictionErrorLevel" ) - def errorLinesFor(evictionError: EvictionError): Seq[String] = + val errorLines: Seq[String] = (if ( evictionError.incompatibleEvictions.isEmpty || evictionLevel != Level.Error @@ -107,20 +99,13 @@ private[sbt] object LibraryManagement { || assumedEvictionErrorLevel != Level.Error ) Nil else evictionError.toAssumedLines) - val errorLines: Seq[String] = - errorLinesFor(evictionErrorCompile) ++ errorLinesFor(evictionErrorTest) if (errorLines.nonEmpty) sys.error((errorLines ++ extraLines).mkString(System.lineSeparator)) else { - if (evictionErrorCompile.incompatibleEvictions.isEmpty) () - else evictionErrorCompile.lines.foreach(log.log(evictionLevel, _: String)) - if (evictionErrorCompile.assumedIncompatibleEvictions.isEmpty) () + if (evictionError.incompatibleEvictions.isEmpty) () + else evictionError.lines.foreach(log.log(evictionLevel, _: String)) + if (evictionError.assumedIncompatibleEvictions.isEmpty) () else - evictionErrorCompile.toAssumedLines.foreach(log.log(assumedEvictionErrorLevel, _: String)) - - if (evictionErrorTest.incompatibleEvictions.isEmpty) () - else evictionErrorTest.lines.foreach(log.log(evictionLevel, _: String)) - if (evictionErrorTest.assumedIncompatibleEvictions.isEmpty) () - else evictionErrorTest.toAssumedLines.foreach(log.log(assumedEvictionErrorLevel, _: String)) + evictionError.toAssumedLines.foreach(log.log(assumedEvictionErrorLevel, _: String)) } CompatibilityWarning.run(compatWarning, module, mavenStyle, log) val report2 = transformDetails(report1, includeCallers, includeDetails) @@ -419,6 +404,7 @@ private[sbt] object LibraryManagement { transitiveUpdates = tu, uwConfig = uwConfig, evictionLevel = Level.Debug, + evictionWarningOptions = EvictionWarningOptions.default, versionSchemeOverrides = Nil, assumedEvictionErrorLevel = Level.Debug, assumedVersionScheme = VersionScheme.Always, diff --git a/sbt-app/src/sbt-test/dependency-management/evicted-test-config/build.sbt b/sbt-app/src/sbt-test/dependency-management/evicted-test-config/build.sbt index 9e5f723ce..d101b9a18 100644 --- a/sbt-app/src/sbt-test/dependency-management/evicted-test-config/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/evicted-test-config/build.sbt @@ -1,6 +1,6 @@ // https://github.com/sbt/sbt/issues/8410 -scalaVersion := "3.3.4" +scalaVersion := "3.3.7" libraryDependencies ++= Seq( - "org.typelevel" %% "weaver-cats" % "0.8.4" % Test, - "com.siriusxm" %% "snapshot4s-weaver" % "0.1.5" % Test, + "org.typelevel" %% "weaver-cats" % "0.11.1" % Test, + "com.siriusxm" %% "snapshot4s-weaver" % "0.2.2" % Test, ) diff --git a/sbt-app/src/sbt-test/dependency-management/evicted-test-config/test b/sbt-app/src/sbt-test/dependency-management/evicted-test-config/test index cecc044a5..eb96dfb34 100644 --- a/sbt-app/src/sbt-test/dependency-management/evicted-test-config/test +++ b/sbt-app/src/sbt-test/dependency-management/evicted-test-config/test @@ -1 +1,9 @@ +# Update should fail as test dependencies have version conflicts -> update +# Update should succeed if the error level is reduced to Warn +> set evictionErrorLevel := Level.Warn +> update +# Update should succeed if eviction options are less strict +> set evictionErrorLevel := Level.Error +> set update / evictionWarningOptions := EvictionWarningOptions.default.withConfigurations(Vector(Compile)) +> update