From 3c955abea626141b5e959a2075f2d58e116f93df Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 21 Dec 2020 00:13:14 -0500 Subject: [PATCH 1/2] Fix compiler bridge concurrency issue Fixes https://github.com/sbt/sbt/issues/5785 --- .../main/scala/sbt/internal/inc/ZincComponentCompiler.scala | 5 +++-- .../main/scala/sbt/internal/inc/ZincComponentManager.scala | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala index 890ad687b..6adc67505 100644 --- a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala +++ b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentCompiler.scala @@ -36,6 +36,7 @@ private[sbt] object ZincComponentCompiler { private[sbt] final lazy val incrementalVersion: String = ZincComponentManager.version private val CompileConf = Some(Configurations.Compile.name) + private val lock: AnyRef = new {} private[sbt] def getDefaultBridgeModule(scalaVersion: String): ModuleID = { val compilerBridgeId = scalaVersion match { @@ -68,7 +69,7 @@ private[sbt] object ZincComponentCompiler { bridgeSources: ModuleID, scalaInstance: ScalaInstance, logger: Logger, - ): File = { + ): File = lock.synchronized { val raw = new RawCompiler(scalaInstance, ClasspathOptionsUtil.auto, logger) val zinc = new ZincComponentCompiler(raw, manager, dependencyResolution, bridgeSources, logger) @@ -312,7 +313,7 @@ private object ZincLMHelper { logger.info(s"Attempting to fetch $dependencies.") dependencyResolution.update(module, updateConfiguration, warningConf, logger) match { case Left(uw) => - logger.debug(s"Couldn't retrieve module(s) ${prettyPrintDependency(module)}.") + logger.debug(s"couldn't retrieve module(s) ${prettyPrintDependency(module)}.") val unretrievedMessage = s"The $desc could not be retrieved." val unresolvedLines = UnresolvedWarning.unresolvedWarningLines.showLines(uw).mkString("\n") throw new InvalidComponent(s"$unretrievedMessage\n$unresolvedLines") diff --git a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala index c44422483..db5525a1a 100644 --- a/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala +++ b/zinc-lm-integration/src/main/scala/sbt/internal/inc/ZincComponentManager.scala @@ -39,7 +39,9 @@ class ZincComponentManager( def notFound = invalid(s"Could not find required component '$id'") def getOrElse(orElse: => Iterable[File]): Iterable[File] = { val existing = provider.component(id) - if (existing.isEmpty) orElse else existing + // log.info(s"[zinc-lm] existing = ${existing.toList}") + if (existing.isEmpty) orElse + else existing } def createAndCache = { From 1111ed09ad326f80163f3f846447ca5353168eab Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 21 Dec 2020 00:24:39 -0500 Subject: [PATCH 2/2] Replace EvictionWarning with EvictionError Fixes https://github.com/sbt/sbt/issues/5976 Ref https://eed3si9n.com/enforcing-semver-with-sbt-strict-update This removes the guess-based EvictionWarning, and runs EvictionError instead. EvictionError uses the `ThisBuild / versionScheme` information supplied by the library authors in addition to `libraryDependencySchemes` that the build users could provide: ```scala ThisBuild / libraryDependencySchemes += "com.example" %% "name" % "early-semver" ``` as the version scheme "early-semver", "semver-spec", "pvp", "strict", or "always" may be used. Here's an example of `update` failure: ``` [error] * org.typelevel:cats-effect_2.13:3.0.0-M4 (early-semver) is selected over {2.2.0, 2.0.0, 2.0.0, 2.2.0} [error] +- com.example:use2_2.13:0.1.0-SNAPSHOT (depends on 3.0.0-M4) [error] +- org.http4s:http4s-core_2.13:0.21.11 (depends on 2.2.0) [error] +- io.chrisdavenport:vault_2.13:2.0.0 (depends on 2.0.0) [error] +- io.chrisdavenport:unique_2.13:2.0.0 (depends on 2.0.0) [error] +- co.fs2:fs2-core_2.13:2.4.5 (depends on 2.2.0) [error] [error] [error] this can be overridden using libraryDependencySchemes or evictionErrorLevel ``` This catches the violation of cats-effect_2.13:3.0.0-M4 version scheme (early-semver) without user setting additional overrides. If the user wants to opt-out of this, the user can do so per module: ```scala ThisBuild / libraryDependencySchemes += "org.typelevel" %% "cats-effect" % "always" ``` or globally as: ``` ThisBuild / evictionErrorLevel := Level.Info ``` --- main/src/main/scala/sbt/Defaults.scala | 13 ++++----- main/src/main/scala/sbt/Keys.scala | 2 ++ .../sbt/internal/LibraryManagement.scala | 28 ++++++++++++------- .../evicted-semver-spec/build.sbt | 21 ++++++++------ .../evicted-semver-spec/test | 1 + 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index d29feb14b..19c5525b2 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2689,6 +2689,8 @@ object Classpaths { defaultConfiguration :== Some(Configurations.Compile), dependencyOverrides :== Vector.empty, libraryDependencies :== Nil, + libraryDependencySchemes :== Nil, + evictionErrorLevel :== Level.Error, excludeDependencies :== Nil, ivyLoggingLevel := (// This will suppress "Resolving..." logs on Jenkins and Travis. if (insideCI.value) @@ -3460,13 +3462,7 @@ object Classpaths { .withMetadataDirectory(dependencyCacheDirectory.value) } - val evictionOptions = Def.taskDyn { - if (executionRoots.value.exists(_.key == evicted.key)) - Def.task(EvictionWarningOptions.empty) - else Def.task((evictionWarningOptions in update).value) - }.value - - val extracted = (Project extract state0) + val extracted = Project.extract(state0) val isPlugin = sbtPlugin.value val thisRef = thisProjectRef.value val label = @@ -3486,7 +3482,8 @@ object Classpaths { force = shouldForce, depsUpdated = transitiveUpdate.value.exists(!_.stats.cached), uwConfig = (unresolvedWarningConfiguration in update).value, - ewo = evictionOptions, + evictionLevel = evictionErrorLevel.value, + versionSchemeOverrides = libraryDependencySchemes.value, mavenStyle = publishMavenStyle.value, compatWarning = compatibilityWarningOptions.value, includeCallers = includeCallers, diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index a7445b08a..2df1d1e3f 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -455,6 +455,7 @@ object Keys { val updateFull = taskKey[UpdateReport]("Resolves and optionally retrieves dependencies, producing a full report with callers.").withRank(CTask) val evicted = taskKey[EvictionWarning]("Display detailed eviction warnings.").withRank(CTask) val evictionWarningOptions = settingKey[EvictionWarningOptions]("Options on eviction warnings after resolving managed dependencies.").withRank(DSetting) + val evictionErrorLevel = settingKey[Level.Value]("The log level for detected eviction errors. Level.Error will throw an error.") val transitiveUpdate = taskKey[Seq[UpdateReport]]("UpdateReports for the internal dependencies of this project.").withRank(DTask) val updateClassifiers = TaskKey[UpdateReport]("updateClassifiers", "Resolves and optionally retrieves classified artifacts, such as javadocs and sources, for dependency definitions, transitively.", BPlusTask, update) val transitiveClassifiers = settingKey[Seq[String]]("List of classifiers used for transitively obtaining extra artifacts for sbt or declared dependencies.").withRank(BSetting) @@ -533,6 +534,7 @@ object Keys { val checksums = settingKey[Seq[String]]("The list of checksums to generate and to verify for dependencies.").withRank(BSetting) val forceUpdatePeriod = settingKey[Option[FiniteDuration]]("Duration after which to force a full update to occur").withRank(CSetting) val versionScheme = settingKey[Option[String]]("""Version scheme used for the subproject: Supported values are Some("early-semver"), Some("pvp"), and Some("semver-spec")""").withRank(BSetting) + val libraryDependencySchemes = settingKey[Seq[ModuleID]]("""Version scheme to use for specific modules set as "org" %% "name" % "": Supported values are "early-semver", "pvp", "semver-spec", "always", and "strict".""").withRank(BSetting) val classifiersModule = taskKey[GetClassifiersModule]("classifiers-module").withRank(CTask) val compatibilityWarningOptions = settingKey[CompatibilityWarningOptions]("Configures warnings around Maven incompatibility.").withRank(CSetting) diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index ee175795e..6ad7d1bc0 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -19,6 +19,7 @@ import sbt.io.IO import sbt.io.syntax._ import sbt.Project.richInitializeTask import sjsonnew.JsonFormat +import scala.compat.Platform.EOL private[sbt] object LibraryManagement { implicit val linter: sbt.dsl.LinterLevel.Ignore.type = sbt.dsl.LinterLevel.Ignore @@ -36,7 +37,8 @@ private[sbt] object LibraryManagement { force: Boolean, depsUpdated: Boolean, uwConfig: UnresolvedWarningConfiguration, - ewo: EvictionWarningOptions, + evictionLevel: Level.Value, + versionSchemeOverrides: Seq[ModuleID], mavenStyle: Boolean, compatWarning: CompatibilityWarningOptions, includeCallers: Boolean, @@ -61,9 +63,19 @@ private[sbt] object LibraryManagement { val report1 = transform(report) // Warn of any eviction and compatibility warnings - val ew = EvictionWarning(module, ewo, report1) - ew.lines.foreach(log.warn(_)) - ew.infoAllTheThings.foreach(log.info(_)) + val evictionError = EvictionError(report1, module, versionSchemeOverrides) + if (evictionError.incompatibleEvictions.isEmpty) () + else + evictionLevel match { + case Level.Error => + val msgs = List( + "", + "this can be overridden using libraryDependencySchemes or evictionErrorLevel" + ) + sys.error((evictionError.lines ++ msgs).mkString(EOL)) + case _ => + evictionError.lines.foreach(log.log(evictionLevel, _: String)) + } CompatibilityWarning.run(compatWarning, module, mavenStyle, log) val report2 = transformDetails(report1, includeCallers, includeDetails) report2 @@ -245,11 +257,6 @@ private[sbt] object LibraryManagement { // logical clock is folded into UpdateConfiguration conf1.withLogicalClock(LogicalClock(state0.hashCode)) } - val evictionOptions = Def.taskDyn { - if (executionRoots.value.exists(_.key == evicted.key)) - Def.task(EvictionWarningOptions.empty) - else Def.task((evictionWarningOptions in update).value) - }.value cachedUpdate( // LM API lm = lm, @@ -263,7 +270,8 @@ private[sbt] object LibraryManagement { force = shouldForce, depsUpdated = transitiveUpdate.value.exists(!_.stats.cached), uwConfig = (unresolvedWarningConfiguration in update).value, - ewo = evictionOptions, + evictionLevel = Level.Debug, + versionSchemeOverrides = Nil, mavenStyle = publishMavenStyle.value, compatWarning = compatibilityWarningOptions.value, includeCallers = false, diff --git a/sbt/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt b/sbt/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt index 2c174b63f..6d1caf3a7 100644 --- a/sbt/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt +++ b/sbt/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt @@ -1,15 +1,10 @@ -// ThisBuild / useCoursier := false ThisBuild / organization := "com.example" -ThisBuild / scalaVersion := "2.12.12" +ThisBuild / scalaVersion := "2.13.3" ThisBuild / versionScheme := Some("semver-spec") ThisBuild / csrCacheDirectory := (ThisBuild / baseDirectory).value / "coursier-cache" def commonSettings: Seq[Def.Setting[_]] = Seq( - ivyPaths := IvyPaths( - (ThisBuild / baseDirectory).value, - Some((LocalRootProject / target).value / "ivy-cache") - ), fullResolvers := fullResolvers.value.filterNot(_.name == "inter-project"), publishTo := Some(MavenCache("local-maven", (LocalRootProject / target).value / "local-maven")), resolvers += MavenCache("local-maven", (LocalRootProject / target).value / "local-maven"), @@ -56,7 +51,17 @@ val use = project } log.info(s"extraAttributes = $extraAttributes") assert(extraAttributes.nonEmpty, s"$extraAttributes is empty") - val ew = EvictionWarning(ivyModule.value, (evicted / evictionWarningOptions).value, report) - assert(ew.directEvictions.isEmpty, s"${ew.directEvictions} is not empty") }, ) + +val use2 = project + .settings(commonSettings) + .settings( + name := "use2", + libraryDependencies ++= Seq( + "org.http4s" %% "http4s-blaze-server" % "0.21.11", + // https://repo1.maven.org/maven2/org/typelevel/cats-effect_2.13/3.0.0-M4/cats-effect_2.13-3.0.0-M4.pom + // is published with early-semver + "org.typelevel" %% "cats-effect" % "3.0.0-M4", + ), + ) diff --git a/sbt/src/sbt-test/dependency-management/evicted-semver-spec/test b/sbt/src/sbt-test/dependency-management/evicted-semver-spec/test index eb5f0c784..93ed91f57 100644 --- a/sbt/src/sbt-test/dependency-management/evicted-semver-spec/test +++ b/sbt/src/sbt-test/dependency-management/evicted-semver-spec/test @@ -3,3 +3,4 @@ > middle/publish > use/check +-> use2/update