mirror of https://github.com/sbt/sbt.git
Implement sbtw Windows drop-in launcher (sbt/sbt#5406)
- Add sbtwProj: Scala 3.3.7 launcher with scopt, drop-in for sbt.bat - Config: .sbtopts, .jvmopts, sbtconfig.txt, JAVA_OPTS/SBT_OPTS precedence - Options: --client, --server, --jvm-client, mem, sbt-version, java-home, etc. - sbt 2.x defaults to native client; --server forces JVM launcher - JVM run via xsbt.boot.Boot; native via sbtn with --sbt-script - build.sbt: sbtwProj in root build and allProjects; NativeImagePlugin - Fixes: JAVA_OPTS then .jvmopts, build.properties trim, shutdownAll PID, Iterator.lastOption
This commit is contained in:
parent
0c017d2e52
commit
40a1cfd6d3
23
build.sbt
23
build.sbt
|
|
@ -968,6 +968,28 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa
|
|||
|
||||
lazy val publishLauncher = TaskKey[Unit]("publish-launcher")
|
||||
|
||||
lazy val sbtwProj = (project in file("sbtw"))
|
||||
.enablePlugins(NativeImagePlugin)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "sbtw",
|
||||
description := "Windows drop-in launcher for sbt (replaces sbt.bat)",
|
||||
scalaVersion := "3.3.7",
|
||||
crossPaths := false,
|
||||
Compile / mainClass := Some("sbtw.Main"),
|
||||
libraryDependencies += "com.github.scopt" %% "scopt" % "4.1.0",
|
||||
nativeImageVersion := "23.0",
|
||||
nativeImageJvm := "graalvm-java23",
|
||||
nativeImageOutput := (target.value / "bin" / "sbtw").toPath.toFile,
|
||||
nativeImageOptions ++= Seq(
|
||||
"--no-fallback",
|
||||
s"--initialize-at-run-time=sbtw",
|
||||
"-H:+ReportExceptionStackTraces",
|
||||
s"-H:Name=${(target.value / "bin" / "sbtw").getAbsolutePath}",
|
||||
),
|
||||
Utils.noPublish,
|
||||
)
|
||||
|
||||
def allProjects =
|
||||
Seq(
|
||||
logicProj,
|
||||
|
|
@ -986,6 +1008,7 @@ def allProjects =
|
|||
sbtProj,
|
||||
bundledLauncherProj,
|
||||
sbtClientProj,
|
||||
sbtwProj,
|
||||
buildFileProj,
|
||||
utilCache,
|
||||
utilTracking,
|
||||
|
|
|
|||
|
|
@ -1,23 +1 @@
|
|||
val sbtwScalaVersion = "3.3.7"
|
||||
|
||||
lazy val sbtwProj = (project in file("."))
|
||||
.enablePlugins(NativeImagePlugin)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "sbtw",
|
||||
description := "Windows drop-in launcher for sbt (replaces sbt.bat)",
|
||||
scalaVersion := sbtwScalaVersion,
|
||||
crossPaths := false,
|
||||
Compile / mainClass := Some("sbtw.Main"),
|
||||
libraryDependencies += "com.github.scopt" %% "scopt" % "4.1.0",
|
||||
nativeImageVersion := "23.0",
|
||||
nativeImageJvm := "graalvm-java23",
|
||||
nativeImageOutput := (target.value / "bin" / "sbtw").toPath.toFile,
|
||||
nativeImageOptions ++= Seq(
|
||||
"--no-fallback",
|
||||
s"--initialize-at-run-time=sbtw",
|
||||
"-H:+ReportExceptionStackTraces",
|
||||
s"-H:Name=${(target.value / "bin" / "sbtw").getAbsolutePath}",
|
||||
),
|
||||
Utils.noPublish,
|
||||
)
|
||||
// sbtw project is defined in the root build.sbt (sbtwProj)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ object ArgParser {
|
|||
def parse(args: Array[String]): Option[LauncherOptions] = {
|
||||
val b = OParser.builder[LauncherOptions]
|
||||
val parser = {
|
||||
import b._
|
||||
import b.*
|
||||
OParser.sequence(
|
||||
programName("sbtw"),
|
||||
head("sbtw", "Windows launcher for sbt"),
|
||||
|
|
@ -43,9 +43,10 @@ object ArgParser {
|
|||
opt[String]("color").action((x, c) => c.copy(color = Some(x))),
|
||||
opt[Int]("jvm-debug").action((x, c) => c.copy(jvmDebug = Some(x))),
|
||||
opt[String]("java-home").action((x, c) => c.copy(javaHome = Some(x))),
|
||||
arg[String]("<arg>").unbounded().optional().action((x, c) =>
|
||||
c.copy(residual = c.residual :+ x)
|
||||
),
|
||||
arg[String]("<arg>")
|
||||
.unbounded()
|
||||
.optional()
|
||||
.action((x, c) => c.copy(residual = c.residual :+ x)),
|
||||
)
|
||||
}
|
||||
OParser.parse(parser, args, LauncherOptions()).map { opts =>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package sbtw
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.{ Files, Paths }
|
||||
import scala.io.Source
|
||||
import scala.util.Using
|
||||
|
||||
|
|
@ -47,12 +46,16 @@ object ConfigLoader {
|
|||
if (!f.isFile) return None
|
||||
try
|
||||
Using.resource(Source.fromFile(f)) { src =>
|
||||
src.getLines()
|
||||
src
|
||||
.getLines()
|
||||
.map(_.trim)
|
||||
.filterNot(_.startsWith("#"))
|
||||
.find(_.startsWith("sbt.version="))
|
||||
.map(_.drop("sbt.version=".length).trim)
|
||||
.filter(_.nonEmpty)
|
||||
.find(line => line.startsWith("sbt.version") && (line.contains("=")))
|
||||
.flatMap { line =>
|
||||
val eq = line.indexOf('=')
|
||||
if (eq >= 0) Some(line.substring(eq + 1).trim).filter(_.nonEmpty)
|
||||
else None
|
||||
}
|
||||
}
|
||||
catch { case _: Exception => None }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
package sbtw
|
||||
|
||||
case class LauncherOptions(
|
||||
help: Boolean = false,
|
||||
verbose: Boolean = false,
|
||||
debug: Boolean = false,
|
||||
version: Boolean = false,
|
||||
numericVersion: Boolean = false,
|
||||
scriptVersion: Boolean = false,
|
||||
shutdownAll: Boolean = false,
|
||||
allowEmpty: Boolean = false,
|
||||
client: Boolean = false,
|
||||
jvmClient: Boolean = false,
|
||||
noServer: Boolean = false,
|
||||
noColors: Boolean = false,
|
||||
noGlobal: Boolean = false,
|
||||
noShare: Boolean = false,
|
||||
noHideJdkWarnings: Boolean = false,
|
||||
debugInc: Boolean = false,
|
||||
timings: Boolean = false,
|
||||
traces: Boolean = false,
|
||||
batch: Boolean = false,
|
||||
sbtDir: Option[String] = None,
|
||||
sbtBoot: Option[String] = None,
|
||||
sbtCache: Option[String] = None,
|
||||
sbtJar: Option[String] = None,
|
||||
sbtVersion: Option[String] = None,
|
||||
ivy: Option[String] = None,
|
||||
mem: Option[Int] = None,
|
||||
supershell: Option[String] = None,
|
||||
color: Option[String] = None,
|
||||
jvmDebug: Option[Int] = None,
|
||||
javaHome: Option[String] = None,
|
||||
server: Boolean = false,
|
||||
residual: Seq[String] = Nil,
|
||||
sbtNew: Boolean = false,
|
||||
help: Boolean = false,
|
||||
verbose: Boolean = false,
|
||||
debug: Boolean = false,
|
||||
version: Boolean = false,
|
||||
numericVersion: Boolean = false,
|
||||
scriptVersion: Boolean = false,
|
||||
shutdownAll: Boolean = false,
|
||||
allowEmpty: Boolean = false,
|
||||
client: Boolean = false,
|
||||
jvmClient: Boolean = false,
|
||||
noServer: Boolean = false,
|
||||
noColors: Boolean = false,
|
||||
noGlobal: Boolean = false,
|
||||
noShare: Boolean = false,
|
||||
noHideJdkWarnings: Boolean = false,
|
||||
debugInc: Boolean = false,
|
||||
timings: Boolean = false,
|
||||
traces: Boolean = false,
|
||||
batch: Boolean = false,
|
||||
sbtDir: Option[String] = None,
|
||||
sbtBoot: Option[String] = None,
|
||||
sbtCache: Option[String] = None,
|
||||
sbtJar: Option[String] = None,
|
||||
sbtVersion: Option[String] = None,
|
||||
ivy: Option[String] = None,
|
||||
mem: Option[Int] = None,
|
||||
supershell: Option[String] = None,
|
||||
color: Option[String] = None,
|
||||
jvmDebug: Option[Int] = None,
|
||||
javaHome: Option[String] = None,
|
||||
server: Boolean = false,
|
||||
residual: Seq[String] = Nil,
|
||||
sbtNew: Boolean = false,
|
||||
)
|
||||
|
||||
object LauncherOptions {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ object Main {
|
|||
}
|
||||
|
||||
if (!opts.allowEmpty && !opts.sbtNew && !ConfigLoader.isSbtProjectDir(cwd)) {
|
||||
System.err.println("[error] Neither build.sbt nor a 'project' directory in the current directory: " + cwd)
|
||||
System.err.println(
|
||||
"[error] Neither build.sbt nor a 'project' directory in the current directory: " + cwd
|
||||
)
|
||||
System.err.println("[error] run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'.")
|
||||
return 1
|
||||
}
|
||||
|
|
@ -82,7 +84,10 @@ object Main {
|
|||
sbtOpts = finalSbt
|
||||
|
||||
if (!opts.noHideJdkWarnings && javaVer == 25) {
|
||||
sbtOpts = sbtOpts ++ Seq("--sun-misc-unsafe-memory-access=allow", "--enable-native-access=ALL-UNNAMED")
|
||||
sbtOpts = sbtOpts ++ Seq(
|
||||
"--sun-misc-unsafe-memory-access=allow",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
}
|
||||
val javaOptsWithDebug = opts.jvmDebug.fold(finalJava)(port =>
|
||||
finalJava :+ s"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$port"
|
||||
|
|
@ -91,7 +96,10 @@ object Main {
|
|||
Runner.runJvm(javaCmd, javaOptsWithDebug, sbtOpts, sbtJar, bootArgs, opts.verbose)
|
||||
}
|
||||
|
||||
private def shouldRunNativeClient(opts: LauncherOptions, buildPropsVersion: Option[String]): Boolean = {
|
||||
private def shouldRunNativeClient(
|
||||
opts: LauncherOptions,
|
||||
buildPropsVersion: Option[String]
|
||||
): Boolean = {
|
||||
if (opts.sbtNew) return false
|
||||
if (opts.jvmClient) return false
|
||||
val version = buildPropsVersion.getOrElse(LauncherOptions.initSbtVersion)
|
||||
|
|
@ -102,7 +110,12 @@ object Main {
|
|||
else false
|
||||
}
|
||||
|
||||
private def handleVersionCommands(cwd: File, sbtHome: File, sbtBinDir: File, opts: LauncherOptions): Int = {
|
||||
private def handleVersionCommands(
|
||||
cwd: File,
|
||||
sbtHome: File,
|
||||
sbtBinDir: File,
|
||||
opts: LauncherOptions
|
||||
): Int = {
|
||||
if (opts.scriptVersion) {
|
||||
println(LauncherOptions.initSbtVersion)
|
||||
return 0
|
||||
|
|
@ -118,19 +131,23 @@ object Main {
|
|||
if (opts.numericVersion) {
|
||||
try {
|
||||
val out = Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!!
|
||||
println(out.linesIterator.lastOption.map(_.trim).getOrElse(""))
|
||||
println(out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse(""))
|
||||
return 0
|
||||
} catch { case _: Exception => return 1 }
|
||||
}
|
||||
if (opts.version) {
|
||||
if (ConfigLoader.isSbtProjectDir(cwd)) {
|
||||
val out = try Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!! catch { case _: Exception => "" }
|
||||
val ver = out.linesIterator.lastOption.map(_.trim).getOrElse("")
|
||||
val out =
|
||||
try Process(Seq(javaCmd, "-jar", sbtJar, "sbtVersion")).!!
|
||||
catch { case _: Exception => "" }
|
||||
val ver = out.linesIterator.toSeq.lastOption.map(_.trim).getOrElse("")
|
||||
println("sbt version in this project: " + ver)
|
||||
}
|
||||
println("sbt runner version: " + LauncherOptions.initSbtVersion)
|
||||
System.err.println("[info] sbt runner (sbtw) is a runner to run any declared version of sbt.")
|
||||
System.err.println("[info] Actual version of sbt is declared using project\\build.properties for each build.")
|
||||
System.err.println(
|
||||
"[info] Actual version of sbt is declared using project\\build.properties for each build."
|
||||
)
|
||||
return 0
|
||||
}
|
||||
0
|
||||
|
|
|
|||
|
|
@ -3,10 +3,17 @@ package sbtw
|
|||
object Memory {
|
||||
|
||||
private val memoryOptPrefixes = Set(
|
||||
"-Xmx", "-Xms", "-Xss",
|
||||
"-XX:MaxPermSize", "-XX:MaxMetaspaceSize", "-XX:ReservedCodeCacheSize",
|
||||
"-XX:+UseCGroupMemoryLimitForHeap", "-XX:MaxRAM", "-XX:InitialRAMPercentage",
|
||||
"-XX:MaxRAMPercentage", "-XX:MinRAMPercentage"
|
||||
"-Xmx",
|
||||
"-Xms",
|
||||
"-Xss",
|
||||
"-XX:MaxPermSize",
|
||||
"-XX:MaxMetaspaceSize",
|
||||
"-XX:ReservedCodeCacheSize",
|
||||
"-XX:+UseCGroupMemoryLimitForHeap",
|
||||
"-XX:MaxRAM",
|
||||
"-XX:InitialRAMPercentage",
|
||||
"-XX:MaxRAMPercentage",
|
||||
"-XX:MinRAMPercentage"
|
||||
)
|
||||
|
||||
def hasMemoryOpts(opts: Seq[String]): Boolean =
|
||||
|
|
@ -31,13 +38,14 @@ object Memory {
|
|||
}
|
||||
|
||||
def addDefaultMemory(
|
||||
javaOpts: Seq[String],
|
||||
sbtOpts: Seq[String],
|
||||
javaVersion: Int,
|
||||
defaultMemMb: Int
|
||||
javaOpts: Seq[String],
|
||||
sbtOpts: Seq[String],
|
||||
javaVersion: Int,
|
||||
defaultMemMb: Int
|
||||
): (Seq[String], Seq[String]) = {
|
||||
val fromJava = hasMemoryOpts(javaOpts)
|
||||
val fromTool = sys.env.get("JAVA_TOOL_OPTIONS").exists(s => hasMemoryOpts(s.split("\\s+").toSeq))
|
||||
val fromTool =
|
||||
sys.env.get("JAVA_TOOL_OPTIONS").exists(s => hasMemoryOpts(s.split("\\s+").toSeq))
|
||||
val fromJdk = sys.env.get("JDK_JAVA_OPTIONS").exists(s => hasMemoryOpts(s.split("\\s+").toSeq))
|
||||
val fromSbt = hasMemoryOpts(sbtOpts)
|
||||
if (fromJava || fromTool || fromJdk || fromSbt)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package sbtw
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.{ Paths, Files }
|
||||
import scala.sys.process.*
|
||||
|
||||
object Runner {
|
||||
|
|
@ -11,10 +10,16 @@ object Runner {
|
|||
case Some(h) =>
|
||||
val exe = new File(h, "bin/java.exe")
|
||||
if (exe.isFile) exe.getAbsolutePath
|
||||
else sys.env.get("JAVACMD").orElse(sys.env.get("JAVA_HOME").map(h0 =>
|
||||
new File(h0, "bin/java.exe").getAbsolutePath)).getOrElse("java")
|
||||
else
|
||||
sys.env
|
||||
.get("JAVACMD")
|
||||
.orElse(
|
||||
sys.env.get("JAVA_HOME").map(h0 => new File(h0, "bin/java.exe").getAbsolutePath)
|
||||
)
|
||||
.getOrElse("java")
|
||||
case None =>
|
||||
sys.env.get("JAVACMD")
|
||||
sys.env
|
||||
.get("JAVACMD")
|
||||
.orElse(sys.env.get("JAVA_HOME").map(h => new File(h, "bin/java.exe").getAbsolutePath))
|
||||
.getOrElse("java")
|
||||
}
|
||||
|
|
@ -41,7 +46,12 @@ object Runner {
|
|||
if (opts.debugInc) s = s :+ "-Dxsbt.inc.debug=true"
|
||||
if (opts.noColors) s = s :+ "-Dsbt.log.noformat=true"
|
||||
if (opts.noGlobal) s = s :+ "-Dsbt.global.base=project/.sbtboot"
|
||||
if (opts.noShare) s = s ++ Seq("-Dsbt.global.base=project/.sbtboot", "-Dsbt.boot.directory=project/.boot", "-Dsbt.ivy.home=project/.ivy")
|
||||
if (opts.noShare)
|
||||
s = s ++ Seq(
|
||||
"-Dsbt.global.base=project/.sbtboot",
|
||||
"-Dsbt.boot.directory=project/.boot",
|
||||
"-Dsbt.ivy.home=project/.ivy"
|
||||
)
|
||||
opts.supershell.foreach(v => s = s :+ s"-Dsbt.supershell=$v")
|
||||
opts.sbtVersion.foreach(v => s = s :+ s"-Dsbt.version=$v")
|
||||
opts.sbtDir.foreach(v => s = s :+ s"-Dsbt.global.base=$v")
|
||||
|
|
@ -75,14 +85,15 @@ object Runner {
|
|||
}
|
||||
|
||||
def runJvm(
|
||||
javaCmd: String,
|
||||
javaOpts: Seq[String],
|
||||
sbtOpts: Seq[String],
|
||||
sbtJar: String,
|
||||
bootArgs: Seq[String],
|
||||
verbose: Boolean
|
||||
javaCmd: String,
|
||||
javaOpts: Seq[String],
|
||||
sbtOpts: Seq[String],
|
||||
sbtJar: String,
|
||||
bootArgs: Seq[String],
|
||||
verbose: Boolean
|
||||
): Int = {
|
||||
val toolOpts = sys.env.get("JAVA_TOOL_OPTIONS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val toolOpts =
|
||||
sys.env.get("JAVA_TOOL_OPTIONS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val jdkOpts = sys.env.get("JDK_JAVA_OPTIONS").toSeq.flatMap(_.split("\\s+").filter(_.nonEmpty))
|
||||
val fullJavaOpts = javaOpts ++ sbtOpts ++ toolOpts ++ jdkOpts
|
||||
val cmd = Seq(javaCmd) ++ fullJavaOpts ++ Seq("-cp", sbtJar, "xsbt.boot.Boot") ++ bootArgs
|
||||
|
|
@ -98,7 +109,10 @@ object Runner {
|
|||
val jpsOut = Process(Seq("jps", "-lv")).!!
|
||||
val pids = jpsOut.linesIterator
|
||||
.filter(_.contains("xsbt.boot.Boot"))
|
||||
.flatMap(line => scala.util.Try(line.takeWhile(_.isDigit).toLong).toOption)
|
||||
.flatMap { line =>
|
||||
val pidStr = line.trim.takeWhile(_.isDigit)
|
||||
if (pidStr.nonEmpty) scala.util.Try(pidStr.toLong).toOption else None
|
||||
}
|
||||
.toList
|
||||
pids.foreach { pid =>
|
||||
try Process(Seq("taskkill", "/F", "/PID", pid.toString)).!
|
||||
|
|
|
|||
Loading…
Reference in New Issue