From 00632754abea171be7d18b276d62384fd596fd5b Mon Sep 17 00:00:00 2001 From: BrianHotopp Date: Fri, 10 Apr 2026 01:31:52 -0400 Subject: [PATCH 01/24] [2.x] fix: Add --fail to curl in launcher to prevent corrupt jars (#9003) When the download URL returns an HTTP error (e.g. 404), curl without --fail exits 0 and saves the error page HTML as the jar file. This leaves a corrupt jar that causes confusing errors on subsequent runs. With --fail, curl returns a non-zero exit code on HTTP errors and does not write the response body to the output file. Fixes #6654 Co-authored-by: Claude Opus 4.6 (1M context) --- sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt b/sbt index 5ab69d315..c32343fe0 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 From d765ba263ae999ca947f9e427a080e7b33194e84 Mon Sep 17 00:00:00 2001 From: BrianHotopp Date: Fri, 10 Apr 2026 01:33:20 -0400 Subject: [PATCH 02/24] [2.x] fix: Return JSON-RPC error for unknown server methods (#9002) The fallback ServerHandler silently dropped requests with unknown methods, violating the JSON-RPC spec. Clients like Metals that send unrecognized requests would never receive a response, potentially causing timeouts or hangs. Return a -32601 (Method not found) error response instead. --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../main/scala/sbt/internal/server/ServerHandler.scala | 5 ++++- server-test/src/test/scala/testpkg/ResponseTest.scala | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) 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/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 = From e0bdb531f73bc2737fd64339614896f7ef908c5a Mon Sep 17 00:00:00 2001 From: BitToby <218712309+bittoby@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:44:08 -0600 Subject: [PATCH 03/24] [2.x] fix: Widen IOException catch in organicTask to cover result serialization (#9050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem** When a file referenced during cache serialization is deleted between task execution and cache storage, sbt crashes with an uncaught NoSuchFileException. The IOException catch added in #8699 only wraps store.put(), but Converter.toJsonUnsafe(result) and mkInput() also call hashedVirtualFileRefToStr which calls Files.readAttributes — and these are outside the try-catch. **Solution** Move the try/catch IOException to wrap the entire post-action cache storage section, so NoSuchFileException from result serialization or cache key computation is caught and handled gracefully (skip caching, return result). Fixes #9044 Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.co> --- .../src/main/scala/sbt/util/ActionCache.scala | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/util-cache/src/main/scala/sbt/util/ActionCache.scala b/util-cache/src/main/scala/sbt/util/ActionCache.scala index 04c62f8f9..0b60013e3 100644 --- a/util-cache/src/main/scala/sbt/util/ActionCache.scala +++ b/util-cache/src/main/scala/sbt/util/ActionCache.scala @@ -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 From f8a8742dbee1dd3acf882159cbc615357200c57d Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sat, 11 Apr 2026 08:20:02 +0900 Subject: [PATCH 04/24] [2.x] Format pom.xml (#9059) --- main/src/main/scala/sbt/Defaults.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 }) From 7408fc93c788c4b67479687a72943d1e27a84be6 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sat, 11 Apr 2026 15:28:38 +0900 Subject: [PATCH 05/24] [2.x] Delete bootDirectory in cleanFull (#9060) --- main/src/main/scala/sbt/internal/Clean.scala | 7 +++++++ 1 file changed, 7 insertions(+) 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) } From 160a6c1fb0849c85c55ea7ee1a137ec5cc806a9e Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sat, 11 Apr 2026 15:29:54 +0900 Subject: [PATCH 06/24] [2.x] Fix scaladoc issue in mainProj and sbtIvyProj (#9061) --- build.sbt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 311c9435e..ead4b846f 100644 --- a/build.sbt +++ b/build.sbt @@ -742,8 +742,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 +784,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) From 78afa8fb963b382f18223eda0bcf483d649b7a89 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sat, 11 Apr 2026 16:07:52 +0900 Subject: [PATCH 07/24] [2.x] test: Fix warnings in scripted tests (#9062) --- sbt-app/src/sbt-test/actions/completions/build.sbt | 2 +- .../actions/configuration-delegation/build.sbt | 2 +- sbt-app/src/sbt-test/actions/depends-on/build.sbt | 2 +- sbt-app/src/sbt-test/actions/external-doc/build.sbt | 2 +- sbt-app/src/sbt-test/actions/state/build.sbt | 12 ++++++------ .../dependency-management/artifact/build.sbt | 4 ++-- .../cache-classifiers/multi.sbt | 2 +- .../classifier-report/build.sbt | 2 +- .../dependency-management/classifier/build.sbt | 2 +- .../dependency-management/classifier2/build.sbt | 2 +- .../dependency-management/cross-ivy-maven/build.sbt | 4 ++-- .../dependency-management/default-artifact/build.sbt | 6 +++--- .../evicted-semver-spec/build.sbt | 2 +- .../exclude-bundle/changes/build.sbt | 2 +- .../dependency-management/exclude-scala/build.sbt | 2 +- .../sbt-test/dependency-management/force/build.sbt | 2 +- .../ivyless-publish-http/build.sbt | 2 +- .../dependency-management/ivyless-publish/build.sbt | 2 +- .../latest-local-plugin/changes/def.sbt | 2 +- .../latest-local-plugin/changes/use-plugins.sbt | 2 +- .../dependency-management/make-pom/build.sbt | 4 ++-- .../dependency-management/module-name/build.sbt | 2 +- .../multiple-classifiers/build.sbt | 2 +- .../dependency-management/mvn-local/build.sbt | 2 +- .../dependency-management/override2/build.sbt | 2 +- .../dependency-management/pom-advanced/build.sbt | 2 +- .../pom-artifact-type/build.sbt | 2 +- .../dependency-management/pom-packaging/build.sbt | 6 +++--- .../dependency-management/pom-scope/build.sbt | 4 ++-- .../dependency-management/provided/build.sbt | 2 +- .../dependency-management/publish-local/build.sbt | 2 +- .../publish-local/changes/RetrieveTest.sbt | 2 +- .../scala-version-check-exempt/build.sbt | 8 ++++---- .../dependency-management/snapshot-local/build.sbt | 2 +- .../snapshot-resolution/build.sbt | 2 +- .../sources-transitive-classifiers/build.sbt | 4 ++-- .../sbt-test/dependency-management/sources/build.sbt | 4 ++-- .../test-artifact/changes/use/pom.sbt | 2 +- .../transitive-excludes/build.sbt | 4 ++-- .../dependency-management/url-no-head/build.sbt | 2 +- .../src/sbt-test/dependency-management/url/build.sbt | 2 +- .../update-classifiers-snapshot-srcs/build.sbt | 4 ++-- .../project/plugins.sbt | 2 +- .../src/sbt-test/ivy/exclude-dependencies/build.sbt | 4 ++-- sbt-app/src/sbt-test/lm-coursier/api-url/build.sbt | 2 +- .../src/sbt-test/lm-coursier/classifiers/build.sbt | 2 +- .../src/sbt-test/lm-coursier/from-no-head/build.sbt | 2 +- .../lm-coursier/whitespace-resolver/build.sbt | 2 +- sbt-app/src/sbt-test/package/manifest/build.sbt | 2 +- sbt-app/src/sbt-test/package/resources/build.sbt | 2 +- .../build.sbt | 6 +++--- .../src/sbt-test/project/default-settings/build.sbt | 2 +- sbt-app/src/sbt-test/project/load-hooks/build.sbt | 4 ++-- .../session-update-from-cmd/project/Common.scala | 2 +- .../sbt-test/project/transitive-plugins/build.sbt | 2 +- .../sbt-test/project/update-classifiers/build.sbt | 2 +- .../sbt-test/source-dependencies/binary/build.sbt | 2 +- 57 files changed, 81 insertions(+), 81 deletions(-) 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/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/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..6df5b827a 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,6 +1,6 @@ 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 @@ -71,7 +71,7 @@ lazy val checkPom = Def.task { 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 ) From 2e0a724c63583557b16306e85755b2cf7f0131c5 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 02:06:50 +0900 Subject: [PATCH 08/24] [2.x] refactor: Remove return from sbtw/Main (#9066) --- build.sbt | 4 - sbtw/src/main/scala/sbtw/Main.scala | 156 ++++++++++++++-------------- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/build.sbt b/build.sbt index ead4b846f..b1db91c4d 100644 --- a/build.sbt +++ b/build.sbt @@ -1023,10 +1023,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/sbtw/src/main/scala/sbtw/Main.scala b/sbtw/src/main/scala/sbtw/Main.scala index fe6db0421..3efeb8d28 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) From 6fe83aa021b5e15ae745c85b735b7d4bb3f08e98 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 02:39:02 +0900 Subject: [PATCH 09/24] [2.x] Remove `whitesource` from LintUnused (#9064) --- main/src/main/scala/sbt/internal/LintUnused.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(_)) }, From 767ebec906af9b810092230edc816f29927c1bc8 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 02:40:28 +0900 Subject: [PATCH 10/24] [2.x] override sbt.Tests.Group hashCode (#9065) --- main-actions/src/main/scala/sbt/Tests.scala | 2 ++ 1 file changed, 2 insertions(+) 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] From d86587ad54434191591c104aafcbb5640dc544fd Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 10:47:48 +0900 Subject: [PATCH 11/24] [2.x] hide JDK warnings if JDK 26 or later (#9068) --- sbt | 2 +- sbtw/src/main/scala/sbtw/Main.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sbt b/sbt index c32343fe0..3631d4ead 100755 --- a/sbt +++ b/sbt @@ -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/sbtw/src/main/scala/sbtw/Main.scala b/sbtw/src/main/scala/sbtw/Main.scala index 3efeb8d28..59d13a3e8 100644 --- a/sbtw/src/main/scala/sbtw/Main.scala +++ b/sbtw/src/main/scala/sbtw/Main.scala @@ -85,7 +85,7 @@ object Main: else Memory.addDefaultMemory(javaOpts, sbtOpts, javaVer, LauncherOptions.defaultMemMb) sbtOpts = finalSbt - if !opts.noHideJdkWarnings && javaVer == 25 then + if !opts.noHideJdkWarnings && javaVer >= 25 then sbtOpts = sbtOpts ++ Seq( "--sun-misc-unsafe-memory-access=allow", "--enable-native-access=ALL-UNNAMED" From b156871557c9c6c6f3afe9a810ebeda022926c6e Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 14:55:18 +0900 Subject: [PATCH 12/24] [2.x] ci: Delete unused code in project/Dependencies.scala (#9070) --- project/Dependencies.scala | 2 -- 1 file changed, 2 deletions(-) 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( From d46ba8bfc9827e5e1806c151290a7c0838f23219 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 14:56:17 +0900 Subject: [PATCH 13/24] [2.x] ci: Add -source-links option (#9063) --- build.sbt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index b1db91c4d..2eca78eea 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 := From c47fc6bbd8126a1800f9bc5f6760760b25c44417 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 14:57:00 +0900 Subject: [PATCH 14/24] [2.x] fix: Delete src/main/resources/log4j2.xml (#9071) --- sbt-app/src/main/resources/log4j2.xml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 sbt-app/src/main/resources/log4j2.xml 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 @@ - - From 852505f9f78d72710e2585bf8817550c02567931 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 12 Apr 2026 14:57:41 +0900 Subject: [PATCH 15/24] [2.x] ci: Remove -Ymacro-expand option (#9072) --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2eca78eea..5722eacdd 100644 --- a/build.sbt +++ b/build.sbt @@ -188,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, From 512d2d460c5d00ca7821108c35e844e53844489f Mon Sep 17 00:00:00 2001 From: Dream <42954461+eureka0928@users.noreply.github.com> Date: Sun, 12 Apr 2026 01:59:02 -0400 Subject: [PATCH 16/24] [2.x] fix: Add description and url to PomGenerator output (#9069) **Problem** PomGenerator (introduced in #8873) was missing and elements that the old Ivy-based MakePom included. Setting `description` and `homepage` in build.sbt had no effect on the generated pom.xml. **Solution** Add makeDescription and makeHomePage helpers to PomGenerator, matching the behavior of MakePom. Add test assertions for both fields. Fixes #9054 --- main/src/main/scala/sbt/internal/PomGenerator.scala | 12 ++++++++++++ .../dependency-management/make-pom/build.sbt | 6 ++++++ 2 files changed, 18 insertions(+) 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/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt b/sbt-app/src/sbt-test/dependency-management/make-pom/build.sbt index 6df5b827a..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 @@ -6,6 +6,8 @@ lazy val root = (project in file(".")).settings( 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,6 +67,10 @@ 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" From c52ce78d7797c10a248ec60de9b0faa7ba173e6b Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Mon, 13 Apr 2026 02:51:06 +0900 Subject: [PATCH 17/24] [2.x] Update .gitattributes for contraband files (#9076) --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) 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 From 05cd00e13544f4c6d2be50777bfdccdbf7762186 Mon Sep 17 00:00:00 2001 From: BitToby <218712309+bittoby@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:24:30 +0200 Subject: [PATCH 18/24] [2.x] fix: stack traces suppressed in thin client batch mode (#9058) In sbt 2.x, running batch commands through the thin client (sbtn) suppresses stack traces for all tasks because the server's shell command unconditionally sets state.interactive = true. This causes LogManager.defaultTraceLevel to return -1 (suppressed) even when the client explicitly signals non-interactive (batch) mode via Attach(interactive=false). This fixes the shell command to check the originating NetworkChannel's interactive flag before setting state.interactive, so thin client batch commands correctly get Int.MaxValue trace level and display full stack traces. --- main/src/main/scala/sbt/Main.scala | 10 +++++++++- .../sbt-test/actions/streams-trace-level/build.sbt | 11 +++++++++++ sbt-app/src/sbt-test/actions/streams-trace-level/test | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 sbt-app/src/sbt-test/actions/streams-trace-level/build.sbt create mode 100644 sbt-app/src/sbt-test/actions/streams-trace-level/test 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/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 From 25dd9b73634d18ab1294d25a10a337263712575c Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Mon, 13 Apr 2026 00:41:35 -0400 Subject: [PATCH 19/24] [2.x] fix: Fixes client-side run status (#9081) **Problem** In sbt 2.x, if we execute a run task from the shell, and if the program fails, it ends up taking down the entire shell because client-side run rethrows, which is not desirable for the sbt shell. **Solution** 1. Omit printing out success for ClientJobParams, which is the runinfo. 2. Print out success or error on the client-side for shell usage case. --- .../sbt/internal/client/NetworkClient.scala | 21 ++++++++++++++----- .../main/scala/sbt/internal/Aggregation.scala | 11 ++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) 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/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 () From 936a7e1ffb9c15efa3d57e90ec7758e2645f48ee Mon Sep 17 00:00:00 2001 From: bitloi <89318445+bitloi@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:28:43 -0300 Subject: [PATCH 20/24] [2.x] fix: clear compiler bridge zinc cache on reboot dev (#9057) Reboot dev now removes zinc/org.scala-sbt under the resolved global zinc directory (same layout as ZincComponentManager secondary cache). Closes #5735 --- .../main/scala/sbt/BasicCommandStrings.scala | 4 +- main/src/main/scala/sbt/MainLoop.scala | 14 +++++- .../scala/sbt/MainLoopZincCacheTest.scala | 49 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 main/src/test/scala/sbt/MainLoopZincCacheTest.scala 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/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/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 From 840887820c57b9828025dac33ef038fd289ca410 Mon Sep 17 00:00:00 2001 From: bitloi <89318445+bitloi@users.noreply.github.com> Date: Mon, 13 Apr 2026 05:30:45 -0300 Subject: [PATCH 21/24] [2.x] fix: Fix FileAlreadyExistsException in packageDirectory (#9047) **Problem** Intermittent java.nio.file.FileAlreadyExistsException when publishing classes.sbtdir.zip during action-cache packaging under parallel tasks (#9043). Copying from a temp directory straight into the final path races on the fixed destination name. **Solution** Stage the built zip next to the destination with a unique temp file, then replace the final path via Files.move with REPLACE_EXISTING and ATOMIC_MOVE, falling back to a non-atomic move when needed. Add a concurrent packageDirectory test. Closes #9043 Generated-by: Cursor (AI-assisted) --- .../src/main/scala/sbt/util/ActionCache.scala | 34 +++++++++++++- .../test/scala/sbt/util/ActionCacheTest.scala | 44 ++++++++++++++++--- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/util-cache/src/main/scala/sbt/util/ActionCache.scala b/util-cache/src/main/scala/sbt/util/ActionCache.scala index 0b60013e3..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 @@ -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) From 8bcc6ae420c06d706c965fee289102df96e2c0d3 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Tue, 14 Apr 2026 04:07:32 +0900 Subject: [PATCH 22/24] [2.x] refactor: Use `IO.withTemporaryDirectory` instead of `File.createTempFile` (#9084) * Use IO.withTemporaryDirectory instead of File.createTempFile * fix JUnitXmlTestsListenerSpec --- .../scala/sbt/JUnitXmlTestsListenerSpec.scala | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala b/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala index 98074f666..959b5f538 100644 --- a/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala +++ b/testing/src/test/scala/sbt/JUnitXmlTestsListenerSpec.scala @@ -13,16 +13,14 @@ 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 verify.BasicTestSuite object JUnitXmlTestsListenerSpec extends BasicTestSuite: test("JUnitXmlTestsListener should log debug message when writing test report"): - val tempDir = File.createTempFile("junit-test", "") - tempDir.delete() - tempDir.mkdirs() - try + IO.withTemporaryDirectory: tempDir => val loggedMessages = new AtomicReference[List[String]](Nil) val mockLogger = new AbstractLogger: def getLevel: Level.Value = Level.Debug @@ -66,17 +64,9 @@ object JUnitXmlTestsListenerSpec extends BasicTestSuite: messages.exists(_.contains("TEST-TestSuite.xml")), s"Expected log message containing 'TEST-TestSuite.xml', but got: $messages" ) - finally - // Cleanup - if tempDir.exists() then - tempDir.listFiles().foreach(_.delete()) - tempDir.delete() test("JUnitXmlTestsListener should handle null logger gracefully"): - val tempDir = File.createTempFile("junit-test", "") - tempDir.delete() - tempDir.mkdirs() - try + IO.withTemporaryDirectory: tempDir => val listener = new JUnitXmlTestsListener(tempDir, false, null) listener.doInit() listener.startGroup("TestSuite") @@ -97,9 +87,5 @@ object JUnitXmlTestsListenerSpec extends BasicTestSuite: // Verify XML file was still created val xmlFile = new File(tempDir, "TEST-TestSuite.xml") assert(xmlFile.exists(), "XML file should be created even when logger is null") - finally - if tempDir.exists() then - tempDir.listFiles().foreach(_.delete()) - tempDir.delete() end JUnitXmlTestsListenerSpec From dd93c92ebc19b3998aa004adefc21acb1b8e19b4 Mon Sep 17 00:00:00 2001 From: Jonathan Chang Date: Wed, 15 Apr 2026 05:03:48 +0200 Subject: [PATCH 23/24] [2.x] feat: Add English aliases cross and switch for + / ++ (#9051) Fixes #3137. Adds discoverable English-name alternatives for the symbolic cross-build commands using the | parser combinator, making them more beginner-friendly without adding separate commands. --- main/src/main/scala/sbt/Cross.scala | 25 +++++++++++-------- .../scala/sbt/internal/CommandStrings.scala | 12 ++++++--- .../sbt-test/actions/cross-alias/build.sbt | 9 +++++++ sbt-app/src/sbt-test/actions/cross-alias/test | 11 ++++++++ 4 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 sbt-app/src/sbt-test/actions/cross-alias/build.sbt create mode 100644 sbt-app/src/sbt-test/actions/cross-alias/test 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/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/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 From 60cbde453f6eca69b38c7109f0e94136b1e052f4 Mon Sep 17 00:00:00 2001 From: Full Stack Developer <30417830+jsdevninja@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:07:51 -0500 Subject: [PATCH 24/24] [2.x] feat: Implement BSP buildTarget/dependencyModules (#9085) - implement BSP buildTarget/dependencyModules endpoint support in sbt --- main/src/main/scala/sbt/Keys.scala | 2 + .../internal/server/BuildServerProtocol.scala | 49 ++++++++++++++++ .../sbt/internal/bsp/DependencyModule.scala | 57 +++++++++++++++++++ .../internal/bsp/DependencyModulesItem.scala | 40 +++++++++++++ .../bsp/DependencyModulesParams.scala | 33 +++++++++++ .../bsp/DependencyModulesResult.scala | 33 +++++++++++ .../bsp/codec/DependencyModuleFormats.scala | 33 +++++++++++ .../codec/DependencyModulesItemFormats.scala | 29 ++++++++++ .../DependencyModulesParamsFormats.scala | 27 +++++++++ .../DependencyModulesResultFormats.scala | 27 +++++++++ .../sbt/internal/bsp/codec/JsonProtocol.scala | 4 ++ protocol/src/main/contraband/bsp.contra | 29 ++++++++++ .../test/scala/testpkg/BuildServerTest.scala | 18 ++++++ 13 files changed, 381 insertions(+) create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModule.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesItem.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesParams.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/DependencyModulesResult.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModuleFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesItemFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesParamsFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/DependencyModulesResultFormats.scala 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/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/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/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