Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a simple jlink wrapper #1220

Merged
merged 7 commits into from
May 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ jobs:
if: type = pull_request OR (type = push AND branch = master)
# docker configuration as described in
# https://docs.travis-ci.com/user/docker/
- script: sbt "^validateJlink"
name: "scripted jlink tests"
jdk: oraclejdk11
if: type = pull_request OR (type = push AND branch = master)
- script: sbt "^validateDocker"
name: "scripted docker integration-tests"
if: type = pull_request OR (type = push AND branch = master)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ different archetypes for common configurations, such as simple Java apps or serv
* Systemv
* Upstart
* Java8 [jdkpackager][] wrapper
* Java11 [jlink][] wrapper
* Optional JDeb integration for cross-platform Debian builds
* Optional Spotify docker client integration

Expand Down Expand Up @@ -134,6 +135,7 @@ or provide a richer API for a single packaging format.
[Java server application]: http://www.scala-sbt.org/sbt-native-packager/archetypes/java_server/index.html
[My First Packaged Server Project guide]: http://www.scala-sbt.org/sbt-native-packager/GettingStartedServers/MyFirstProject.html
[jdkpackager]: http://www.scala-sbt.org/sbt-native-packager/formats/jdkpackager.html
[jlink]: https://docs.oracle.com/en/java/javase/11/tools/jlink.html

## Maintainers ##

Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,6 @@ addCommandAlias("validateOSX", "; validate ; validateUniversal")
// TODO check the cygwin scripted tests and run them on appveyor
addCommandAlias("validateWindows", "; testOnly * -- -n windows ; scripted universal/dist universal/stage windows/*")

addCommandAlias("validateJlink", "scripted jlink/*")

addCommandAlias("releaseFromTravis", "release with-defaults")
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ loadConfigFile() {
cat "$1" | sed '/^\#/d;s/\r$//' | sed 's/^-J-X/-X/' | tr '\r\n' ' '
}

# Detect which JVM we should use.
get_java_cmd() {
# High-priority override for Jlink images
if [ -n "$bundled_jvm" ]; then
echo "$bundled_jvm/bin/java"
elif [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ]; then
echo "$JAVA_HOME/bin/java"
else
echo "java"
fi
}

real_script_path="$(realpath "$0")"
app_home="$(realpath "$(dirname "$real_script_path")")"
Expand All @@ -52,7 +63,9 @@ app_mainclass=${{app_mainclass}}

${{template_declares}}

java_cmd="$(get_java_cmd)"

# If a configuration file exist, read the contents to $opts
[ -f "$script_conf_file" ] && opts=$(loadConfigFile "$script_conf_file")

