Add installNativeThinClient task

This allows a user to install the native thin client into a particular
directory (e.g. /usr/local/bin). I also made buildNativeThinClient have
a file dependency on the classpath so that it can be incremental if the
classpath hasn't changed. This is useful if the user has run
buildNativeThinClient for testing and then decides to install it once
it's been validated without having to rebuild (which takes a minimum of
about 30 seconds on my laptop).
This commit is contained in:
Ethan Atkins 2020-07-27 14:33:19 -07:00
parent 26d7331283
commit 48fa28e566
2 changed files with 72 additions and 49 deletions

View File

@ -39,7 +39,7 @@ for:
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/bin" - export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/bin"
- export PATH="$PATH:graalvm-ce-java8-20.1.0/bin" - export PATH="$PATH:graalvm-ce-java8-20.1.0/bin"
- gu install native-image - gu install native-image
- sbt -Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/bin/native-image "sbtClientProj/genNativeExecutable" - sbt -Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/bin/native-image "sbtClientProj/buildNativeThinClient"
- -
matrix: matrix:
@ -70,7 +70,7 @@ for:
- export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/Contents/Home/bin" - export PATH="$PATH:~/.jabba/jdk/adopt@1.8.0-222/Contents/Home/bin"
- export PATH="$PATH:graalvm-ce-java8-20.1.0/Contents/Home/bin" - export PATH="$PATH:graalvm-ce-java8-20.1.0/Contents/Home/bin"
- gu install native-image - gu install native-image
- sbt -Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/Contents/Home/bin/native-image "sbtClientProj/genNativeExecutable" - sbt -Dsbt.native-image=$(pwd)/graalvm-ce-java8-20.1.0/Contents/Home/bin/native-image "sbtClientProj/buildNativeThinClient"
- -
matrix: matrix:
@ -123,7 +123,7 @@ for:
- '%USERPROFILE%\.sbt' - '%USERPROFILE%\.sbt'
test_script: test_script:
- sbt "-Dsbt.native-image=C:\graalvm-ce-java8-20.1.0\bin\native-image.cmd" "sbtClientProj/genNativeExecutable" - sbt "-Dsbt.native-image=C:\graalvm-ce-java8-20.1.0\bin\native-image.cmd" "sbtClientProj/buildNativeThinClient"
- -
matrix: matrix:
only: only:

115
build.sbt
View File

