Skip to content

Commit f3772f4

Browse files
committed
Added some configuration of how template start scripts are generated.
* Use same classpath ordering as sbt in start scripts * Allow user configuration of additional hooks for the script. Review by @huntc to see if we're any closer to parity with Play.
1 parent e6696d7 commit f3772f4

File tree

6 files changed

+124
-43
lines changed

6 files changed

+124
-43
lines changed

src/main/resources/com/typesafe/sbt/packager/archetypes/bash-template

+6-9
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,15 @@ is_function_defined() {
137137
declare -f "$1" > /dev/null
138138
}
139139

140-
# If we're *not* running in a terminal, and we don't have any arguments, then we need to add the 'ui' parameter
140+
# Attempt to detect if the script is running via a GUI or not
141+
# TODO - Determine where/how we use this generically
141142
detect_terminal_for_ui() {
142143
[[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && {
143-
addResidual "ui"
144+
echo "true"
144145
}
145146
# SPECIAL TEST FOR MAC
146147
[[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && {
147-
echo "Detected MAC OSX launched script...."
148-
echo "Swapping to UI"
149-
addResidual "ui"
148+
echo "true"
150149
}
151150
}
152151

@@ -182,7 +181,6 @@ run() {
182181

183182
# process the combined args, then reset "$@" to the residuals
184183
process_args "$@"
185-
detect_terminal_for_ui
186184
set -- "${residual_args[@]}"
187185
argumentCount=$#
188186

@@ -251,11 +249,10 @@ declare -a residual_args
251249
declare -a java_args
252250
declare -a app_commands
253251
declare -r app_home="$(realpath "$(dirname "$0")")"
254-
${{template_declares}}
255-
declare -r java_cmd=$(get_java_cmd)
256252
# TODO - Check whether this is ok in cygwin...
257253
declare -r lib_dir="${app_home}/../lib"
258-
declare -r app_classpath="$lib_dir/*"
254+
${{template_declares}}
255+
declare -r java_cmd=$(get_java_cmd)
259256

260257
# Now check to see if it's a good enough version
261258
# TODO - Check to see if we have a configured default java version, otherwise use 1.6

src/main/resources/com/typesafe/sbt/packager/archetypes/bat-template

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
if "%@@APP_ENV_NAME@@_HOME%"=="" set "@@APP_ENV_NAME@@_HOME=%~dp0\\.."
1212
set ERROR_CODE=0
1313

14+
set "APP_LIB_DIR=%@@APP_ENV_NAME@@_HOME%\lib\"
15+
1416
rem Detect if we were double clicked, although theoretically A user could
1517
rem manually run cmd /c
1618
for %%x in (%cmdcmdline%) do if %%~x==/c set DOUBLECLICKED=1
@@ -85,9 +87,10 @@ set _JAVA_OPTS=%JAVA_OPTS%
8587
if "%_JAVA_OPTS%"=="" set _JAVA_OPTS=%CFG_OPTS%
8688

8789
:run
88-
90+
91+
set "APP_CLASSPATH=@@APP_CLASSPATH@@"
8992
rem TODO - figure out how to pass arguments....
90-
"%_JAVACMD%" %_JAVA_OPTS% %@@APP_ENV_NAME@@_OPTS% -cp "%@@APP_ENV_NAME@@_HOME%\lib\*" @@APP_MAIN_CLASS@@ %CMDS%
93+
"%_JAVACMD%" %_JAVA_OPTS% %@@APP_ENV_NAME@@_OPTS% -cp "%APP_CLASSPATH%" @@APP_MAIN_CLASS@@ %CMDS%
9194
if ERRORLEVEL 1 goto error
9295
goto end
9396

src/main/scala/com/typesafe/sbt/packager/Keys.scala

+7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ object Keys extends linux.Keys
1010
with universal.UniversalKeys {
1111

1212
// TODO - Do these keys belong here?
13+
14+
// These keys are used by the JavaApp archetype.
15+
val classpathOrdering = TaskKey[Seq[(File, String)]]("classpathOrdering", "The order of the classpath used at runtime for the bat/bash scripts.")
1316
val makeBashScript = TaskKey[Option[File]]("makeBashScript", "Creates or discovers the bash script used by this project.")
17+
val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.")
18+
val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.")
19+
val scriptClasspath = TaskKey[Seq[String]]("scriptClasspath", "A list of relative filenames (to the lib/ folder in the distribution) of what to include on the classpath.")
1420
val makeBatScript = TaskKey[Option[File]]("makeBatScript", "Creates or discovers the bat script used by this project.")
21+
val batScriptReplacements = TaskKey[Seq[(String,String)]]("batScriptReplacements", "Replacements of template parameters used in the windows bat script.")
1522

1623
}

src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala

+51-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package archetypes
44

55
import Keys._
66
import sbt._
7-
import sbt.Keys.{mappings, target, name, mainClass}
7+
import sbt.Keys.{mappings, target, name, mainClass, normalizedName}
88
import linux.LinuxPackageMapping
99
import SbtNativePackager._
1010

@@ -19,40 +19,71 @@ import SbtNativePackager._
1919
object JavaAppPackaging {
2020

2121
def settings: Seq[Setting[_]] = Seq(
22-
mappings in Universal <++= (Keys.managedClasspath in Compile) map universalDepMappings,
23-
mappings in Universal <+= (Keys.packageBin in Compile) map { jar =>
24-
jar -> ("lib/" + jar.getName)
22+
// Here we record the classpath as it's added to the mappings separately, so
23+
// we cna use it to generate the bash/bat scripts.
24+
classpathOrdering := Nil,
25+
classpathOrdering <+= (Keys.packageBin in Compile) map { jar =>
26+
jar -> ("lib/" + jar.getName)
27+
},
28+
classpathOrdering <++= (Keys.managedClasspath in Compile) map universalDepMappings,
29+
mappings in Universal <++= classpathOrdering,
30+
scriptClasspath <<= classpathOrdering map makeRelativeClasspathNames,
31+
bashScriptExtraDefines := Nil,
32+
bashScriptDefines <<= (Keys.mainClass in Compile, scriptClasspath, bashScriptExtraDefines) map { (mainClass, cp, extras) =>
33+
val hasMain =
34+
for {
35+
cn <- mainClass
36+
} yield JavaAppBashScript.makeDefines(cn, appClasspath = cp, extras = extras)
37+
hasMain getOrElse Nil
2538
},
26-
makeBashScript <<= (Keys.mainClass in Compile, target in Universal, name in Universal) map makeUniversalBinScript,
27-
makeBatScript <<= (Keys.mainClass in Compile, target in Universal, name) map makeUniversalBatScript,
28-
mappings in Universal <++= (makeBashScript, name) map { (script, name) =>
39+
makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName) map makeUniversalBinScript,
40+
batScriptReplacements <<= (normalizedName, Keys.mainClass in Compile, scriptClasspath) map { (name, mainClass, cp) =>
41+
mainClass map { mc =>
42+
JavaAppBatScript.makeReplacements(name = name, mainClass = mc, appClasspath = cp)
43+
} getOrElse Nil
44+
45+
},
46+
makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName) map makeUniversalBatScript,
47+
mappings in Universal <++= (makeBashScript, normalizedName) map { (script, name) =>
2948
for {
3049
s <- script.toSeq
3150
} yield s -> ("bin/" + name)
3251
},
33-
mappings in Universal <++= (makeBatScript, name) map { (script, name) =>
52+
mappings in Universal <++= (makeBatScript, normalizedName) map { (script, name) =>
3453
for {
3554
s <- script.toSeq
3655
} yield s -> ("bin/" + name + ".bat")
3756
}
3857
)
3958

40-
def makeUniversalBinScript(mainClass: Option[String], tmpDir: File, name: String): Option[File] =
41-
for(mc <- mainClass) yield {
42-
val scriptBits = JavaAppBashScript.generateScript(mc)
59+
def makeRelativeClasspathNames(mappings: Seq[(File, String)]): Seq[String] =
60+
for {
61+
(file, name) <- mappings
62+
} yield {
63+
// Here we want the name relative to the lib/ folder...
64+
// For now we just cheat...
65+
if(name startsWith "lib/") name drop 4
66+
else "../" + name
67+
}
68+
69+
def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String): Option[File] =
70+
if(defines.isEmpty) None
71+
else {
72+
val scriptBits = JavaAppBashScript.generateScript(defines)
4373
val script = tmpDir / "tmp" / "bin" / name
4474
IO.write(script, scriptBits)
4575
// TODO - Better control over this!
4676
script.setExecutable(true)
47-
script
77+
Some(script)
4878
}
4979

50-
def makeUniversalBatScript(mainClass: Option[String], tmpDir: File, name: String): Option[File] =
51-
for(mc <- mainClass) yield {
52-
val scriptBits = JavaAppBatScript.generateScript(name, mc)
80+
def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] =
81+
if(replacements.isEmpty) None
82+
else {
83+
val scriptBits = JavaAppBatScript.generateScript(replacements)
5384
val script = tmpDir / "tmp" / "bin" / (name + ".bat")
5485
IO.write(script, scriptBits)
55-
script
86+
Some(script)
5687
}
5788

5889
// Converts a managed classpath into a set of lib mappings.
@@ -64,10 +95,10 @@ object JavaAppPackaging {
6495
// TODO - Figure out what to do with jar files.
6596
} yield {
6697
val filename: Option[String] = for {
67-
module <- dep.metadata.get(AttributeKey[ModuleID]("module-id"))
68-
artifact <- dep.metadata.get(AttributeKey[Artifact]("artifact"))
69-
} yield {
70-
module.organization + "." +
98+
module <- dep.metadata.get(AttributeKey[ModuleID]("module-id"))
99+
artifact <- dep.metadata.get(AttributeKey[Artifact]("artifact"))
100+
} yield {
101+
module.organization + "." +
71102
module.name + "-" +
72103
Option(artifact.name.replace(module.name, "")).filterNot(_.isEmpty).map(_ + "-").getOrElse("") +
73104
module.revision + ".jar"

src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBashScript.scala

+30-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,42 @@
11
package com.typesafe.sbt.packager.archetypes
22

3+
/**
4+
* Constructs a bash script for running a java application.
5+
*
6+
* Makes use of the associated bash-template, with a few hooks
7+
*
8+
*/
39
object JavaAppBashScript {
410

511
private[this] def bashTemplateSource =
612
getClass.getResource("bash-template")
713
private[this] def charset =
814
java.nio.charset.Charset.forName("UTF-8")
9-
def generateScript(mainClass: String,
10-
configFile: Option[String] = None): String = {
11-
val sb = new StringBuffer
12-
val defines: Seq[String] =
15+
16+
/** Creates the block of defines for a script.
17+
*
18+
* @param mainClass The required "main" method class we use to run the program.
19+
* @param appClasspath A sequence of relative-locations (to the lib/ folder) of jars
20+
* to include on the classpath.
21+
* @param configFile An (optional) filename from which the script will read arguments.
22+
* @param extras Any additional defines/commands that should be run in this script.
23+
*/
24+
def makeDefines(
25+
mainClass: String,
26+
appClasspath: Seq[String] = Seq("*"),
27+
configFile: Option[String] = None,
28+
extras: Seq[String] = Nil): Seq[String] =
29+
Seq(mainClassDefine(mainClass)) ++
1330
(configFile map configFileDefine).toSeq ++
14-
Seq(mainClassDefine(mainClass))
31+
Seq(makeClasspathDefine(appClasspath)) ++
32+
extras
33+
34+
private def makeClasspathDefine(cp: Seq[String]): String = {
35+
val fullString = cp map (n => "$lib_dir/"+n) mkString ":"
36+
"declare -r app_classpath=\""+fullString+"\"\n"
37+
}
38+
def generateScript(defines: Seq[String]): String = {
39+
val sb = new StringBuffer
1540
for(line <- sbt.IO.readLinesURL(bashTemplateSource, charset)) {
1641
if(line contains """${{template_declares}}""") {
1742
sb append (defines mkString "\n")

src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBatScript.scala

+25-7
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,36 @@ object JavaAppBatScript {
88

99
def makeEnvFriendlyName(name: String): String =
1010
name.toUpperCase.replaceAll("\\W", "_")
11+
12+
def makeWindowsRelativeClasspath(cp: Seq[String]): String = {
13+
def cleanPath(path: String): String = path.replaceAll("/", "\\")
14+
def makeRelativePath(path: String): String =
15+
"%APP_LIB_DIR%\\" + cleanPath(path)
16+
cp map makeRelativePath mkString ":"
17+
}
18+
// TODO - Allow recursive replacements....
19+
def makeReplacements(
20+
name: String,
21+
mainClass: String,
22+
appClasspath: Seq[String] = Seq("*"),
23+
extras: Seq[(String,String)] = Nil): Seq[(String, String)] = {
24+
Seq(
25+
"APP_NAME" -> name,
26+
"APP_MAIN_CLASS" -> mainClass,
27+
"APP_ENV_NAME" -> makeEnvFriendlyName(name),
28+
"APP_CLASSPATH" -> makeWindowsRelativeClasspath(appClasspath)
29+
) ++ extras
30+
}
1131

1232
def generateScript(
13-
name: String,
14-
mainClass: String): String = {
33+
replacements: Seq[(String,String)]): String = {
1534
val sb = new StringBuffer
1635
for(line <- sbt.IO.readLinesURL(bashTemplateSource, charset)) {
17-
1836
val fixed =
19-
line.replaceAll("@@APP_NAME@@", name).
20-
replaceAll("@@APP_MAIN_CLASS@@", mainClass).
21-
replaceAll("@@APP_ENV_NAME@@", makeEnvFriendlyName(name))
22-
37+
replacements.foldLeft(line) {
38+
case (line, (key, value)) =>
39+
line.replaceAll("@@"+key+"@@", java.util.regex.Matcher.quoteReplacement(value))
40+
}
2341
sb append fixed
2442
sb append "\r\n"
2543
}

0 commit comments

Comments
 (0)