exec java $java_opts -classpath $app_classpath $opts $app_mainclass "$@"
exec "$java_cmd" $java_opts -classpath $app_classpath $opts $app_mainclass "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ fix_classpath() {
}
# Detect if we should use JAVA_HOME or just try PATH.
get_java_cmd() {
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
# High-priority override for Jlink images
if [[ -n "$bundled_jvm" ]]; then
echo "$bundled_jvm/bin/java"
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
echo "$JAVA_HOME/bin/java"
else
echo "java"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@REM Configuration:
@REM @@APP_ENV_NAME@@_config.txt found in the @@APP_ENV_NAME@@_HOME.
@setlocal enabledelayedexpansion
@setlocal enableextensions
muuki88 marked this conversation as resolved.
Show resolved Hide resolved

@echo off

Expand All @@ -30,12 +31,27 @@ set "CFG_FILE=%APP_HOME%\@@APP_ENV_NAME@@_config.txt"
set CFG_OPTS=
call :parse_config "%CFG_FILE%" CFG_OPTS

rem We use the value of the JAVACMD environment variable if defined
set _JAVACMD=%JAVACMD%
rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config.
set _JAVA_OPTS=%JAVA_OPTS%
if "!_JAVA_OPTS!"=="" set _JAVA_OPTS=!CFG_OPTS!

if "%_JAVACMD%"=="" (
if not "%JAVA_HOME%"=="" (
if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
rem We keep in _JAVA_PARAMS all -J-prefixed and -D-prefixed arguments
rem "-J" is stripped, "-D" is left as is, and everything is appended to JAVA_OPTS
set _JAVA_PARAMS=
set _APP_ARGS=

@@APP_DEFINES@@

rem Bundled JRE has priority over standard environment variables
if defined BUNDLED_JVM (
set "_JAVACMD=%BUNDLED_JVM%\bin\java.exe"
) else (
if "%JAVACMD%" neq "" (
set "_JAVACMD=%JAVACMD%"
) else (
if "%JAVA_HOME%" neq "" (
if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
)
)
)

Expand Down Expand Up @@ -70,18 +86,6 @@ if "%JAVAOK%"=="false" (
exit /B 1
)


rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config.
set _JAVA_OPTS=%JAVA_OPTS%
if "!_JAVA_OPTS!"=="" set _JAVA_OPTS=!CFG_OPTS!

rem We keep in _JAVA_PARAMS all -J-prefixed and -D-prefixed arguments
rem "-J" is stripped, "-D" is left as is, and everything is appended to JAVA_OPTS
set _JAVA_PARAMS=
set _APP_ARGS=

@@APP_DEFINES@@

rem if configuration files exist, prepend their contents to the script arguments so it can be processed by this runner
call :parse_config "%SCRIPT_CONF_FILE%" SCRIPT_CONF_ARGS

Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ object Keys
with debian.DebianKeys
with rpm.RpmKeys
with archetypes.JavaAppKeys
with archetypes.JavaAppKeys2
with archetypes.JavaServerAppKeys
with archetypes.jlink.JlinkKeys
with archetypes.systemloader.SystemloaderKeys
with archetypes.scripts.BashStartScriptKeys
with archetypes.scripts.BatStartScriptKeys
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.typesafe.sbt.packager.archetypes

import sbt._

/**
* See [[JavaAppKeys]]. These are private to preserve binary compatibility.
*/
// TODO: merge with JavaAppKeys when we can break binary compatibility
private[packager] trait JavaAppKeys2 {
val bundledJvmLocation =
TaskKey[Option[String]]("bundledJvmLocation", "The location of the bundled JVM image")
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object JavaAppPackaging extends AutoPlugin {
*/
val batTemplate = "bat-template"

object autoImport extends JavaAppKeys with MaintainerScriptHelper
object autoImport extends JavaAppKeys with JavaAppKeys2 with MaintainerScriptHelper

import JavaAppPackaging.autoImport._

Expand Down Expand Up @@ -71,7 +71,8 @@ object JavaAppPackaging extends AutoPlugin {
val d = targetDir / installLocation
d.mkdirs()
LinuxPackageMapping(Seq(d -> (installLocation + "/" + name)), LinuxFileMetaData())
}
},
bundledJvmLocation := (bundledJvmLocation ?? None).value
)

private def makeRelativeClasspathNames(mappings: Seq[(File, String)]): Seq[String] =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.typesafe.sbt.packager.archetypes
package jlink

import sbt._

/**
* Available settings/tasks for the [[com.typesafe.sbt.packager.archetypes.jlink.JlinkPlugin]].
*/
private[packager] trait JlinkKeys {

val jlinkBundledJvmLocation =
TaskKey[String]("jlinkBundledJvmLocation", "The location of the resulting JVM image")

val jlinkOptions =
TaskKey[Seq[String]]("jlinkOptions", "Options for the jlink utility")

val jlinkBuildImage =
TaskKey[File]("jlinkBuildImage", "Runs jlink. Yields the directory with the runtime image")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.typesafe.sbt.packager.archetypes
package jlink

import scala.sys.process.{Process, ProcessBuilder}
import sbt._
import sbt.Keys._
import com.typesafe.sbt.SbtNativePackager.{Debian, Universal}
import com.typesafe.sbt.packager.Keys.{bundledJvmLocation, packageName}
import com.typesafe.sbt.packager.Compat._
import com.typesafe.sbt.packager.archetypes.jlink._
import com.typesafe.sbt.packager.archetypes.scripts.BashStartScriptKeys
import com.typesafe.sbt.packager.universal.UniversalPlugin

/**
* == Jlink Application ==
*
* This class contains the default settings for creating and deploying an
* application as a runtime image using the standard `jlink` utility.
*
* == Configuration ==
*
* This plugin adds new settings to configure your packaged application.
*
* @example Enable this plugin in your `build.sbt` with
*
* {{{
* enablePlugins(JlinkPlugin)
* }}}
*/
object JlinkPlugin extends AutoPlugin {

object autoImport extends JlinkKeys

import autoImport._

override def requires = JavaAppPackaging

override lazy val projectSettings: Seq[Setting[_]] = Seq(
target in jlinkBuildImage := target.value / "jlink" / "output",
jlinkBundledJvmLocation := "jre",
bundledJvmLocation := Some(jlinkBundledJvmLocation.value),
jlinkOptions := (jlinkOptions ?? Nil).value,
jlinkOptions ++= {
val log = streams.value.log
val run = runJavaTool(javaHome.in(jlinkBuildImage).value, log) _

val paths = fullClasspath.in(Compile).value.map(_.data.getPath)
val modules =
(run("jdeps", "-R" +: "--print-module-deps" +: paths) !! log).trim
.split(",")

JlinkOptions(addModules = modules, output = Some(target.in(jlinkBuildImage).value))
},
jlinkBuildImage := {
val log = streams.value.log
val run = runJavaTool(javaHome.in(jlinkBuildImage).value, log) _
val outDir = target.in(jlinkBuildImage).value

IO.delete(outDir)

run("jlink", jlinkOptions.value) !! log

outDir
},
mappings in jlinkBuildImage := {
val prefix = jlinkBundledJvmLocation.value
// make sure the prefix has a terminating slash
val prefix0 = if (prefix.isEmpty) prefix else (prefix + "/")

findFiles(jlinkBuildImage.value).map {
case (file, string) => (file, prefix0 + string)
}
},
mappings in Universal ++= mappings.in(jlinkBuildImage).value
)

// TODO: deduplicate with UniversalPlugin and DebianPlugin
/** Finds all files in a directory. */
private def findFiles(dir: File): Seq[(File, String)] =
((PathFinder(dir) ** AllPassFilter) --- dir)
.pair(file => IO.relativize(dir, file))

private def runJavaTool(jvm: Option[File], log: Logger)(exeName: String, args: Seq[String]): ProcessBuilder = {
val exe = jvm match {
case None => exeName
case Some(jh) => (jh / "bin" / exeName).getAbsolutePath
}

log.info("Running: " + (exe +: args).mkString(" "))

Process(exe, args)
}

private object JlinkOptions {
def apply(addModules: Seq[String] = Nil, output: Option[File] = None): Seq[String] =
option("--output", output) ++
list("--add-modules", addModules)

private def option[A](arg: String, value: Option[A]): Seq[String] =
value.toSeq.flatMap(a => Seq(arg, a.toString))

private def list(arg: String, values: Seq[String]): Seq[String] =
if (values.nonEmpty) Seq(arg, values.mkString(",")) else Nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ object AshScriptPlugin extends AutoPlugin {

override def projectSettings = Seq(
bashScriptTemplateLocation := (sourceDirectory.value / "templates" / ashTemplate),
bashScriptDefines := Defines((scriptClasspath in bashScriptDefines).value, bashScriptConfigLocation.value),
bashScriptDefines := Defines(
(scriptClasspath in bashScriptDefines).value,
bashScriptConfigLocation.value,
bundledJvmLocation.value
),
bashScriptDefines ++= bashScriptExtraDefines.value
)

Expand All @@ -86,15 +90,21 @@ object AshScriptPlugin extends AutoPlugin {
*/
object Defines {

@deprecated("1.3.21", "")
def apply(appClasspath: Seq[String], configFile: Option[String]): Seq[String] =
apply(appClasspath, configFile, None)

/**
* Creates the block of defines for a script.
*
* @param appClasspath A sequence of relative-locations (to the lib/ folder) of jars
* to include on the classpath.
* @param configFile An (optional) filename from which the script will read arguments.
*/
def apply(appClasspath: Seq[String], configFile: Option[String]): Seq[String] =
(configFile map configFileDefine).toSeq ++ Seq(makeClasspathDefine(appClasspath))
def apply(appClasspath: Seq[String], configFile: Option[String], bundledJvm: Option[String]): Seq[String] =
(configFile map configFileDefine).toSeq ++
Seq(makeClasspathDefine(appClasspath)) ++
(bundledJvm map bundledJvmDefine).toSeq

private[this] def makeClasspathDefine(cp: Seq[String]): String = {
val fullString = cp map (
Expand All @@ -107,5 +117,8 @@ object AshScriptPlugin extends AutoPlugin {

private[this] def configFileDefine(configFile: String) =
"script_conf_file=\"%s\"" format (configFile)

private[this] def bundledJvmDefine(bundledJvm: String) =
"""bundled_jvm="$(realpath "${app_home}/../%s")"""" format bundledJvm
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator wit
override def projectSettings: Seq[Setting[_]] = Seq(
bashScriptTemplateLocation := (sourceDirectory.value / "templates" / bashTemplate),
bashScriptExtraDefines := Nil,
bashScriptDefines := Defines((scriptClasspath in bashScriptDefines).value, bashScriptConfigLocation.value),
bashScriptDefines := Defines(
(scriptClasspath in bashScriptDefines).value,
bashScriptConfigLocation.value,
bundledJvmLocation.value
),
bashScriptDefines ++= bashScriptExtraDefines.value,
bashScriptReplacements := generateScriptReplacements(bashScriptDefines.value),
// Create a bashConfigLocation if options are set in build.sbt
Expand Down Expand Up @@ -100,15 +104,21 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator wit
*/
object Defines {

@deprecated("1.3.21", "")
def apply(appClasspath: Seq[String], configFile: Option[String]): Seq[String] =
apply(appClasspath, configFile, None)

/**
* Creates the block of defines for a script.
*
* @param appClasspath A sequence of relative-locations (to the lib/ folder) of jars
* to include on the classpath.
* @param configFile An (optional) filename from which the script will read arguments.
*/
def apply(appClasspath: Seq[String], configFile: Option[String]): Seq[String] =
(configFile map configFileDefine).toSeq ++ Seq(makeClasspathDefine(appClasspath))
def apply(appClasspath: Seq[String], configFile: Option[String], bundledJvm: Option[String]): Seq[String] =
(configFile map configFileDefine).toSeq ++
Seq(makeClasspathDefine(appClasspath)) ++
(bundledJvm map bundledJvmDefine).toSeq

private[this] def makeClasspathDefine(cp: Seq[String]): String = {
val fullString = cp map (
Expand All @@ -121,6 +131,9 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator wit

private[this] def configFileDefine(configFile: String) =
"declare -r script_conf_file=\"%s\"" format configFile

private[this] def bundledJvmDefine(bundledJvm: String) =
"""declare -r bundled_jvm="$(dirname "$app_home")/%s"""" format bundledJvm
}

private[this] def usageMainClassReplacement(mainClasses: Seq[String]): String =
Expand Down
Loading