@ -206,8 +206,26 @@ lazy val sbtRoot: Project = (project in file("."))
.single("sbtOn")((state, dir) => s"sbtProj/test:runMain sbt.RunFromSourceMain $dir" :: state), .single("sbtOn")((state, dir) => s"sbtProj/test:runMain sbt.RunFromSourceMain $dir" :: state),
mimaSettings, mimaSettings,
mimaPreviousArtifacts := Set.empty, mimaPreviousArtifacts := Set.empty,
genExecutable := (sbtClientProj / genExecutable).evaluated, buildThinClient := (sbtClientProj / buildThinClient).evaluated,
genNativeExecutable := (sbtClientProj / genNativeExecutable).value, buildNativeThinClient := (sbtClientProj / buildNativeThinClient).value,
installNativeThinClient := {
// nativeInstallDirectory can be set globally or in a gitignored local file
val dir = nativeInstallDirectory.?.value
val target = Def.spaceDelimited("").parsed.headOption match {
case Some(p) => file(p).toPath
case _ =>
dir match {
case Some(d) => d / "sbtc"
case _ =>
val msg = "Expected input parameter <path>: installNativeExecutable /usr/local/bin"
throw new IllegalStateException(msg)
}
}
val base = baseDirectory.value.toPath
val exec = (sbtClientProj / buildNativeThinClient).value
streams.value.log.info(s"installing thin client ${base.relativize(exec)} to ${target}")
Files.copy(exec, target, java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
) )
// This is used to configure an sbt-launcher for this version of sbt. // This is used to configure an sbt-launcher for this version of sbt.
@ -1049,15 +1067,18 @@ lazy val serverTestProj = (project in file("server-test"))
) )
val isWin = scala.util.Properties.isWin val isWin = scala.util.Properties.isWin
val generateReflectionConfig = taskKey[Unit]("generate the graalvm reflection config") val buildThinClient =
val genExecutable =
inputKey[JPath]("generate a java implementation of the thin client") inputKey[JPath]("generate a java implementation of the thin client")
val graalClasspath = taskKey[String]("Generate the classpath for graal (compacted for windows)") val thinClientClasspath =
val graalNativeImageCommand = taskKey[String]("The native image command") taskKey[Seq[JPath]]("Generate the classpath for thin client (compacted for windows)")
val graalNativeImageOptions = settingKey[Seq[String]]("The native image options") val thinClientNativeImageCommand = taskKey[String]("The native image command")
val graalNativeImageClass = settingKey[String]("The class for the native image") val thinClientNativeImageOptions = settingKey[Seq[String]]("The native image options")
val genNativeExecutable = taskKey[JPath]("Generate a native executable") val thinClientNativeImageClass = settingKey[String]("The class for the native image")
val nativeExecutablePath = settingKey[JPath]("The location of the native executable") val buildNativeThinClient = taskKey[JPath]("Generate a native executable")
// Use a TaskKey rather than SettingKey for nativeInstallDirectory so it can left unset by default
val nativeInstallDirectory = taskKey[JPath]("The install directory for the native executable")
val installNativeThinClient = inputKey[JPath]("Install the native executable")
val nativeThinClientPath = settingKey[JPath]("The location of the native executable")
lazy val sbtClientProj = (project in file("client")) lazy val sbtClientProj = (project in file("client"))
.dependsOn(commandProj) .dependsOn(commandProj)
.settings( .settings(
@ -1075,54 +1096,56 @@ lazy val sbtClientProj = (project in file("client"))
* external process so we create symbolic links with short names to get the * external process so we create symbolic links with short names to get the
* classpath length under the limit. * classpath length under the limit.
*/ */
graalClasspath := { thinClientClasspath := {
val original = (Compile / fullClasspathAsJars).value.map(_.data) val original = (Compile / fullClasspathAsJars).value.map(_.data)
val outputDir = target.value / "graalcp" val outputDir = target.value / "thinclientcp"
IO.createDirectory(outputDir) IO.createDirectory(outputDir)
Files.walk(outputDir.toPath).forEach { Files.walk(outputDir.toPath).forEach {
case f if f.getFileName.toString.endsWith(".jar") => Files.deleteIfExists(f) case f if f.getFileName.toString.endsWith(".jar") => Files.deleteIfExists(f)
case _ => case _ =>
} }
original.zipWithIndex original.zipWithIndex.map {
.map { case (f, i) => Files.createSymbolicLink(outputDir.toPath / s"$i.jar", f.toPath)
case (f, i) => }
Files.createSymbolicLink(outputDir.toPath / s"$i.jar", f.toPath)
s"$i.jar"
}
.mkString(java.io.File.pathSeparator)
}, },
graalNativeImageCommand := System.getProperty("sbt.native-image", "native-image").toString, thinClientNativeImageCommand := System.getProperty("sbt.native-image", "native-image").toString,
genNativeExecutable / name := s"sbtc${if (isWin) ".exe" else ""}", buildNativeThinClient / name := s"sbtc${if (isWin) ".exe" else ""}",
nativeExecutablePath := target.value.toPath / "bin" / (genNativeExecutable / name).value, nativeThinClientPath := target.value.toPath / "bin" / (buildNativeThinClient / name).value,
graalNativeImageClass := "sbt.client.Client", thinClientNativeImageClass := "sbt.client.Client",
genNativeExecutable := { buildNativeThinClient := {
val prefix = Seq(graalNativeImageCommand.value, "-cp", graalClasspath.value) val hasChanges = thinClientClasspath.outputFileChanges.hasChanges
val full = prefix ++ graalNativeImageOptions.value :+ graalNativeImageClass.value val cpString =
val pb = new java.lang.ProcessBuilder(full: _*) thinClientClasspath.value.map(_.getFileName).mkString(java.io.File.pathSeparator)
pb.directory(target.value / "graalcp") val prefix = Seq(thinClientNativeImageCommand.value, "-cp", cpString)
val proc = pb.start() val full = prefix ++ thinClientNativeImageOptions.value :+ thinClientNativeImageClass.value
val thread = new Thread { val dir = target.value
setDaemon(true) if (hasChanges || !Files.exists(nativeThinClientPath.value)) {
val is = proc.getInputStream val pb = new java.lang.ProcessBuilder(full: _*)
val es = proc.getErrorStream pb.directory(dir / "thinclientcp")
val proc = pb.start()
val thread = new Thread {
setDaemon(true)
val is = proc.getInputStream
val es = proc.getErrorStream
override def run(): Unit = { override def run(): Unit = {
Thread.sleep(100) Thread.sleep(100)
while (proc.isAlive) { while (proc.isAlive) {
if (is.available > 0 || es.available > 0) { if (is.available > 0 || es.available > 0) {
while (is.available > 0) System.out.print(is.read.toChar) while (is.available > 0) System.out.print(is.read.toChar)
while (es.available > 0) System.err.print(es.read.toChar) while (es.available > 0) System.err.print(es.read.toChar)
}
if (proc.isAlive) Thread.sleep(10)
} }
if (proc.isAlive) Thread.sleep(10)
} }
} }
thread.start()
proc.waitFor(5, java.util.concurrent.TimeUnit.MINUTES)
assert(proc.exitValue == 0, s"Exit value ${proc.exitValue} was nonzero")
} }
thread.start() nativeThinClientPath.value
proc.waitFor(5, java.util.concurrent.TimeUnit.MINUTES)
assert(proc.exitValue == 0, s"Exit value ${proc.exitValue} was nonzero")
nativeExecutablePath.value
}, },
graalNativeImageOptions := Seq( thinClientNativeImageOptions := Seq(
"--no-fallback", "--no-fallback",
s"--initialize-at-run-time=sbt.client", s"--initialize-at-run-time=sbt.client",
"--verbose", "--verbose",
@ -1131,7 +1154,7 @@ lazy val sbtClientProj = (project in file("client"))
"-H:-ParseRuntimeOptions", "-H:-ParseRuntimeOptions",
s"-H:Name=${target.value / "bin" / "sbtc"}", s"-H:Name=${target.value / "bin" / "sbtc"}",
), ),
genExecutable := { buildThinClient := {
val isFish = Def.spaceDelimited("").parsed.headOption.fold(false)(_ == "--fish") val isFish = Def.spaceDelimited("").parsed.headOption.fold(false)(_ == "--fish")
val ext = if (isWin) ".bat" else if (isFish) ".fish" else ".sh" val ext = if (isWin) ".bat" else if (isFish) ".fish" else ".sh"
val output = target.value.toPath / "bin" / s"${if (isFish) "fish-" else ""}client$ext" val output = target.value.toPath / "bin" / s"${if (isFish) "fish-" else ""}client$ext"