Skip to content

Commit

Permalink
Change startup script name generation (#1020)
Browse files Browse the repository at this point in the history
* Change startup script name generation

This commit tries to lower the chance of generation of multiple scripts
with equal names in one archive. Fixes #1016.

Additionally, an attempt was made to improve the word splitting
for the script names, so, for example "UITest" becomes "ui-test"
and not "u-i-test".

* Heuristics for disambiguating names: second try

* Implement simpler disambiguating heuristics

* Fix indentation

* Fix for the case of single main class

* Drop some beautifying logic

* Fix formatting

* Add note to the documentation on script name generation

* Refactor Universal JavaAppPackaging

Factor out common code from `BashStartScriptPlugin` and `BatStartScriptPlugin`

* Show warning when script name collision is detected

* Fix docs formatting

* Fix comments and simplify executable bit logic
  • Loading branch information
atrosinenko authored and muuki88 committed Jan 8, 2018
1 parent cea4517 commit e0b0a13
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 198 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.typesafe.sbt.packager.archetypes.scripts

import java.io.File
import java.net.URL

import com.typesafe.sbt.SbtNativePackager.Universal
import com.typesafe.sbt.packager.Keys._
Expand All @@ -16,7 +15,7 @@ import sbt._
* [[com.typesafe.sbt.packager.archetypes.JavaAppPackaging]].
*
*/
object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator with CommonStartScriptGenerator {

/**
* Name of the bash template if user wants to provide custom one
Expand All @@ -26,27 +25,32 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
/**
* Name of the bash forwarder template if user wants to provide custom one
*/
val bashForwarderTemplate = "bash-forwarder-template"
override protected[this] val forwarderTemplateName = "bash-forwarder-template"

/**
* Location for the application.ini file used by the bash script to load initialization parameters for jvm and app
*/
val appIniLocation = "${app_home}/../conf/application.ini"

/**
* Script destination in final package
*/
val scriptTargetFolder = "bin"
override protected[this] val scriptSuffix: String = ""
override protected[this] val eol: String = "\n"
override protected[this] val keySurround: String => String = TemplateWriter.bashFriendlyKeySurround
override protected[this] val executableBitValue: Boolean = true

override val requires = JavaAppPackaging
override val trigger = AllRequirements

object autoImport extends BashStartScriptKeys

private[this] case class BashScriptConfig(executableScriptName: String,
scriptClasspath: Seq[String],
bashScriptReplacements: Seq[(String, String)],
bashScriptTemplateLocation: File)
protected[this] case class BashScriptConfig(override val executableScriptName: String,
override val scriptClasspath: Seq[String],
override val replacements: Seq[(String, String)],
override val templateLocation: File)
extends ScriptConfig {
override def withScriptName(scriptName: String): BashScriptConfig = copy(executableScriptName = scriptName)
}

override protected[this] type SpecializedScriptConfig = BashScriptConfig

override def projectSettings: Seq[Setting[_]] = Seq(
bashScriptTemplateLocation := (sourceDirectory.value / "templates" / bashTemplate),
Expand All @@ -69,8 +73,8 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
BashScriptConfig(
executableScriptName = executableScriptName.value,
scriptClasspath = (scriptClasspath in bashScriptDefines).value,
bashScriptReplacements = bashScriptReplacements.value,
bashScriptTemplateLocation = bashScriptTemplateLocation.value
replacements = bashScriptReplacements.value,
templateLocation = bashScriptTemplateLocation.value
),
(mainClass in (Compile, bashScriptDefines)).value,
(discoveredMainClasses in Compile).value,
Expand All @@ -85,44 +89,6 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
Seq("template_declares" -> defineString)
}

private[this] def generateStartScripts(config: BashScriptConfig,
mainClass: Option[String],
discoveredMainClasses: Seq[String],
targetDir: File,
log: Logger): Seq[(File, String)] =
StartScriptMainClassConfig.from(mainClass, discoveredMainClasses) match {
case NoMain =>
log.warn("You have no main class in your project. No start script will be generated.")
Seq.empty
case SingleMain(main) =>
Seq(MainScript(main, config, targetDir, Seq(main)) -> s"$scriptTargetFolder/${config.executableScriptName}")
case MultipleMains(mains) =>
generateMainScripts(mains, config, targetDir)
case ExplicitMainWithAdditional(main, additional) =>
(MainScript(main, config, targetDir, discoveredMainClasses) -> s"$scriptTargetFolder/${config.executableScriptName}") +:
ForwarderScripts(config.executableScriptName, additional, targetDir)
}

private[this] def generateMainScripts(discoveredMainClasses: Seq[String],
config: BashScriptConfig,
targetDir: File): Seq[(File, String)] =
discoveredMainClasses.map { qualifiedClassName =>
val bashConfig =
config.copy(executableScriptName = makeScriptName(qualifiedClassName))
MainScript(qualifiedClassName, bashConfig, targetDir, discoveredMainClasses) -> s"$scriptTargetFolder/${bashConfig.executableScriptName}"
}

private[this] def makeScriptName(qualifiedClassName: String): String = {
val clazz = qualifiedClassName.split("\\.").last

val lowerCased = clazz.drop(1).flatMap {
case c if c.isUpper => Seq('-', c.toLower)
case c => Seq(c)
}

clazz(0).toLower +: lowerCased
}

/**
* @param path that could be relative to app_home
* @return path relative to app_home
Expand Down Expand Up @@ -157,56 +123,16 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
"declare -r script_conf_file=\"%s\"" format configFile
}

object MainScript {

/**
*
* @param mainClass - Main class added to the java command
* @param config - Config data for this script
* @param targetDir - Target directory for this script
* @return File pointing to the created main script
*/
def apply(mainClass: String, config: BashScriptConfig, targetDir: File, mainClasses: Seq[String]): File = {
val template = resolveTemplate(config.bashScriptTemplateLocation)
val replacements = Seq(
"app_mainclass" -> mainClass,
"available_main_classes" -> usageMainClassReplacement(mainClasses)
) ++ config.bashScriptReplacements

val scriptContent = TemplateWriter.generateScript(template, replacements)
val script = targetDir / "scripts" / config.executableScriptName
IO.write(script, scriptContent)
// TODO - Better control over this!
script.setExecutable(true)
script
}

private[this] def usageMainClassReplacement(mainClasses: Seq[String]): String =
if (mainClasses.nonEmpty)
mainClasses.mkString("Available main classes:\n\t", "\n\t", "")
else
""

private[this] def resolveTemplate(defaultTemplateLocation: File): URL =
if (defaultTemplateLocation.exists) defaultTemplateLocation.toURI.toURL
else getClass.getResource(defaultTemplateLocation.getName)
}

object ForwarderScripts {
def apply(executableScriptName: String, discoveredMainClasses: Seq[String], targetDir: File): Seq[(File, String)] = {
val tmp = targetDir / "scripts"
val forwarderTemplate = getClass.getResource(bashForwarderTemplate)
discoveredMainClasses.map { qualifiedClassName =>
val clazz = makeScriptName(qualifiedClassName)
val file = tmp / clazz

val replacements = Seq("startScript" -> executableScriptName, "qualifiedClassName" -> qualifiedClassName)
val scriptContent = TemplateWriter.generateScript(forwarderTemplate, replacements)

IO.write(file, scriptContent)
file.setExecutable(true)
file -> s"bin/$clazz"
}
}
}
private[this] def usageMainClassReplacement(mainClasses: Seq[String]): String =
if (mainClasses.nonEmpty)
mainClasses.mkString("Available main classes:\n\t", "\n\t", "")
else
""

override protected[this] def createReplacementsForMainScript(
mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig
): Seq[(String, String)] =
Seq("app_mainclass" -> mainClass, "available_main_classes" -> usageMainClassReplacement(mainClasses)) ++ config.replacements
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.typesafe.sbt.packager.archetypes.scripts

import java.io.File
import java.net.URL

import com.typesafe.sbt.SbtNativePackager.Universal
import com.typesafe.sbt.packager.Keys._
Expand All @@ -17,7 +16,7 @@ import sbt._
* [[com.typesafe.sbt.packager.archetypes.JavaAppPackaging]].
*
*/
object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator with CommonStartScriptGenerator {

/**
* Name of the bat template if user wants to provide custom one
Expand All @@ -27,30 +26,35 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
/**
* Name of the bat forwarder template if user wants to provide custom one
*/
val batForwarderTemplate = "bat-forwarder-template"

/**
* Script destination in final package
*/
val scriptTargetFolder = "bin"
override protected[this] val forwarderTemplateName = "bat-forwarder-template"

/**
* Location for the application.ini file used by the bat script to load initialization parameters for jvm and app
*/
val appIniLocation = "%APP_HOME%\\conf\\application.ini"

override protected[this] val scriptSuffix = ".bat"
override protected[this] val eol: String = "\r\n"
override protected[this] val keySurround: String => String = TemplateWriter.batFriendlyKeySurround
override protected[this] val executableBitValue: Boolean = false

override val requires = JavaAppPackaging
override val trigger = AllRequirements

object autoImport extends BatStartScriptKeys
import autoImport._

private[this] case class BatScriptConfig(executableScriptName: String,
scriptClasspath: Seq[String],
configLocation: Option[String],
extraDefines: Seq[String],
replacements: Seq[(String, String)],
batScriptTemplateLocation: File)
protected[this] case class BatScriptConfig(override val executableScriptName: String,
override val scriptClasspath: Seq[String],
configLocation: Option[String],
extraDefines: Seq[String],
override val replacements: Seq[(String, String)],
override val templateLocation: File)
extends ScriptConfig {
override def withScriptName(scriptName: String): BatScriptConfig = copy(executableScriptName = scriptName)
}

override protected[this] type SpecializedScriptConfig = BatScriptConfig

override def projectSettings: Seq[Setting[_]] = Seq(
batScriptTemplateLocation := (sourceDirectory.value / "templates" / batTemplate),
Expand All @@ -67,12 +71,12 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
),
makeBatScripts := generateStartScripts(
BatScriptConfig(
executableScriptName = s"${executableScriptName.value}.bat",
executableScriptName = executableScriptName.value,
scriptClasspath = (scriptClasspath in batScriptReplacements).value,
configLocation = batScriptConfigLocation.value,
extraDefines = batScriptExtraDefines.value,
replacements = batScriptReplacements.value,
batScriptTemplateLocation = batScriptTemplateLocation.value
templateLocation = batScriptTemplateLocation.value
),
(mainClass in (Compile, batScriptReplacements)).value,
(discoveredMainClasses in Compile).value,
Expand All @@ -82,43 +86,6 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
mappings in Universal ++= makeBatScripts.value
)

private[this] def generateStartScripts(config: BatScriptConfig,
mainClass: Option[String],
discoveredMainClasses: Seq[String],
targetDir: File,
log: Logger): Seq[(File, String)] =
StartScriptMainClassConfig.from(mainClass, discoveredMainClasses) match {
case NoMain =>
log.warn("You have no main class in your project. No start script will be generated.")
Seq.empty
case SingleMain(main) =>
Seq(MainScript(main, config, targetDir) -> s"$scriptTargetFolder/${config.executableScriptName}")
case MultipleMains(mains) =>
generateMainScripts(discoveredMainClasses, config, targetDir)
case ExplicitMainWithAdditional(main, additional) =>
(MainScript(main, config, targetDir) -> s"$scriptTargetFolder/${config.executableScriptName}") +:
ForwarderScripts(config.executableScriptName, additional, targetDir)
}

private[this] def generateMainScripts(discoveredMainClasses: Seq[String],
config: BatScriptConfig,
targetDir: File): Seq[(File, String)] =
discoveredMainClasses.map { qualifiedClassName =>
val batConfig = config.copy(executableScriptName = makeScriptName(qualifiedClassName))
MainScript(qualifiedClassName, batConfig, targetDir) -> s"$scriptTargetFolder/${batConfig.executableScriptName}"
}

private[this] def makeScriptName(qualifiedClassName: String): String = {
val clazz = qualifiedClassName.split("\\.").last

val lowerCased = clazz.drop(1).flatMap {
case c if c.isUpper => Seq('-', c.toLower)
case c => Seq(c)
}

clazz(0).toLower + lowerCased + ".bat"
}

/**
* @param path that could be relative to APP_HOME
* @return path relative to APP_HOME
Expand Down Expand Up @@ -173,47 +140,11 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator {
}
}

object MainScript {

/**
*
* @param mainClass - Main class added to the java command
* @param config - Config data for this script
* @param targetDir - Target directory for this script
* @return File pointing to the created main script
*/
def apply(mainClass: String, config: BatScriptConfig, targetDir: File): File = {
val template = resolveTemplate(config.batScriptTemplateLocation)
val replacements = config.replacements :+ Replacements.appDefines(mainClass, config, config.replacements)
val scriptContent =
TemplateWriter.generateScript(template, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround)
val script = targetDir / "scripts" / config.executableScriptName
IO.write(script, scriptContent)
script
}

private[this] def resolveTemplate(defaultTemplateLocation: File): URL =
if (defaultTemplateLocation.exists) defaultTemplateLocation.toURI.toURL
else getClass.getResource(defaultTemplateLocation.getName)

}

object ForwarderScripts {
def apply(executableScriptName: String, discoveredMainClasses: Seq[String], targetDir: File): Seq[(File, String)] = {
val tmp = targetDir / "scripts"
val forwarderTemplate = getClass.getResource(batForwarderTemplate)
discoveredMainClasses.map { qualifiedClassName =>
val scriptName = makeScriptName(qualifiedClassName)
val file = tmp / scriptName

val replacements = Seq("startScript" -> executableScriptName, "qualifiedClassName" -> qualifiedClassName)
val scriptContent =
TemplateWriter.generateScript(forwarderTemplate, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround)

IO.write(file, scriptContent)
file -> s"bin/$scriptName"
}
}
override protected[this] def createReplacementsForMainScript(
mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig
): Seq[(String, String)] =
config.replacements :+ Replacements.appDefines(mainClass, config, config.replacements)

}
}
Loading

0 comments on commit e0b0a13

Please sign in to comment.