diff --git a/.gitattributes b/.gitattributes index 5b6b22eaf..90a9f7949 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,5 @@ launcher-package/src/windows/sbt text eol=lf **/contraband-scala/**/* -diff merge=ours **/contraband-scala/**/* linguist-generated=true **/contraband-scala/**/* diff + +*.contra linguist-language=graphql diff --git a/build.sbt b/build.sbt index 311c9435e..5722eacdd 100644 --- a/build.sbt +++ b/build.sbt @@ -84,19 +84,21 @@ def commonSettings: Seq[Setting[?]] = Def.settings( Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"), Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"), compile / javacOptions ++= Seq("-Xlint", "-Xlint:-serial"), - /* Compile / doc / scalacOptions ++= { - import scala.sys.process._ - val devnull = ProcessLogger(_ => ()) - val tagOrSha = ("git describe --exact-match" #|| "git rev-parse HEAD").lineStream(devnull).head - Seq( - "-sourcepath", - (LocalRootProject / baseDirectory).value.getAbsolutePath, - "-doc-source-url", - s"https://github.com/sbt/sbt/tree/$tagOrSha€{FILE_PATH}.scala" - ) + if (Dependencies.sbtIoPath.isEmpty && Dependencies.sbtZincPath.isEmpty) { + import scala.sys.process.* + val devnull = ProcessLogger(_ => ()) + val tagOrSha = + ("git describe --exact-match" #|| "git rev-parse HEAD").lineStream(devnull).head + Seq( + "-source-links:github://sbt/sbt", + "-revision", + tagOrSha + ) + } else { + Nil + } }, - */ Compile / javafmtOnCompile := scalafmtOnCompile.value, Test / javafmtOnCompile := (Test / scalafmtOnCompile).value, Compile / unmanagedSources / inputFileStamps := @@ -186,7 +188,6 @@ lazy val sbtRoot: Project = (project in file(".")) }, Utils.baseScalacOptions, Docs.settings, - scalacOptions += "-Ymacro-expand:none", // for both sxr and doc Utils.publishPomSettings, otherRootSettings, Utils.noPublish, @@ -742,8 +743,10 @@ lazy val mainProj = (project in file("main")) Test / testOptions += Tests .Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"), SettingKey[Boolean]("usePipelining") := false, - // TODO: Fix doc - Compile / doc / sources := Nil, + libraryDependencies += { + // https://github.com/scala/scala3/issues/18487 + "net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided + }, mimaSettings, mimaBinaryIssueFilters ++= Vector( // Moved to sbt-ivy module (Step 5 of sbt#7640) @@ -782,8 +785,10 @@ lazy val sbtIvyProj = (project in file("sbt-ivy")) name := "sbt-ivy", sbtPlugin := true, pluginCrossBuild / sbtVersion := version.value, - // TODO: Fix doc - Compile / doc / sources := Nil, + libraryDependencies += { + // https://github.com/scala/scala3/issues/18487 + "net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided + }, mimaPreviousArtifacts := Set.empty, // new module, no previous artifacts ) .configure(addSbtIO) @@ -1019,10 +1024,6 @@ lazy val sbtwProj = (project in file("sbtw")) description := "Windows drop-in launcher for sbt (replaces sbt.bat)", scalaVersion := "3.8.3", crossPaths := false, - Compile / scalafix / unmanagedSources := { - // https://github.com/scalameta/scalameta/issues/4531 - (Compile / unmanagedSources).value.filterNot(_.getName == "Main.scala") - }, Compile / mainClass := Some("sbtw.Main"), libraryDependencies += "com.github.scopt" %% "scopt" % "4.1.0", libraryDependencies += scalaVerify % Test, diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index 729107d20..5fae9287f 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -200,6 +200,8 @@ object Tests { new Group(name, tests, runPolicy, tags) } + override def hashCode(): Int = (name, tests, runPolicy, tags).## + override def equals(x$1: Any): Boolean = { this.eq(x$1.asInstanceOf[Object]) || (x$1.isInstanceOf[Group] && ({ val Group$1: Group = x$1.asInstanceOf[Group] diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 386208441..0034d48de 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -149,7 +149,9 @@ $HelpCommand remaining commands with the exception that the JVM is not shut down. If 'dev' is specified, the current sbt artifacts from the boot directory - (`~/.sbt/boot` by default) are deleted before restarting. + (under the default global base; `sbt.global.base` selects that location) are deleted + before restarting, and the compiler bridge secondary cache at `zinc/org.scala-sbt` + (honoring `sbt.global.base` and `sbt.global.zinc`) is removed. This forces an update of sbt and Scala, which is useful when working with development versions of sbt. If 'full' is specified, the boot directory is wiped out before restarting. diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index fcb81ab92..7b0b97183 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -181,6 +181,7 @@ class NetworkClient( def success(message: => String): Unit = () def log(level: Level.Value, message: => String): Unit = console.appendLog(level, message) } + private val interactive = arguments.commandArguments.isEmpty private[sbt] def connectOrStartServerAndConnect( promptCompleteUsers: Boolean, @@ -712,8 +713,19 @@ class NetworkClient( case (`clientJob`, Some(json)) => import sbt.internal.worker.codec.JsonProtocol.given Converter.fromJson[ClientJobParams](json) match { - case Success(params) => clientSideRun(params).get; Vector.empty - case Failure(_) => Vector.empty + case Success(params) => + clientSideRun(params) match + case Success(_) => + if interactive then console.success("ok") + else () + Vector.empty + case Failure(e) => + if interactive then + Vector( + (Level.Error, e.getMessage) + ) + else throw e + case Failure(_) => Vector.empty } case (`Shutdown`, Some(_)) => Vector.empty case (msg, _) if msg.startsWith("build/") => Vector.empty @@ -921,9 +933,8 @@ class NetworkClient( withSignalHandler(contHandler, Signals.CONT) { interactiveThread.set(Thread.currentThread) val cleaned = arguments.commandArguments - val userCommands = cleaned.takeWhile(_ != TerminateAction) - val interactive = cleaned.isEmpty - val exit = cleaned.nonEmpty && userCommands.isEmpty + val userCommands = arguments.commandArguments.takeWhile(_ != TerminateAction) + val exit = arguments.commandArguments.nonEmpty && userCommands.isEmpty attachUUID.set(sendJson(attach, s"""{"interactive": $interactive}""")) val handler: () => Unit = () => { def exitAbruptly() = { diff --git a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala index 5356b710d..56046fa13 100644 --- a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala @@ -30,7 +30,10 @@ object ServerHandler { lazy val fallback: ServerHandler = ServerHandler({ handler => ServerIntent( - onRequest = { case x => handler.log.debug(s"Unhandled request received: ${x.method}: $x") }, + onRequest = { case x => + handler.log.debug(s"Unhandled request received: ${x.method}: $x") + handler.jsonRpcRespondError(Some(x.id), -32601, s"Unknown method: ${x.method}") + }, onResponse = { case x => handler.log.debug(s"Unhandled response received") }, onNotification = { case x => handler.log.debug(s"Unhandled notification received: ${x.method}: $x") diff --git a/main/src/main/scala/sbt/Cross.scala b/main/src/main/scala/sbt/Cross.scala index 3bc838184..6a78cb08b 100644 --- a/main/src/main/scala/sbt/Cross.scala +++ b/main/src/main/scala/sbt/Cross.scala @@ -41,7 +41,7 @@ object Cross { private def switchParser(state: State): Parser[Switch] = { import DefaultParsers.* - def versionAndCommand(spacePresent: Boolean) = { + def versionAndCommand(commandName: String)(spacePresent: Boolean) = { val x = Project.extract(state) import x.* val knownVersions = crossVersions(x, currentRef) @@ -56,7 +56,7 @@ object Cross { ScalaHomeVersion(new File(home), Some(v).filterNot(_.isEmpty), force) } } - val spacedVersion = if (spacePresent) version else version & spacedFirst(SwitchCommand) + val spacedVersion = if (spacePresent) version else version & spacedFirst(commandName) val verboseOpt = Parser.opt(token(Space ~> "-v")) // Accept valid commands, or project/command patterns that may reference projects // not yet available after version switch (fixes #7574) @@ -74,19 +74,24 @@ object Cross { switch1 | switch2 } - token(SwitchCommand ~> OptSpace) flatMap { sp => - versionAndCommand(sp.nonEmpty) - } + def parse(commandName: String) = + token(commandName ~> OptSpace) flatMap { sp => + versionAndCommand(commandName)(sp.nonEmpty) + } + parse(SwitchCommand) | parse(SwitchAlias) } private case class CrossArgs(command: String, verbose: Boolean) - private def crossParser(state: State): Parser[CrossArgs] = - token(CrossCommand <~ OptSpace) flatMap { _ => - (token(Parser.opt("-v" <~ Space)) ~ token(matched(state.combinedParser))).map { - (verbose, command) => CrossArgs(command, verbose.isDefined) + private def crossParser(state: State): Parser[CrossArgs] = { + def parse(commandName: String) = + token(commandName <~ OptSpace) flatMap { _ => + (token(Parser.opt("-v" <~ Space)) ~ token(matched(state.combinedParser))).map { + (verbose, command) => CrossArgs(command, verbose.isDefined) + } } - } + parse(CrossCommand) | parse(CrossAlias) + } private def crossRestoreSessionParser: Parser[String] = token(CrossRestoreSessionCommand) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1b567de2c..90b9a5655 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -4289,7 +4289,9 @@ object Classpaths { configuration.allRepositories, ) val processed = configuration.process(pomXml) - scala.xml.XML.save(file.getAbsolutePath, processed, "UTF-8", xmlDecl = true) + val printer = new scala.xml.PrettyPrinter(1000, 4) + val formatted = scala.xml.XML.loadString(printer.format(processed)) + scala.xml.XML.save(file.getAbsolutePath, formatted, "UTF-8", xmlDecl = true) log.info("Wrote " + file.getAbsolutePath) file }) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 28a1db68e..08ca8ae68 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -479,6 +479,8 @@ object Keys { val bspBuildTargetResourcesItem = taskKey[ResourcesItem]("").withRank(DTask) val bspBuildTargetDependencySources = inputKey[Unit]("").withRank(DTask) val bspBuildTargetDependencySourcesItem = taskKey[DependencySourcesItem]("").withRank(DTask) + val bspBuildTargetDependencyModules = inputKey[Unit]("").withRank(DTask) + val bspBuildTargetDependencyModulesItem = taskKey[DependencyModulesItem]("").withRank(DTask) val bspBuildTargetOutputPaths = inputKey[Unit]("").withRank(DTask) val bspBuildTargetOutputPathsItem = taskKey[OutputPathsItem]("").withRank(DTask) val bspBuildTargetCompile = inputKey[Unit]("").withRank(DTask) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index f7bbb12b9..37fe90b49 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -1144,12 +1144,20 @@ object BuiltinCommands { .getOpt(Keys.minForcegcInterval) .getOrElse(GCUtil.defaultMinForcegcInterval) val exec: Exec = getExec(s1, minGCInterval) + val isInteractive = exec.source match { + case Some(src) if src.channelName.startsWith("network") => + exchange.channelForName(src.channelName) match { + case Some(nc: NetworkChannel) => nc.isInteractive + case _ => true + } + case _ => true + } val newState = s1 .copy( onFailure = Some(Exec(Shell, None)), remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands ) - .setInteractive(true) + .setInteractive(isInteractive) val res = if (exec.commandLine.trim.isEmpty) newState else newState.clearGlobalLog diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index f2ebab9ba..1d98aeb51 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -22,6 +22,8 @@ import sbt.internal.util.{ Terminal as ITerminal } import sbt.io.{ IO, Using } +import sbt.io.syntax.* +import sbt.librarymanagement.SbtArtifacts import sbt.protocol.* import sbt.util.{ Logger, LoggerContext } @@ -75,6 +77,10 @@ private[sbt] object MainLoop: case e: RebootCurrent => deleteLastLog(logBacking) deleteCurrentArtifacts(state) + deleteZincBridgeSecondaryCache( + state.log, + BuildPaths.getZincDirectory(state, BuildPaths.getGlobalBase(state)), + ) throw new xsbti.FullReload(e.arguments.toArray, false) case NonFatal(e) => System.err.println( @@ -89,7 +95,6 @@ private[sbt] object MainLoop: /** Deletes the current sbt artifacts from boot. */ private[sbt] def deleteCurrentArtifacts(state: State): Unit = { - import sbt.io.syntax.* val provider = state.configuration.provider val appId = provider.id // If we can obtain boot directory more accurately it'd be better. @@ -109,6 +114,13 @@ private[sbt] object MainLoop: } } + /** Removes the Zinc compiler bridge secondary cache (`zincDir/org.scala-sbt`). */ + private[sbt] def deleteZincBridgeSecondaryCache(log: Logger, zincDir: File): Unit = + val bridgeCache = zincDir / SbtArtifacts.Organization + if bridgeCache.exists() then + log.info(s"deleting $bridgeCache") + IO.delete(bridgeCache) + /** Runs the next sequence of commands with global logging in place. */ def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = Using.fileWriter(append = true)(logBacking.file) { writer => diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index bdde24c48..6e1105484 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -17,6 +17,7 @@ import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser.{ failure, seq, success } import sbt.internal.util.* import sbt.internal.client.NetworkClient +import sbt.internal.worker.ClientJobParams import sbt.std.Transform.DummyTaskMap import sbt.util.{ Logger, Show } import scala.annotation.tailrec @@ -87,16 +88,18 @@ object Aggregation { import complete.* val log = state.log val extracted = Project.extract(state) - val success = results match - case Result.Value(_) => true - case Result.Inc(_) => false + // omit success printing for client-side run + val (success, jobParams) = results match + case Result.Value(Seq(KeyValue(_, p: ClientJobParams))) => (true, true) + case Result.Value(_) => (true, false) + case Result.Inc(_) => (false, false) val isPaused = currentChannel(state) match case Some(channel) => channel.isPaused case None => false results.toEither.foreach { r => if show.taskValues then printSettings(r, show.print) else () } - if !isPaused && show.success && !state.get(suppressShow).getOrElse(false) then + if !isPaused && show.success && !state.get(suppressShow).getOrElse(false) && !jobParams then printSuccess(start, stop, extracted, success, cacheSummary, log) else () diff --git a/main/src/main/scala/sbt/internal/Clean.scala b/main/src/main/scala/sbt/internal/Clean.scala index 9d0094ce3..b6cda9a74 100644 --- a/main/src/main/scala/sbt/internal/Clean.scala +++ b/main/src/main/scala/sbt/internal/Clean.scala @@ -226,6 +226,13 @@ private[sbt] object Clean { case d: DiskActionCacheStore => d.clear() case _ => () IO.delete(outputDirectory.toFile()) + IO.delete( + s.configuration + .provider() + .scalaProvider() + .launcher() + .bootDirectory() + ) s Command.command(CleanFull, h)(expunge andThen clearCachesFun) } diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index 9761fc724..08c7e7b74 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -331,15 +331,19 @@ defaults Nil val CrossCommand = "+" + val CrossAlias = "cross" val CrossRestoreSessionCommand = "+-" val SwitchCommand = "++" + val SwitchAlias = "switch" - def crossHelp: Help = Help.more(CrossCommand, CrossDetailed) + def crossHelp: Help = + Help.more(CrossCommand, CrossDetailed) ++ Help.more(CrossAlias, CrossDetailed) def crossRestoreSessionHelp = Help.more(CrossRestoreSessionCommand, CrossRestoreSessionDetailed) - def switchHelp: Help = Help.more(SwitchCommand, SwitchDetailed) + def switchHelp: Help = + Help.more(SwitchCommand, SwitchDetailed) ++ Help.more(SwitchAlias, SwitchDetailed) def CrossDetailed = - s"""$CrossCommand [-v] + s"""$CrossCommand (or $CrossAlias) [-v] Runs for each Scala version specified for cross-building. For each string in `crossScalaVersions` in each project project, this command sets @@ -359,7 +363,7 @@ defaults """ def SwitchDetailed = - s"""$SwitchCommand [!] [-v] [] + s"""$SwitchCommand (or $SwitchAlias) [!] [-v] [] Changes the Scala version and runs a command. may be an actual Scala version such as 3.1.3, or a Semantic Version selector diff --git a/main/src/main/scala/sbt/internal/LintUnused.scala b/main/src/main/scala/sbt/internal/LintUnused.scala index 936caf31d..32ff02568 100644 --- a/main/src/main/scala/sbt/internal/LintUnused.scala +++ b/main/src/main/scala/sbt/internal/LintUnused.scala @@ -23,7 +23,7 @@ object LintUnused { keyName => includes(keyName) }, lintExcludeFilter := { - val excludedPrefixes = List("release", "sonatype", "watch", "whitesource") + val excludedPrefixes = List("release", "sonatype", "watch") val excludes = excludeLintKeys.value.map(_.scopedKey.key.label) keyName => excludes(keyName) || excludedPrefixes.exists(keyName.startsWith(_)) }, diff --git a/main/src/main/scala/sbt/internal/PomGenerator.scala b/main/src/main/scala/sbt/internal/PomGenerator.scala index 99816eb16..34137cb91 100644 --- a/main/src/main/scala/sbt/internal/PomGenerator.scala +++ b/main/src/main/scala/sbt/internal/PomGenerator.scala @@ -46,6 +46,8 @@ private[sbt] object PomGenerator: 4.0.0 {makeModuleID(crossMid)} {info.map(i => {i.nameFormal}).getOrElse(NodeSeq.Empty)} + {info.map(makeDescription).getOrElse(NodeSeq.Empty)} + {info.map(makeHomePage).getOrElse(NodeSeq.Empty)} {info.map(makeStartYear).getOrElse(NodeSeq.Empty)} {info.map(makeOrganization).getOrElse(NodeSeq.Empty)} {info.map(makeScmInfo).getOrElse(NodeSeq.Empty)} @@ -96,6 +98,16 @@ private[sbt] object PomGenerator: private val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType) + private def makeDescription(info: ModuleInfo): NodeSeq = + if info.description != null && info.description.nonEmpty then + {info.description} + else NodeSeq.Empty + + private def makeHomePage(info: ModuleInfo): NodeSeq = + info.homepage match + case Some(h) => {h} + case _ => NodeSeq.Empty + private def makeStartYear(info: ModuleInfo): NodeSeq = info.startYear match case Some(y) => {y} diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 29043daa2..fdd4115b6 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -166,6 +166,21 @@ object BuildServerProtocol { state.value.respondEvent(result) }.evaluated, bspBuildTargetDependencySources / aggregate := false, + bspBuildTargetDependencyModules := bspInputTask { (workspace, filter) => + val items = bspBuildTargetDependencyModulesItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val buildItems = workspace.builds + .map { case (targetId, loadedBuildUnit) => + val projRef = ProjectRef(loadedBuildUnit.unit.uri, loadedBuildUnit.root) + (projRef / updateSbtClassifiers).map(getDependencyModulesItem(targetId, _)) + } + .toSeq + .join + .value + val result = DependencyModulesResult((successfulItems ++ buildItems).toVector) + state.value.respondEvent(result) + }.evaluated, + bspBuildTargetDependencyModules / aggregate := false, bspBuildTargetCompile := bspInputTask { (workspace, filter) => val s = state.value workspace.warnIfBuildsNonEmpty(Method.Compile, s.log) @@ -306,6 +321,7 @@ object BuildServerProtocol { ResourcesItem(id, uris) }, bspBuildTargetDependencySourcesItem := dependencySourcesItemTask.value, + bspBuildTargetDependencyModulesItem := dependencyModulesItemTask.value, bspBuildTargetOutputPathsItem := { val id = bspTargetIdentifier.value OutputPathsItem(id, Vector(OutputPathItem(target.value.toURI, OutputPathItemKind.Directory))) @@ -374,6 +390,7 @@ object BuildServerProtocol { final val Resources = "buildTarget/resources" final val OutputPaths = "buildTarget/outputPaths" final val DependencySources = "buildTarget/dependencySources" + final val DependencyModules = "buildTarget/dependencyModules" final val Compile = "buildTarget/compile" final val Test = "buildTarget/test" final val Run = "buildTarget/run" @@ -439,6 +456,12 @@ object BuildServerProtocol { val command = Keys.bspBuildTargetDependencySources.key val _ = callback.appendExec(s"$command $targets", Some(r.id)) + case r if r.method == Method.DependencyModules => + val param = Converter.fromJson[DependencyModulesParams](json(r)).get + val targets = param.targets.map(_.uri).mkString(" ") + val command = Keys.bspBuildTargetDependencyModules.key + val _ = callback.appendExec(s"$command $targets", Some(r.id)) + case r if r.method == Method.Compile => val param = Converter.fromJson[CompileParams](json(r)).get val targets = param.targets.map(_.uri).mkString(" ") @@ -856,6 +879,13 @@ object BuildServerProtocol { ) } + private def dependencyModulesItemTask: Def.Initialize[Task[DependencyModulesItem]] = Def.task { + getDependencyModulesItem( + Keys.bspTargetIdentifier.value, + Keys.updateClassifiers.value + ) + } + private def getDependencySourceItem( targetId: BuildTargetIdentifier, updateReport: UpdateReport @@ -869,6 +899,25 @@ object BuildServerProtocol { DependencySourcesItem(targetId, sources.toVector.distinct) } + private def getDependencyModulesItem( + targetId: BuildTargetIdentifier, + updateReport: UpdateReport + ): DependencyModulesItem = { + val modules = for { + configuration <- updateReport.configurations.view + module <- configuration.modules.view + } yield { + val moduleId = module.module + DependencyModule( + name = s"${moduleId.organization}:${moduleId.name}", + version = moduleId.revision, + dataKind = None, + data = None, + ) + } + DependencyModulesItem(targetId, modules.toVector.distinct) + } + private def bspCompileState: Initialize[BuildServerProtocol.BspCompileState] = Def.setting { new BuildServerProtocol.BspCompileState() } diff --git a/main/src/test/scala/sbt/MainLoopZincCacheTest.scala b/main/src/test/scala/sbt/MainLoopZincCacheTest.scala new file mode 100644 index 000000000..c3f6d1247 --- /dev/null +++ b/main/src/test/scala/sbt/MainLoopZincCacheTest.scala @@ -0,0 +1,49 @@ +/* + * sbt + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +import java.io.File + +import sbt.internal.util.{ ConsoleOut, GlobalLogging, MainAppender } +import sbt.io.IO +import sbt.io.syntax.* +import sbt.librarymanagement.SbtArtifacts +import sbt.util.Logger + +object MainLoopZincCacheTest extends verify.BasicTestSuite: + + private def withTestLog[A](f: Logger => A): A = + val logFile = File.createTempFile("sbt-mlz", ".log") + try + val gl = GlobalLogging.initial( + MainAppender.globalDefault(ConsoleOut.globalProxy), + logFile, + ConsoleOut.globalProxy + ) + f(gl.full) + finally IO.delete(logFile) + + test("deleteZincBridgeSecondaryCache removes org.scala-sbt under zincDir"): + IO.withTemporaryDirectory: tmp => + val zincRoot = tmp / "zinc" + val bridge = zincRoot / SbtArtifacts.Organization + IO.write(bridge / "marker.txt", "cached") + withTestLog: log => + MainLoop.deleteZincBridgeSecondaryCache(log, zincRoot) + assert(!bridge.exists(), s"expected $bridge deleted") + + test("deleteZincBridgeSecondaryCache is a no-op when org.scala-sbt is absent"): + IO.withTemporaryDirectory: tmp => + val zincRoot = tmp / "zinc" + IO.createDirectory(zincRoot) + withTestLog: log => + MainLoop.deleteZincBridgeSecondaryCache(log, zincRoot) + assert(zincRoot.exists()) + +end MainLoopZincCacheTest diff --git a/project/Dependencies.scala b/project/Dependencies.scala index b3c3735dc..89fd6dde8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -36,8 +36,6 @@ object Dependencies { } lazy val sbtIoPath = getSbtModulePath("sbtio.path") - lazy val sbtUtilPath = getSbtModulePath("sbtutil.path") - lazy val sbtLmPath = getSbtModulePath("sbtlm.path") lazy val sbtZincPath = getSbtModulePath("sbtzinc.path") def addSbtModule( diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModule.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModule.scala new file mode 100644 index 000000000..ad3d58842 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModule.scala @@ -0,0 +1,57 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * @param name Module name + * @param version Module version + * @param dataKind Kind of data to expect in the `data` field. + * @param data Language-specific metadata about this module. + */ +final class DependencyModule private ( + val name: String, + val version: String, + val dataKind: Option[String], + val data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: DependencyModule => (this.name == x.name) && (this.version == x.version) && (this.dataKind == x.dataKind) && (this.data == x.data) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.DependencyModule".##) + name.##) + version.##) + dataKind.##) + data.##) + } + override def toString: String = { + "DependencyModule(" + name + ", " + version + ", " + dataKind + ", " + data + ")" + } + private def copy(name: String = name, version: String = version, dataKind: Option[String] = dataKind, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = data): DependencyModule = { + new DependencyModule(name, version, dataKind, data) + } + def withName(name: String): DependencyModule = { + copy(name = name) + } + def withVersion(version: String): DependencyModule = { + copy(version = version) + } + def withDataKind(dataKind: Option[String]): DependencyModule = { + copy(dataKind = dataKind) + } + def withDataKind(dataKind: String): DependencyModule = { + copy(dataKind = Option(dataKind)) + } + def withData(data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): DependencyModule = { + copy(data = data) + } + def withData(data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): DependencyModule = { + copy(data = Option(data)) + } +} +object DependencyModule { + + def apply(name: String, version: String, dataKind: Option[String], data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): DependencyModule = new DependencyModule(name, version, dataKind, data) + def apply(name: String, version: String, dataKind: String, data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): DependencyModule = new DependencyModule(name, version, Option(dataKind), Option(data)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesItem.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesItem.scala new file mode 100644 index 000000000..38a977a3a --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesItem.scala @@ -0,0 +1,40 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +final class DependencyModulesItem private ( + val target: Option[sbt.internal.bsp.BuildTargetIdentifier], + val modules: Vector[sbt.internal.bsp.DependencyModule]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: DependencyModulesItem => (this.target == x.target) && (this.modules == x.modules) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.DependencyModulesItem".##) + target.##) + modules.##) + } + override def toString: String = { + "DependencyModulesItem(" + target + ", " + modules + ")" + } + private def copy(target: Option[sbt.internal.bsp.BuildTargetIdentifier] = target, modules: Vector[sbt.internal.bsp.DependencyModule] = modules): DependencyModulesItem = { + new DependencyModulesItem(target, modules) + } + def withTarget(target: Option[sbt.internal.bsp.BuildTargetIdentifier]): DependencyModulesItem = { + copy(target = target) + } + def withTarget(target: sbt.internal.bsp.BuildTargetIdentifier): DependencyModulesItem = { + copy(target = Option(target)) + } + def withModules(modules: Vector[sbt.internal.bsp.DependencyModule]): DependencyModulesItem = { + copy(modules = modules) + } +} +object DependencyModulesItem { + + def apply(target: Option[sbt.internal.bsp.BuildTargetIdentifier], modules: Vector[sbt.internal.bsp.DependencyModule]): DependencyModulesItem = new DependencyModulesItem(target, modules) + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, modules: Vector[sbt.internal.bsp.DependencyModule]): DependencyModulesItem = new DependencyModulesItem(Option(target), modules) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesParams.scala new file mode 100644 index 000000000..3a0212faa --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesParams.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** Dependency Modules Request */ +final class DependencyModulesParams private ( + val targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: DependencyModulesParams => (this.targets == x.targets) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (17 + "sbt.internal.bsp.DependencyModulesParams".##) + targets.##) + } + override def toString: String = { + "DependencyModulesParams(" + targets + ")" + } + private def copy(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): DependencyModulesParams = { + new DependencyModulesParams(targets) + } + def withTargets(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): DependencyModulesParams = { + copy(targets = targets) + } +} +object DependencyModulesParams { + + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): DependencyModulesParams = new DependencyModulesParams(targets) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesResult.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesResult.scala new file mode 100644 index 000000000..d7a6c6063 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesResult.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** Dependency Modules Result */ +final class DependencyModulesResult private ( + val items: Vector[sbt.internal.bsp.DependencyModulesItem]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: DependencyModulesResult => (this.items == x.items) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (17 + "sbt.internal.bsp.DependencyModulesResult".##) + items.##) + } + override def toString: String = { + "DependencyModulesResult(" + items + ")" + } + private def copy(items: Vector[sbt.internal.bsp.DependencyModulesItem]): DependencyModulesResult = { + new DependencyModulesResult(items) + } + def withItems(items: Vector[sbt.internal.bsp.DependencyModulesItem]): DependencyModulesResult = { + copy(items = items) + } +} +object DependencyModulesResult { + + def apply(items: Vector[sbt.internal.bsp.DependencyModulesItem]): DependencyModulesResult = new DependencyModulesResult(items) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModuleFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModuleFormats.scala new file mode 100644 index 000000000..e8a1548c5 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModuleFormats.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait DependencyModuleFormats { self: sbt.internal.util.codec.JValueFormats & sjsonnew.BasicJsonProtocol => +given DependencyModuleFormat: JsonFormat[sbt.internal.bsp.DependencyModule] = new JsonFormat[sbt.internal.bsp.DependencyModule] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.DependencyModule = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val name = unbuilder.readField[String]("name") + val version = unbuilder.readField[String]("version") + val dataKind = unbuilder.readField[Option[String]]("dataKind") + val data = unbuilder.readField[Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]]("data") + unbuilder.endObject() + sbt.internal.bsp.DependencyModule(name, version, dataKind, data) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.DependencyModule, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("name", obj.name) + builder.addField("version", obj.version) + builder.addField("dataKind", obj.dataKind) + builder.addField("data", obj.data) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesItemFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesItemFormats.scala new file mode 100644 index 000000000..ae4a3fde6 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesItemFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait DependencyModulesItemFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats & sjsonnew.BasicJsonProtocol & sbt.internal.bsp.codec.DependencyModuleFormats & sbt.internal.util.codec.JValueFormats => +given DependencyModulesItemFormat: JsonFormat[sbt.internal.bsp.DependencyModulesItem] = new JsonFormat[sbt.internal.bsp.DependencyModulesItem] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.DependencyModulesItem = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val target = unbuilder.readField[Option[sbt.internal.bsp.BuildTargetIdentifier]]("target") + val modules = unbuilder.readField[Vector[sbt.internal.bsp.DependencyModule]]("modules") + unbuilder.endObject() + sbt.internal.bsp.DependencyModulesItem(target, modules) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.DependencyModulesItem, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("target", obj.target) + builder.addField("modules", obj.modules) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesParamsFormats.scala new file mode 100644 index 000000000..c617fd356 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesParamsFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait DependencyModulesParamsFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats & sjsonnew.BasicJsonProtocol => +given DependencyModulesParamsFormat: JsonFormat[sbt.internal.bsp.DependencyModulesParams] = new JsonFormat[sbt.internal.bsp.DependencyModulesParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.DependencyModulesParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val targets = unbuilder.readField[Vector[sbt.internal.bsp.BuildTargetIdentifier]]("targets") + unbuilder.endObject() + sbt.internal.bsp.DependencyModulesParams(targets) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.DependencyModulesParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("targets", obj.targets) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesResultFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesResultFormats.scala new file mode 100644 index 000000000..be4d510b4 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesResultFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait DependencyModulesResultFormats { self: sbt.internal.bsp.codec.DependencyModulesItemFormats & sbt.internal.bsp.codec.BuildTargetIdentifierFormats & sjsonnew.BasicJsonProtocol & sbt.internal.bsp.codec.DependencyModuleFormats & sbt.internal.util.codec.JValueFormats => +given DependencyModulesResultFormat: JsonFormat[sbt.internal.bsp.DependencyModulesResult] = new JsonFormat[sbt.internal.bsp.DependencyModulesResult] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.DependencyModulesResult = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val items = unbuilder.readField[Vector[sbt.internal.bsp.DependencyModulesItem]]("items") + unbuilder.endObject() + sbt.internal.bsp.DependencyModulesResult(items) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.DependencyModulesResult, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("items", obj.items) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala index 5b601a070..0b0b567c8 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala @@ -43,6 +43,10 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.DependencySourcesParamsFormats with sbt.internal.bsp.codec.DependencySourcesItemFormats with sbt.internal.bsp.codec.DependencySourcesResultFormats + with sbt.internal.bsp.codec.DependencyModulesParamsFormats + with sbt.internal.bsp.codec.DependencyModuleFormats + with sbt.internal.bsp.codec.DependencyModulesItemFormats + with sbt.internal.bsp.codec.DependencyModulesResultFormats with sbt.internal.bsp.codec.TaskStartParamsFormats with sbt.internal.bsp.codec.TaskProgressParamsFormats with sbt.internal.bsp.codec.TaskFinishParamsFormats diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index e9e1c2fab..f6333dcaa 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -409,6 +409,35 @@ type DependencySourcesItem { sources: [java.net.URI] } +## Dependency Modules Request +type DependencyModulesParams { + targets: [sbt.internal.bsp.BuildTargetIdentifier] +} + +## Dependency Modules Result +type DependencyModulesResult { + items: [sbt.internal.bsp.DependencyModulesItem] +} + +type DependencyModulesItem { + target: sbt.internal.bsp.BuildTargetIdentifier + modules: [sbt.internal.bsp.DependencyModule] +} + +type DependencyModule { + ## Module name + name: String! + + ## Module version + version: String! + + ## Kind of data to expect in the `data` field. + dataKind: String + + ## Language-specific metadata about this module. + data: sjsonnew.shaded.scalajson.ast.unsafe.JValue +} + ## Task Notifications type TaskStartParams { diff --git a/sbt b/sbt index 5ab69d315..3631d4ead 100755 --- a/sbt +++ b/sbt @@ -142,7 +142,7 @@ download_url () { local jar="$2" mkdir -p $(dirname "$jar") && { if command -v curl > /dev/null; then - curl --silent -L "$url" --output "$jar" + curl --fail --silent -L "$url" --output "$jar" elif command -v wget > /dev/null; then wget --quiet -O "$jar" "$url" else @@ -362,7 +362,7 @@ addSbtScriptProperty () { } addJdkWorkaround () { - local is_25="$(expr $java_version "=" 25)" + local is_25="$(expr $java_version ">=" 25)" if [[ "$hide_jdk_warnings" == "0" ]]; then : else diff --git a/sbt-app/src/main/resources/log4j2.xml b/sbt-app/src/main/resources/log4j2.xml deleted file mode 100644 index 5fa86616d..000000000 --- a/sbt-app/src/main/resources/log4j2.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/sbt-app/src/sbt-test/actions/completions/build.sbt b/sbt-app/src/sbt-test/actions/completions/build.sbt index 072ab22e7..76be7c404 100644 --- a/sbt-app/src/sbt-test/actions/completions/build.sbt +++ b/sbt-app/src/sbt-test/actions/completions/build.sbt @@ -15,7 +15,7 @@ def completionsParser(state: State) = { val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => (nq +: s).mkString } val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) - applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state)) + applyEffect(token((quotedOrUnquotedSingleArgument ?? "").examples("", " ")))(runCompletions(state)) } def runCompletions(state: State)(input: String): State = { val xs = Parser.completions(state.combinedParser, input, 9).get map { diff --git a/sbt-app/src/sbt-test/actions/configuration-delegation/build.sbt b/sbt-app/src/sbt-test/actions/configuration-delegation/build.sbt index b93d6ca15..cdba97d87 100644 --- a/sbt-app/src/sbt-test/actions/configuration-delegation/build.sbt +++ b/sbt-app/src/sbt-test/actions/configuration-delegation/build.sbt @@ -5,7 +5,7 @@ lazy val bar = taskKey[Unit]("Runs the bar task") def makeFoo(config: Configuration): Setting[?] = config / foo := IO.write(file(s"${config.name}-foo"), "foo") -lazy val PerformanceTest = (config("pt") extend Test) +lazy val PerformanceTest = config("pt").extend(Test) lazy val root = (project in file(".")) .configs(PerformanceTest) diff --git a/sbt-app/src/sbt-test/actions/cross-alias/build.sbt b/sbt-app/src/sbt-test/actions/cross-alias/build.sbt new file mode 100644 index 000000000..c7a4657c7 --- /dev/null +++ b/sbt-app/src/sbt-test/actions/cross-alias/build.sbt @@ -0,0 +1,9 @@ +lazy val scala212 = "2.12.21" +lazy val scala213 = "2.13.12" + +ThisBuild / scalaVersion := scala212 + +lazy val root = (project in file(".")) + .settings( + crossScalaVersions := Seq(scala212, scala213), + ) diff --git a/sbt-app/src/sbt-test/actions/cross-alias/test b/sbt-app/src/sbt-test/actions/cross-alias/test new file mode 100644 index 000000000..2ecf14b37 --- /dev/null +++ b/sbt-app/src/sbt-test/actions/cross-alias/test @@ -0,0 +1,11 @@ +# Test that "cross" works as an alias for "+" +> cross clean + +# Test that "cross" works with verbose flag +> cross -v compile + +# Test that "switch" works as an alias for "++" +> switch 2.13.12! + +# Test that "switch" works with a command +> switch 2.12.21! compile diff --git a/sbt-app/src/sbt-test/actions/depends-on/build.sbt b/sbt-app/src/sbt-test/actions/depends-on/build.sbt index b9039dda9..1b5744e9b 100644 --- a/sbt-app/src/sbt-test/actions/depends-on/build.sbt +++ b/sbt-app/src/sbt-test/actions/depends-on/build.sbt @@ -2,7 +2,7 @@ import sbt.TupleSyntax.* lazy val root = (project in file(".")).settings( - a := (baseDirectory mapN (b => if ((b / "succeed").exists) () else sys.error("fail"))).value, + a := baseDirectory.mapN(b => if ((b / "succeed").exists) () else sys.error("fail")).value, // deprecated? // b := (a.task(at => nop dependsOn(at))).value, c := (a mapN { _ => () }).value, diff --git a/sbt-app/src/sbt-test/actions/external-doc/build.sbt b/sbt-app/src/sbt-test/actions/external-doc/build.sbt index 2b4bfd8f5..ac76a0a4d 100644 --- a/sbt-app/src/sbt-test/actions/external-doc/build.sbt +++ b/sbt-app/src/sbt-test/actions/external-doc/build.sbt @@ -17,7 +17,7 @@ val aResolver = Def.setting { val bResolver = Def.setting { val dir = (ThisBuild / baseDirectory).value / "b-repo" - Resolver.file("b-resolver", dir)(Resolver.defaultIvyPatterns) + Resolver.file("b-resolver", dir)(using Resolver.defaultIvyPatterns) } val apiBaseSetting = apiURL := Some(apiBase(name.value)) diff --git a/sbt-app/src/sbt-test/actions/state/build.sbt b/sbt-app/src/sbt-test/actions/state/build.sbt index 7433c3c01..3cdf14189 100644 --- a/sbt-app/src/sbt-test/actions/state/build.sbt +++ b/sbt-app/src/sbt-test/actions/state/build.sbt @@ -14,11 +14,11 @@ val check = inputKey[Unit]("") val sample = AttributeKey[Int]("demo-key") val dummyKey = taskKey[Unit]("") -def updateDemoInit = state map { s => (s get sample getOrElse 9) + 1 } +def updateDemoInit = state map { s => s.get(sample).getOrElse(9) + 1 } lazy val root = (project in file(".")). settings( - updateDemo := (updateDemoInit updateState demoState).value, + updateDemo := updateDemoInit.updateState(demoState).value, check := checkInit.evaluated, inMemorySetting, persistedSetting, @@ -27,19 +27,19 @@ lazy val root = (project in file(".")). dummyKey := (), ) -def demoState(s: State, i: Int): State = s put (sample, i + 1) +def demoState(s: State, i: Int): State = s.put(sample, i + 1) def checkInit: Initialize[InputTask[Unit]] = Def inputTask { val key = (token(Space ~> IntBasic) ~ token(Space ~> IntBasic).?).parsed val (curExpected, prevExpected) = key val value = updateDemo.value - val prev = state.value get sample + val prev = state.value.get(sample) assert(value == curExpected, s"Expected current value to be $curExpected, got $value") assert(prev == prevExpected, s"Expected previous value to be $prevExpected, got $prev") } -def inMemorySetting = keep := (getPrevious(keep) map { case None => 3; case Some(x) => x + 1} keepAs(keep)).value -def persistedSetting = persist := (loadPrevious(persist) map { case None => 17; case Some(x) => x + 1} storeAs(persist)).value +def inMemorySetting = keep := getPrevious(keep) .map { case None => 3; case Some(x) => x + 1}.keepAs(keep).value +def persistedSetting = persist := loadPrevious(persist).map { case None => 17; case Some(x) => x + 1}.storeAs(persist).value def inMemoryCheck = checkKeep := (inputCheck( (ctx, s) => Space ~> str( getFromContext( keep, ctx, s)) )).evaluated def persistedCheck = checkPersist := (inputCheck( (ctx, s) => Space ~> str(loadFromContext(persist, ctx, s)) )).evaluated diff --git a/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt b/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt new file mode 100644 index 000000000..e2b32014e --- /dev/null +++ b/sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt @@ -0,0 +1,11 @@ +lazy val helloWithoutStreams = taskKey[Unit]("") +lazy val helloWithStreams = taskKey[Unit]("") + +helloWithoutStreams := { + throw new RuntimeException("boom without streams!") +} + +helloWithStreams := { + val log = streams.value.log + throw new RuntimeException("boom with streams!") +} diff --git a/sbt-app/src/sbt-test/actions/streams-trace-level/test b/sbt-app/src/sbt-test/actions/streams-trace-level/test new file mode 100644 index 000000000..92bc6373f --- /dev/null +++ b/sbt-app/src/sbt-test/actions/streams-trace-level/test @@ -0,0 +1,2 @@ +-> helloWithoutStreams +-> helloWithStreams diff --git a/sbt-app/src/sbt-test/dependency-management/artifact/build.sbt b/sbt-app/src/sbt-test/dependency-management/artifact/build.sbt index 24c87e9e4..1a440e52f 100644 --- a/sbt-app/src/sbt-test/dependency-management/artifact/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/artifact/build.sbt @@ -63,7 +63,7 @@ def org = "test" def mainArtifact = Artifact(artifactID, tpe, ext, classifier) // define the IDs to use for publishing and retrieving -def publishedID = org % artifactID % vers artifacts(mainArtifact) +def publishedID = (org % artifactID % vers).artifacts(mainArtifact) def retrieveID = org % "test-retrieve" % "2.0" // check that the test class is on the compile classpath, either because it was compiled or because it was properly retrieved @@ -80,5 +80,5 @@ def checkTask(classpath: TaskKey[Classpath]) = // use the user local resolver to fetch the SNAPSHOT version of the compiler-bridge def userLocalFileResolver(appConfig: AppConfiguration): Resolver = { val ivyHome = appConfig.provider.scalaProvider.launcher.ivyHome - Resolver.file("User Local", ivyHome / "local")(Resolver.defaultIvyPatterns) + Resolver.file("User Local", ivyHome / "local")(using Resolver.defaultIvyPatterns) } diff --git a/sbt-app/src/sbt-test/dependency-management/cache-classifiers/multi.sbt b/sbt-app/src/sbt-test/dependency-management/cache-classifiers/multi.sbt index 9dc58ff79..9db6cce45 100644 --- a/sbt-app/src/sbt-test/dependency-management/cache-classifiers/multi.sbt +++ b/sbt-app/src/sbt-test/dependency-management/cache-classifiers/multi.sbt @@ -33,5 +33,5 @@ val a = project // use the user local resolver to fetch the SNAPSHOT version of the compiler-bridge def userLocalFileResolver(appConfig: AppConfiguration): Resolver = { val ivyHome = appConfig.provider.scalaProvider.launcher.ivyHome - Resolver.file("User Local", ivyHome / "local")(Resolver.defaultIvyPatterns) + Resolver.file("User Local", ivyHome / "local")(using Resolver.defaultIvyPatterns) } diff --git a/sbt-app/src/sbt-test/dependency-management/classifier-report/build.sbt b/sbt-app/src/sbt-test/dependency-management/classifier-report/build.sbt index 56b15ea03..3e03fc9de 100644 --- a/sbt-app/src/sbt-test/dependency-management/classifier-report/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/classifier-report/build.sbt @@ -2,7 +2,7 @@ lazy val check = taskKey[Unit]("check classifier in update report") lazy val root = (project in file(".")).settings( scalaVersion := "2.13.16", - libraryDependencies += "io.netty" % "netty-transport-native-epoll" % "4.1.118.Final" classifier "linux-x86_64", + libraryDependencies += ("io.netty" % "netty-transport-native-epoll" % "4.1.118.Final").classifier("linux-x86_64"), check := { val report = update.value val modules = report.configurations.flatMap(_.modules) diff --git a/sbt-app/src/sbt-test/dependency-management/classifier/build.sbt b/sbt-app/src/sbt-test/dependency-management/classifier/build.sbt index e784cdba8..1e0363c2b 100644 --- a/sbt-app/src/sbt-test/dependency-management/classifier/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/classifier/build.sbt @@ -1,4 +1,4 @@ ivyPaths := IvyPaths(baseDirectory.value.toString, Some(((ThisBuild / baseDirectory).value / "ivy" / "cache").toString)) ThisBuild / csrCacheDirectory := (ThisBuild / baseDirectory).value / "coursier-cache" -libraryDependencies += "org.testng" % "testng" % "5.7" classifier "jdk15" +libraryDependencies += ("org.testng" % "testng" % "5.7").classifier("jdk15") diff --git a/sbt-app/src/sbt-test/dependency-management/classifier2/build.sbt b/sbt-app/src/sbt-test/dependency-management/classifier2/build.sbt index 133599913..35dd4f7fe 100644 --- a/sbt-app/src/sbt-test/dependency-management/classifier2/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/classifier2/build.sbt @@ -1,2 +1,2 @@ ThisBuild / scalaVersion := "2.11.12" -libraryDependencies += "org.jclouds.api" % "nova" % "1.5.9" classifier "tests" +libraryDependencies += ("org.jclouds.api" % "nova" % "1.5.9").classifier("tests") diff --git a/sbt-app/src/sbt-test/dependency-management/cross-ivy-maven/build.sbt b/sbt-app/src/sbt-test/dependency-management/cross-ivy-maven/build.sbt index 0e0d5e0f9..1e01d9e9d 100644 --- a/sbt-app/src/sbt-test/dependency-management/cross-ivy-maven/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/cross-ivy-maven/build.sbt @@ -10,8 +10,8 @@ libraryDependencies += "bad" % "mvn" % "1.0" TaskKey[Unit]("check") := { val cp = (Compile / fullClasspath).value def isTestJar(n: String): Boolean = - (n contains "scalacheck") || - (n contains "specs2") + n.contains("scalacheck") || + n.contains("specs2") val testLibs = cp map (_.data.name) filter isTestJar assert(testLibs.isEmpty, s"Compile Classpath has test libs:\n * ${testLibs.mkString("\n * ")}") } \ No newline at end of file diff --git a/sbt-app/src/sbt-test/dependency-management/default-artifact/build.sbt b/sbt-app/src/sbt-test/dependency-management/default-artifact/build.sbt index 6afa36b3a..2b04e4075 100644 --- a/sbt-app/src/sbt-test/dependency-management/default-artifact/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/default-artifact/build.sbt @@ -1,4 +1,4 @@ -resolvers += Resolver.file("buggy", file("repo"))( +resolvers += Resolver.file("buggy", file("repo"))(using Patterns( ivyPatterns = Vector("[organization]/[module]/[revision]/ivy.xml"), artifactPatterns = Vector("[organization]/[module]/[revision]/[artifact].[ext]"), @@ -8,5 +8,5 @@ resolvers += Resolver.file("buggy", file("repo"))( ) ) -libraryDependencies += "a" % "b" % "1.0.0" % "compile->runtime" artifacts(Artifact("b1", "jar", "jar")) -libraryDependencies += "a" % "b" % "1.0.0" % "test->runtime" artifacts(Artifact("b1", "jar", "jar")) +libraryDependencies += ("a" % "b" % "1.0.0" % "compile->runtime").artifacts(Artifact("b1", "jar", "jar")) +libraryDependencies += ("a" % "b" % "1.0.0" % "test->runtime").artifacts(Artifact("b1", "jar", "jar")) diff --git a/sbt-app/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt b/sbt-app/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt index 882434a86..3994ef57e 100644 --- a/sbt-app/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/evicted-semver-spec/build.sbt @@ -61,5 +61,5 @@ val use2 = project // use the user local resolver to fetch the SNAPSHOT version of the compiler-bridge def userLocalFileResolver(appConfig: AppConfiguration): Resolver = { val ivyHome = appConfig.provider.scalaProvider.launcher.ivyHome - Resolver.file("User Local", ivyHome / "local")(Resolver.defaultIvyPatterns) + Resolver.file("User Local", ivyHome / "local")(using Resolver.defaultIvyPatterns) } diff --git a/sbt-app/src/sbt-test/dependency-management/exclude-bundle/changes/build.sbt b/sbt-app/src/sbt-test/dependency-management/exclude-bundle/changes/build.sbt index d6348fa72..7503f55ff 100644 --- a/sbt-app/src/sbt-test/dependency-management/exclude-bundle/changes/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/exclude-bundle/changes/build.sbt @@ -1,3 +1,3 @@ -libraryDependencies += "org.vaadin" % "dontpush-addon-ozonelayer" % "0.4.6" exclude("org.atmosphere", "atmosphere-compat-jetty") +libraryDependencies += ("org.vaadin" % "dontpush-addon-ozonelayer" % "0.4.6").exclude("org.atmosphere", "atmosphere-compat-jetty") resolvers += "asdf" at "https://maven.vaadin.com/vaadin-addons" diff --git a/sbt-app/src/sbt-test/dependency-management/exclude-scala/build.sbt b/sbt-app/src/sbt-test/dependency-management/exclude-scala/build.sbt index 64b58339c..13de01935 100644 --- a/sbt-app/src/sbt-test/dependency-management/exclude-scala/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/exclude-scala/build.sbt @@ -15,7 +15,7 @@ def check(className: String): Def.Initialize[Task[Unit]] = import sbt.TupleSyntax.* (Compile / fullClasspath, fileConverter.toTaskable) mapN { (cp, c) => given FileConverter = c - val existing = cp.files.filter(_.toFile.getName contains "scala-library") + val existing = cp.files.filter(_.toFile.getName.contains("scala-library")) println("Full classpath: " + cp.mkString("\n\t", "\n\t", "")) println("scala-library.jar: " + existing.mkString("\n\t", "\n\t", "")) val loader = ClasspathUtilities.toLoader(existing.map(_.toFile())) diff --git a/sbt-app/src/sbt-test/dependency-management/force/build.sbt b/sbt-app/src/sbt-test/dependency-management/force/build.sbt index 7af3315f7..600aa3989 100644 --- a/sbt-app/src/sbt-test/dependency-management/force/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/force/build.sbt @@ -19,7 +19,7 @@ def libraryDeps(base: File) = { def check(ver: String) = (Compile / dependencyClasspath) map { jars => val log4j = jars map (_.data) collect { - case f if f.name contains "log4j-" => f.name + case f if f.name.contains("log4j-") => f.name } if (log4j.size != 1 || !log4j.head.contains(ver)) sys.error("Did not download the correct jar.") diff --git a/sbt-app/src/sbt-test/dependency-management/ivyless-publish-http/build.sbt b/sbt-app/src/sbt-test/dependency-management/ivyless-publish-http/build.sbt index 4567ec7c5..d5b056e6d 100644 --- a/sbt-app/src/sbt-test/dependency-management/ivyless-publish-http/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/ivyless-publish-http/build.sbt @@ -13,7 +13,7 @@ val publishPort = 3030 // Publish to HTTP server (localhost) - ivyless publish uses PUT // Resolver.url expects java.net.URL; in build.sbt "url" is sbt.URI, so use java.net.URL explicitly publishTo := Some( - Resolver.url("test-repo", new java.net.URI(s"http://localhost:$publishPort/").toURL)(Resolver.ivyStylePatterns) + Resolver.url("test-repo", new java.net.URI(s"http://localhost:$publishPort/").toURL)(using Resolver.ivyStylePatterns) .withAllowInsecureProtocol(true) ) diff --git a/sbt-app/src/sbt-test/dependency-management/ivyless-publish/build.sbt b/sbt-app/src/sbt-test/dependency-management/ivyless-publish/build.sbt index 483b47282..23d6e97bf 100644 --- a/sbt-app/src/sbt-test/dependency-management/ivyless-publish/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/ivyless-publish/build.sbt @@ -9,7 +9,7 @@ scalaVersion := "3.8.3" val publishRepoBase = settingKey[File]("Base directory for publish repo") publishRepoBase := baseDirectory.value / "repo" -publishTo := Some(Resolver.file("test-repo", publishRepoBase.value)(Resolver.ivyStylePatterns)) +publishTo := Some(Resolver.file("test-repo", publishRepoBase.value)(using Resolver.ivyStylePatterns)) useIvy := false diff --git a/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/def.sbt b/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/def.sbt index 2815ceedc..c2f294855 100644 --- a/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/def.sbt +++ b/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/def.sbt @@ -12,7 +12,7 @@ version := ( if(stable.value) "1.0" else "1.1-SNAPSHOT" ) publishTo := { val base = baseDirectory.value / ( if(stable.value) "stable" else "snapshot" ) - Some( Resolver.file("local-" + base, base)(Resolver.ivyStylePatterns) ) + Some( Resolver.file("local-" + base, base)(using Resolver.ivyStylePatterns) ) } publishMavenStyle := false diff --git a/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/use-plugins.sbt b/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/use-plugins.sbt index 777b3f0ae..c5183b68e 100644 --- a/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/use-plugins.sbt +++ b/sbt-app/src/sbt-test/dependency-management/latest-local-plugin/changes/use-plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("org.example" % "def" % "latest.integration") resolvers ++= { - def r(tpe: String) = Resolver.file(s"local-$tpe", baseDirectory.value / ".." / tpe)(Resolver.ivyStylePatterns) + def r(tpe: String) = Resolver.file(s"local-$tpe", baseDirectory.value / ".." / tpe)(using Resolver.ivyStylePatterns) r("snapshot") :: r("stable") :: Nil } diff --git a/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt b/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt index ff88b76d0..ecf866e0e 100644 --- a/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt @@ -1,11 +1,13 @@ import scala.xml._ -lazy val root = (project in file(".")) settings ( +lazy val root = (project in file(".")).settings( readPom := Def.uncached { val vf = makePom.value val converter = fileConverter.value XML.loadFile(converter.toPath(vf).toFile) }, + description := "pom.xml test description", + homepage := Some(url("https://example.com/pom_test_url")), TaskKey[Unit]("checkPom") := checkPom.value, TaskKey[Unit]("checkExtra") := checkExtra.value, TaskKey[Unit]("checkVersionPlusMapping") := checkVersionPlusMapping.value, @@ -65,13 +67,17 @@ lazy val checkReleaseNotesURL = readPom.map: pomXml => lazy val checkPom = Def.task { val pomXML = readPom.value checkProject(pomXML) + val urlFromPom = (pomXML \ "url").text + assert(urlFromPom == "https://example.com/pom_test_url", s"Expected homepage url, got: $urlFromPom") + val descriptionFromPom = (pomXML \ "description").text + assert(descriptionFromPom == "pom.xml test description", s"Expected description, got: $descriptionFromPom") val ivyRepositories = fullResolvers.value withRepositories(pomXML) { repositoriesElement => val repositories = repositoriesElement \ "repository" val writtenRepositories = repositories.map(read).distinct val mavenStyleRepositories = (ivyRepositories.collect { case x: MavenRepository - if (x.name != "public") && (x.name != "jcenter") && !(x.root startsWith "file:") => + if (x.name != "public") && (x.name != "jcenter") && !(x.root.startsWith("file:")) => normalize(x) }).distinct diff --git a/sbt-app/src/sbt-test/dependency-management/module-name/build.sbt b/sbt-app/src/sbt-test/dependency-management/module-name/build.sbt index a369e73ab..434253757 100644 --- a/sbt-app/src/sbt-test/dependency-management/module-name/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/module-name/build.sbt @@ -10,7 +10,7 @@ TaskKey[Unit]("checkName") := Def.uncached { val path = converter.toPath(vf).toAbsolutePath.toString val module = moduleName.value val n = name.value - assert(path contains module, s"Path $path did not contain module name $module") + assert(path.contains(module), s"Path $path did not contain module name $module") assert(!path.contains(n), s"Path $path contained $n") () } diff --git a/sbt-app/src/sbt-test/dependency-management/multiple-classifiers/build.sbt b/sbt-app/src/sbt-test/dependency-management/multiple-classifiers/build.sbt index 9d9b2b8d3..4f80db351 100644 --- a/sbt-app/src/sbt-test/dependency-management/multiple-classifiers/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/multiple-classifiers/build.sbt @@ -1,5 +1,5 @@ libraryDependencies ++= Seq("natives-windows", "natives-linux", "natives-osx") map ( c => - "org.lwjgl.lwjgl" % "lwjgl-platform" % "2.8.2" classifier c + ("org.lwjgl.lwjgl" % "lwjgl-platform" % "2.8.2").classifier(c) ) autoScalaLibrary := false diff --git a/sbt-app/src/sbt-test/dependency-management/mvn-local/build.sbt b/sbt-app/src/sbt-test/dependency-management/mvn-local/build.sbt index c8387f69e..f680d41e6 100644 --- a/sbt-app/src/sbt-test/dependency-management/mvn-local/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/mvn-local/build.sbt @@ -7,7 +7,7 @@ def commonSettings: Seq[Def.Setting[?]] = scalaVersion := "2.10.4", ThisBuild / organization := "org.example", ThisBuild / version := "1.0-SNAPSHOT", - resolvers += Resolver.file("old-local", file(sys.props("user.home") + "/.ivy2/local"))(Resolver.ivyStylePatterns) + resolvers += Resolver.file("old-local", file(sys.props("user.home") + "/.ivy2/local"))(using Resolver.ivyStylePatterns) ) lazy val main = project. diff --git a/sbt-app/src/sbt-test/dependency-management/override2/build.sbt b/sbt-app/src/sbt-test/dependency-management/override2/build.sbt index d4f465bf8..1ddd10979 100644 --- a/sbt-app/src/sbt-test/dependency-management/override2/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/override2/build.sbt @@ -7,7 +7,7 @@ lazy val root = (project in file(".")) dependencyOverrides += "org.webjars.npm" % "is-number" % "5.0.0", check := { val cp = (Compile / externalDependencyClasspath).value.map {_.data.name}.sorted - if (!(cp contains "is-number-5.0.0.jar")) { + if (!cp.contains("is-number-5.0.0.jar")) { sys.error("is-number-5.0.0 not found when it should be included: " + cp.toString) } } diff --git a/sbt-app/src/sbt-test/dependency-management/pom-advanced/build.sbt b/sbt-app/src/sbt-test/dependency-management/pom-advanced/build.sbt index 15ed8b529..47c78a1bc 100644 --- a/sbt-app/src/sbt-test/dependency-management/pom-advanced/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/pom-advanced/build.sbt @@ -29,7 +29,7 @@ def pomIncludeRepository(base: File, prev: MavenRepository => Boolean): MavenRep } def addSlash(s: String): String = s match { - case s if s endsWith "/" => s + case s if s.endsWith("/") => s case _ => s + "/" } diff --git a/sbt-app/src/sbt-test/dependency-management/pom-artifact-type/build.sbt b/sbt-app/src/sbt-test/dependency-management/pom-artifact-type/build.sbt index 07570f4c5..bb67c2691 100644 --- a/sbt-app/src/sbt-test/dependency-management/pom-artifact-type/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/pom-artifact-type/build.sbt @@ -4,7 +4,7 @@ lazy val root = (project in file(".")) .settings( scalaVersion := "2.13.16", autoScalaLibrary := false, - libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "11.0.15" artifacts (Artifact("jetty-webapp", "war", "war")), + libraryDependencies += ("org.eclipse.jetty" % "jetty-webapp" % "11.0.15").artifacts(Artifact("jetty-webapp", "war", "war")), libraryDependencies += "com.typesafe" % "config" % "1.4.3", // classified artifact with non-default type: both and must appear libraryDependencies += ("com.example" % "classified-war" % "1.0") diff --git a/sbt-app/src/sbt-test/dependency-management/pom-packaging/build.sbt b/sbt-app/src/sbt-test/dependency-management/pom-packaging/build.sbt index 18d2b74d3..16629deea 100644 --- a/sbt-app/src/sbt-test/dependency-management/pom-packaging/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/pom-packaging/build.sbt @@ -1,8 +1,8 @@ val root = project in file(".") val subJar = project in file("subJar") -def warArtifact = (Compile / packageBin / artifact) ~= (_ withType "war" withExtension "war") -val subWar = project in file("subWar") settings warArtifact -val subParent = project in file("subParent") settings ((Compile / publishArtifact) := false) +def warArtifact = (Compile / packageBin / artifact) ~= (_.withType("war").withExtension("war")) +val subWar = project.in(file("subWar")).settings(warArtifact) +val subParent = project.in(file("subParent")).settings((Compile / publishArtifact) := false) val checkPom = taskKey[Unit]("") (ThisBuild / checkPom) := { diff --git a/sbt-app/src/sbt-test/dependency-management/pom-scope/build.sbt b/sbt-app/src/sbt-test/dependency-management/pom-scope/build.sbt index 310258a92..ba74971a4 100644 --- a/sbt-app/src/sbt-test/dependency-management/pom-scope/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/pom-scope/build.sbt @@ -15,8 +15,8 @@ lazy val root = (project in file(".")). "d" % "d" % "1.0" % "test", "e" % "e" % "1.0" % Custom, "f" % "f" % "1.0" % "custom,optional,runtime", - "g" % "g" % "1.0" % "custom,runtime" classifier "foo", - "h" % "h" % "1.0" % "custom,optional,runtime" classifier "foo" + ("g" % "g" % "1.0" % "custom,runtime").classifier("foo"), + ("h" % "h" % "1.0" % "custom,optional,runtime").classifier("foo") ) ) diff --git a/sbt-app/src/sbt-test/dependency-management/provided/build.sbt b/sbt-app/src/sbt-test/dependency-management/provided/build.sbt index e8d926fab..aaa6d6f2f 100644 --- a/sbt-app/src/sbt-test/dependency-management/provided/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/provided/build.sbt @@ -27,7 +27,7 @@ lazy val root = (project in file(".")). ) def checkServletAPI(paths: Seq[File], shouldBeIncluded: Boolean, label: String) = { - val servletAPI = paths.find(_.getName contains "servlet-api") + val servletAPI = paths.find(_.getName.contains("servlet-api")) if (shouldBeIncluded) { if (servletAPI.isEmpty) sys.error(s"Servlet API should have been included in $label.") } else diff --git a/sbt-app/src/sbt-test/dependency-management/publish-local/build.sbt b/sbt-app/src/sbt-test/dependency-management/publish-local/build.sbt index 8076f5a1b..f8bb03bf8 100644 --- a/sbt-app/src/sbt-test/dependency-management/publish-local/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/publish-local/build.sbt @@ -7,7 +7,7 @@ lazy val root = (project in file(".")). organization := "A", version := "1.0", ivyPaths := baseDirectory( dir => IvyPaths(dir, Some(dir / "ivy" / "cache")) ).value, - externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(Resolver.ivyStylePatterns) :: Nil }).value + externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(using Resolver.ivyStylePatterns) :: Nil }).value )), mavenStyle, interProject, diff --git a/sbt-app/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt b/sbt-app/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt index c7ee8cd16..57bf83398 100644 --- a/sbt-app/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt +++ b/sbt-app/src/sbt-test/dependency-management/publish-local/changes/RetrieveTest.sbt @@ -5,7 +5,7 @@ lazy val root = (project in file(".")). organization := "A", version := "1.0", ivyPaths := baseDirectory( dir => IvyPaths(dir, Some(dir / "ivy" / "cache")) ).value, - externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(Resolver.ivyStylePatterns) :: Nil }).value + externalResolvers := (baseDirectory map { base => Resolver.file("local", base / "ivy" / "local" asFile)(using Resolver.ivyStylePatterns) :: Nil }).value )), mavenStyle, name := "Retrieve Test", diff --git a/sbt-app/src/sbt-test/dependency-management/scala-version-check-exempt/build.sbt b/sbt-app/src/sbt-test/dependency-management/scala-version-check-exempt/build.sbt index 26eff543b..7df384a10 100644 --- a/sbt-app/src/sbt-test/dependency-management/scala-version-check-exempt/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/scala-version-check-exempt/build.sbt @@ -17,11 +17,11 @@ libraryDependencies += "org.scala-lang" % "scala-actors" % "2.11.12" lazy val check = taskKey[Unit]("Runs the check") check := { - val lastLog = BuiltinCommands lastLogFile state.value + val lastLog = BuiltinCommands.lastLogFile(state.value) val last = IO read lastLog.get - def containsWarn1 = last contains "Binary version (1.1.0) for dependency org.scala-lang#scala-actors-migration_2.11;1.1.0" - def containsWarn2 = last contains "Binary version (0.9.1) for dependency org.scala-lang#scala-pickling_2.11;0.9.1" - def containsWarn3 = last contains "differs from Scala binary version in project (2.11)." + def containsWarn1 = last.contains("Binary version (1.1.0) for dependency org.scala-lang#scala-actors-migration_2.11;1.1.0") + def containsWarn2 = last.contains("Binary version (0.9.1) for dependency org.scala-lang#scala-pickling_2.11;0.9.1") + def containsWarn3 = last.contains("differs from Scala binary version in project (2.11).") if (containsWarn1 && containsWarn3) sys error "scala-actors-migration isn't exempted from the Scala binary version check" if (containsWarn2 && containsWarn3) sys error "scala-pickling isn't exempted from the Scala binary version check" } diff --git a/sbt-app/src/sbt-test/dependency-management/snapshot-local/build.sbt b/sbt-app/src/sbt-test/dependency-management/snapshot-local/build.sbt index ddd17b6e8..b4915c13f 100644 --- a/sbt-app/src/sbt-test/dependency-management/snapshot-local/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/snapshot-local/build.sbt @@ -7,7 +7,7 @@ def localCache = lazy val sharedResolver: Resolver = { val r = Resolver.defaultShared - r withConfiguration (r.configuration withIsLocal false) + r.withConfiguration(r.configuration.withIsLocal(false)) //MavenRepository("example-shared-repo", "file:///tmp/shared-maven-repo-bad-example") //Resolver.file("example-shared-repo", repoDir)(Resolver.defaultPatterns) } diff --git a/sbt-app/src/sbt-test/dependency-management/snapshot-resolution/build.sbt b/sbt-app/src/sbt-test/dependency-management/snapshot-resolution/build.sbt index a1f361782..b4f43244f 100644 --- a/sbt-app/src/sbt-test/dependency-management/snapshot-resolution/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/snapshot-resolution/build.sbt @@ -9,7 +9,7 @@ def localCache = lazy val sharedResolver: Resolver = { val r = Resolver.defaultShared - r withConfiguration (r.configuration withIsLocal false) + r.withConfiguration(r.configuration.withIsLocal(false)) //MavenRepository("example-shared-repo", "file:///tmp/shared-maven-repo-bad-example") //Resolver.file("example-shared-repo", repoDir)(Resolver.defaultPatterns) } diff --git a/sbt-app/src/sbt-test/dependency-management/sources-transitive-classifiers/build.sbt b/sbt-app/src/sbt-test/dependency-management/sources-transitive-classifiers/build.sbt index 8bf912e4b..83adeb124 100644 --- a/sbt-app/src/sbt-test/dependency-management/sources-transitive-classifiers/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/sources-transitive-classifiers/build.sbt @@ -5,8 +5,8 @@ lazy val root = (project in file(".")) autoScalaLibrary := false, managedScalaInstance := false, transitiveClassifiers := Seq("sources"), - TaskKey[Unit]("checkSources") := (updateClassifiers map checkSources).value, - TaskKey[Unit]("checkBinaries") := (update map checkBinaries).value, + TaskKey[Unit]("checkSources") := updateClassifiers.map(checkSources).value, + TaskKey[Unit]("checkBinaries") := update.map(checkBinaries).value, ) def getSources(report: UpdateReport) = report.matching(artifactFilter(`classifier` = "sources") ) diff --git a/sbt-app/src/sbt-test/dependency-management/sources/build.sbt b/sbt-app/src/sbt-test/dependency-management/sources/build.sbt index 64f5ba3f3..19e2d19af 100644 --- a/sbt-app/src/sbt-test/dependency-management/sources/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/sources/build.sbt @@ -3,8 +3,8 @@ ThisBuild / scalaVersion := "2.12.12" lazy val root = (project in file(".")) .settings( libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.22", - TaskKey[Unit]("checkSources") := (updateClassifiers map checkSources).value, - TaskKey[Unit]("checkBinaries") := (update map checkBinaries).value + TaskKey[Unit]("checkSources") := updateClassifiers.map(checkSources).value, + TaskKey[Unit]("checkBinaries") := update.map(checkBinaries).value ) def getSources(report: UpdateReport) = report.matching(artifactFilter(`classifier` = "sources") ) diff --git a/sbt-app/src/sbt-test/dependency-management/test-artifact/changes/use/pom.sbt b/sbt-app/src/sbt-test/dependency-management/test-artifact/changes/use/pom.sbt index 562c488f9..118a44dde 100644 --- a/sbt-app/src/sbt-test/dependency-management/test-artifact/changes/use/pom.sbt +++ b/sbt-app/src/sbt-test/dependency-management/test-artifact/changes/use/pom.sbt @@ -1,3 +1,3 @@ -libraryDependencies += "org.example" % "def" % "2.0" classifier("tests") +libraryDependencies += ("org.example" % "def" % "2.0").classifier("tests") externalResolvers := Seq("example" at (baseDirectory.value / "ivy-repo").toURI.toString) diff --git a/sbt-app/src/sbt-test/dependency-management/transitive-excludes/build.sbt b/sbt-app/src/sbt-test/dependency-management/transitive-excludes/build.sbt index 766a4efe9..63b5ccb24 100644 --- a/sbt-app/src/sbt-test/dependency-management/transitive-excludes/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/transitive-excludes/build.sbt @@ -9,9 +9,9 @@ libraryDependencies += "exclude.test" % "app" % "1.0.0" val checkDependencies = taskKey[Unit]("Checks that dependencies are correct.") checkDependencies := { - val hasBadJar = (Compile / fullClasspath).value.exists { jar => jar.data.name contains "bottom-1.0.0.jar"} + val hasBadJar = (Compile / fullClasspath).value.exists { jar => jar.data.name.contains("bottom-1.0.0.jar")} val errorJarString = (Compile / fullClasspath).value.map(_.data.name).mkString(" * ", "\n * ", "") - val hasBadMiddleJar = (Compile / fullClasspath).value.exists { jar => jar.data.name contains "middle-1.0.0.jar"} + val hasBadMiddleJar = (Compile / fullClasspath).value.exists { jar => jar.data.name.contains("middle-1.0.0.jar")} assert(!hasBadMiddleJar, s"Failed to exclude excluded dependency on classpath!\nFound:\n$errorJarString") assert(!hasBadJar, s"Failed to exclude transitive excluded dependency on classpath!\nFound:\n$errorJarString") val modules = diff --git a/sbt-app/src/sbt-test/dependency-management/url-no-head/build.sbt b/sbt-app/src/sbt-test/dependency-management/url-no-head/build.sbt index 9fdc2681d..a31213fdc 100644 --- a/sbt-app/src/sbt-test/dependency-management/url-no-head/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/url-no-head/build.sbt @@ -1,3 +1,3 @@ ThisBuild / scalaVersion := "2.11.12" -libraryDependencies += "ccl.northwestern.edu" % "netlogo" % "5.3.1" % "provided" from s"https://github.com/NetLogo/NetLogo/releases/download/5.3.1/NetLogo.jar" +libraryDependencies += ("ccl.northwestern.edu" % "netlogo" % "5.3.1" % "provided").from(s"https://github.com/NetLogo/NetLogo/releases/download/5.3.1/NetLogo.jar") diff --git a/sbt-app/src/sbt-test/dependency-management/url/build.sbt b/sbt-app/src/sbt-test/dependency-management/url/build.sbt index 7f497aa55..915d925aa 100644 --- a/sbt-app/src/sbt-test/dependency-management/url/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/url/build.sbt @@ -8,7 +8,7 @@ def localCache = lazy val root = (project in file(".")). settings( localCache, - libraryDependencies += "org.jsoup" % "jsoup" % "1.9.1" % Test from "https://jsoup.org/packages/jsoup-1.9.1.jar", + libraryDependencies += ("org.jsoup" % "jsoup" % "1.9.1" % Test).from("https://jsoup.org/packages/jsoup-1.9.1.jar"), ivyLoggingLevel := UpdateLogging.Full, TaskKey[Unit]("checkInTest") := checkClasspath(Test).value, TaskKey[Unit]("checkInCompile") := checkClasspath(Compile).value diff --git a/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/build.sbt b/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/build.sbt index 20b16f73c..35723da8c 100644 --- a/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/build.sbt +++ b/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/build.sbt @@ -14,7 +14,7 @@ val commonSettings = Seq[Def.Setting[?]]( lazy val bippy = project settings ( commonSettings, resolvers += Resolver - .file("ivy-local", file(sys.props("user.home")) / ".ivy2" / "local")(Resolver.ivyStylePatterns), + .file("ivy-local", file(sys.props("user.home")) / ".ivy2" / "local")(using Resolver.ivyStylePatterns), publishTo := Some(Resolver.file("local-repo", localRepo.value)) ) @@ -40,6 +40,6 @@ InputKey[Unit]("check") := { val s = IO readStream jar.getInputStream(jar.getJarEntry("Bippy.scala")) val expected = s"def release = $n" - assert(s contains expected, s"""Bippy should contain $expected, contents:\n$s""") + assert(s.contains(expected), s"""Bippy should contain $expected, contents:\n$s""") () } diff --git a/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/project/plugins.sbt b/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/project/plugins.sbt index 661896f9b..d46e06a88 100644 --- a/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/project/plugins.sbt +++ b/sbt-app/src/sbt-test/ivy-deps-management/update-classifiers-snapshot-srcs/project/plugins.sbt @@ -1 +1 @@ -resolvers += Resolver.file("ivy-local", file(sys.props("user.home")) / ".ivy2" / "local")(Resolver.ivyStylePatterns) +resolvers += Resolver.file("ivy-local", file(sys.props("user.home")) / ".ivy2" / "local")(using Resolver.ivyStylePatterns) diff --git a/sbt-app/src/sbt-test/ivy/exclude-dependencies/build.sbt b/sbt-app/src/sbt-test/ivy/exclude-dependencies/build.sbt index 6654c4859..e0c75bbdf 100644 --- a/sbt-app/src/sbt-test/ivy/exclude-dependencies/build.sbt +++ b/sbt-app/src/sbt-test/ivy/exclude-dependencies/build.sbt @@ -31,10 +31,10 @@ lazy val root = (project in file(".")). val acp = (a / Compile / externalDependencyClasspath).value.sortBy {_.data.name} val bcp = (b / Compile / externalDependencyClasspath).value.sortBy {_.data.name} - if (acp exists { _.data.name contains "slf4j-api-1.7.5.jar" }) { + if (acp exists { _.data.name.contains("slf4j-api-1.7.5.jar") }) { sys.error("slf4j-api-1.7.5.jar found when it should NOT be included: " + acp.toString) } - if (bcp exists { _.data.name contains "dispatch-core_2.11-0.11.1.jar" }) { + if (bcp exists { _.data.name.contains("dispatch-core_2.11-0.11.1.jar") }) { sys.error("dispatch-core_2.11-0.11.1.jar found when it should NOT be included: " + bcp.toString) } diff --git a/sbt-app/src/sbt-test/lm-coursier/api-url/build.sbt b/sbt-app/src/sbt-test/lm-coursier/api-url/build.sbt index fd71f923a..dbb371794 100644 --- a/sbt-app/src/sbt-test/lm-coursier/api-url/build.sbt +++ b/sbt-app/src/sbt-test/lm-coursier/api-url/build.sbt @@ -10,7 +10,7 @@ lazy val b = project lazy val bResolver = Def.setting { val dir = (ThisBuild / baseDirectory).value / "b-repo" - Resolver.file("b-resolver", dir)(Resolver.defaultIvyPatterns) + Resolver.file("b-resolver", dir)(using Resolver.defaultIvyPatterns) } lazy val check = taskKey[Unit]("") diff --git a/sbt-app/src/sbt-test/lm-coursier/classifiers/build.sbt b/sbt-app/src/sbt-test/lm-coursier/classifiers/build.sbt index 5948041f3..c15546960 100644 --- a/sbt-app/src/sbt-test/lm-coursier/classifiers/build.sbt +++ b/sbt-app/src/sbt-test/lm-coursier/classifiers/build.sbt @@ -1,2 +1,2 @@ scalaVersion := "2.12.8" -libraryDependencies += "org.jclouds.api" % "nova" % "1.5.9" classifier "tests" +libraryDependencies += ("org.jclouds.api" % "nova" % "1.5.9").classifier("tests") diff --git a/sbt-app/src/sbt-test/lm-coursier/from-no-head/build.sbt b/sbt-app/src/sbt-test/lm-coursier/from-no-head/build.sbt index c3235d86a..e21f705c1 100644 --- a/sbt-app/src/sbt-test/lm-coursier/from-no-head/build.sbt +++ b/sbt-app/src/sbt-test/lm-coursier/from-no-head/build.sbt @@ -1,3 +1,3 @@ scalaVersion := "2.12.21" -libraryDependencies += "ccl.northwestern.edu" % "netlogo" % "5.3.1" % "provided" from s"https://github.com/NetLogo/NetLogo/releases/download/5.3.1/NetLogo.jar" +libraryDependencies += ("ccl.northwestern.edu" % "netlogo" % "5.3.1" % "provided").from(s"https://github.com/NetLogo/NetLogo/releases/download/5.3.1/NetLogo.jar") diff --git a/sbt-app/src/sbt-test/lm-coursier/whitespace-resolver/build.sbt b/sbt-app/src/sbt-test/lm-coursier/whitespace-resolver/build.sbt index 97a9c838d..985672530 100644 --- a/sbt-app/src/sbt-test/lm-coursier/whitespace-resolver/build.sbt +++ b/sbt-app/src/sbt-test/lm-coursier/whitespace-resolver/build.sbt @@ -1,3 +1,3 @@ scalaVersion := "2.12.8" -resolvers += Resolver.file("space-repo", file(raw"/tmp/space the final frontier/repo"))(Resolver.ivyStylePatterns) +resolvers += Resolver.file("space-repo", file(raw"/tmp/space the final frontier/repo"))(using Resolver.ivyStylePatterns) diff --git a/sbt-app/src/sbt-test/package/manifest/build.sbt b/sbt-app/src/sbt-test/package/manifest/build.sbt index b0ad580d1..d05052ae3 100644 --- a/sbt-app/src/sbt-test/package/manifest/build.sbt +++ b/sbt-app/src/sbt-test/package/manifest/build.sbt @@ -10,7 +10,7 @@ mainClass := Some("jartest.Main") Compile / packageBin / packageOptions := { def manifestExtra = { val mf = new Manifest - mf.getMainAttributes.put(Attributes.Name.CLASS_PATH, makeString(scalaInstance.value.libraryJars)) + mf.getMainAttributes.put(Attributes.Name.CLASS_PATH, makeString(scalaInstance.value.libraryJars.toSeq)) mf } (Compile / packageBin / packageOptions).value :+ Package.JarManifest(manifestExtra) diff --git a/sbt-app/src/sbt-test/package/resources/build.sbt b/sbt-app/src/sbt-test/package/resources/build.sbt index e6e9709e7..26ce9d15f 100644 --- a/sbt-app/src/sbt-test/package/resources/build.sbt +++ b/sbt-app/src/sbt-test/package/resources/build.sbt @@ -9,7 +9,7 @@ packageOptions := { def manifestExtra = { import java.util.jar._ val mf = new Manifest - mf.getMainAttributes.put(Attributes.Name.CLASS_PATH, makeString(scalaInstance.value.libraryJars)) + mf.getMainAttributes.put(Attributes.Name.CLASS_PATH, makeString(scalaInstance.value.libraryJars.toSeq)) mf } Package.JarManifest(manifestExtra) +: packageOptions.value diff --git a/sbt-app/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt b/sbt-app/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt index 6d3062486..92d5f26f9 100644 --- a/sbt-app/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt +++ b/sbt-app/src/sbt-test/project/auto-plugins-default-requires-jvmplugin/build.sbt @@ -1,4 +1,4 @@ -val test123 = project in file(".") enablePlugins TestP settings( +val test123 = project.in(file(".")).enablePlugins(TestP).settings( Compile / resourceGenerators += Def.task { streams.value.log.info("resource generated in settings") Seq.empty[File] @@ -6,9 +6,9 @@ val test123 = project in file(".") enablePlugins TestP settings( ) TaskKey[Unit]("check") := { - val last = IO read (BuiltinCommands lastLogFile state.value).get + val last = IO.read(BuiltinCommands.lastLogFile(state.value).get) def assertContains(expectedString: String) = - if (!(last contains expectedString)) sys error s"Expected string $expectedString to be present" + if (!last.contains(expectedString)) sys error s"Expected string $expectedString to be present" assertContains("resource generated in settings") assertContains("resource generated in plugin") } diff --git a/sbt-app/src/sbt-test/project/default-settings/build.sbt b/sbt-app/src/sbt-test/project/default-settings/build.sbt index 8ffb5bcd4..e4577269b 100644 --- a/sbt-app/src/sbt-test/project/default-settings/build.sbt +++ b/sbt-app/src/sbt-test/project/default-settings/build.sbt @@ -2,7 +2,7 @@ val root = (project in file(".")) TaskKey[Unit]("checkScalaVersion", "test") := Def.uncached { val sv = scalaVersion.value - assert(sv startsWith "3.", s"Found $sv!") + assert(sv.startsWith("3."), s"Found $sv!") } TaskKey[Unit]("checkArtifacts", "test") := Def.uncached { diff --git a/sbt-app/src/sbt-test/project/load-hooks/build.sbt b/sbt-app/src/sbt-test/project/load-hooks/build.sbt index 9e12fedd5..0e722e77a 100644 --- a/sbt-app/src/sbt-test/project/load-hooks/build.sbt +++ b/sbt-app/src/sbt-test/project/load-hooks/build.sbt @@ -2,7 +2,7 @@ val loadCount = AttributeKey[Int]("load-count") val unloadCount = AttributeKey[Int]("unload-count") def f(key: AttributeKey[Int]) = (s: State) => { - val previous = s get key getOrElse 0 + val previous = s.get(key)getOrElse(0) s.put(key, previous + 1) } Seq( @@ -14,7 +14,7 @@ InputKey[Unit]("checkCount") := { val s = state.value val args = Def.spaceDelimited().parsed - def get(label: String) = s get AttributeKey[Int](label) getOrElse 0 + def get(label: String) = s.get(AttributeKey[Int](label)).getOrElse(0) val loadCount = get("load-count") val unloadCount = get("unload-count") val expectedLoad = args(0).toInt diff --git a/sbt-app/src/sbt-test/project/session-update-from-cmd/project/Common.scala b/sbt-app/src/sbt-test/project/session-update-from-cmd/project/Common.scala index 57772a073..9bdce307c 100644 --- a/sbt-app/src/sbt-test/project/session-update-from-cmd/project/Common.scala +++ b/sbt-app/src/sbt-test/project/session-update-from-cmd/project/Common.scala @@ -21,7 +21,7 @@ object Common { val UpdateK3 = Command.command("UpdateK3"): (st: State) => - val ex = Project extract st + val ex = Project.extract(st) import ex._ val session2 = BuiltinCommands.setThis(ex, Seq(k3 := {}), """k3 := { |// diff --git a/sbt-app/src/sbt-test/project/transitive-plugins/build.sbt b/sbt-app/src/sbt-test/project/transitive-plugins/build.sbt index 0dd0cad82..384c95d75 100644 --- a/sbt-app/src/sbt-test/project/transitive-plugins/build.sbt +++ b/sbt-app/src/sbt-test/project/transitive-plugins/build.sbt @@ -15,7 +15,7 @@ lazy val commonSettings = Seq( val ivyHome = Classpaths.bootIvyHome(appConfiguration.value) getOrElse sys.error( "Launcher did not provide the Ivy home directory." ) - Resolver.file("real-local", ivyHome / "local")(Resolver.ivyStylePatterns) + Resolver.file("real-local", ivyHome / "local")(using Resolver.ivyStylePatterns) }, resolvers += Resolver.mavenLocal, resolvers += ("test-repo" at ((ThisBuild / baseDirectory).value / "repo/").asURL.toString) diff --git a/sbt-app/src/sbt-test/project/update-classifiers/build.sbt b/sbt-app/src/sbt-test/project/update-classifiers/build.sbt index 675f26517..741f8601d 100644 --- a/sbt-app/src/sbt-test/project/update-classifiers/build.sbt +++ b/sbt-app/src/sbt-test/project/update-classifiers/build.sbt @@ -1,2 +1,2 @@ -lazy val a = project in file(".") dependsOn(b) +lazy val a = project.in(file(".")).dependsOn(b) lazy val b = project diff --git a/sbt-app/src/sbt-test/source-dependencies/binary/build.sbt b/sbt-app/src/sbt-test/source-dependencies/binary/build.sbt index 3350d0a98..964e70e21 100644 --- a/sbt-app/src/sbt-test/source-dependencies/binary/build.sbt +++ b/sbt-app/src/sbt-test/source-dependencies/binary/build.sbt @@ -4,5 +4,5 @@ lazy val dep = project lazy val use = project. settings( - (Compile / unmanagedJars) += ((dep / Compile / packageBin) map Attributed.blank).value + (Compile / unmanagedJars) += (dep / Compile / packageBin).map(Attributed.blank).value ) diff --git a/sbtw/src/main/scala/sbtw/Main.scala b/sbtw/src/main/scala/sbtw/Main.scala index fe6db0421..59d13a3e8 100644 --- a/sbtw/src/main/scala/sbtw/Main.scala +++ b/sbtw/src/main/scala/sbtw/Main.scala @@ -10,8 +10,9 @@ object Main: val sbtHome = new File( sys.env .get("SBT_HOME") - .getOrElse: + .getOrElse( sys.env.get("SBT_BIN_DIR").map(d => new File(d).getParent).getOrElse(cwd.getAbsolutePath) + ) ) val sbtBinDir = new File(sbtHome, "bin") @@ -26,86 +27,88 @@ object Main: System.exit(if exitCode == 0 then 0 else 1) private def run(cwd: File, sbtHome: File, sbtBinDir: File, opts: LauncherOptions): Int = - if opts.help then return printUsage() - if opts.version || opts.numericVersion || opts.scriptVersion then - return handleVersionCommands(cwd, sbtHome, sbtBinDir, opts) - if opts.shutdownAll then + if opts.help then printUsage() + else if opts.version || opts.numericVersion || opts.scriptVersion then + handleVersionCommands(cwd, sbtHome, sbtBinDir, opts) + else if opts.shutdownAll then val javaCmd = Runner.findJavaCmd(opts.javaHome) - return Runner.shutdownAll(javaCmd) - - if !opts.allowEmpty && !opts.sbtNew && !ConfigLoader.isSbtProjectDir(cwd) then + Runner.shutdownAll(javaCmd) + else if !opts.allowEmpty && !opts.sbtNew && !ConfigLoader.isSbtProjectDir(cwd) then System.err.println( "[error] Neither build.sbt nor a 'project' directory in the current directory: " + cwd ) System.err.println("[error] run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'.") - return 1 + 1 + else + val buildPropsVersion = ConfigLoader.sbtVersionFromBuildProperties(cwd) - val buildPropsVersion = ConfigLoader.sbtVersionFromBuildProperties(cwd) + val javaCmd = Runner.findJavaCmd(opts.javaHome) + val javaVer = Runner.javaVersion(javaCmd) + val minJdk = Runner.minimumJdkVersion(buildPropsVersion) + if javaVer > 0 && javaVer < minJdk then + if minJdk >= 17 then + System.err.println( + "[error] sbt 2.x requires JDK 17 or above, but you have JDK " + javaVer + ) + else System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer) + 1 + else + val bspMode = opts.residual.exists(a => a == "bsp" || a == "-bsp" || a == "--bsp") + val clientOpt = opts.client || sys.env.get("SBT_NATIVE_CLIENT").contains("true") + val useNativeClient = + if bspMode then false + else shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion) - val javaCmd = Runner.findJavaCmd(opts.javaHome) - val javaVer = Runner.javaVersion(javaCmd) - val minJdk = Runner.minimumJdkVersion(buildPropsVersion) - if javaVer > 0 && javaVer < minJdk then - if minJdk >= 17 then - System.err.println("[error] sbt 2.x requires JDK 17 or above, but you have JDK " + javaVer) - else System.err.println("[error] sbt requires at least JDK 8+, you have " + javaVer) - return 1 + if useNativeClient then + val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat" + Runner.runNativeClient(sbtBinDir, scriptPath, opts) + else + val sbtJar = opts.sbtJar + .filter(p => new File(p).isFile) + .getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath) + if !new File(sbtJar).isFile then + System.err.println("[error] Launcher jar not found: " + sbtJar) + 1 + else + var javaOpts = ConfigLoader.loadJvmOpts(cwd) + if javaOpts.isEmpty then javaOpts = ConfigLoader.defaultJavaOpts + var sbtOpts = Runner.buildSbtOpts(opts) - val bspMode = opts.residual.exists(a => a == "bsp" || a == "-bsp" || a == "--bsp") - val clientOpt = opts.client || sys.env.get("SBT_NATIVE_CLIENT").contains("true") - val useNativeClient = - if bspMode then false - else shouldRunNativeClient(opts.copy(client = clientOpt), buildPropsVersion) + val (residualJava, bootArgs) = Runner.splitResidual(opts.residual) + javaOpts = javaOpts ++ residualJava - if useNativeClient then - val scriptPath = sbtBinDir.getAbsolutePath.replace("\\", "/") + "/sbt.bat" - return Runner.runNativeClient(sbtBinDir, scriptPath, opts) + val (finalJava, finalSbt) = if opts.mem.isDefined then + val evictedJava = Memory.evictMemoryOpts(javaOpts) + val evictedSbt = Memory.evictMemoryOpts(sbtOpts) + val memOpts = Memory.addMemory(opts.mem.get, javaVer) + (evictedJava ++ memOpts, evictedSbt) + else Memory.addDefaultMemory(javaOpts, sbtOpts, javaVer, LauncherOptions.defaultMemMb) + sbtOpts = finalSbt - val sbtJar = opts.sbtJar - .filter(p => new File(p).isFile) - .getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath) - if !new File(sbtJar).isFile then - System.err.println("[error] Launcher jar not found: " + sbtJar) - return 1 + if !opts.noHideJdkWarnings && javaVer >= 25 then + sbtOpts = sbtOpts ++ Seq( + "--sun-misc-unsafe-memory-access=allow", + "--enable-native-access=ALL-UNNAMED" + ) + val javaOptsWithDebug = opts.jvmDebug.fold(finalJava)(port => + finalJava :+ s"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$port" + ) - var javaOpts = ConfigLoader.loadJvmOpts(cwd) - if javaOpts.isEmpty then javaOpts = ConfigLoader.defaultJavaOpts - var sbtOpts = Runner.buildSbtOpts(opts) - - val (residualJava, bootArgs) = Runner.splitResidual(opts.residual) - javaOpts = javaOpts ++ residualJava - - val (finalJava, finalSbt) = if opts.mem.isDefined then - val evictedJava = Memory.evictMemoryOpts(javaOpts) - val evictedSbt = Memory.evictMemoryOpts(sbtOpts) - val memOpts = Memory.addMemory(opts.mem.get, javaVer) - (evictedJava ++ memOpts, evictedSbt) - else Memory.addDefaultMemory(javaOpts, sbtOpts, javaVer, LauncherOptions.defaultMemMb) - sbtOpts = finalSbt - - if !opts.noHideJdkWarnings && javaVer == 25 then - sbtOpts = sbtOpts ++ Seq( - "--sun-misc-unsafe-memory-access=allow", - "--enable-native-access=ALL-UNNAMED" - ) - val javaOptsWithDebug = opts.jvmDebug.fold(finalJava)(port => - finalJava :+ s"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$port" - ) - - Runner.runJvm(javaCmd, javaOptsWithDebug, sbtOpts, sbtJar, bootArgs, opts.verbose) + Runner.runJvm(javaCmd, javaOptsWithDebug, sbtOpts, sbtJar, bootArgs, opts.verbose) private def shouldRunNativeClient( opts: LauncherOptions, buildPropsVersion: Option[String] ): Boolean = - if opts.sbtNew then return false - if opts.jvmClient then return false - val version = buildPropsVersion.getOrElse(LauncherOptions.initSbtVersion) - val parts = version.split("[.-]").take(2).flatMap(s => scala.util.Try(s.toInt).toOption) - val (major, minor) = (parts.lift(0).getOrElse(0), parts.lift(1).getOrElse(0)) - if major >= 2 then !opts.server - else if major >= 1 && minor >= 4 then opts.client - else false + if opts.sbtNew then false + else if opts.jvmClient then false + else + val version = buildPropsVersion.getOrElse(LauncherOptions.initSbtVersion) + val parts = version.split("[.-]").take(2).flatMap(s => scala.util.Try(s.toInt).toOption) + val (major, minor) = (parts.lift(0).getOrElse(0), parts.lift(1).getOrElse(0)) + if major >= 2 then !opts.server + else if major >= 1 && minor >= 4 then opts.client + else false private def handleVersionCommands( cwd: File, @@ -115,8 +118,8 @@ object Main: ): Int = if opts.scriptVersion then println(LauncherOptions.initSbtVersion) - return 0 - if opts.version then + 0 + else if opts.version then if ConfigLoader.isSbtProjectDir(cwd) then projectSbtVersion(cwd).foreach: version => println("sbt version in this project: " + version) @@ -125,21 +128,22 @@ object Main: System.err.println( "[info] Actual version of sbt is declared using project\\build.properties for each build." ) - return 0 - if opts.numericVersion then + 0 + else if opts.numericVersion then val javaCmd = Runner.findJavaCmd(opts.javaHome) val sbtJar = opts.sbtJar .filter(p => new File(p).isFile) .getOrElse(new File(sbtBinDir, "sbt-launch.jar").getAbsolutePath) if !new File(sbtJar).isFile then System.err.println("[error] Launcher jar not found for version check") - return 1 - try - val out = Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!! - println(out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse("")) - return 0 - catch { case _: Exception => return 1 } - 0 + 1 + else + try + val out = Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!! + println(out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse("")) + 0 + catch { case _: Exception => 1 } + else 0 private def projectSbtVersion(cwd: File): Option[String] = ConfigLoader.sbtVersionFromBuildProperties(cwd).flatMap(normalizeVersion) diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 30230e5a7..fb7cfdee7 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -524,6 +524,19 @@ class BuildServerTest extends AbstractServerTest { ) } + test("buildTarget/dependencyModules") { + val buildTarget = buildTargetUri("runAndTest", "Compile") + val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") + val targets = Vector(buildTarget, badBuildTarget).map(BuildTargetIdentifier.apply) + val id = dependencyModules(targets.map(_.uri)) + val res = svr.session.waitForResultInResponseMsg[DependencyModulesResult](10.seconds, id).get + val runAndTestItem = res.items.find(_.target.contains(BuildTargetIdentifier(buildTarget))).get + assert( + runAndTestItem.modules.exists(_.name.contains("jsoniter-scala-core")), + s"dependencyModules should include jsoniter-scala-core, got: ${runAndTestItem.modules.map(_.name)}" + ) + } + test("buildTarget/outputPaths") { val buildTarget = buildTargetUri("util", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") @@ -753,6 +766,11 @@ class BuildServerTest extends AbstractServerTest { sendRequest("buildTarget/sources", SourcesParams(targets)) } + private def dependencyModules(buildTargets: Seq[URI]): String = { + val targets = buildTargets.map(BuildTargetIdentifier.apply).toVector + sendRequest("buildTarget/dependencyModules", DependencyModulesParams(targets)) + } + private def sendRequest(method: String): String = { val id = svr.session.nextId() svr.session.sendJsonRpc(id, method, "{}").get diff --git a/server-test/src/test/scala/testpkg/ResponseTest.scala b/server-test/src/test/scala/testpkg/ResponseTest.scala index 4f248fc01..48f914932 100644 --- a/server-test/src/test/scala/testpkg/ResponseTest.scala +++ b/server-test/src/test/scala/testpkg/ResponseTest.scala @@ -79,6 +79,16 @@ class ResponseTest extends AbstractServerTest { } } + test("unknown method returns error") { + val id = svr.session.nextId() + svr.session.sendJsonRpc(id, "build/foo", "{}").get + val response = svr.session.waitForResponseMsg(10.seconds, id).get + assert( + response.error.exists(_.code == -32601), + s"Expected method-not-found error, got: $response" + ) + } + private def neverReceiveResponse( duration: FiniteDuration )(predicate: sbt.internal.protocol.JsonRpcResponseMessage => Boolean): Unit = diff --git a/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala b/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala index 204162222..31e3bb6e0 100644 --- a/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala +++ b/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala @@ -13,9 +13,9 @@ import java.util.concurrent.atomic.AtomicReference import testing.{ Event as TEvent, OptionalThrowable, Status as TStatus, TestSelector } import util.{ AbstractLogger, Level, ControlEvent, LogEvent } +import sbt.io.IO import sbt.protocol.testing.TestResult import sbt.internal.worker1.ForkTestMain -import sbt.io.IO import verify.BasicTestSuite import scala.xml.XML diff --git a/util-cache/src/main/scala/sbt/util/ActionCache.scala b/util-cache/src/main/scala/sbt/util/ActionCache.scala index 04c62f8f9..5aeaac286 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCache.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCache.scala @@ -10,7 +10,7 @@ package sbt.util import java.io.{ File, IOException } import java.nio.charset.StandardCharsets -import java.nio.file.{ Files, Path, Paths, StandardCopyOption } +import java.nio.file.{ AtomicMoveNotSupportedException, Files, Path, Paths, StandardCopyOption } import sbt.internal.util.{ ActionCacheEvent, CacheEventLog, StringVirtualFile1 } import sbt.io.syntax.* import sbt.io.IO @@ -82,40 +82,40 @@ object ActionCache: case e: Exception => cacheEventLog.append(ActionCacheEvent.Error) throw e - val json = Converter.toJsonUnsafe(result) - val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize() - val uncacheableOutputs = - outputs.filter(f => - f match - case vf if vf.id.endsWith(ActionCache.dirZipExt) => - false - case _ => - val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize() - !outputPath.startsWith(normalizedOutputDir) - ) - if uncacheableOutputs.nonEmpty then - cacheEventLog.append(ActionCacheEvent.Error) - logger.error( - s"Cannot cache task because its output files are outside the output directory: \n" + - uncacheableOutputs.mkString(" - ", "\n - ", "") - ) - result - else - cacheEventLog.append(ActionCacheEvent.OnsiteTask) - val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion) - val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json)) - val newOutputs = Vector(valueFile) ++ outputs.toVector - try + try + val json = Converter.toJsonUnsafe(result) + val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize() + val uncacheableOutputs = + outputs.filter(f => + f match + case vf if vf.id.endsWith(ActionCache.dirZipExt) => + false + case _ => + val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize() + !outputPath.startsWith(normalizedOutputDir) + ) + if uncacheableOutputs.nonEmpty then + cacheEventLog.append(ActionCacheEvent.Error) + logger.error( + s"Cannot cache task because its output files are outside the output directory: \n" + + uncacheableOutputs.mkString(" - ", "\n - ", "") + ) + result + else + cacheEventLog.append(ActionCacheEvent.OnsiteTask) + val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion) + val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json)) + val newOutputs = Vector(valueFile) ++ outputs.toVector store.put(UpdateActionResultRequest(input, newOutputs, exitCode = 0)) match case Right(cachedResult) => store.syncBlobs(cachedResult.outputFiles, outputDirectory) result case Left(e) => throw e - catch - case e: IOException => - logger.debug(s"Skipping cache storage due to error: ${e.getMessage}") - cacheEventLog.append(ActionCacheEvent.Error) - result + catch + case e: IOException => + logger.debug(s"Skipping cache storage due to error: ${e.getMessage}") + cacheEventLog.append(ActionCacheEvent.Error) + result // Single cache lookup - use exitCode to distinguish success from failure getWithFailure(key, codeContentHash, extraHash, tags, config) match @@ -270,6 +270,36 @@ object ActionCache: private val default2010Timestamp: Long = 1262304000000L + /** + * Publishes `builtZip` as `destZip` by staging next to the destination and renaming into place. + * Avoids races from a direct `Files.copy` into `destZip` under parallel task execution. + */ + private def installPackagedZip(builtZip: Path, destZip: Path, fallbackStagingDir: Path): Unit = + val stagingDir = Option(destZip.getParent) match + case Some(parent) => + Files.createDirectories(parent) + parent + case None => fallbackStagingDir + + val staging = Files.createTempFile( + stagingDir, + destZip.getFileName.toString + ".", + dirZipExt + ".tmp", + ) + try + Files.copy(builtZip, staging, StandardCopyOption.REPLACE_EXISTING) + try + Files.move( + staging, + destZip, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE, + ) + catch + case _: AtomicMoveNotSupportedException => + Files.move(staging, destZip, StandardCopyOption.REPLACE_EXISTING) + finally Files.deleteIfExists(staging) + def packageDirectory( dir: VirtualFileRef, conv: FileConverter, @@ -311,7 +341,7 @@ object ActionCache: tempZipPath.toFile(), Some(default2010Timestamp) ) - Files.copy(tempZipPath, zipPath, StandardCopyOption.REPLACE_EXISTING) + installPackagedZip(tempZipPath, zipPath, tempDir.toPath()) conv.toVirtualFile(zipPath) diff --git a/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala b/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala index 708e9a646..5054218f3 100644 --- a/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala +++ b/util-cache/src/test/scala/sbt/util/ActionCacheTest.scala @@ -1,5 +1,10 @@ package sbt.util +import java.nio.charset.StandardCharsets +import java.nio.file.{ Files, Path, Paths } +import java.util.Optional +import java.util.concurrent.{ CyclicBarrier, ExecutorService, Executors, TimeUnit } + import sbt.internal.util.CacheEventLog import sbt.internal.util.StringVirtualFile1 import sbt.io.IO @@ -7,18 +12,15 @@ import sbt.io.syntax.* import verify.BasicTestSuite import xsbti.{ CompileFailed, + FileConverter, HashedVirtualFileRef, Problem, Position, Severity, VirtualFile, - FileConverter, - VirtualFileRef + VirtualFileRef, } -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.Files -import java.util.Optional + import ActionCache.InternalActionResult object ActionCacheTest extends BasicTestSuite: @@ -259,6 +261,36 @@ object ActionCacheTest extends BasicTestSuite: assert(v2 == 42) assert(called == 2) + test("packageDirectory is safe when many threads package the same directory concurrently"): + IO.withTemporaryDirectory: tmp => + val root = tmp.toPath + val classesDir = root.resolve("classes") + Files.createDirectories(classesDir) + Files.writeString(classesDir.resolve("A.class"), "compiled") + val classesPathStr = classesDir.toString + val dirRef = VirtualFileRef.of(classesPathStr) + val conv = new FileConverter: + override def toPath(ref: VirtualFileRef): Path = Paths.get(ref.id) + override def toVirtualFile(path: Path): VirtualFile = + val content = + if Files.isRegularFile(path) then + new String(Files.readAllBytes(path), StandardCharsets.UTF_8) + else "" + StringVirtualFile1(path.toString, content) + val threadCount = 64 + val barrier = new CyclicBarrier(threadCount) + val pool: ExecutorService = Executors.newFixedThreadPool(threadCount) + try + val tasks = + for _ <- 1 to threadCount yield pool.submit: () => + barrier.await(30, TimeUnit.SECONDS) + ActionCache.packageDirectory(dirRef, conv, root) + tasks.foreach(_.get(60, TimeUnit.SECONDS)) + val zipPath = Paths.get(classesPathStr + ActionCache.dirZipExt) + assert(Files.isRegularFile(zipPath)) + assert(Files.size(zipPath) > 0L) + finally pool.shutdown() + test("Changing cacheVersion invalidates the cache"): withDiskCache(testCacheVersionInvalidation)