diff --git a/build.sbt b/build.sbt index f10004658..8f6afffae 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ import scala.util.Try // ThisBuild settings take lower precedence, // but can be shared across the multi projects. ThisBuild / version := { - val v = "1.11.8-SNAPSHOT" + val v = "1.12.0-SNAPSHOT" nightlyVersion.getOrElse(v) } ThisBuild / version2_13 := "2.0.0-SNAPSHOT" diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index a6c2c5a56..1b149e855 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -671,89 +671,94 @@ object Defaults extends BuildCommon { ) // This is included into JvmPlugin.projectSettings - def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq( - useScalaReplJLine :== false, - scalaInstanceTopLoader := { - val topLoader = if (!useScalaReplJLine.value) { - // the JLineLoader contains the SbtInterfaceClassLoader - classOf[org.jline.terminal.Terminal].getClassLoader - } else classOf[Compilers].getClassLoader // the SbtInterfaceClassLoader + def compileBase = + inTask(console)( + Seq( + scalaInstance := Compiler.scalaInstanceTask(Some(Configurations.ScalaReplTool)).value, + ) ++ compilersSetting + ) ++ compileBaseGlobal ++ Seq( + useScalaReplJLine :== false, + scalaInstanceTopLoader := { + val topLoader = if (!useScalaReplJLine.value) { + // the JLineLoader contains the SbtInterfaceClassLoader + classOf[org.jline.terminal.Terminal].getClassLoader + } else classOf[Compilers].getClassLoader // the SbtInterfaceClassLoader - // Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible - // jansi version. Because of the shading, console does not work with the thin client for 2.10.x. - if (scalaVersion.value.startsWith("2.10.")) new ClassLoader(topLoader) { - override protected def loadClass(name: String, resolve: Boolean): Class[_] = { - if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name) - super.loadClass(name, resolve) + // Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible + // jansi version. Because of the shading, console does not work with the thin client for 2.10.x. + if (scalaVersion.value.startsWith("2.10.")) new ClassLoader(topLoader) { + override protected def loadClass(name: String, resolve: Boolean): Class[_] = { + if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name) + super.loadClass(name, resolve) + } } - } - else topLoader - }, - scalaInstance := Compiler.scalaInstanceTask.value, - crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled), - pluginCrossBuild / sbtBinaryVersion := binarySbtVersion( - (pluginCrossBuild / sbtVersion).value - ), - // Use (sbtVersion in pluginCrossBuild) to pick the sbt module to depend from the plugin. - // Because `sbtVersion in pluginCrossBuild` can be scoped to project level, - // this setting needs to be set here too. - pluginCrossBuild / sbtDependency := { - val app = appConfiguration.value - val id = app.provider.id - val sv = (pluginCrossBuild / sbtVersion).value - val scalaV = (pluginCrossBuild / scalaVersion).value - val binVersion = (pluginCrossBuild / scalaBinaryVersion).value - val cross = id.crossVersionedValue match { - case CrossValue.Disabled => Disabled() - case CrossValue.Full => CrossVersion.full - case CrossValue.Binary => CrossVersion.binary - } - val base = ModuleID(id.groupID, id.name, sv).withCrossVersion(cross) - CrossVersion(scalaV, binVersion)(base).withCrossVersion(Disabled()) - }, - crossSbtVersions := Vector((pluginCrossBuild / sbtVersion).value), - crossTarget := makeCrossTarget( - target.value, - scalaVersion.value, - scalaBinaryVersion.value, - (pluginCrossBuild / sbtBinaryVersion).value, - sbtPlugin.value, - crossPaths.value - ), - cleanIvy := IvyActions.cleanCachedResolutionCache(ivyModule.value, streams.value.log), - clean := { - val _ = cleanIvy.value - try { - val store = AnalysisUtil.staticCachedStore( - analysisFile = (Compile / compileAnalysisFile).value.toPath, - useTextAnalysis = !(Compile / enableBinaryCompileAnalysis).value, - useConsistent = (Compile / enableConsistentCompileAnalysis).value, - ) - store.clearCache() - } catch { - case NonFatal(_) => () - } - clean.value - }, - scalaCompilerBridgeBinaryJar := Def.settingDyn { - val sv = scalaVersion.value - if (ScalaArtifacts.isScala3(sv) || VersionNumber(sv) - .matchesSemVer(SemanticSelector(s"=2.13 >=${ZincLmUtil.scala2SbtBridgeStart}"))) - fetchBridgeBinaryJarTask(sv) - else Def.task[Option[File]](None) - }.value, - scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule(scalaVersion.value), - auxiliaryClassFiles ++= { - if (ScalaArtifacts.isScala3(scalaVersion.value)) List(TastyFiles.instance) - else Nil - }, - consoleProject / scalaCompilerBridgeBinaryJar := None, - consoleProject / scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule( - appConfiguration.value.provider.scalaProvider.version - ), - classpathOptions := ClasspathOptionsUtil.noboot(scalaVersion.value), - console / classpathOptions := ClasspathOptionsUtil.replNoboot(scalaVersion.value), - ) + else topLoader + }, + scalaInstance := Compiler.scalaInstanceTask(None).value, + crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled), + pluginCrossBuild / sbtBinaryVersion := binarySbtVersion( + (pluginCrossBuild / sbtVersion).value + ), + // Use (sbtVersion in pluginCrossBuild) to pick the sbt module to depend from the plugin. + // Because `sbtVersion in pluginCrossBuild` can be scoped to project level, + // this setting needs to be set here too. + pluginCrossBuild / sbtDependency := { + val app = appConfiguration.value + val id = app.provider.id + val sv = (pluginCrossBuild / sbtVersion).value + val scalaV = (pluginCrossBuild / scalaVersion).value + val binVersion = (pluginCrossBuild / scalaBinaryVersion).value + val cross = id.crossVersionedValue match { + case CrossValue.Disabled => Disabled() + case CrossValue.Full => CrossVersion.full + case CrossValue.Binary => CrossVersion.binary + } + val base = ModuleID(id.groupID, id.name, sv).withCrossVersion(cross) + CrossVersion(scalaV, binVersion)(base).withCrossVersion(Disabled()) + }, + crossSbtVersions := Vector((pluginCrossBuild / sbtVersion).value), + crossTarget := makeCrossTarget( + target.value, + scalaVersion.value, + scalaBinaryVersion.value, + (pluginCrossBuild / sbtBinaryVersion).value, + sbtPlugin.value, + crossPaths.value + ), + cleanIvy := IvyActions.cleanCachedResolutionCache(ivyModule.value, streams.value.log), + clean := { + val _ = cleanIvy.value + try { + val store = AnalysisUtil.staticCachedStore( + analysisFile = (Compile / compileAnalysisFile).value.toPath, + useTextAnalysis = !(Compile / enableBinaryCompileAnalysis).value, + useConsistent = (Compile / enableConsistentCompileAnalysis).value, + ) + store.clearCache() + } catch { + case NonFatal(_) => () + } + clean.value + }, + scalaCompilerBridgeBinaryJar := Def.settingDyn { + val sv = scalaVersion.value + if (ScalaArtifacts.isScala3(sv) || VersionNumber(sv) + .matchesSemVer(SemanticSelector(s"=2.13 >=${ZincLmUtil.scala2SbtBridgeStart}"))) + fetchBridgeBinaryJarTask(sv) + else Def.task[Option[File]](None) + }.value, + scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule(scalaVersion.value), + auxiliaryClassFiles ++= { + if (ScalaArtifacts.isScala3(scalaVersion.value)) List(TastyFiles.instance) + else Nil + }, + consoleProject / scalaCompilerBridgeBinaryJar := None, + consoleProject / scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule( + appConfiguration.value.provider.scalaProvider.version + ), + classpathOptions := ClasspathOptionsUtil.noboot(scalaVersion.value), + console / classpathOptions := ClasspathOptionsUtil.replNoboot(scalaVersion.value), + ) // must be a val: duplication detected by object identity private[this] lazy val compileBaseGlobal: Seq[Setting[_]] = globalDefaults( Seq( @@ -1140,7 +1145,7 @@ object Defaults extends BuildCommon { } @deprecated("Use Compiler.scalaInstanceTask", "1.12.0") - def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Compiler.scalaInstanceTask + def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Compiler.scalaInstanceTask(None) // Returns the ScalaInstance only if it was not constructed via `update` // This is necessary to prevent cycles between `update` and `scalaInstance` @@ -1149,8 +1154,9 @@ object Defaults extends BuildCommon { if (scalaHome.value.isDefined) Def.task(Some(scalaInstance.value)) else Def.task(None) } + @deprecated("Use Compiler.scalaInstanceFromUpdate", "1.12.0") def scalaInstanceFromUpdate: Initialize[Task[ScalaInstance]] = - Compiler.scalaInstanceFromUpdate + Compiler.scalaInstanceFromUpdate(None) def makeScalaInstance( version: String, @@ -2062,6 +2068,7 @@ object Defaults extends BuildCommon { def docTaskSettings(key: TaskKey[File] = doc): Seq[Setting[_]] = inTask(key)( Seq( + scalaInstance := Compiler.scalaInstanceTask(Some(Configurations.ScalaDocTool)).value, apiMappings ++= { val dependencyCp = dependencyClasspath.value val log = streams.value.log @@ -2139,7 +2146,7 @@ object Defaults extends BuildCommon { } out } - ) + ) ++ compilersSetting ) def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime)) @@ -3189,7 +3196,7 @@ object Classpaths { ivyConfigurations ++= Configurations.auxiliary, ivyConfigurations ++= { if (managedScalaInstance.value && scalaHome.value.isEmpty) - Configurations.ScalaTool :: Configurations.ScalaDocTool :: Nil + Configurations.ScalaTool :: Configurations.ScalaDocTool :: Configurations.ScalaReplTool :: Nil else Nil }, // Coursier needs these @@ -3385,17 +3392,18 @@ object Classpaths { val pluginAdjust = if (isPlugin) sbtdeps +: base else base - val sbtOrg = scalaOrganization.value + val scalaOrg = scalaOrganization.value val version = scalaVersion.value - val extResolvers = externalResolvers.value val isScala3M123 = ScalaArtifacts.isScala3M123(version) val allToolDeps = if (scalaHome.value.isDefined || scalaModuleInfo.value.isEmpty || !managedScalaInstance.value) Nil - else if (!isScala3M123 || extResolvers.contains(Resolver.JCenterRepository)) { - ScalaArtifacts.toolDependencies(sbtOrg, version) ++ - ScalaArtifacts.docToolDependencies(sbtOrg, version) - } else ScalaArtifacts.toolDependencies(sbtOrg, version) + else if (isScala3M123) + ScalaArtifacts.toolDependencies(scalaOrg, version) + else + ScalaArtifacts.toolDependencies(scalaOrg, version) ++ + ScalaArtifacts.docToolDependencies(scalaOrg, version) ++ + ScalaArtifacts.replToolDependencies(scalaOrg, version) allToolDeps ++ pluginAdjust }, // in case of meta build, exclude all sbt modules from the dependency graph, so we can use the sbt resolved by the launcher diff --git a/main/src/main/scala/sbt/internal/Compiler.scala b/main/src/main/scala/sbt/internal/Compiler.scala index d52b33db5..473e10137 100644 --- a/main/src/main/scala/sbt/internal/Compiler.scala +++ b/main/src/main/scala/sbt/internal/Compiler.scala @@ -1,6 +1,7 @@ /* * sbt - * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. * Copyright 2008 - 2010, Mark Harrah * Licensed under Apache License 2.0 (see LICENSE) */ @@ -12,16 +13,23 @@ import java.io.File import sbt.internal.inc.ScalaInstance import sbt.librarymanagement.{ Artifact, + Configuration, Configurations, ConfigurationReport, ScalaArtifacts, SemanticSelector, + UpdateReport, VersionNumber } import xsbti.ScalaProvider private[sbt] object Compiler { - def scalaInstanceTask: Def.Initialize[Task[ScalaInstance]] = + + /** + * Returns a ScalaInstance. + * extraToolConf is used for Scala 3 since it started splitting up scaladoc and repl. + */ + def scalaInstanceTask(extraToolConf: Option[Configuration]): Def.Initialize[Task[ScalaInstance]] = Def.taskDyn { val sh = Keys.scalaHome.value val app = Keys.appConfiguration.value @@ -35,7 +43,7 @@ private[sbt] object Compiler { val scalaProvider = app.provider.scalaProvider if (!managed) emptyScalaInstance else if (sv == scalaProvider.version) optimizedScalaInstance(sv, scalaProvider) - else scalaInstanceFromUpdate + else scalaInstanceFromUpdate(extraToolConf) } } @@ -95,60 +103,49 @@ private[sbt] object Compiler { ) } - def scalaInstanceFromUpdate: Def.Initialize[Task[ScalaInstance]] = Def.task { - val sv = Keys.scalaVersion.value - val fullReport = Keys.update.value - val s = Keys.streams.value + /** + * Returns a ScalaInstance. + * extraToolConf is used for Scala 3 since it started splitting up scaladoc and repl. + */ + def scalaInstanceFromUpdate( + extraToolConf: Option[Configuration] + ): Def.Initialize[Task[ScalaInstance]] = + Def.task { + val sv = Keys.scalaVersion.value + val fullReport = Keys.update.value + val s = Keys.streams.value - // For Scala 3, update scala-library.jar in `scala-tool` and `scala-doc-tool` in case a newer version - // is present in the `compile` configuration. This is needed once forwards binary compatibility is dropped - // to avoid NoSuchMethod exceptions when expanding macros. - def updateLibraryToCompileConfiguration(report: ConfigurationReport) = - if (!ScalaArtifacts.isScala3(sv)) report - else - (for { - compileConf <- fullReport.configuration(Configurations.Compile) - compileLibMod <- compileConf.modules.find(_.module.name == ScalaArtifacts.LibraryID) - reportLibMod <- report.modules.find(_.module.name == ScalaArtifacts.LibraryID) - if VersionNumber(reportLibMod.module.revision) - .matchesSemVer(SemanticSelector(s"<${compileLibMod.module.revision}")) - } yield { - val newMods = report.modules - .filterNot(_.module.name == ScalaArtifacts.LibraryID) :+ compileLibMod - report.withModules(newMods) - }).getOrElse(report) + val toolReport = updateLibraryToCompileConfiguration(sv, fullReport)( + fullReport + .configuration(Configurations.ScalaTool) + .getOrElse(sys.error(noToolConfiguration(Keys.managedScalaInstance.value))) + ) - val toolReport = updateLibraryToCompileConfiguration( - fullReport - .configuration(Configurations.ScalaTool) - .getOrElse(sys.error(noToolConfiguration(Keys.managedScalaInstance.value))) - ) - - if (Classpaths.isScala213(sv)) { - val scalaDeps = for { - compileReport <- fullReport.configuration(Configurations.Compile).iterator - libName <- ScalaArtifacts.Artifacts.iterator - lib <- compileReport.modules.find(_.module.name == libName) - } yield lib - for (lib <- scalaDeps.take(1)) { - val libVer = lib.module.revision - val libName = lib.module.name - val proj = - Def.displayBuildRelative(Keys.thisProjectRef.value.build, Keys.thisProjectRef.value) - if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) { - val err = !Keys.allowUnsafeScalaLibUpgrade.value - val fix = - if (err) - """Upgrade the `scalaVersion` to fix the build. If upgrading the Scala compiler version is + if (Classpaths.isScala213(sv)) { + val scalaDeps = for { + compileReport <- fullReport.configuration(Configurations.Compile).iterator + libName <- ScalaArtifacts.Artifacts.iterator + lib <- compileReport.modules.find(_.module.name == libName) + } yield lib + for (lib <- scalaDeps.take(1)) { + val libVer = lib.module.revision + val libName = lib.module.name + val proj = + Def.displayBuildRelative(Keys.thisProjectRef.value.build, Keys.thisProjectRef.value) + if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) { + val err = !Keys.allowUnsafeScalaLibUpgrade.value + val fix = + if (err) + """Upgrade the `scalaVersion` to fix the build. If upgrading the Scala compiler version is |not possible (for example due to a regression in the compiler or a missing dependency), |this error can be demoted by setting `allowUnsafeScalaLibUpgrade := true`.""".stripMargin - else - s"""Note that the dependency classpath and the runtime classpath of your project + else + s"""Note that the dependency classpath and the runtime classpath of your project |contain the newer $libName $libVer, even if the scalaVersion is $sv. |Compilation (macro expansion) or using the Scala REPL in sbt may fail with a LinkageError.""".stripMargin - val msg = - s"""Expected `$proj scalaVersion` to be $libVer or later, but found $sv. + val msg = + s"""Expected `$proj scalaVersion` to be $libVer or later, but found $sv. |To support backwards-only binary compatibility (SIP-51), the Scala 2.13 compiler |should not be older than $libName on the dependency classpath. | @@ -156,60 +153,84 @@ private[sbt] object Compiler { | |See `$proj evicted` to know why $libName $libVer is getting pulled in. |""".stripMargin - if (err) sys.error(msg) - else s.log.warn(msg) + if (err) sys.error(msg) + else s.log.warn(msg) + } } } - } - def file(id: String): File = { - val files = for { - m <- toolReport.modules if m.module.name.startsWith(id) - (art, file) <- m.artifacts if art.`type` == Artifact.DefaultType - } yield file - files.headOption getOrElse sys.error(s"Missing $id jar file") + def file(id: String): File = { + val files = for { + m <- toolReport.modules if m.module.name.startsWith(id) + (art, file) <- m.artifacts if art.`type` == Artifact.DefaultType + } yield file + files.headOption getOrElse sys.error(s"Missing $id jar file") + } + + val allCompilerJars = toolReport.modules.flatMap(_.artifacts.map(_._2)) + val extraToolJars = + extraToolConf match { + case Some(extra) => + fullReport + .configuration(extra) + .map(updateLibraryToCompileConfiguration(sv, fullReport)) + .toSeq + .flatMap(_.modules) + .flatMap(_.artifacts.map(_._2)) + case None => Nil + } + val libraryJars = ScalaArtifacts.libraryIds(sv).map(file) + + makeScalaInstance( + sv, + libraryJars, + allCompilerJars, + extraToolJars, + Keys.state.value, + Keys.scalaInstanceTopLoader.value, + ) } - val allCompilerJars = toolReport.modules.flatMap(_.artifacts.map(_._2)) - val allDocJars = - fullReport - .configuration(Configurations.ScalaDocTool) - .map(updateLibraryToCompileConfiguration) - .toSeq - .flatMap(_.modules) - .flatMap(_.artifacts.map(_._2)) - val libraryJars = ScalaArtifacts.libraryIds(sv).map(file) - - makeScalaInstance( - sv, - libraryJars, - allCompilerJars, - allDocJars, - Keys.state.value, - Keys.scalaInstanceTopLoader.value, - ) - } + // For Scala 3, update scala-library.jar in `scala-tool` and `scala-doc-tool` in case a newer version + // is present in the `compile` configuration. This is needed once forwards binary compatibility is dropped + // to avoid NoSuchMethod exceptions when expanding macros. + private def updateLibraryToCompileConfiguration(sv: String, fullReport: UpdateReport)( + report: ConfigurationReport + ) = + if (!ScalaArtifacts.isScala3(sv)) report + else + (for { + compileConf <- fullReport.configuration(Configurations.Compile) + compileLibMod <- compileConf.modules.find(_.module.name == ScalaArtifacts.LibraryID) + reportLibMod <- report.modules.find(_.module.name == ScalaArtifacts.LibraryID) + if VersionNumber(reportLibMod.module.revision) + .matchesSemVer(SemanticSelector(s"<${compileLibMod.module.revision}")) + } yield { + val newMods = report.modules + .filterNot(_.module.name == ScalaArtifacts.LibraryID) :+ compileLibMod + report.withModules(newMods) + }).getOrElse(report) def makeScalaInstance( version: String, libraryJars: Array[File], allCompilerJars: Seq[File], - allDocJars: Seq[File], + extraToolJars: Seq[File], state: State, topLoader: ClassLoader, ): ScalaInstance = { val classLoaderCache = state.extendedClassLoaderCache val compilerJars = allCompilerJars.filterNot(libraryJars.contains).distinct.toArray - val docJars = allDocJars + val toolJars = extraToolJars .filterNot(jar => libraryJars.contains(jar) || compilerJars.contains(jar)) .distinct .toArray - val allJars = libraryJars ++ compilerJars ++ docJars + val allJars = libraryJars ++ compilerJars ++ toolJars val libraryLoader = classLoaderCache(libraryJars.toList, topLoader) val compilerLoader = classLoaderCache(compilerJars.toList, libraryLoader) val fullLoader = - if (docJars.isEmpty) compilerLoader - else classLoaderCache(docJars.distinct.toList, compilerLoader) + if (toolJars.isEmpty) compilerLoader + else classLoaderCache(toolJars.distinct.toList, compilerLoader) new ScalaInstance( version = version, loader = fullLoader, diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c14030431..47d798632 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,8 +14,8 @@ object Dependencies { // sbt modules private val ioVersion = nightlyVersion.getOrElse("1.10.5") private val lmVersion = - sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.6") - val zincVersion = nightlyVersion.getOrElse("1.11.0") + sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.12.0-M1") + val zincVersion = nightlyVersion.getOrElse("1.12.0-M1") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion