Skip to content
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ following to `build.sbt` after the above import statement:

startScriptName <<= target / "run"

There is also the possibility to define a list of arguments which is always
passed to the main class, prepended to the arguments specified when running the
start script:

startScriptArgs := Seq("one arg", "another")

## Migration from earlier versions of xsbt-start-script-plugin

After 0.5.2, the plugin and its APIs were renamed to use
Expand Down
33 changes: 21 additions & 12 deletions src/main/scala/com/typesafe/sbt/SbtStartScript.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ object SbtStartScript extends Plugin {

object StartScriptKeys {
val startScriptFile = SettingKey[File]("start-script-name")
val startScriptArgs = SettingKey[Seq[String]]("start-script-args", "arguments to be prepended to the command line arguments to the main class")
// this is newly-added to make the val name consistent with the
// string name, and preferred over startScriptFile
val startScriptName = startScriptFile
Expand Down Expand Up @@ -62,6 +63,7 @@ object SbtStartScript extends Plugin {
// these settings to any project.
val genericStartScriptSettings: Seq[Project.Setting[_]] = Seq(
startScriptFile <<= (target) { (target) => target / scriptname },
startScriptArgs := Seq(),
// maybe not the right way to do this...
startScriptBaseDirectory <<= (thisProjectRef) { (ref) => new File(ref.build) },
startScriptNotDefined in Compile <<= (streams, startScriptFile in Compile) map startScriptNotDefinedTask,
Expand All @@ -77,17 +79,17 @@ object SbtStartScript extends Plugin {
startScriptJettyURL in Compile <<= (startScriptJettyVersion in Compile) { (version) => "http://archive.eclipse.org/jetty/" + version + "/dist/jetty-distribution-" + version + ".zip" },
startScriptJettyContextPath in Compile := "/",
startScriptJettyHome in Compile <<= (streams, target, startScriptJettyURL in Compile, startScriptJettyChecksum in Compile) map startScriptJettyHomeTask,
startScriptForWar in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, packageWar in Compile, startScriptJettyHome in Compile, startScriptJettyContextPath in Compile) map startScriptForWarTask,
startScriptForWar in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, packageWar in Compile, startScriptJettyHome in Compile, startScriptJettyContextPath in Compile, startScriptArgs in Compile) map startScriptForWarTask,
startScript in Compile <<= startScriptForWar in Compile) ++ genericStartScriptSettings

// settings to be added to a project with an exported jar
val startScriptForJarSettings: Seq[Project.Setting[_]] = Seq(
startScriptForJar in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, packageBin in Compile, relativeDependencyClasspathString in Compile, mainClass in Compile) map startScriptForJarTask,
startScriptForJar in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, packageBin in Compile, relativeDependencyClasspathString in Compile, mainClass in Compile, startScriptArgs in Compile) map startScriptForJarTask,
startScript in Compile <<= startScriptForJar in Compile) ++ genericStartScriptSettings

// settings to be added to a project that doesn't export a jar
val startScriptForClassesSettings: Seq[Project.Setting[_]] = Seq(
startScriptForClasses in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, relativeFullClasspathString in Compile, mainClass in Compile) map startScriptForClassesTask,
startScriptForClasses in Compile <<= (streams, startScriptBaseDirectory, startScriptFile in Compile, relativeFullClasspathString in Compile, mainClass in Compile, startScriptArgs in Compile) map startScriptForClassesTask,
startScript in Compile <<= startScriptForClasses in Compile) ++ genericStartScriptSettings

// Extracted.getOpt is not in 10.1 and earlier
Expand Down Expand Up @@ -246,6 +248,10 @@ object SbtStartScript extends Plugin {
})
}

private def argsToString(args: Seq[String]): String = if (isWindows()) argsToStringWindows(args) else argsToStringLinux(args)
private def argsToStringLinux(args: Seq[String]): String = args.map(x => "'" + x.replaceAllLiterally("'", """'\''""") + "'").mkString(" ")
private def argsToStringWindows(args: Seq[String]): String = args.map(x => "\"" + x + "\"").mkString(" ")

private def relativeClasspathStringTask(baseDirectory: File, cp: Classpath) = {
RelativeClasspathString(cp.files map { f => relativizeFile(baseDirectory, f, "$PROJECT_DIR") } mkString ("", java.io.File.pathSeparator, ""))
}
Expand Down Expand Up @@ -305,26 +311,27 @@ fi
scriptFile.setExecutable(true)
}

def startScriptForClassesTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, cpString: RelativeClasspathString, maybeMainClass: Option[String]) = {
def startScriptForClassesTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, cpString: RelativeClasspathString, maybeMainClass: Option[String], args: Seq[String]) = {
val templateWindows = """@echo off
@SCRIPT_ROOT_DETECT@

@MAIN_CLASS_SETUP@

java %JOPTS% -cp "@CLASSPATH@" "%MAINCLASS%" %*
java %JOPTS% -cp "@CLASSPATH@" "%MAINCLASS%" @ARGS@ %*

"""
val templateLinux = """#!/bin/bash
@SCRIPT_ROOT_DETECT@

@MAIN_CLASS_SETUP@

exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" "$@"
exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" @ARGS@ "$@"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the args are passed through as-is (no quoting), people would have to do any quoting themselves, and separately for linux/mac and windows - should we auto-quote each arg for them? I know how to do that for unix but not Windows. The way we are doing classpath is bogus and doesn't handle backslashes or double quotes in the classpath, though we've gotten away with it so far.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh just saw below that you are doing double quotes


"""
val template: String = if (isWindows()) templateWindows else templateLinux
val script = renderTemplate(template, Map("SCRIPT_ROOT_DETECT" -> scriptRootDetect(baseDirectory, scriptFile, None),
"CLASSPATH" -> cpString.value,
"ARGS" -> argsToString(args),
"MAIN_CLASS_SETUP" -> mainClassSetup(maybeMainClass)))
writeScript(scriptFile, script)
streams.log.info("Wrote start script for mainClass := " + maybeMainClass + " to " + scriptFile)
Expand All @@ -337,28 +344,29 @@ exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" "$@"
// We put jar on the classpath and supply a mainClass because with "java -jar"
// the deps have to be bundled in the jar (classpath is ignored), and SBT does
// not normally do that.
def startScriptForJarTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, jarFile: File, cpString: RelativeClasspathString, maybeMainClass: Option[String]) = {
def startScriptForJarTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, jarFile: File, cpString: RelativeClasspathString, maybeMainClass: Option[String], args: Seq[String]) = {
val templateWindows = """@echo off
@SCRIPT_ROOT_DETECT@

@MAIN_CLASS_SETUP@

java %JOPTS% -cp "@CLASSPATH@" %MAINCLASS% %*
java %JOPTS% -cp "@CLASSPATH@" %MAINCLASS% @ARGS@ %*

"""
val templateLinux = """#!/bin/bash
@SCRIPT_ROOT_DETECT@

@MAIN_CLASS_SETUP@

exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" "$@"
exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" @ARGS@ "$@"

"""
val template: String = if (isWindows()) templateWindows else templateLinux
val relativeJarFile = relativizeFile(baseDirectory, jarFile)

val script = renderTemplate(template, Map("SCRIPT_ROOT_DETECT" -> scriptRootDetect(baseDirectory, scriptFile, Some(relativeJarFile)),
"CLASSPATH" -> cpString.value,
"ARGS" -> argsToString(args),
"MAIN_CLASS_SETUP" -> mainClassSetup(maybeMainClass)))
writeScript(scriptFile, script)
streams.log.info("Wrote start script for jar " + relativeJarFile + " to " + scriptFile + " with mainClass := " + maybeMainClass)
Expand All @@ -369,7 +377,7 @@ exec java $JAVA_OPTS -cp "@CLASSPATH@" "$MAINCLASS" "$@"
// we need to download and unpack the Jetty "distribution" which isn't
// a normal jar dependency. Not sure if Ivy can do that, may have to just
// have a configurable URL and checksum.
def startScriptForWarTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, warFile: File, jettyHome: File, jettyContextPath: String) = {
def startScriptForWarTask(streams: TaskStreams, baseDirectory: File, scriptFile: File, warFile: File, jettyHome: File, jettyContextPath: String, args: Seq[String]) = {

// First we need a Jetty config to move us to the right context path
val contextFile = jettyHome / "contexts" / "start-script.xml"
Expand All @@ -394,7 +402,7 @@ copy "@WARFILE@" "@JETTY_HOME@\webapps" || (echo "Failed to copy @WARFILE@ to @J

if "%PORT%"=="" (set PORT=8080)

java %JAVA_OPTS% -Djetty.port="%PORT%" -Djetty.home="@JETTY_HOME@" -jar "@JETTY_HOME@\start.jar" %*
java %JAVA_OPTS% -Djetty.port="%PORT%" -Djetty.home="@JETTY_HOME@" -jar "@JETTY_HOME@\start.jar" @ARGS@ %*

"""
val templateLinux = """#!/bin/bash
Expand All @@ -406,7 +414,7 @@ if test x"$PORT" = x ; then
PORT=8080
fi

exec java $JAVA_OPTS -Djetty.port="$PORT" -Djetty.home="@JETTY_HOME@" -jar "@JETTY_HOME@/start.jar" "$@"
exec java $JAVA_OPTS -Djetty.port="$PORT" -Djetty.home="@JETTY_HOME@" -jar "@JETTY_HOME@/start.jar" @ARGS@ "$@"

"""
val template: String = if (isWindows()) templateWindows else templateLinux
Expand All @@ -415,6 +423,7 @@ exec java $JAVA_OPTS -Djetty.port="$PORT" -Djetty.home="@JETTY_HOME@" -jar "@JET
val script = renderTemplate(template,
Map("SCRIPT_ROOT_DETECT" -> scriptRootDetect(baseDirectory, scriptFile, Some(relativeWarFile)),
"WARFILE" -> relativeWarFile.toString,
"ARGS" -> argsToString(args),
"JETTY_HOME" -> jettyHome.toString))
writeScript(scriptFile, script)

Expand Down