From 08469d8b8ce3ff17120e22f18b5bb40f00643d36 Mon Sep 17 00:00:00 2001 From: MkDev11 Date: Sun, 11 Jan 2026 04:17:24 -0800 Subject: [PATCH 1/3] [2.x] fix: Upgrade launcher-package to sbt 1.10.7 and replace dispatch-http (#8465) Fixes #7826 Changes: - Upgrade sbt version from 0.13.18 to 1.10.7 in launcher-package/project/build.properties - Remove dead dispatch-http 0.8.10 dependency from plugins.sbt - Update sbt-native-packager from 1.7.6 to 1.10.4 (com.github.sbt) - Update sbt-pgp from 2.1.2 to 2.3.1 - Replace all dispatch.classic._ HTTP calls with Java URL.openStream() The dispatch-http library is dead and doesn't support sbt 1.x. This change uses standard Java networking APIs for HTTP downloads, eliminating the dependency on outdated libraries. All HTTP download operations now use: - java.net.URL.openConnection() - InputStream with buffered reading - Proper resource cleanup with try-finally blocks - Removes the sbt-export-repo plugin dependency - Defines exportRepo and exportRepoDirectory settings locally - Removes ExportRepoPlugin from dist project - Fixes exportRepo task to not reference itself * fix: Update PackageSignerPlugin.scala for sbt 1.x compatibility - Use withExtension instead of copy(extension = ...) for Artifact - Update Classpaths.publishConfig to use sbt 1.x signature with all required parameters - Use new slash syntax for scoped keys (pgpSigner / skip instead of skip in pgpSigner) * fix: Use computed status instead of status key in PackageSignerPlugin * fix: Use withRevision instead of copy for ModuleID in build.sbt - Add scala.sys.process._ import for shell command execution (! method) - Fix resolvers setting to use new slash syntax and proper Option handling - Fix publish/publishLocal to use {} instead of () to avoid deprecation warning - Fix Classpaths.publishTask to use variant without delivery key (deprecated since 1.1.1) - Add scala.sys.process._ import for shell command execution - Fix publish/publishLocal to use {} instead of () - Remove problematic resolvers setting that caused task/setting dependency issues - Use IvyActions.publish directly instead of deprecated Classpaths.publishTask - Fix ModuleID.copy to use withRevision - Fix Artifact.copy to use withExtension - Use computed status value instead of status key Build now compiles successfully with sbt 1.10.7. --- launcher-package/build.sbt | 135 +++++++++++++----- .../project/PackageSignerPlugin.scala | 47 ++++-- launcher-package/project/build.properties | 2 +- launcher-package/project/plugins.sbt | 6 +- 4 files changed, 138 insertions(+), 52 deletions(-) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index 53e00cb55..cfefbf7da 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -1,4 +1,5 @@ import scala.util.control.Exception.catching +import scala.sys.process._ import NativePackagerHelper._ import com.typesafe.sbt.packager.SettingsHelper._ import DebianConstants._ @@ -69,6 +70,8 @@ val debianBuildId = settingKey[Int]("build id for Debian") val exportRepoUsingCoursier = taskKey[File]("export Maven style repository") val exportRepoCsrDirectory = settingKey[File]("") +val exportRepo = taskKey[File]("export Ivy style repository") +val exportRepoDirectory = settingKey[File]("directory for exported repository") val universalMacPlatform = "universal-apple-darwin" val x86LinuxPlatform = "x86_64-pc-linux" @@ -109,13 +112,24 @@ val root = (project in file(".")). sbtLaunchJar := { val uri = sbtLaunchJarUrl.value val file = sbtLaunchJarLocation.value - import dispatch.classic._ if(!file.exists) { // oddly, some places require us to create the file before writing... IO.touch(file) + val url = new java.net.URL(uri) + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file)) - try Http(url(uri) >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } } // TODO - GPG Trust validation. file @@ -135,12 +149,23 @@ val root = (project in file(".")). val linuxX86Tar = t / linuxX86ImageTar val linuxAarch64Tar = t / linuxAarch64ImageTar val windowsZip = t / windowsImageZip - import dispatch.classic._ if(!macosUniversalTar.exists && !isWindows && sbtIncludeSbtn) { IO.touch(macosUniversalTar) + val url = new java.net.URL(s"$baseUrl/v$v/$macosUniversalImageTar") + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(macosUniversalTar)) - try Http(url(s"$baseUrl/v$v/$macosUniversalImageTar") >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } val platformDir = t / universalMacPlatform IO.createDirectory(platformDir) s"tar zxvf $macosUniversalTar --directory $platformDir".! @@ -148,9 +173,21 @@ val root = (project in file(".")). } if(!linuxX86Tar.exists && !isWindows && sbtIncludeSbtn) { IO.touch(linuxX86Tar) + val url = new java.net.URL(s"$baseUrl/v$v/$linuxX86ImageTar") + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(linuxX86Tar)) - try Http(url(s"$baseUrl/v$v/$linuxX86ImageTar") >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } val platformDir = t / x86LinuxPlatform IO.createDirectory(platformDir) s"""tar zxvf $linuxX86Tar --directory $platformDir""".! @@ -158,9 +195,21 @@ val root = (project in file(".")). } if(!linuxAarch64Tar.exists && !isWindows && sbtIncludeSbtn) { IO.touch(linuxAarch64Tar) + val url = new java.net.URL(s"$baseUrl/v$v/$linuxAarch64ImageTar") + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(linuxAarch64Tar)) - try Http(url(s"$baseUrl/v$v/$linuxAarch64ImageTar") >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } val platformDir = t / aarch64LinuxPlatform IO.createDirectory(platformDir) s"""tar zxvf $linuxAarch64Tar --directory $platformDir""".! @@ -168,9 +217,21 @@ val root = (project in file(".")). } if(!windowsZip.exists && sbtIncludeSbtn) { IO.touch(windowsZip) + val url = new java.net.URL(s"$baseUrl/v$v/$windowsImageZip") + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(windowsZip)) - try Http(url(s"$baseUrl/v$v/$windowsImageZip") >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } val platformDir = t / x86WindowsPlatform IO.unzip(windowsZip, platformDir) IO.move(platformDir / "sbtn.exe", t / x86WindowsImageName) @@ -336,22 +397,22 @@ val root = (project in file(".")). else Seq[(File, String)](base.getParentFile / "LICENSE" -> "LICENSE", base / "NOTICE" -> "NOTICE") }, - // Misccelaneous publishing stuff... - projectID in Debian := { + // Miscellaneous publishing stuff... + (Debian / projectID) := { val m = moduleID.value - m.copy(revision = (version in Debian).value) + m.withRevision((Debian / version).value) }, - projectID in Windows := { + (Windows / projectID) := { val m = moduleID.value - m.copy(revision = (version in Windows).value) + m.withRevision((Windows / version).value) }, - projectID in Rpm := { + (Rpm / projectID) := { val m = moduleID.value - m.copy(revision = (version in Rpm).value) + m.withRevision((Rpm / version).value) }, - projectID in Universal := { + (Universal / projectID) := { val m = moduleID.value - m.copy(revision = (version in Universal).value) + m.withRevision((Universal / version).value) } ) @@ -406,9 +467,7 @@ def makePublishToForConfig(config: Configuration) = { val resolver = Resolver.url(id, new URL(url))(Patterns(pattern)) Some(resolver) } - )) ++ Seq( - resolvers ++= ((publishTo in config) apply (_.toSeq)).value - ) + )) } def publishToSettings = @@ -416,19 +475,29 @@ def publishToSettings = def downloadUrl(uri: URI, out: File): Unit = { - import dispatch.classic._ if(!out.exists) { IO.touch(out) + val url = new java.net.URL(uri.toString) + val connection = url.openConnection() + val input = connection.getInputStream val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(out)) - try Http(url(uri.toString) >>> writer) - finally writer.close() + try { + val buffer = new Array[Byte](8192) + var bytesRead = input.read(buffer) + while (bytesRead != -1) { + writer.write(buffer, 0, bytesRead) + bytesRead = input.read(buffer) + } + } finally { + input.close() + writer.close() + } } } def colonName(m: ModuleID): String = s"${m.organization}:${m.name}:${m.revision}" lazy val dist = (project in file("dist")) - .enablePlugins(ExportRepoPlugin) .settings( name := "dist", scalaVersion := { @@ -437,19 +506,19 @@ lazy val dist = (project in file("dist")) }, libraryDependencies ++= Seq(sbtActual, jansi, scala212Compiler, scala212Jline, scala212Xml) ++ sbt013ExtraDeps, exportRepo := { - val old = exportRepo.value + val outDir = exportRepoDirectory.value sbtVersionToRelease match { case v if v.startsWith("1.") => sys.error("sbt 1.x should use coursier") case v if v.startsWith("0.13.") => - val outbase = exportRepoDirectory.value / "org.scala-sbt" / "compiler-interface" / v + val outbase = outDir / "org.scala-sbt" / "compiler-interface" / v val uribase = s"https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/compiler-interface/$v/" downloadUrl(uri(uribase + "ivys/ivy.xml"), outbase / "ivys" / "ivy.xml") downloadUrl(uri(uribase + "jars/compiler-interface.jar"), outbase / "jars" / "compiler-interface.jar") downloadUrl(uri(uribase + "srcs/compiler-interface-sources.jar"), outbase / "srcs" / "compiler-interface-sources.jar") case _ => } - old + outDir }, exportRepoDirectory := target.value / "lib" / "local-preloaded", exportRepoCsrDirectory := exportRepoDirectory.value, @@ -481,7 +550,7 @@ lazy val dist = (project in file("dist")) outDirectory }, conflictWarning := ConflictWarning.disable, - publish := (), - publishLocal := (), + publish := {}, + publishLocal := {}, resolvers += Resolver.typesafeIvyRepo("releases") ) diff --git a/launcher-package/project/PackageSignerPlugin.scala b/launcher-package/project/PackageSignerPlugin.scala index c81401872..c819dff6c 100644 --- a/launcher-package/project/PackageSignerPlugin.scala +++ b/launcher-package/project/PackageSignerPlugin.scala @@ -1,5 +1,6 @@ import sbt._ import Keys._ +import sbt.internal.librarymanagement.IvyActions import com.jsuereth.sbtpgp.SbtPgp import com.typesafe.sbt.packager.universal.{ UniversalPlugin, UniversalDeployPlugin } import com.typesafe.sbt.packager.debian.{ DebianPlugin, DebianDeployPlugin } @@ -21,13 +22,13 @@ object PackageSignerPlugin extends sbt.AutoPlugin { inConfig(Rpm)(packageSignerSettings) def subExtension(art: Artifact, ext: String): Artifact = - art.copy(extension = ext) + art.withExtension(ext) def packageSignerSettings: Seq[Setting[_]] = Seq( signedArtifacts := { val artifacts = packagedArtifacts.value val r = pgpSigner.value - val skipZ = (skip in pgpSigner).value + val skipZ = (pgpSigner / skip).value val s = streams.value if (!skipZ) { artifacts flatMap { case (art, f) => @@ -39,21 +40,39 @@ object PackageSignerPlugin extends sbt.AutoPlugin { else artifacts }, publishSignedConfiguration := Classpaths.publishConfig( - signedArtifacts.value, - None, + publishMavenStyle = publishMavenStyle.value, + deliverIvyPattern = (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", + status = if (isSnapshot.value) "integration" else "release", + configurations = Vector.empty, + artifacts = signedArtifacts.value.toVector, + checksums = (publish / checksums).value.toVector, resolverName = Classpaths.getPublishTo(publishTo.value).name, - checksums = (checksums in publish).value, - logging = ivyLoggingLevel.value, - overwrite = isSnapshot.value), + logging = ivyLoggingLevel.value, + overwrite = isSnapshot.value), publishLocalSignedConfiguration := Classpaths.publishConfig( - signedArtifacts.value, - None, + publishMavenStyle = publishMavenStyle.value, + deliverIvyPattern = (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", + status = if (isSnapshot.value) "integration" else "release", + configurations = Vector.empty, + artifacts = signedArtifacts.value.toVector, + checksums = (publish / checksums).value.toVector, resolverName = "local", - checksums = (checksums in publish).value, - logging = ivyLoggingLevel.value, - overwrite = isSnapshot.value), - publishSigned := Classpaths.publishTask(publishSignedConfiguration, deliver).value, - publishLocalSigned := Classpaths.publishTask(publishLocalSignedConfiguration, deliver).value + logging = ivyLoggingLevel.value, + overwrite = isSnapshot.value), + publishSigned := Def.taskDyn { + val config = publishSignedConfiguration.value + val s = streams.value + Def.task { + IvyActions.publish(ivyModule.value, config, s.log) + } + }.value, + publishLocalSigned := Def.taskDyn { + val config = publishLocalSignedConfiguration.value + val s = streams.value + Def.task { + IvyActions.publish(ivyModule.value, config, s.log) + } + }.value ) } diff --git a/launcher-package/project/build.properties b/launcher-package/project/build.properties index 8e682c526..73df629ac 100644 --- a/launcher-package/project/build.properties +++ b/launcher-package/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.18 +sbt.version=1.10.7 diff --git a/launcher-package/project/plugins.sbt b/launcher-package/project/plugins.sbt index e4069706c..10a9e1796 100644 --- a/launcher-package/project/plugins.sbt +++ b/launcher-package/project/plugins.sbt @@ -1,4 +1,2 @@ -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.6") -libraryDependencies += "net.databinder" %% "dispatch-http" % "0.8.10" -addSbtPlugin("com.eed3si9n" % "sbt-export-repo" % "0.1.1") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") From 5bdb08ea2320d0b05f3de408e2af035940bf5390 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 11 Jan 2026 15:24:50 -0500 Subject: [PATCH 2/3] ci: Use slash syntax in build.sbt --- launcher-package/build.sbt | 99 ++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index cfefbf7da..b9fed1866 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -82,8 +82,8 @@ val x86LinuxImageName = s"sbtn-$x86LinuxPlatform" val aarch64LinuxImageName = s"sbtn-$aarch64LinuxPlatform" val x86WindowsImageName = s"sbtn-$x86WindowsPlatform.exe" -organization in ThisBuild := "org.scala-sbt" -version in ThisBuild := "0.1.0" +ThisBuild / organization := "org.scala-sbt" +ThisBuild / version := "0.1.0" // This build creates a SBT plugin with handy features *and* bundles the SBT script for distribution. val root = (project in file(".")). @@ -94,7 +94,7 @@ val root = (project in file(".")). packageName := "sbt", crossTarget := target.value, clean := { - val _ = (clean in dist).value + val _ = (dist / clean).value clean.value }, credentials ++= { @@ -261,30 +261,30 @@ val root = (project in file(".")). // DEBIAN SPECIFIC debianBuildId := sys.props.getOrElse("sbt.build.patch", sys.env.getOrElse("DIST_PATCHVER", "0")).toInt, - version in Debian := { + Debian / version := { if (debianBuildId.value == 0) sbtVersionToRelease else sbtVersionToRelease + "." + debianBuildId.value }, // Used to have "openjdk-8-jdk" but that doesn't work on Ubuntu 14.04 https://github.com/sbt/sbt/issues/3105 // before that we had java6-runtime-headless" and that was pulling in JDK9 on Ubuntu 16.04 https://github.com/sbt/sbt/issues/2931 - debianPackageDependencies in Debian ++= Seq("bash (>= 3.2)"), - debianPackageRecommends in Debian += "git", - linuxPackageMappings in Debian += { + Debian / debianPackageDependencies ++= Seq("bash (>= 3.2)", "curl | wget"), + Debian / debianPackageRecommends += "git", + Debian / linuxPackageMappings += { val bd = sourceDirectory.value (packageMapping( (bd / "debian" / "changelog") -> "/usr/share/doc/sbt/changelog.gz" ) withUser "root" withGroup "root" withPerms "0644" gzipped) asDocs() }, - debianChangelog in Debian := { Some(sourceDirectory.value / "debian" / "changelog") }, - addPackage(Debian, packageBin in Debian, "deb"), - debianNativeBuildOptions in Debian := Seq("-Zgzip", "-z3"), + Debian / debianChangelog := { Some(sourceDirectory.value / "debian" / "changelog") }, + addPackage(Debian, (Debian / packageBin), "deb"), + Debian / debianNativeBuildOptions := Seq("-Zgzip", "-z3"), // use the following instead of DebianDeployPlugin to skip changelog - makeDeploymentSettings(Debian, packageBin in Debian, "deb"), + makeDeploymentSettings(Debian, (Debian / packageBin), "deb"), // RPM SPECIFIC rpmRelease := debianBuildId.value.toString, - version in Rpm := { + Rpm / version := { val stable0 = (sbtVersionToRelease split "[^\\d]" filterNot (_.isEmpty) mkString ".") val stable = if (rpmRelease.value == "0") stable0 else stable0 + "." + rpmRelease.value @@ -294,8 +294,8 @@ val root = (project in file(".")). else stable }, // remove sbtn from RPM because it complains about it being noarch - linuxPackageMappings in Rpm := { - val orig = (linuxPackageMappings in Rpm).value + Rpm / linuxPackageMappings := { + val orig = ((Rpm / linuxPackageMappings)).value val nativeMappings = sbtnJarsMappings.value orig.map(o => o.copy(mappings = o.mappings.toList filterNot { case (x, p) => p.contains("sbtn-x86_64") || p.contains("sbtn-aarch64") @@ -313,7 +313,7 @@ val root = (project in file(".")). // WINDOWS SPECIFIC windowsBuildId := 0, - version in Windows := { + Windows / version := { val bid = windowsBuildId.value val sv = sbtVersionToRelease (sv split "[^\\d]" filterNot (_.isEmpty)) match { @@ -323,26 +323,26 @@ val root = (project in file(".")). case Array(major) => Seq(major, "0", "0", bid.toString) mkString "." } }, - maintainer in Windows := "Scala Center", - packageSummary in Windows := "sbt " + (version in Windows).value, - packageDescription in Windows := "The interactive build tool.", + Windows / maintainer := "Scala Center", + Windows / packageSummary := "sbt " + (Windows / version).value, + Windows / packageDescription := "The interactive build tool.", wixProductId := "ce07be71-510d-414a-92d4-dff47631848a", - wixProductUpgradeId := Hash.toHex(Hash((version in Windows).value)).take(32), + wixProductUpgradeId := Hash.toHex(Hash((Windows / version).value)).take(32), javacOptions := Seq("-source", "1.8", "-target", "1.8"), // Universal ZIP download install. - packageName in Universal := packageName.value, // needs to be set explicitly due to a bug in native-packager - name in Windows := packageName.value, - packageName in Windows := packageName.value, - version in Universal := sbtVersionToRelease, + Universal / packageName := packageName.value, // needs to be set explicitly due to a bug in native-packager + Windows / name := packageName.value, + Windows / packageName := packageName.value, + Universal / version := sbtVersionToRelease, - mappings in Universal += { + Universal / mappings += { (baseDirectory.value.getParentFile / "sbt") -> ("bin" + java.io.File.separator + "sbt") }, - mappings in Universal := { - val t = (target in Universal).value - val prev = (mappings in Universal).value + Universal / mappings := { + val t = (Universal / target).value + val prev = (Universal / mappings).value val BinSbt = "bin" + java.io.File.separator + "sbt" val BinBat = BinSbt + ".bat" prev.toList map { @@ -368,7 +368,7 @@ val root = (project in file(".")). case (k, v) => (k, v) } }, - mappings in Universal ++= (Def.taskDyn { + Universal / mappings ++= (Def.taskDyn { if (sbtIncludeSbtLaunch) Def.task { Seq( @@ -377,40 +377,40 @@ val root = (project in file(".")). } else Def.task { Seq[(File, String)]() } }).value, - mappings in Universal ++= sbtnJarsMappings.value, - mappings in Universal ++= (Def.taskDyn { + Universal / mappings ++= sbtnJarsMappings.value, + Universal / mappings ++= (Def.taskDyn { if (sbtOfflineInstall && sbtVersionToRelease.startsWith("1.")) Def.task { - val _ = (exportRepoUsingCoursier in dist).value - directory((target in dist).value / "lib") + val _ = ((dist / exportRepoUsingCoursier)).value + directory(((dist / target)).value / "lib") } else if (sbtOfflineInstall) Def.task { - val _ = (exportRepo in dist).value - directory((target in dist).value / "lib") + val _ = ((dist / exportRepo)).value + directory(((dist / target)).value / "lib") } else Def.task { Seq[(File, String)]() } }).value, - mappings in Universal ++= { + Universal / mappings ++= { val base = baseDirectory.value if (sbtVersionToRelease startsWith "0.13.") Nil else Seq[(File, String)](base.getParentFile / "LICENSE" -> "LICENSE", base / "NOTICE" -> "NOTICE") }, // Miscellaneous publishing stuff... - (Debian / projectID) := { + Debian / projectID := { val m = moduleID.value m.withRevision((Debian / version).value) }, - (Windows / projectID) := { + Windows / projectID := { val m = moduleID.value m.withRevision((Windows / version).value) }, - (Rpm / projectID) := { + Rpm / projectID := { val m = moduleID.value m.withRevision((Rpm / version).value) }, - (Universal / projectID) := { + Universal / projectID := { val m = moduleID.value m.withRevision((Universal / version).value) } @@ -426,16 +426,21 @@ lazy val integrationTest = (project in file("integration-test")) "org.scala-sbt" %% "io" % "1.10.5" % Test ), testFrameworks += new TestFramework("minitest.runner.Framework"), - test in Test := { - (test in Test).dependsOn(((packageBin in Universal) in LocalRootProject).dependsOn(((stage in (Universal) in LocalRootProject)))).value + Test / test := { + (Test / test) + .dependsOn(LocalRootProject / Universal / packageBin) + .dependsOn(LocalRootProject / Universal / stage).value }, - testOnly in Test := { - (testOnly in Test).dependsOn(((packageBin in Universal) in LocalRootProject).dependsOn(((stage in (Universal) in LocalRootProject)))).evaluated + Test / testOnly := { + (Test / testOnly) + .dependsOn(LocalRootProject / Universal / packageBin) + .dependsOn(LocalRootProject / Universal / stage) + .evaluated }, - parallelExecution in Test := false + Test / parallelExecution := false ) -def downloadUrlForVersion(v: String) = (v split "[^\\d]" flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match { +def downloadUrlForVersion(v: String) = (v.split("[^\\d]") flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match { case Array(0, 11, 3, _*) => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.11.3-2/sbt-launch.jar" case Array(0, 11, x, _*) if x >= 3 => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" case Array(0, y, _*) if y >= 12 => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" @@ -525,8 +530,8 @@ lazy val dist = (project in file("dist")) exportRepoUsingCoursier := { val outDirectory = exportRepoCsrDirectory.value val csr = - if (isWindows) (baseDirectory in LocalRootProject).value / "bin" / "coursier.bat" - else (baseDirectory in LocalRootProject).value / "bin" / "coursier" + if (isWindows) (LocalRootProject / baseDirectory).value / "bin" / "coursier.bat" + else (LocalRootProject / baseDirectory).value / "bin" / "coursier" val cache = target.value / "coursier" IO.delete(cache) val v = sbtVersionToRelease From 622add0e6171bf81818a3f09aba068ecf4c62241 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Sun, 11 Jan 2026 20:57:59 -0500 Subject: [PATCH 3/3] [2.x] ci: Integrate launcher package (#8485) **Problem/Solution** This is a follow up to launcher package migrating to sbt 1.x. This joins the launcher package into the main build. --- .github/workflows/client-test.yml | 19 +- .scalafmt.conf | 3 +- build.sbt | 31 ++ launcher-package/build.sbt | 52 +-- ...nerTest.scala => ExtendedRunnerTest.scala} | 48 +- .../src/test/scala/InheritInput.scala | 17 - .../src/test/scala/PowerAssertions.scala | 7 - .../src/test/scala/Process.scala | 216 --------- .../src/test/scala/ProcessImpl.scala | 433 ------------------ .../test/scala/RunnerMemoryScriptTest.scala | 77 ++++ .../src/test/scala/RunnerScriptTest.scala | 125 +++++ .../src/test/scala/ScriptTest.scala | 263 ----------- .../src/test/scala/ShellScriptUtil.scala | 71 +++ .../src/test/scala/SyncVar.scala | 38 -- launcher-package/project/build.properties | 1 - launcher-package/project/plugins.sbt | 2 - project/Dependencies.scala | 1 + .../PackageSignerPlugin.scala | 45 +- project/plugins.sbt | 2 + 19 files changed, 384 insertions(+), 1067 deletions(-) rename launcher-package/integration-test/src/test/scala/{RunnerTest.scala => ExtendedRunnerTest.scala} (75%) delete mode 100644 launcher-package/integration-test/src/test/scala/InheritInput.scala delete mode 100644 launcher-package/integration-test/src/test/scala/PowerAssertions.scala delete mode 100644 launcher-package/integration-test/src/test/scala/Process.scala delete mode 100644 launcher-package/integration-test/src/test/scala/ProcessImpl.scala create mode 100644 launcher-package/integration-test/src/test/scala/RunnerMemoryScriptTest.scala create mode 100644 launcher-package/integration-test/src/test/scala/RunnerScriptTest.scala delete mode 100644 launcher-package/integration-test/src/test/scala/ScriptTest.scala create mode 100644 launcher-package/integration-test/src/test/scala/ShellScriptUtil.scala delete mode 100644 launcher-package/integration-test/src/test/scala/SyncVar.scala delete mode 100644 launcher-package/project/build.properties delete mode 100644 launcher-package/project/plugins.sbt rename {launcher-package/project => project}/PackageSignerPlugin.scala (70%) diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml index 4b7c51903..83dc3a1f0 100644 --- a/.github/workflows/client-test.yml +++ b/.github/workflows/client-test.yml @@ -58,10 +58,9 @@ jobs: ./client/target/bin/sbtn --sbt-script=$(pwd)/sbt shutdown # test launcher script echo build using JDK 8 test using JDK 8 and JDK 25 - cd launcher-package - sbt -Dsbt.build.version=$TEST_SBT_VER rpm:packageBin debian:packageBin - sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test - cd citest && ./test.sh + sbt -Dsbt.build.version=$TEST_SBT_VER launcherPackage/Rpm/packageBin launcherPackage/Debian/packageBin + sbt -Dsbt.build.version=$TEST_SBT_VER launcherPackageIntegrationTest/test + cd launcher-package/citest && ./test.sh JAVA_HOME="$JAVA_HOME_25_X64" PATH="$JAVA_HOME_25_X64/bin:$PATH" java -Xmx32m -version @@ -73,9 +72,8 @@ jobs: # test building sbtn on macOS ./sbt "-Dsbt.io.virtual=false" nativeImage # test launcher script - cd launcher-package - bin/coursier resolve - sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test + launcher-package/bin/coursier resolve + sbt -Dsbt.build.version=$TEST_SBT_VER launcherPackageIntegrationTest/test # This fails due to the JLine issue # cd citest && ./test.sh - name: Client test (Windows) @@ -89,9 +87,8 @@ jobs: ./client/target/bin/sbtn --sbt-script=$(pwd)/launcher-package/src/universal/bin/sbt.bat shutdown # test launcher script echo build using JDK 8 test using JDK 8 and JDK 25 - cd launcher-package - bin/coursier.bat resolve - sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test - cd citest + launcher-package/bin/coursier.bat resolve + sbt -Dsbt.build.version=$TEST_SBT_VER launcherPackageIntegrationTest/test + cd launcher-package/citest ./test.bat test3/test3.bat diff --git a/.scalafmt.conf b/.scalafmt.conf index 06a114f59..8888e3585 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -24,5 +24,6 @@ trailingCommas = preserve # TODO update scalafmt and enable Scala 3 project.excludeFilters = [ - "internal/util-position/src/main/scala-3/sbt/internal/util/SourcePositionMacro.scala" + "internal/util-position/src/main/scala-3/sbt/internal/util/SourcePositionMacro.scala", + "launcher-package/integration-test/src/test/scala/*", ] diff --git a/build.sbt b/build.sbt index 37f9e5e55..695456f8f 100644 --- a/build.sbt +++ b/build.sbt @@ -54,6 +54,8 @@ Global / excludeLint := (Global / excludeLint).?.value.getOrElse(Set.empty) Global / excludeLint += componentID Global / excludeLint += scriptedBufferLog Global / excludeLint += checkPluginCross +Global / excludeLint += nativeImageJvm +Global / excludeLint += nativeImageVersion def commonBaseSettings: Seq[Setting[_]] = Def.settings( headerLicense := Some( @@ -1508,3 +1510,32 @@ ThisBuild / publishTo := { else localStaging.value } ThisBuild / publishMavenStyle := true + +lazy val launcherPackage = (project in file("launcher-package")) +lazy val launcherPackageIntegrationTest = + (project in (file("launcher-package") / "integration-test")) + .settings( + name := "integration-test", + scalaVersion := scala3, + libraryDependencies ++= Seq( + scalaVerify % Test, + hedgehog % Test, + // This needs to be hardcoded here, and not use addSbtIO + "org.scala-sbt" %% "io" % "1.10.5" % Test, + ), + testFrameworks += TestFramework("hedgehog.sbt.Framework"), + testFrameworks += TestFramework("verify.runner.Framework"), + Test / test := { + (Test / test) + .dependsOn(launcherPackage / Universal / packageBin) + .dependsOn(launcherPackage / Universal / stage) + .value + }, + Test / testOnly := { + (Test / testOnly) + .dependsOn(launcherPackage / Universal / packageBin) + .dependsOn(launcherPackage / Universal / stage) + .evaluated + }, + Test / parallelExecution := false + ) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index b9fed1866..a835e698e 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -1,8 +1,9 @@ import scala.util.control.Exception.catching -import scala.sys.process._ -import NativePackagerHelper._ -import com.typesafe.sbt.packager.SettingsHelper._ -import DebianConstants._ +import scala.sys.process.* +import NativePackagerHelper.* +import com.typesafe.sbt.packager.SettingsHelper.* +import DebianConstants.* +import Dependencies.* lazy val sbtOfflineInstall = sys.props.getOrElse("sbt.build.offline", sys.env.getOrElse("sbt.build.offline", "false")) match { @@ -22,12 +23,10 @@ lazy val sbtIncludeSbtLaunch = case "false" | "0" => false case _ => false } -lazy val sbtVersionToRelease = sys.props.getOrElse("sbt.build.version", sys.env.getOrElse("sbt.build.version", { - sys.error("-Dsbt.build.version must be set") - })) +lazy val sbtVersionToRelease = sys.props + .getOrElse("sbt.build.version", sys.env.getOrElse("sbt.build.version", "1.12.0")) lazy val scala210 = "2.10.7" -lazy val scala212 = "2.12.21" lazy val scala210Jline = "org.scala-lang" % "jline" % scala210 lazy val jansi = { if (sbtVersionToRelease startsWith "1.") "org.fusesource.jansi" % "jansi" % "1.12" @@ -82,11 +81,10 @@ val x86LinuxImageName = s"sbtn-$x86LinuxPlatform" val aarch64LinuxImageName = s"sbtn-$aarch64LinuxPlatform" val x86WindowsImageName = s"sbtn-$x86WindowsPlatform.exe" -ThisBuild / organization := "org.scala-sbt" -ThisBuild / version := "0.1.0" +Global / excludeLintKeys += bintrayGenericPackagesUrl // This build creates a SBT plugin with handy features *and* bundles the SBT script for distribution. -val root = (project in file(".")). +val launcherPackage = (project in file(".")). enablePlugins(UniversalPlugin, LinuxPlugin, DebianPlugin, RpmPlugin, WindowsPlugin, UniversalDeployPlugin, RpmDeployPlugin, WindowsDeployPlugin). settings( @@ -289,16 +287,16 @@ val root = (project in file(".")). val stable = if (rpmRelease.value == "0") stable0 else stable0 + "." + rpmRelease.value if (isExperimental) ((sbtVersionToRelease split "[^\\d]" filterNot (_.isEmpty)).toList match { - case List(a, b, c, d) => List(0, 99, c, d).mkString(".") + case List(_, _, c, d) => List(0, 99, c, d).mkString(".") }) else stable }, // remove sbtn from RPM because it complains about it being noarch Rpm / linuxPackageMappings := { val orig = ((Rpm / linuxPackageMappings)).value - val nativeMappings = sbtnJarsMappings.value + val _ = sbtnJarsMappings.value orig.map(o => o.copy(mappings = o.mappings.toList filterNot { - case (x, p) => p.contains("sbtn-x86_64") || p.contains("sbtn-aarch64") + case (_, p) => p.contains("sbtn-x86_64") || p.contains("sbtn-aarch64") })) }, rpmVendor := "scalacenter", @@ -416,30 +414,6 @@ val root = (project in file(".")). } ) -lazy val integrationTest = (project in file("integration-test")) - .settings( - name := "integration-test", - scalaVersion := scala212, - libraryDependencies ++= Seq( - "io.monix" %% "minitest" % "2.3.2" % Test, - "com.eed3si9n.expecty" %% "expecty" % "0.11.0" % Test, - "org.scala-sbt" %% "io" % "1.10.5" % Test - ), - testFrameworks += new TestFramework("minitest.runner.Framework"), - Test / test := { - (Test / test) - .dependsOn(LocalRootProject / Universal / packageBin) - .dependsOn(LocalRootProject / Universal / stage).value - }, - Test / testOnly := { - (Test / testOnly) - .dependsOn(LocalRootProject / Universal / packageBin) - .dependsOn(LocalRootProject / Universal / stage) - .evaluated - }, - Test / parallelExecution := false - ) - def downloadUrlForVersion(v: String) = (v.split("[^\\d]") flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match { case Array(0, 11, 3, _*) => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.11.3-2/sbt-launch.jar" case Array(0, 11, x, _*) if x >= 3 => "https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/"+v+"/sbt-launch.jar" @@ -449,8 +423,6 @@ def downloadUrlForVersion(v: String) = (v.split("[^\\d]") flatMap (i => catching } def makePublishToForConfig(config: Configuration) = { - val v = sbtVersionToRelease - // Add the publish to and ensure global resolvers has the resolver we just configured. inConfig(config)(Seq( name := "sbt", diff --git a/launcher-package/integration-test/src/test/scala/RunnerTest.scala b/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala similarity index 75% rename from launcher-package/integration-test/src/test/scala/RunnerTest.scala rename to launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala index 7a9f38da8..1f4051277 100755 --- a/launcher-package/integration-test/src/test/scala/RunnerTest.scala +++ b/launcher-package/integration-test/src/test/scala/ExtendedRunnerTest.scala @@ -1,30 +1,36 @@ package example.test -import minitest._ -import scala.sys.process._ +import scala.sys.process.* import java.io.File import java.util.Locale import sbt.io.IO +import verify.BasicTestSuite -object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { +object ExtendedRunnerTest extends BasicTestSuite: // 1.3.0, 1.3.0-M4 private[test] val versionRegEx = "\\d(\\.\\d+){2}(-\\w+)?" lazy val isWindows: Boolean = sys.props("os.name").toLowerCase(Locale.ENGLISH).contains("windows") lazy val isMac: Boolean = sys.props("os.name").toLowerCase(Locale.ENGLISH).contains("mac") lazy val sbtScript = - if (isWindows) new File("target/universal/stage/bin/sbt.bat") - else new File("target/universal/stage/bin/sbt") + if (isWindows) new File("launcher-package/target/universal/stage/bin/sbt.bat") + else new File("launcher-package/target/universal/stage/bin/sbt") - def sbtProcess(args: String*) = sbtProcessWithOpts(args: _*)("", "") + def sbtProcess(args: String*) = sbtProcessWithOpts(args*)("", "") def sbtProcessWithOpts(args: String*)(javaOpts: String, sbtOpts: String) = - sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, new File("citest"), + Process( + Seq(sbtScript.getAbsolutePath) ++ args, + new File("launcher-package/citest"), "JAVA_OPTS" -> javaOpts, - "SBT_OPTS" -> sbtOpts) + "SBT_OPTS" -> sbtOpts + ) def sbtProcessInDir(dir: File)(args: String*) = - sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, dir, + Process( + Seq(sbtScript.getAbsolutePath) ++ args, + dir, "JAVA_OPTS" -> "", - "SBT_OPTS" -> "") + "SBT_OPTS" -> "" + ) test("sbt runs") { assert(sbtScript.exists) @@ -52,12 +58,12 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { val out3 = sbtProcess("-V").!!.trim testVersion(out3.linesIterator.toList) } - */ + */ test("sbt -V in empty directory") { IO.withTemporaryDirectory { tmp => val out = sbtProcessInDir(tmp)("-V").!!.trim - val expectedVersion = "^"+versionRegEx+"$" + val expectedVersion = "^" + versionRegEx + "$" val targetDir = new File(tmp, "target") assert(!targetDir.exists, "expected target directory to not exist, but existed") } @@ -71,12 +77,18 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { assert(out.matches(expectedVersion)) () } - */ + */ test("sbt --sbt-jar should run") { - val out = sbtProcess("compile", "-v", "--sbt-jar", "../target/universal/stage/bin/sbt-launch.jar").!!.linesIterator.toList - assert(out.contains[String]("../target/universal/stage/bin/sbt-launch.jar") || - out.contains[String]("\"../target/universal/stage/bin/sbt-launch.jar\"") + val out = sbtProcess( + "compile", + "-v", + "--sbt-jar", + "../target/universal/stage/bin/sbt-launch.jar" + ).!!.linesIterator.toList + assert( + out.contains[String]("../target/universal/stage/bin/sbt-launch.jar") || + out.contains[String]("\"../target/universal/stage/bin/sbt-launch.jar\"") ) () } @@ -105,7 +117,7 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { test("sbt --script-version in empty directory") { IO.withTemporaryDirectory { tmp => val out = sbtProcessInDir(tmp)("--script-version").!!.trim - val expectedVersion = "^"+versionRegEx+"$" + val expectedVersion = "^" + versionRegEx + "$" assert(out.matches(expectedVersion)) } () @@ -126,4 +138,4 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { } () } -} +end ExtendedRunnerTest diff --git a/launcher-package/integration-test/src/test/scala/InheritInput.scala b/launcher-package/integration-test/src/test/scala/InheritInput.scala deleted file mode 100644 index d8304926d..000000000 --- a/launcher-package/integration-test/src/test/scala/InheritInput.scala +++ /dev/null @@ -1,17 +0,0 @@ -package sbt.internal - -import java.lang.{ ProcessBuilder => JProcessBuilder } - -private[sbt] object InheritInput { - def apply(p: JProcessBuilder): Boolean = (redirectInput, inherit) match { - case (Some(m), Some(f)) => - m.invoke(p, f); true - case _ => false - } - - private[this] val pbClass = Class.forName("java.lang.ProcessBuilder") - private[this] val redirectClass = pbClass.getClasses find (_.getSimpleName == "Redirect") - - private[this] val redirectInput = redirectClass map (pbClass.getMethod("redirectInput", _)) - private[this] val inherit = redirectClass map (_ getField "INHERIT" get null) -} diff --git a/launcher-package/integration-test/src/test/scala/PowerAssertions.scala b/launcher-package/integration-test/src/test/scala/PowerAssertions.scala deleted file mode 100644 index eb3671b76..000000000 --- a/launcher-package/integration-test/src/test/scala/PowerAssertions.scala +++ /dev/null @@ -1,7 +0,0 @@ -package example.test - -import com.eed3si9n.expecty.Expecty - -trait PowerAssertions { - lazy val assert: Expecty = new Expecty() -} diff --git a/launcher-package/integration-test/src/test/scala/Process.scala b/launcher-package/integration-test/src/test/scala/Process.scala deleted file mode 100644 index 40798e209..000000000 --- a/launcher-package/integration-test/src/test/scala/Process.scala +++ /dev/null @@ -1,216 +0,0 @@ -package sbt.internal - -import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } -import java.io.{ Closeable, File, IOException } -import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PipedInputStream, PipedOutputStream } -import java.net.URL - -trait ProcessExtra { - import Process._ - implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder) - implicit def fileToProcess(file: File): FilePartialBuilder = apply(file) - implicit def urlToProcess(url: URL): URLPartialBuilder = apply(url) - - implicit def buildersToProcess[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = applySeq(builders) - - implicit def stringToProcess(command: String): ProcessBuilder = apply(command) - implicit def stringSeqToProcess(command: Seq[String]): ProcessBuilder = apply(command) -} - -/** Methods for constructing simple commands that can then be combined. */ -object Process extends ProcessExtra { - def apply(command: String): ProcessBuilder = apply(command, None) - - def apply(command: Seq[String]): ProcessBuilder = apply(command.toArray, None) - - def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command :: arguments.toList, None) - /** create ProcessBuilder with working dir set to File and extra environment variables */ - def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder = - apply(command, Some(cwd), extraEnv: _*) - /** create ProcessBuilder with working dir set to File and extra environment variables */ - def apply(command: Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder = - apply(command, Some(cwd), extraEnv: _*) - /** create ProcessBuilder with working dir optionally set to File and extra environment variables */ - def apply(command: String, cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = { - apply(command.split("""\s+"""), cwd, extraEnv: _*) - // not smart to use this on windows, because CommandParser uses \ to escape ". - /*CommandParser.parse(command) match { - case Left(errorMsg) => error(errorMsg) - case Right((cmd, args)) => apply(cmd :: args, cwd, extraEnv : _*) - }*/ - } - /** create ProcessBuilder with working dir optionally set to File and extra environment variables */ - def apply(command: Seq[String], cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = { - val jpb = new JProcessBuilder(command.toArray: _*) - cwd.foreach(jpb directory _) - extraEnv.foreach { case (k, v) => jpb.environment.put(k, v) } - apply(jpb) - } - def apply(builder: JProcessBuilder): ProcessBuilder = new SimpleProcessBuilder(builder) - def apply(file: File): FilePartialBuilder = new FileBuilder(file) - def apply(url: URL): URLPartialBuilder = new URLBuilder(url) - - def applySeq[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = builders.map(convert) - - def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1) - def apply(name: String, exitValue: => Int): ProcessBuilder = new DummyProcessBuilder(name, exitValue) - - def cat(file: SourcePartialBuilder, files: SourcePartialBuilder*): ProcessBuilder = cat(file :: files.toList) - def cat(files: Seq[SourcePartialBuilder]): ProcessBuilder = - { - require(files.nonEmpty) - files.map(_.cat).reduceLeft(_ #&& _) - } -} - -trait SourcePartialBuilder extends NotNull { - /** Writes the output stream of this process to the given file. */ - def #>(f: File): ProcessBuilder = toFile(f, false) - /** Appends the output stream of this process to the given file. */ - def #>>(f: File): ProcessBuilder = toFile(f, true) - /** - * Writes the output stream of this process to the given OutputStream. The - * argument is call-by-name, so the stream is recreated, written, and closed each - * time this process is executed. - */ - def #>(out: => OutputStream): ProcessBuilder = #>(new OutputStreamBuilder(out)) - def #>(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(toSource, b, false, ExitCodes.firstIfNonzero) - private def toFile(f: File, append: Boolean) = #>(new FileOutput(f, append)) - def cat = toSource - protected def toSource: ProcessBuilder -} -trait SinkPartialBuilder extends NotNull { - /** Reads the given file into the input stream of this process. */ - def #<(f: File): ProcessBuilder = #<(new FileInput(f)) - /** Reads the given URL into the input stream of this process. */ - def #<(f: URL): ProcessBuilder = #<(new URLInput(f)) - /** - * Reads the given InputStream into the input stream of this process. The - * argument is call-by-name, so the stream is recreated, read, and closed each - * time this process is executed. - */ - def #<(in: => InputStream): ProcessBuilder = #<(new InputStreamBuilder(in)) - def #<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, toSink, false, ExitCodes.firstIfNonzero) - protected def toSink: ProcessBuilder -} - -trait URLPartialBuilder extends SourcePartialBuilder -trait FilePartialBuilder extends SinkPartialBuilder with SourcePartialBuilder { - def #<<(f: File): ProcessBuilder - def #<<(u: URL): ProcessBuilder - def #<<(i: => InputStream): ProcessBuilder - def #<<(p: ProcessBuilder): ProcessBuilder -} - -/** - * Represents a process that is running or has finished running. - * It may be a compound process with several underlying native processes (such as 'a #&& b`). - */ -trait Process extends NotNull { - /** Blocks until this process exits and returns the exit code.*/ - def exitValue(): Int - /** Destroys this process. */ - def destroy(): Unit -} -/** Represents a runnable process. */ -trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder { - /** - * Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is - * sent to the console. If the exit code is non-zero, an exception is thrown. - */ - def !! : String - /** - * Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is - * sent to the provided ProcessLogger. If the exit code is non-zero, an exception is thrown. - */ - def !!(log: ProcessLogger): String - /** - * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination and then throw an exception. - */ - def lines: Stream[String] - /** - * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. - */ - def lines(log: ProcessLogger): Stream[String] - /** - * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. - */ - def lines_! : Stream[String] - /** - * Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. - */ - def lines_!(log: ProcessLogger): Stream[String] - /** - * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the console. - */ - def ! : Int - /** - * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger. - */ - def !(log: ProcessLogger): Int - /** - * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the console. The newly started process reads from standard input of the current process. - */ - def !< : Int - /** - * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger. The newly started process reads from standard input of the current process. - */ - def !<(log: ProcessLogger): Int - /** Starts the process represented by this builder. Standard output and error are sent to the console.*/ - def run(): Process - /** Starts the process represented by this builder. Standard output and error are sent to the given ProcessLogger.*/ - def run(log: ProcessLogger): Process - /** Starts the process represented by this builder. I/O is handled by the given ProcessIO instance.*/ - def run(io: ProcessIO): Process - /** - * Starts the process represented by this builder. Standard output and error are sent to the console. - * The newly started process reads from standard input of the current process if `connectInput` is true. - */ - def run(connectInput: Boolean): Process - /** - * Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger. - * The newly started process reads from standard input of the current process if `connectInput` is true. - */ - def run(log: ProcessLogger, connectInput: Boolean): Process - - def runBuffered(log: ProcessLogger, connectInput: Boolean): Process - - /** Constructs a command that runs this command first and then `other` if this command succeeds.*/ - def #&&(other: ProcessBuilder): ProcessBuilder - /** Constructs a command that runs this command first and then `other` if this command does not succeed.*/ - def #||(other: ProcessBuilder): ProcessBuilder - /** - * Constructs a command that will run this command and pipes the output to `other`. - * `other` must be a simple command. - * The exit code will be that of `other` regardless of whether this command succeeds. - */ - def #|(other: ProcessBuilder): ProcessBuilder - /** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/ - def ###(other: ProcessBuilder): ProcessBuilder - - def canPipeTo: Boolean -} -/** Each method will be called in a separate thread.*/ -final class ProcessIO(val writeInput: OutputStream => Unit, val processOutput: InputStream => Unit, val processError: InputStream => Unit, val inheritInput: JProcessBuilder => Boolean) extends NotNull { - def withOutput(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, process, processError, inheritInput) - def withError(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, processOutput, process, inheritInput) - def withInput(write: OutputStream => Unit): ProcessIO = new ProcessIO(write, processOutput, processError, inheritInput) -} -trait ProcessLogger { - def info(s: => String): Unit - def error(s: => String): Unit - def buffer[T](f: => T): T -} diff --git a/launcher-package/integration-test/src/test/scala/ProcessImpl.scala b/launcher-package/integration-test/src/test/scala/ProcessImpl.scala deleted file mode 100644 index 7c8e4bc01..000000000 --- a/launcher-package/integration-test/src/test/scala/ProcessImpl.scala +++ /dev/null @@ -1,433 +0,0 @@ -package sbt.internal - -import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } -import java.io.{ BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream, PrintStream } -import java.io.{ FilterInputStream, FilterOutputStream, PipedInputStream, PipedOutputStream } -import java.io.{ File, FileInputStream, FileOutputStream } -import java.net.URL - -/** Runs provided code in a new Thread and returns the Thread instance. */ -private object Spawn { - def apply(f: => Unit): Thread = apply(f, false) - def apply(f: => Unit, daemon: Boolean): Thread = - { - val thread = new Thread() { override def run() = { f } } - thread.setDaemon(daemon) - thread.start() - thread - } -} -private object Future { - def apply[T](f: => T): () => T = - { - val result = new SyncVar[Either[Throwable, T]] - def run(): Unit = - try { result.set(Right(f)) } - catch { case e: Exception => result.set(Left(e)) } - Spawn(run) - () => - result.get match { - case Right(value) => value - case Left(exception) => throw exception - } - } -} - -object BasicIO { - def apply(buffer: StringBuffer, log: Option[ProcessLogger], withIn: Boolean) = new ProcessIO(input(withIn), processFully(buffer), getErr(log), inheritInput(withIn)) - def apply(log: ProcessLogger, withIn: Boolean) = new ProcessIO(input(withIn), processInfoFully(log), processErrFully(log), inheritInput(withIn)) - - def getErr(log: Option[ProcessLogger]) = log match { case Some(lg) => processErrFully(lg); case None => toStdErr } - - private def processErrFully(log: ProcessLogger) = processFully(s => log.error(s)) - private def processInfoFully(log: ProcessLogger) = processFully(s => log.info(s)) - - def closeOut = (_: OutputStream).close() - final val BufferSize = 8192 - final val Newline = System.getProperty("line.separator") - - def close(c: java.io.Closeable) = try { c.close() } catch { case _: java.io.IOException => () } - def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer)) - def processFully(processLine: String => Unit): InputStream => Unit = - in => - { - val reader = new BufferedReader(new InputStreamReader(in)) - processLinesFully(processLine)(reader.readLine) - reader.close() - } - def processLinesFully(processLine: String => Unit)(readLine: () => String): Unit = { - def readFully(): Unit = { - val line = readLine() - if (line != null) { - processLine(line) - readFully() - } - } - readFully() - } - def connectToIn(o: OutputStream): Unit = transferFully(Uncloseable protect System.in, o) - def input(connect: Boolean): OutputStream => Unit = if (connect) connectToIn else closeOut - def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput), inheritInput(connectInput)) - def standard(in: OutputStream => Unit, inheritIn: JProcessBuilder => Boolean): ProcessIO = new ProcessIO(in, toStdOut, toStdErr, inheritIn) - - def toStdErr = (in: InputStream) => transferFully(in, System.err) - def toStdOut = (in: InputStream) => transferFully(in, System.out) - - def transferFully(in: InputStream, out: OutputStream): Unit = - try { transferFullyImpl(in, out) } - catch { case _: InterruptedException => () } - - private[this] def appendLine(buffer: Appendable): String => Unit = - line => - { - buffer.append(line) - buffer.append(Newline) - } - - private[this] def transferFullyImpl(in: InputStream, out: OutputStream): Unit = { - val continueCount = 1 //if(in.isInstanceOf[PipedInputStream]) 1 else 0 - val buffer = new Array[Byte](BufferSize) - def read(): Unit = { - val byteCount = in.read(buffer) - if (byteCount >= continueCount) { - out.write(buffer, 0, byteCount) - out.flush() - read - } - } - read - in.close() - } - - def inheritInput(connect: Boolean) = { p: JProcessBuilder => if (connect) InheritInput(p) else false } -} -private[sbt] object ExitCodes { - def ignoreFirst: (Int, Int) => Int = (a, b) => b - def firstIfNonzero: (Int, Int) => Int = (a, b) => if (a != 0) a else b -} - -private[sbt] abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPartialBuilder with SourcePartialBuilder { - def #&&(other: ProcessBuilder): ProcessBuilder = new AndProcessBuilder(this, other) - def #||(other: ProcessBuilder): ProcessBuilder = new OrProcessBuilder(this, other) - def #|(other: ProcessBuilder): ProcessBuilder = - { - require(other.canPipeTo, "Piping to multiple processes is not supported.") - new PipedProcessBuilder(this, other, false, exitCode = ExitCodes.ignoreFirst) - } - def ###(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other) - - protected def toSource = this - protected def toSink = this - - def run(): Process = run(false) - def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput)) - def run(log: ProcessLogger): Process = run(log, false) - def run(log: ProcessLogger, connectInput: Boolean): Process = run(BasicIO(log, connectInput)) - - private[this] def getString(log: Option[ProcessLogger], withIn: Boolean): String = - { - val buffer = new StringBuffer - val code = this ! BasicIO(buffer, log, withIn) - if (code == 0) buffer.toString else sys.error("Nonzero exit value: " + code) - } - def !! = getString(None, false) - def !!(log: ProcessLogger) = getString(Some(log), false) - def !!< = getString(None, true) - def !!<(log: ProcessLogger) = getString(Some(log), true) - - def lines: Stream[String] = lines(false, true, None) - def lines(log: ProcessLogger): Stream[String] = lines(false, true, Some(log)) - def lines_! : Stream[String] = lines(false, false, None) - def lines_!(log: ProcessLogger): Stream[String] = lines(false, false, Some(log)) - - private[this] def lines(withInput: Boolean, nonZeroException: Boolean, log: Option[ProcessLogger]): Stream[String] = - { - val streamed = Streamed[String](nonZeroException) - val process = run(new ProcessIO(BasicIO.input(withInput), BasicIO.processFully(streamed.process), BasicIO.getErr(log), BasicIO.inheritInput(withInput))) - Spawn { streamed.done(process.exitValue()) } - streamed.stream() - } - - def ! = run(false).exitValue() - def !< = run(true).exitValue() - def !(log: ProcessLogger) = runBuffered(log, false).exitValue() - def !<(log: ProcessLogger) = runBuffered(log, true).exitValue() - def runBuffered(log: ProcessLogger, connectInput: Boolean) = - log.buffer { run(log, connectInput) } - def !(io: ProcessIO) = run(io).exitValue() - - def canPipeTo = false -} - -private[sbt] class URLBuilder(url: URL) extends URLPartialBuilder with SourcePartialBuilder { - protected def toSource = new URLInput(url) -} -private[sbt] class FileBuilder(base: File) extends FilePartialBuilder with SinkPartialBuilder with SourcePartialBuilder { - protected def toSource = new FileInput(base) - protected def toSink = new FileOutput(base, false) - def #<<(f: File): ProcessBuilder = #<<(new FileInput(f)) - def #<<(u: URL): ProcessBuilder = #<<(new URLInput(u)) - def #<<(s: => InputStream): ProcessBuilder = #<<(new InputStreamBuilder(s)) - def #<<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, new FileOutput(base, true), false, ExitCodes.firstIfNonzero) -} - -private abstract class BasicBuilder extends AbstractProcessBuilder { - protected[this] def checkNotThis(a: ProcessBuilder) = require(a != this, "Compound process '" + a + "' cannot contain itself.") - final def run(io: ProcessIO): Process = - { - val p = createProcess(io) - p.start() - p - } - protected[this] def createProcess(io: ProcessIO): BasicProcess -} -private abstract class BasicProcess extends Process { - def start(): Unit -} - -private abstract class CompoundProcess extends BasicProcess { - def destroy(): Unit = destroyer() - def exitValue() = getExitValue().getOrElse(sys.error("No exit code: process destroyed.")) - - def start() = getExitValue - - protected lazy val (getExitValue, destroyer) = - { - val code = new SyncVar[Option[Int]]() - code.set(None) - val thread = Spawn(code.set(runAndExitValue())) - - ( - Future { thread.join(); code.get }, - () => thread.interrupt() - ) - } - - /** Start and block until the exit value is available and then return it in Some. Return None if destroyed (use 'run')*/ - protected[this] def runAndExitValue(): Option[Int] - - protected[this] def runInterruptible[T](action: => T)(destroyImpl: => Unit): Option[T] = - { - try { Some(action) } - catch { case _: InterruptedException => destroyImpl; None } - } -} - -private abstract class SequentialProcessBuilder(a: ProcessBuilder, b: ProcessBuilder, operatorString: String) extends BasicBuilder { - checkNotThis(a) - checkNotThis(b) - override def toString = " ( " + a + " " + operatorString + " " + b + " ) " -} -private class PipedProcessBuilder(first: ProcessBuilder, second: ProcessBuilder, toError: Boolean, exitCode: (Int, Int) => Int) extends SequentialProcessBuilder(first, second, if (toError) "#|!" else "#|") { - override def createProcess(io: ProcessIO) = new PipedProcesses(first, second, io, toError, exitCode) -} -private class AndProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#&&") { - override def createProcess(io: ProcessIO) = new AndProcess(first, second, io) -} -private class OrProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#||") { - override def createProcess(io: ProcessIO) = new OrProcess(first, second, io) -} -private class SequenceProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "###") { - override def createProcess(io: ProcessIO) = new ProcessSequence(first, second, io) -} - -private class SequentialProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO, evaluateSecondProcess: Int => Boolean) extends CompoundProcess { - protected[this] override def runAndExitValue() = - { - val first = a.run(io) - runInterruptible(first.exitValue)(first.destroy()) flatMap - { codeA => - if (evaluateSecondProcess(codeA)) { - val second = b.run(io) - runInterruptible(second.exitValue)(second.destroy()) - } else - Some(codeA) - } - } -} -private class AndProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ == 0) -private class OrProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ != 0) -private class ProcessSequence(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, ignore => true) - -private class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean, exitCode: (Int, Int) => Int) extends CompoundProcess { - protected[this] override def runAndExitValue() = - { - val currentSource = new SyncVar[Option[InputStream]] - val pipeOut = new PipedOutputStream - val source = new PipeSource(currentSource, pipeOut, a.toString) - source.start() - - val pipeIn = new PipedInputStream(pipeOut) - val currentSink = new SyncVar[Option[OutputStream]] - val sink = new PipeSink(pipeIn, currentSink, b.toString) - sink.start() - - def handleOutOrError(fromOutput: InputStream) = currentSource.put(Some(fromOutput)) - - val firstIO = - if (toError) - defaultIO.withError(handleOutOrError) - else - defaultIO.withOutput(handleOutOrError) - val secondIO = defaultIO.withInput(toInput => currentSink.put(Some(toInput))) - - val second = b.run(secondIO) - val first = a.run(firstIO) - try { - runInterruptible { - val firstResult = first.exitValue - currentSource.put(None) - currentSink.put(None) - val secondResult = second.exitValue - exitCode(firstResult, secondResult) - } { - first.destroy() - second.destroy() - } - } finally { - BasicIO.close(pipeIn) - BasicIO.close(pipeOut) - } - } -} -private class PipeSource(currentSource: SyncVar[Option[InputStream]], pipe: PipedOutputStream, label: => String) extends Thread { - final override def run(): Unit = { - currentSource.get match { - case Some(source) => - try { BasicIO.transferFully(source, pipe) } - catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() } - finally { - BasicIO.close(source) - currentSource.unset() - } - run() - case None => - currentSource.unset() - BasicIO.close(pipe) - } - } -} -private class PipeSink(pipe: PipedInputStream, currentSink: SyncVar[Option[OutputStream]], label: => String) extends Thread { - final override def run(): Unit = { - currentSink.get match { - case Some(sink) => - try { BasicIO.transferFully(pipe, sink) } - catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() } - finally { - BasicIO.close(sink) - currentSink.unset() - } - run() - case None => - currentSink.unset() - } - } -} - -private[sbt] class DummyProcessBuilder(override val toString: String, exitValue: => Int) extends AbstractProcessBuilder { - override def run(io: ProcessIO): Process = new DummyProcess(exitValue) - override def canPipeTo = true -} -/** - * A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O. - * The implementation of `exitValue` waits until these threads die before returning. - */ -private class DummyProcess(action: => Int) extends Process { - private[this] val exitCode = Future(action) - override def exitValue() = exitCode() - override def destroy(): Unit = () -} -/** Represents a simple command without any redirection or combination. */ -private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProcessBuilder { - override def run(io: ProcessIO): Process = - { - import io._ - val inherited = inheritInput(p) - val process = p.start() - - // spawn threads that process the output and error streams, and also write input if not inherited. - if (!inherited) - Spawn(writeInput(process.getOutputStream)) - val outThread = Spawn(processOutput(process.getInputStream)) - val errorThread = - if (!p.redirectErrorStream) - Spawn(processError(process.getErrorStream)) :: Nil - else - Nil - new SimpleProcess(process, outThread :: errorThread) - } - override def toString = p.command.toString - override def canPipeTo = true -} - -/** - * A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the - * output and error streams of the process. - * The implementation of `exitValue` wait for the process to finish and then waits until the threads reading output and error streams die before - * returning. Note that the thread that reads the input stream cannot be interrupted, see https://github.com/sbt/sbt/issues/327 and - * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4514257 - */ -private class SimpleProcess(p: JProcess, outputThreads: List[Thread]) extends Process { - override def exitValue() = - { - try { - p.waitFor() - } catch { - case _: InterruptedException => p.destroy() - } - outputThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this) - p.exitValue() - } - override def destroy() = p.destroy() -} - -private[sbt] class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath) -private[sbt] class URLInput(url: URL) extends InputStreamBuilder(url.openStream, url.toString) -private[sbt] class FileInput(file: File) extends InputStreamBuilder(new FileInputStream(file), file.getAbsolutePath) - -import Uncloseable.protect -private[sbt] class OutputStreamBuilder(stream: => OutputStream, label: String) extends ThreadProcessBuilder(label, _.writeInput(protect(stream))) { - def this(stream: => OutputStream) = this(stream, "") -} -private[sbt] class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream))) { - def this(stream: => InputStream) = this(stream, "") -} - -private[sbt] abstract class ThreadProcessBuilder(override val toString: String, runImpl: ProcessIO => Unit) extends AbstractProcessBuilder { - override def run(io: ProcessIO): Process = - { - val success = new SyncVar[Boolean] - success.put(false) - new ThreadProcess(Spawn { runImpl(io); success.set(true) }, success) - } -} -private[sbt] final class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process { - override def exitValue() = - { - thread.join() - if (success.get) 0 else 1 - } - override def destroy(): Unit = thread.interrupt() -} - -object Uncloseable { - def apply(in: InputStream): InputStream = new FilterInputStream(in) { override def close(): Unit = () } - def apply(out: OutputStream): OutputStream = new FilterOutputStream(out) { override def close(): Unit = () } - def protect(in: InputStream): InputStream = if (in eq System.in) Uncloseable(in) else in - def protect(out: OutputStream): OutputStream = if ((out eq System.out) || (out eq System.err)) Uncloseable(out) else out -} -private[sbt] object Streamed { - def apply[T](nonzeroException: Boolean): Streamed[T] = - { - val q = new java.util.concurrent.LinkedBlockingQueue[Either[Int, T]] - def next(): Stream[T] = - q.take match { - case Left(0) => Stream.empty - case Left(code) => if (nonzeroException) sys.error("Nonzero exit code: " + code) else Stream.empty - case Right(s) => Stream.cons(s, next) - } - new Streamed((s: T) => q.put(Right(s)), code => q.put(Left(code)), () => next()) - } -} - -private[sbt] final class Streamed[T](val process: T => Unit, val done: Int => Unit, val stream: () => Stream[T]) extends NotNull \ No newline at end of file diff --git a/launcher-package/integration-test/src/test/scala/RunnerMemoryScriptTest.scala b/launcher-package/integration-test/src/test/scala/RunnerMemoryScriptTest.scala new file mode 100644 index 000000000..3978c4fb3 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/RunnerMemoryScriptTest.scala @@ -0,0 +1,77 @@ +package example.test + +/** + * RunnerMemoryScriptTest is used to test the sbt shell script, for both macOS/Linux and Windows. + */ +object RunnerMemoryScriptTest extends verify.BasicTestSuite with ShellScriptUtil: + + testOutput("sbt -mem 503")("-mem", "503", "-v"): (out: List[String]) => + assert(out.contains[String]("-Xmx503m")) + + testOutput("sbt with -mem 503, -Xmx in JAVA_OPTS", javaOpts = "-Xmx1024m")("-mem", "503", "-v"): + (out: List[String]) => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xmx1024m")) + + testOutput("sbt with -mem 503, -Xmx in SBT_OPTS", sbtOpts = "-Xmx1024m")("-mem", "503", "-v"): + (out: List[String]) => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xmx1024m")) + + testOutput("sbt with -mem 503, -Xss in JAVA_OPTS", javaOpts = "-Xss6m")("-mem", "503", "-v"): + (out: List[String]) => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xss6m")) + + testOutput("sbt with -mem 503, -Xss in SBT_OPTS", sbtOpts = "-Xss6m")("-mem", "503", "-v"): + (out: List[String]) => + assert(out.contains[String]("-Xmx503m")) + assert(!out.contains[String]("-Xss6m")) + + testOutput( + "sbt with -Xms2048M -Xmx2048M -Xss6M in JAVA_OPTS", + javaOpts = "-Xms2048M -Xmx2048M -Xss6M" + )("-v"): (out: List[String]) => + assert(out.contains[String]("-Xms2048M")) + assert(out.contains[String]("-Xmx2048M")) + assert(out.contains[String]("-Xss6M")) + + testOutput( + "sbt with -Xms2048M -Xmx2048M -Xss6M in SBT_OPTS", + sbtOpts = "-Xms2048M -Xmx2048M -Xss6M" + )("-v"): (out: List[String]) => + assert(out.contains[String]("-Xms2048M")) + assert(out.contains[String]("-Xmx2048M")) + assert(out.contains[String]("-Xss6M")) + + testOutput( + "sbt use .sbtopts file for memory options", + sbtOptsFileContents = """-J-XX:MaxInlineLevel=20 + |-J-Xmx222m + |-J-Xms111m + |-J-Xss12m""".stripMargin + )("compile", "-v"): (out: List[String]) => + assert(out.contains[String]("-XX:MaxInlineLevel=20")) + assert(out.contains[String]("-Xmx222m")) + assert(out.contains[String]("-Xms111m")) + assert(out.contains[String]("-Xss12m")) + + testOutput( + "sbt use JAVA_OPTS for memory options", + javaOpts = "-XX:MaxInlineLevel=20 -Xmx222m -Xms111m -Xss12m" + )("compile", "-v"): (out: List[String]) => + assert(out.contains[String]("-XX:MaxInlineLevel=20")) + assert(out.contains[String]("-Xmx222m")) + assert(out.contains[String]("-Xms111m")) + assert(out.contains[String]("-Xss12m")) + + testOutput( + "sbt use JAVA_TOOL_OPTIONS for memory options", + javaToolOptions = "-XX:MaxInlineLevel=20 -Xmx222m -Xms111m -Xss12m" + )("compile", "-v"): (out: List[String]) => + assert(out.contains[String]("-XX:MaxInlineLevel=20")) + assert(out.contains[String]("-Xmx222m")) + assert(out.contains[String]("-Xms111m")) + assert(out.contains[String]("-Xss12m")) + +end RunnerMemoryScriptTest diff --git a/launcher-package/integration-test/src/test/scala/RunnerScriptTest.scala b/launcher-package/integration-test/src/test/scala/RunnerScriptTest.scala new file mode 100644 index 000000000..df02ee0ef --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/RunnerScriptTest.scala @@ -0,0 +1,125 @@ +package example.test + +/** + * RunnerScriptTest is used to test the sbt shell script, for both macOS/Linux and Windows. + */ +object RunnerScriptTest extends verify.BasicTestSuite with ShellScriptUtil: + + testOutput("sbt -no-colors")("compile", "-no-colors", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.log.noformat=true")) + + testOutput("sbt --no-colors")("compile", "--no-colors", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.log.noformat=true")) + + testOutput("sbt --color=false")("compile", "--color=false", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.color=false")) + + testOutput("sbt --no-colors in SBT_OPTS", sbtOpts = "--no-colors")("compile", "-v"): + (out: List[String]) => + if (isWindows) cancel("Test not supported on windows") + assert(out.contains[String]("-Dsbt.log.noformat=true")) + + testOutput("sbt --no-server")("compile", "--no-server", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.server.autostart=false")) + + testOutput("sbt --debug-inc")("compile", "--debug-inc", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dxsbt.inc.debug=true")) + + testOutput("sbt --supershell=never")("compile", "--supershell=never", "-v"): + (out: List[String]) => assert(out.contains[String]("-Dsbt.supershell=never")) + + testOutput("sbt --timings")("compile", "--timings", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.task.timings=true")) + + testOutput("sbt -D arguments")("-Dsbt.supershell=false", "compile", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.supershell=false")) + + testOutput("sbt --sbt-version")("--sbt-version", "1.3.13", "-v"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.version=1.3.13")) + + testOutput( + name = "sbt with -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 in SBT_OPTS", + sbtOpts = "-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080", + )("-v"): (out: List[String]) => + assert(out.contains[String]("-Dhttp.proxyHost=proxy")) + assert(out.contains[String]("-Dhttp.proxyPort=8080")) + + testOutput( + name = "sbt with -XX:ParallelGCThreads=16 -XX:PermSize=128M in SBT_OPTS", + sbtOpts = "-XX:ParallelGCThreads=16 -XX:PermSize=128M", + )("-v"): (out: List[String]) => + assert(out.contains[String]("-XX:ParallelGCThreads=16")) + assert(out.contains[String]("-XX:PermSize=128M")) + + testOutput( + "sbt with -XX:+UseG1GC -XX:+PrintGC in JAVA_OPTS", + javaOpts = "-XX:+UseG1GC -XX:+PrintGC" + )("-v"): (out: List[String]) => + assert(out.contains[String]("-XX:+UseG1GC")) + assert(out.contains[String]("-XX:+PrintGC")) + assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) + + testOutput( + "sbt with -XX:-UseG1GC -XX:-PrintGC in SBT_OPTS", + sbtOpts = "-XX:+UseG1GC -XX:+PrintGC" + )( + "-v" + ): (out: List[String]) => + assert(out.contains[String]("-XX:+UseG1GC")) + assert(out.contains[String]("-XX:+PrintGC")) + assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) + + testOutput( + "sbt with -debug in SBT_OPTS appears in sbt commands", + javaOpts = "", + sbtOpts = "-debug" + )("compile", "-v"): (out: List[String]) => + if (isWindows) cancel("Test not supported on windows") + + // Debug argument must appear in the 'commands' section (after the sbt-launch.jar argument) to work + val sbtLaunchMatcher = """^.+sbt-launch.jar["]{0,1}$""".r + val locationOfSbtLaunchJarArg = out.zipWithIndex.collectFirst { + case (arg, index) if sbtLaunchMatcher.findFirstIn(arg).nonEmpty => index + } + + assert(locationOfSbtLaunchJarArg.nonEmpty) + + val argsAfterSbtLaunch = out.drop(locationOfSbtLaunchJarArg.get) + assert(argsAfterSbtLaunch.contains("-debug")) + () + + testOutput("sbt --jvm-debug ")("--jvm-debug", "12345", "-v"): (out: List[String]) => + assert( + out.contains[String]("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12345") + ) + + // Regression test for https://github.com/sbt/sbt/issues/8100 + // Debug agent output in SBT_OPTS should not break the launcher on Windows + testOutput( + "sbt with debug agent in SBT_OPTS", + sbtOpts = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12346" + )("-v"): (out: List[String]) => + assert( + out.contains[String]("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12346") + ) + + testOutput("sbt --no-share adds three system properties")("--no-share"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.global.base=project/.sbtboot")) + assert(out.contains[String]("-Dsbt.boot.directory=project/.boot")) + assert(out.contains[String]("-Dsbt.ivy.home=project/.ivy")) + + testOutput("accept `--ivy` in `SBT_OPTS`", sbtOpts = "--ivy /ivy/dir")("-v"): + (out: List[String]) => + if (isWindows) cancel("Test not supported on windows") + else assert(out.contains[String]("-Dsbt.ivy.home=/ivy/dir")) + + testOutput("sbt --script-version should print sbtVersion")("--script-version"): + (out: List[String]) => + val expectedVersion = "^" + ExtendedRunnerTest.versionRegEx + "$" + assert(out.mkString(System.lineSeparator()).trim.matches(expectedVersion)) + () + + testOutput("--sbt-cache")("--sbt-cache", "./cachePath"): (out: List[String]) => + assert(out.contains[String]("-Dsbt.global.localcache=./cachePath")) + +end RunnerScriptTest diff --git a/launcher-package/integration-test/src/test/scala/ScriptTest.scala b/launcher-package/integration-test/src/test/scala/ScriptTest.scala deleted file mode 100644 index 222987887..000000000 --- a/launcher-package/integration-test/src/test/scala/ScriptTest.scala +++ /dev/null @@ -1,263 +0,0 @@ -package example.test - -import minitest._ -import sbt.io.IO - -import java.io.File -import java.io.PrintWriter -import java.nio.file.Files - -object SbtScriptTest extends SimpleTestSuite with PowerAssertions { - lazy val isWindows: Boolean = - sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows") - lazy val sbtScript = - if (isWindows) new File("target/universal/stage/bin/sbt.bat") - else new File("target/universal/stage/bin/sbt") - - private val javaBinDir = new File("integration-test", "bin").getAbsolutePath - - private def retry[A1](f: () => A1, maxAttempt: Int = 10): A1 = - try { - f() - } catch { - case _ if maxAttempt <= 1 => - Thread.sleep(100) - retry(f, maxAttempt - 1) - } - - def makeTest( - name: String, - javaOpts: String = "", - sbtOpts: String = "", - sbtOptsFileContents: String = "", - javaToolOptions: String = "" - )(args: String*)(f: List[String] => Any) = { - test(name) { - val workingDirectory = Files.createTempDirectory("sbt-launcher-package-test").toFile - retry(() => IO.copyDirectory(new File("citest"), workingDirectory)) - - try { - val sbtOptsFile = new File(workingDirectory, ".sbtopts") - sbtOptsFile.createNewFile() - val writer = new PrintWriter(sbtOptsFile) - try { - writer.write(sbtOptsFileContents) - } finally { - writer.close() - } - val path = sys.env.getOrElse("PATH", sys.env("Path")) - val out = sbt.internal.Process( - Seq(sbtScript.getAbsolutePath) ++ args, - workingDirectory, - "JAVA_OPTS" -> javaOpts, - "SBT_OPTS" -> sbtOpts, - "JAVA_TOOL_OPTIONS" -> javaToolOptions, - if (isWindows) - "JAVACMD" -> new File(javaBinDir, "java").getAbsolutePath() - else - "PATH" -> (javaBinDir + File.pathSeparator + path) - ).!!.linesIterator.toList - f(out) - () - } finally { - IO.delete(workingDirectory) - } - } - } - - makeTest("sbt -no-colors")("compile", "-no-colors", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.log.noformat=true")) - } - - makeTest("sbt --no-colors")("compile", "--no-colors", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.log.noformat=true")) - } - - makeTest("sbt --color=false")("compile", "--color=false", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.color=false")) - } - - makeTest("sbt --no-colors in SBT_OPTS", sbtOpts = "--no-colors")("compile", "-v") { - out: List[String] => - if (isWindows) cancel("Test not supported on windows") - assert(out.contains[String]("-Dsbt.log.noformat=true")) - } - - makeTest("sbt --no-server")("compile", "--no-server", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.server.autostart=false")) - } - - makeTest("sbt --debug-inc")("compile", "--debug-inc", "-v") { out: List[String] => - assert(out.contains[String]("-Dxsbt.inc.debug=true")) - } - - makeTest("sbt --supershell=never")("compile", "--supershell=never", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.supershell=never")) - } - - makeTest("sbt --timings")("compile", "--timings", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.task.timings=true")) - } - - makeTest("sbt -D arguments")("-Dsbt.supershell=false", "compile", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.supershell=false")) - } - - makeTest("sbt --sbt-version")("--sbt-version", "1.3.13", "-v") { out: List[String] => - assert(out.contains[String]("-Dsbt.version=1.3.13")) - } - - makeTest("sbt -mem 503")("-mem", "503", "-v") { out: List[String] => - assert(out.contains[String]("-Xmx503m")) - } - - makeTest("sbt with -mem 503, -Xmx in JAVA_OPTS", javaOpts = "-Xmx1024m")("-mem", "503", "-v") { - out: List[String] => - assert(out.contains[String]("-Xmx503m")) - assert(!out.contains[String]("-Xmx1024m")) - } - - makeTest("sbt with -mem 503, -Xmx in SBT_OPTS", sbtOpts = "-Xmx1024m")("-mem", "503", "-v") { - out: List[String] => - assert(out.contains[String]("-Xmx503m")) - assert(!out.contains[String]("-Xmx1024m")) - } - - makeTest("sbt with -mem 503, -Xss in JAVA_OPTS", javaOpts = "-Xss6m")("-mem", "503", "-v") { - out: List[String] => - assert(out.contains[String]("-Xmx503m")) - assert(!out.contains[String]("-Xss6m")) - } - - makeTest("sbt with -mem 503, -Xss in SBT_OPTS", sbtOpts = "-Xss6m")("-mem", "503", "-v") { - out: List[String] => - assert(out.contains[String]("-Xmx503m")) - assert(!out.contains[String]("-Xss6m")) - } - - makeTest( - "sbt with -Xms2048M -Xmx2048M -Xss6M in JAVA_OPTS", - javaOpts = "-Xms2048M -Xmx2048M -Xss6M" - )("-v") { out: List[String] => - assert(out.contains[String]("-Xms2048M")) - assert(out.contains[String]("-Xmx2048M")) - assert(out.contains[String]("-Xss6M")) - } - - makeTest( - "sbt with -Xms2048M -Xmx2048M -Xss6M in SBT_OPTS", - sbtOpts = "-Xms2048M -Xmx2048M -Xss6M" - )("-v") { out: List[String] => - assert(out.contains[String]("-Xms2048M")) - assert(out.contains[String]("-Xmx2048M")) - assert(out.contains[String]("-Xss6M")) - } - - makeTest( - name = "sbt with -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 in SBT_OPTS", - sbtOpts = "-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080", - )("-v") { out: List[String] => - assert(out.contains[String]("-Dhttp.proxyHost=proxy")) - assert(out.contains[String]("-Dhttp.proxyPort=8080")) - } - - makeTest( - name = "sbt with -XX:ParallelGCThreads=16 -XX:PermSize=128M in SBT_OPTS", - sbtOpts = "-XX:ParallelGCThreads=16 -XX:PermSize=128M", - )("-v") { out: List[String] => - assert(out.contains[String]("-XX:ParallelGCThreads=16")) - assert(out.contains[String]("-XX:PermSize=128M")) - } - - makeTest( - "sbt with -XX:+UseG1GC -XX:+PrintGC in JAVA_OPTS", - javaOpts = "-XX:+UseG1GC -XX:+PrintGC" - )("-v") { out: List[String] => - assert(out.contains[String]("-XX:+UseG1GC")) - assert(out.contains[String]("-XX:+PrintGC")) - assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) - } - - makeTest("sbt with -XX:-UseG1GC -XX:-PrintGC in SBT_OPTS", sbtOpts = "-XX:+UseG1GC -XX:+PrintGC")( - "-v" - ) { out: List[String] => - assert(out.contains[String]("-XX:+UseG1GC")) - assert(out.contains[String]("-XX:+PrintGC")) - assert(!out.contains[String]("-XX:+UseG1GC=-XX:+PrintGC")) - } - - makeTest("sbt with -debug in SBT_OPTS appears in sbt commands", javaOpts = "", sbtOpts = "-debug")("compile", "-v") {out: List[String] => - if (isWindows) cancel("Test not supported on windows") - - // Debug argument must appear in the 'commands' section (after the sbt-launch.jar argument) to work - val sbtLaunchMatcher = """^.+sbt-launch.jar["]{0,1}$""".r - val locationOfSbtLaunchJarArg = out.zipWithIndex.collectFirst { - case (arg, index) if sbtLaunchMatcher.findFirstIn(arg).nonEmpty => index - } - - assert(locationOfSbtLaunchJarArg.nonEmpty) - - val argsAfterSbtLaunch = out.drop(locationOfSbtLaunchJarArg.get) - assert(argsAfterSbtLaunch.contains("-debug")) - () - } - - makeTest("sbt --jvm-debug ")("--jvm-debug", "12345", "-v") { out: List[String] => - assert( - out.contains[String]("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12345") - ) - } - - makeTest("sbt --no-share adds three system properties")("--no-share") { out: List[String] => - assert(out.contains[String]("-Dsbt.global.base=project/.sbtboot")) - assert(out.contains[String]("-Dsbt.boot.directory=project/.boot")) - assert(out.contains[String]("-Dsbt.ivy.home=project/.ivy")) - } - - makeTest("accept `--ivy` in `SBT_OPTS`", sbtOpts = "--ivy /ivy/dir")("-v") { out: List[String] => - if (isWindows) cancel("Test not supported on windows") - assert(out.contains[String]("-Dsbt.ivy.home=/ivy/dir")) - } - - makeTest("sbt --script-version should print sbtVersion")("--script-version") { out: List[String] => - val expectedVersion = "^" + SbtRunnerTest.versionRegEx + "$" - assert(out.mkString(System.lineSeparator()).trim.matches(expectedVersion)) - () - } - - makeTest("--sbt-cache")("--sbt-cache", "./cachePath") { out: List[String] => - assert(out.contains[String](s"-Dsbt.global.localcache=./cachePath")) - } - - makeTest( - "sbt use .sbtopts file for memory options", sbtOptsFileContents = - """-J-XX:MaxInlineLevel=20 - |-J-Xmx222m - |-J-Xms111m - |-J-Xss12m""".stripMargin - - )("compile", "-v") { out: List[String] => - assert(out.contains[String]("-XX:MaxInlineLevel=20")) - assert(out.contains[String]("-Xmx222m")) - assert(out.contains[String]("-Xms111m")) - assert(out.contains[String]("-Xss12m")) - } - - makeTest( - "sbt use JAVA_OPTS for memory options", javaOpts = "-XX:MaxInlineLevel=20 -Xmx222m -Xms111m -Xss12m" - )("compile", "-v") { out: List[String] => - assert(out.contains[String]("-XX:MaxInlineLevel=20")) - assert(out.contains[String]("-Xmx222m")) - assert(out.contains[String]("-Xms111m")) - assert(out.contains[String]("-Xss12m")) - } - - makeTest( - "sbt use JAVA_TOOL_OPTIONS for memory options", javaToolOptions = "-XX:MaxInlineLevel=20 -Xmx222m -Xms111m -Xss12m" - )("compile", "-v") { out: List[String] => - assert(out.contains[String]("-XX:MaxInlineLevel=20")) - assert(out.contains[String]("-Xmx222m")) - assert(out.contains[String]("-Xms111m")) - assert(out.contains[String]("-Xss12m")) - } -} diff --git a/launcher-package/integration-test/src/test/scala/ShellScriptUtil.scala b/launcher-package/integration-test/src/test/scala/ShellScriptUtil.scala new file mode 100644 index 000000000..87dcc8830 --- /dev/null +++ b/launcher-package/integration-test/src/test/scala/ShellScriptUtil.scala @@ -0,0 +1,71 @@ +package example.test + +import java.io.File +import java.io.PrintWriter +import java.nio.file.Files +import sbt.io.IO +import verify.BasicTestSuite + +trait ShellScriptUtil extends BasicTestSuite { + val isWindows: Boolean = + sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows") + + private val javaBinDir = new File("launcher-package/integration-test/bin").getAbsolutePath + + private def retry[A1](f: () => A1, maxAttempt: Int = 10): A1 = + try { + f() + } catch { + case _ if maxAttempt <= 1 => + Thread.sleep(100) + retry(f, maxAttempt - 1) + } + + val sbtScript = + if (isWindows) new File("launcher-package/target/universal/stage/bin/sbt.bat") + else new File("launcher-package/target/universal/stage/bin/sbt") + + /** + * testOutput is a helper function to create a test for shell script. + */ + inline def testOutput( + name: String, + javaOpts: String = "", + sbtOpts: String = "", + sbtOptsFileContents: String = "", + javaToolOptions: String = "" + )(args: String*)(f: List[String] => Any) = + test(name) { + val workingDirectory = Files.createTempDirectory("sbt-launcher-package-test").toFile + retry(() => IO.copyDirectory(new File("launcher-package/citest"), workingDirectory)) + + try + val sbtOptsFile = new File(workingDirectory, ".sbtopts") + sbtOptsFile.createNewFile() + val writer = new PrintWriter(sbtOptsFile) + try { + writer.write(sbtOptsFileContents) + } finally { + writer.close() + } + val path = sys.env.getOrElse("PATH", sys.env("Path")) + val out = scala.sys.process + .Process( + Seq(sbtScript.getAbsolutePath) ++ args, + workingDirectory, + "JAVA_OPTS" -> javaOpts, + "SBT_OPTS" -> sbtOpts, + "JAVA_TOOL_OPTIONS" -> javaToolOptions, + if (isWindows) + "JAVACMD" -> new File(javaBinDir, "java").getAbsolutePath() + else + "PATH" -> (javaBinDir + File.pathSeparator + path) + ) + .!! + .linesIterator + .toList + f(out) + () + finally IO.delete(workingDirectory) + } +} diff --git a/launcher-package/integration-test/src/test/scala/SyncVar.scala b/launcher-package/integration-test/src/test/scala/SyncVar.scala deleted file mode 100644 index 5754e6da0..000000000 --- a/launcher-package/integration-test/src/test/scala/SyncVar.scala +++ /dev/null @@ -1,38 +0,0 @@ -package sbt.internal - -// minimal copy of scala.concurrent.SyncVar since that version deprecated put and unset -private[sbt] final class SyncVar[A] { - private[this] var isDefined: Boolean = false - private[this] var value: Option[A] = None - - /** Waits until a value is set and then gets it. Does not clear the value */ - def get: A = synchronized { - while (!isDefined) wait() - value.get - } - - /** Waits until a value is set, gets it, and finally clears the value. */ - def take(): A = synchronized { - try get finally unset() - } - - /** Sets the value, whether or not it is currently defined. */ - def set(x: A): Unit = synchronized { - isDefined = true - value = Some(x) - notifyAll() - } - - /** Sets the value, first waiting until it is undefined if it is currently defined. */ - def put(x: A): Unit = synchronized { - while (isDefined) wait() - set(x) - } - - /** Clears the value, whether or not it is current defined. */ - def unset(): Unit = synchronized { - isDefined = false - value = None - notifyAll() - } -} diff --git a/launcher-package/project/build.properties b/launcher-package/project/build.properties deleted file mode 100644 index 73df629ac..000000000 --- a/launcher-package/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.10.7 diff --git a/launcher-package/project/plugins.sbt b/launcher-package/project/plugins.sbt deleted file mode 100644 index 10a9e1796..000000000 --- a/launcher-package/project/plugins.sbt +++ /dev/null @@ -1,2 +0,0 @@ -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 389f0bbb0..1ed315c61 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,6 +6,7 @@ object Dependencies { // WARNING: Please Scala update versions in PluginCross.scala too val scala212 = "2.12.21" val scala213 = "2.13.18" + val scala3 = "3.7.4" val checkPluginCross = settingKey[Unit]("Make sure scalaVersion match up") val baseScalaVersion = scala212 def nightlyVersion: Option[String] = diff --git a/launcher-package/project/PackageSignerPlugin.scala b/project/PackageSignerPlugin.scala similarity index 70% rename from launcher-package/project/PackageSignerPlugin.scala rename to project/PackageSignerPlugin.scala index c819dff6c..23c4b1b42 100644 --- a/launcher-package/project/PackageSignerPlugin.scala +++ b/project/PackageSignerPlugin.scala @@ -1,5 +1,5 @@ -import sbt._ -import Keys._ +import sbt.* +import Keys.* import sbt.internal.librarymanagement.IvyActions import com.jsuereth.sbtpgp.SbtPgp import com.typesafe.sbt.packager.universal.{ UniversalPlugin, UniversalDeployPlugin } @@ -11,54 +11,60 @@ object PackageSignerPlugin extends sbt.AutoPlugin { override def trigger = allRequirements override def requires = SbtPgp && UniversalDeployPlugin && DebianDeployPlugin && RpmDeployPlugin - import com.jsuereth.sbtpgp.PgpKeys._ - import UniversalPlugin.autoImport._ - import DebianPlugin.autoImport._ - import RpmPlugin.autoImport._ + import com.jsuereth.sbtpgp.PgpKeys.* + import UniversalPlugin.autoImport.* + import DebianPlugin.autoImport.* + import RpmPlugin.autoImport.* - override def projectSettings: Seq[Setting[_]] = + override def projectSettings: Seq[Setting[?]] = inConfig(Universal)(packageSignerSettings) ++ - inConfig(Debian)(packageSignerSettings) ++ - inConfig(Rpm)(packageSignerSettings) + inConfig(Debian)(packageSignerSettings) ++ + inConfig(Rpm)(packageSignerSettings) def subExtension(art: Artifact, ext: String): Artifact = art.withExtension(ext) - def packageSignerSettings: Seq[Setting[_]] = Seq( + def packageSignerSettings: Seq[Setting[?]] = Seq( signedArtifacts := { val artifacts = packagedArtifacts.value val r = pgpSigner.value val skipZ = (pgpSigner / skip).value val s = streams.value if (!skipZ) { - artifacts flatMap { case (art, f) => - Seq(art -> f, + artifacts flatMap { + case (art, f) => + Seq( + art -> f, subExtension(art, art.extension + gpgExtension) -> - r.sign(f, file(f.getAbsolutePath + gpgExtension), s)) + r.sign(f, file(f.getAbsolutePath + gpgExtension), s) + ) } - } - else artifacts + } else artifacts }, publishSignedConfiguration := Classpaths.publishConfig( publishMavenStyle = publishMavenStyle.value, - deliverIvyPattern = (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", + deliverIvyPattern = + (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", status = if (isSnapshot.value) "integration" else "release", configurations = Vector.empty, artifacts = signedArtifacts.value.toVector, checksums = (publish / checksums).value.toVector, resolverName = Classpaths.getPublishTo(publishTo.value).name, logging = ivyLoggingLevel.value, - overwrite = isSnapshot.value), + overwrite = isSnapshot.value + ), publishLocalSignedConfiguration := Classpaths.publishConfig( publishMavenStyle = publishMavenStyle.value, - deliverIvyPattern = (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", + deliverIvyPattern = + (Compile / packageBin / artifactPath).value.getParent + "/[artifact]-[revision](-[classifier]).[ext]", status = if (isSnapshot.value) "integration" else "release", configurations = Vector.empty, artifacts = signedArtifacts.value.toVector, checksums = (publish / checksums).value.toVector, resolverName = "local", logging = ivyLoggingLevel.value, - overwrite = isSnapshot.value), + overwrite = isSnapshot.value + ), publishSigned := Def.taskDyn { val config = publishSignedConfiguration.value val s = streams.value @@ -76,4 +82,3 @@ object PackageSignerPlugin extends sbt.AutoPlugin { ) } - diff --git a/project/plugins.sbt b/project/plugins.sbt index 6e2346199..663914ec5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -12,3 +12,5 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") addSbtPlugin("com.swoval" % "sbt-java-format" % "0.3.1") addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.1") addDependencyTreePlugin +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.3") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4")