Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
29 changes: 17 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 @@ -305,26 +307,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" -> args.map(x => "\"" + x + "\"").mkString,
Copy link
Contributor

Choose a reason for hiding this comment

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

on unix the easiest way to quote is single quotes; something like this:

 "'" + x.replaceAllLiterally("'", """'\''""") + "'"

i.e. put all single quotes outside of single quotes and backslash them, single quote everything else. Double quotes are hard to get right because they support backslash escapes and $VARIABLEs.

On Windows, who knows. I guess we have been just adding double quotes so far.

Copy link
Author

Choose a reason for hiding this comment

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

No matter what we do, we’d have to document it, and people would need to be aware of how exactly it works; there is no path we can take which Just Works. In this light I propose to just document that no translation is done whatsoever apart from surrounding it with double quotes. Then the user has full control over what goes on in the script.

(reflection: the goal could be that strings can be passed literally from the build definition into the main method’s argument array, but is that really achievable?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd propose either no translation (no double quotes) and say that people have to quote it themselves in platform-aware fashion; or quote correctly for the two platforms. Surrounding with double quotes and nothing else is pretty much just wrong, since it won't handle $, ", "", and so forth.

I know on unix it is correct to replace all single quotes in the original string with close single, backslash-single, and open single. Then put single quotes around the outside. So we may as well do that instead of something broken with double quotes.

I don't really know what to do on Windows but we could leave the double quotes around the outside and let someone send a patch to improve it, I suppose.

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, I see your point; I cannot test the Windows part myself, though. Will amend for Linux only.

"MAIN_CLASS_SETUP" -> mainClassSetup(maybeMainClass)))
writeScript(scriptFile, script)
streams.log.info("Wrote start script for mainClass := " + maybeMainClass + " to " + scriptFile)
Expand All @@ -337,28 +340,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" -> args.map(x => "\"" + x + "\"").mkString,
Copy link
Contributor

Choose a reason for hiding this comment

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

might be good to factor this args-to-string thing out especially if making it any more complicated

Copy link
Author

Choose a reason for hiding this comment

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

yes, good point

"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 +373,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 +398,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 +410,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 +419,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" -> args.map(x => "\"" + x + "\"").mkString,
"JETTY_HOME" -> jettyHome.toString))
writeScript(scriptFile, script)

Expand Down