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

wip/ash #647

Merged
merged 1 commit into from
Aug 13, 2015
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.typesafe.sbt
package packager
package archetypes

import sbt._
import sbt.Keys.{ mappings, target, name, mainClass, sourceDirectory, javaOptions, streams }
import packager.Keys.{ packageName, executableScriptName }
import linux.{ LinuxFileMetaData, LinuxPackageMapping }
import linux.LinuxPlugin.autoImport.{ linuxPackageMappings, defaultLinuxInstallLocation }
import SbtNativePackager.{ Universal, Debian }

/**
* == Java Application ==
*
* This class is an alternate to JavaAppPackaging designed to support the ash shell. JavaAppPackaging
* generates bash-specific code that is not compatible with ash, a very stripped-down, lightweight shell
* used by popular micro base Docker images like BusyBox. The AshScriptPlugin will generate simple
* ash-compatible output.
*
* Just like with JavaAppPackaging you can override the bash-template file by creating a src/templates
* directory and adding your own bash-template file. Actually this isn't a bad idea as the default
* bash-template file inherited from JavaAppPackaging has a lot of stuff you probably don't want/need
* in a highly-constrained environment like ash+BusyBox. Something much simpler will do, for example:
*
* #!/usr/bin/env sh
*
* realpath () {
* (
* TARGET_FILE="$1"
*
* cd "$(dirname "$TARGET_FILE")"
* TARGET_FILE=$(basename "$TARGET_FILE")
*
* COUNT=0
* while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
* do
* TARGET_FILE=$(readlink "$TARGET_FILE")
* cd "$(dirname "$TARGET_FILE")"
* TARGET_FILE=$(basename "$TARGET_FILE")
* COUNT=$(($COUNT + 1))
* done
*
* if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then
* cd "$TARGET_FILE"
* TARGET_FILEPATH=
* else
* TARGET_FILEPATH=/$TARGET_FILE
* fi
*
* echo "$(pwd -P)/$TARGET_FILE"
* )
* }
*
* real_script_path="$(realpath "$0")"
* app_home="$(realpath "$(dirname "$real_script_path")")"
* lib_dir="$(realpath "${app_home}/../lib")"
*
* ${{template_declares}}
*
* java -classpath $app_classpath $app_mainclass $@
*
*
* == Configuration ==
*
* This plugin adds new settings to configure your packaged application.
* The keys are defined in [[com.typesafe.sbt.packager.archetypes.JavaAppKeys]]
*
* @example Enable this plugin in your `build.sbt` with
*
* {{{
* enablePlugins(AshScriptPlugin)
* }}}
*/
object AshScriptPlugin extends AutoPlugin {

val bashTemplate = "bash-template"

override def requires = JavaAppPackaging

//object autoImport extends JavaAppKeys

import JavaAppPackaging.autoImport._

override def projectSettings = Seq(
makeBashScript <<= (bashScriptTemplateLocation, bashScriptDefines, target in Universal, executableScriptName, sourceDirectory) map makeUniversalAshScript,
bashScriptDefines <<= (Keys.mainClass in (Compile, bashScriptDefines), scriptClasspath in bashScriptDefines, bashScriptExtraDefines, bashScriptConfigLocation) map { (mainClass, cp, extras, config) =>
val hasMain =
for {
cn <- mainClass
} yield JavaAppAshScript.makeDefines(cn, appClasspath = cp, extras = extras, configFile = config)
hasMain getOrElse Nil
}
)

def makeUniversalAshScript(defaultTemplateLocation: File, defines: Seq[String], tmpDir: File, name: String, sourceDir: File): Option[File] =
if (defines.isEmpty) None
else {
val template = resolveTemplate(defaultTemplateLocation)
val scriptBits = JavaAppAshScript.generateScript(defines, template)
val script = tmpDir / "tmp" / "bin" / name
IO.write(script, scriptBits)
// TODO - Better control over this!
script.setExecutable(true)
Some(script)
}

private def resolveTemplate(defaultTemplateLocation: File): URL = {
if (defaultTemplateLocation.exists)
defaultTemplateLocation.toURI.toURL
else
getClass.getResource(defaultTemplateLocation.getName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.typesafe.sbt.packager.archetypes

import java.net.URL

/**
* Constructs a bash script for running a java application.
*
* Makes use of the associated bash-template, with a few hooks
*
*/
object JavaAppAshScript {

private[this] def bashTemplateSource =
getClass.getResource("bash-template")

/**
* Creates the block of defines for a script.
*
* @param mainClass The required "main" method class we use to run the program.
* @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.
* @param extras Any additional defines/commands that should be run in this script.
*/
def makeDefines(
mainClass: String,
appClasspath: Seq[String] = Seq("*"),
configFile: Option[String] = None,
extras: Seq[String] = Nil): Seq[String] =
Seq(mainClassDefine(mainClass)) ++
(configFile map configFileDefine).toSeq ++
Seq(makeClasspathDefine(appClasspath)) ++
extras

private def makeClasspathDefine(cp: Seq[String]): String = {
val fullString = cp map (n => "$lib_dir/" + n) mkString ":"
"app_classpath=\"" + fullString + "\"\n"
}
def generateScript(defines: Seq[String], template: URL = bashTemplateSource): String = {
val defineString = defines mkString "\n"
val replacements = Seq("template_declares" -> defineString)
TemplateWriter.generateScript(template, replacements)
}

def configFileDefine(configFile: String) =
"script_conf_file=\"%s\"" format (configFile)

def mainClassDefine(mainClass: String) = {
val jarPrefixed = """^\-jar (.*)""".r
val args = mainClass match {
case jarPrefixed(jarName) => Seq("-jar", jarName)
case className => Seq(className)
}
val quotedArgsSpaceSeparated = args.map(s => "\"" + s + "\"").mkString(" ")
"app_mainclass=%s\n" format (quotedArgsSpaceSeparated)
}

}
18 changes: 18 additions & 0 deletions src/sbt-test/ash/override-templates/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import scala.io.Source

enablePlugins(AshScriptPlugin)

name := "override-templates"

version := "0.1.0"

bashScriptTemplateLocation := baseDirectory.value / "custom-templates" / "custom-ash-template"

TaskKey[Unit]("run-check-ash") := {
val cwd = (stagingDirectory in Universal).value
val source = scala.io.Source.fromFile((cwd / "bin" / packageName.value).getAbsolutePath)
val contents = try source.getLines mkString "\n" finally source.close()
assert(contents contains "this is the custom bash template", "Bash template didn't contain the right text: \n" + contents)
assert(contents contains "app_mainclass=","Template didn't contain the right text: \n"+contents)
assert( !(contents contains "declare"),"Template didn't contain the right text: \n"+contents)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh

# this is the custom bash template

${{template_declares}}
1 change: 1 addition & 0 deletions src/sbt-test/ash/override-templates/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object MainApp extends App {
println("SUCCESS!")
}
3 changes: 3 additions & 0 deletions src/sbt-test/ash/override-templates/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Run the staging and check the script.
> stage
> run-check-ash
12 changes: 12 additions & 0 deletions src/sbt-test/ash/simple-app/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
enablePlugins(AshScriptPlugin)

name := "simple-app"

version := "0.1.0"

TaskKey[Unit]("run-check") := {
val cwd = (stagingDirectory in Universal).value
val cmd = Seq((cwd / "bin" / packageName.value).getAbsolutePath)
val output = Process(cmd, cwd).!!
assert(output contains "SUCCESS!", "Output didn't contain success: " + output)
}
1 change: 1 addition & 0 deletions src/sbt-test/ash/simple-app/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
3 changes: 3 additions & 0 deletions src/sbt-test/ash/simple-app/src/main/scala/MainApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object MainApp extends App {
println("SUCCESS!")
}
3 changes: 3 additions & 0 deletions src/sbt-test/ash/simple-app/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Run the staging and check the script.
> stage
> run-check