Compare commits

...

2 Commits

Author SHA1 Message Date
eugene yokota 8d6627929d
[2.x] desp: Gigahorse 0.9.4 (#9125) 2026-04-23 23:40:54 -04:00
Zainab Ali 3b1dbae74a
[2.x] Add Test configuration to evictionWarningOptions (#9102)
* 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.
2026-04-23 23:06:54 -04:00
9 changed files with 212 additions and 145 deletions

View File

@ -1228,6 +1228,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.<init>$*"
),
ProblemFilters.exclude[DirectMissingMethodProblem](
"sbt.librarymanagement.EvictionError.configuration"
)
),
)
.dependsOn(utilLogging, utilPosition, utilCache)

View File

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

View File

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

View File

@ -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)",
"",
)
)
}
@ -143,6 +150,27 @@ object EvictionErrorSpec extends BaseIvySpecification {
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")

View File

@ -3943,6 +3943,7 @@ object Classpaths {
Seq[UpdateReport],
UnresolvedWarningConfiguration,
Level.Value,
EvictionWarningOptions,
Seq[ModuleID],
Level.Value,
String,
@ -3973,6 +3974,7 @@ object Classpaths {
transitiveUpdate.toTaskable,
(update / unresolvedWarningConfiguration).toTaskable,
evictionErrorLevel.toTaskable,
(update / evictionWarningOptions).toTaskable,
libraryDependencySchemes.toTaskable,
assumedEvictionErrorLevel.toTaskable,
assumedVersionScheme.toTaskable,
@ -4003,6 +4005,7 @@ object Classpaths {
tu,
uwConfig,
eel,
ewo,
lds,
aeel,
avs,
@ -4071,6 +4074,7 @@ object Classpaths {
transitiveUpdates = tu,
uwConfig = uwConfig,
evictionLevel = eel,
evictionWarningOptions = ewo,
versionSchemeOverrides = lds,
assumedEvictionErrorLevel = aeel,
assumedVersionScheme = avs,

View File

@ -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)
@ -408,6 +393,7 @@ private[sbt] object LibraryManagement {
transitiveUpdates = tu,
uwConfig = uwConfig,
evictionLevel = Level.Debug,
evictionWarningOptions = EvictionWarningOptions.default,
versionSchemeOverrides = Nil,
assumedEvictionErrorLevel = Level.Debug,
assumedVersionScheme = VersionScheme.Always,

View File

@ -115,7 +115,7 @@ object Dependencies {
// lm dependencies
val jsch = ("com.github.mwiede" % "jsch" % "0.2.23").intransitive()
val gigahorseApacheHttp = "com.eed3si9n" %% "gigahorse-apache-http" % "0.9.3"
val gigahorseApacheHttp = "com.eed3si9n" %% "gigahorse-apache-http" % "0.9.4"
// lm-coursier dependencies
val dataclassScalafixVersion = "0.3.0"

View File

@ -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,
)

View File

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