Skip to content

Commit

Permalink
Working on #42
Browse files Browse the repository at this point in the history
- Added simple upstart script
- Added Keys
- Added Tasks
- Incremented sbt version to 0.13.0 for testing (maybe reverted in next
commits)

Moving upstart script to DebianPlugin and adding TaskKeys #42
  • Loading branch information
muuki88 authored and jsuereth committed Sep 27, 2013
1 parent dc11fe1 commit 9f9e0e4
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 162 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# generated upstart config

description "${{descr}}"
author "${{author}}"

# Stanzas
#
# Stanzas control when and how a process is started and stopped
# See a list of stanzas here: http://upstart.ubuntu.com/wiki/Stanzas#respawn

# When to start the service
start on runlevel [2345]

# Automatically restart process if crashed. Tries ${{retries}} times every ${{retryTimeout}} seconds
respawn
respawn limit ${{retries}} ${{retryTimeout}}

# set the working directory of the job processes
chdir ${{chdir}}

# Start the process
exec ${{exec}}
13 changes: 6 additions & 7 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package packager

import sbt._

object Keys extends linux.Keys
with debian.DebianKeys
with rpm.RpmKeys
object Keys extends linux.Keys
with debian.DebianKeys
with rpm.RpmKeys
with windows.WindowsKeys
with universal.UniversalKeys {

// TODO - Do these keys belong here?
def normalizedName = sbt.Keys.normalizedName

Expand All @@ -21,12 +21,11 @@ object Keys extends linux.Keys
val projectDependencyArtifacts = TaskKey[Seq[Attributed[File]]]("projectDependencyArtifacts", "The set of exported artifacts from our dependent projects.")
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.")
val makeBatScript = TaskKey[Option[File]]("makeBatScript", "Creates or discovers the bat script used by this project.")
val batScriptReplacements = TaskKey[Seq[(String,String)]]("batScriptReplacements",
"""|Replacements of template parameters used in the windows bat script.
val batScriptReplacements = TaskKey[Seq[(String, String)]]("batScriptReplacements",
"""|Replacements of template parameters used in the windows bat script.
| Default supported templates:
| APP_ENV_NAME - the name of the application for defining <name>_HOME variables
| APP_NAME - the name of the app
| APP_DEFINES - the defines to go into the app
| """.stripMargin)

}
93 changes: 51 additions & 42 deletions src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ package archetypes
import Keys._
import sbt._
import sbt.Project.Initialize
import sbt.Keys.{mappings, target, name, mainClass, normalizedName}
import sbt.Keys.{ mappings, target, name, mainClass, normalizedName }
import linux.LinuxPackageMapping
import SbtNativePackager._

/** This class contains the default settings for creating and deploying an archetypical Java application.
/**
* This class contains the default settings for creating and deploying an archetypical Java application.
* A Java application archetype is defined as a project that has a main method and is run by placing
* all of its JAR files on the classpath and calling that main method.
*
*
* This doesn't create the best of distributions, but it can simplify the distribution of code.
*
*
* **NOTE: EXPERIMENTAL** This currently only supports universal distributions.
*/
object JavaAppPackaging {

def settings: Seq[Setting[_]] = Seq(
// Here we record the classpath as it's added to the mappings separately, so
// we can use its order to generate the bash/bat scripts.
scriptClasspathOrdering := Nil,
scriptClasspathOrdering := Nil,
// Note: This is sometimes on the classpath via dependencyClasspath in Runtime.
// We need to figure out why sometimes the Attributed[File] is corrrectly configured
// and sometimes not.
scriptClasspathOrdering <+= (Keys.packageBin in Compile, Keys.projectID, Keys.artifact in Compile in Keys.packageBin) map { (jar, id, art) =>
jar -> ("lib/" + makeJarName(id.organization,id.name,id.revision, art.name))
},
jar -> ("lib/" + makeJarName(id.organization, id.name, id.revision, art.name))
},
projectDependencyArtifacts <<= findProjectDependencyArtifacts,
scriptClasspathOrdering <++= (Keys.dependencyClasspath in Runtime, projectDependencyArtifacts) map universalDepMappings,
scriptClasspathOrdering <<= (scriptClasspathOrdering) map {_.distinct},
scriptClasspathOrdering <<= (scriptClasspathOrdering) map { _.distinct },
mappings in Universal <++= scriptClasspathOrdering,
scriptClasspath <<= scriptClasspathOrdering map makeRelativeClasspathNames,
bashScriptExtraDefines := Nil,
Expand All @@ -45,36 +46,35 @@ object JavaAppPackaging {
makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName) map makeUniversalBinScript,
batScriptExtraDefines := Nil,
batScriptReplacements <<= (normalizedName, Keys.mainClass in Compile, scriptClasspath, batScriptExtraDefines) map { (name, mainClass, cp, extras) =>
mainClass map { mc =>
mainClass map { mc =>
JavaAppBatScript.makeReplacements(name = name, mainClass = mc, appClasspath = cp, extras = extras)
} getOrElse Nil

},
makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName) map makeUniversalBatScript,
mappings in Universal <++= (makeBashScript, normalizedName) map { (script, name) =>
for {
s <- script.toSeq
s <- script.toSeq
} yield s -> ("bin/" + name)
},
mappings in Universal <++= (makeBatScript, normalizedName) map { (script, name) =>
for {
s <- script.toSeq
s <- script.toSeq
} yield s -> ("bin/" + name + ".bat")
}
)

})

def makeRelativeClasspathNames(mappings: Seq[(File, String)]): Seq[String] =
for {
(file, name) <- mappings
} yield {
// Here we want the name relative to the lib/ folder...
// For now we just cheat...
if(name startsWith "lib/") name drop 4
if (name startsWith "lib/") name drop 4
else "../" + name
}
def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String): Option[File] =
if(defines.isEmpty) None

def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String): Option[File] =
if (defines.isEmpty) None
else {
val scriptBits = JavaAppBashScript.generateScript(defines)
val script = tmpDir / "tmp" / "bin" / name
Expand All @@ -83,23 +83,32 @@ object JavaAppPackaging {
script.setExecutable(true)
Some(script)
}
def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] =
if(replacements.isEmpty) None

def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] =
if (replacements.isEmpty) None
else {
val scriptBits = JavaAppBatScript.generateScript(replacements)
val script = tmpDir / "tmp" / "bin" / (name + ".bat")
IO.write(script, scriptBits)
Some(script)
}

def makeUniversalUpstartScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] =
if (replacements.isEmpty) None
else {
val scriptBits = JavaAppUpstartScript.generateScript(replacements)
val script = tmpDir / "tmp" / "bin" / (name + ".conf")
IO.write(script, scriptBits)
Some(script)
}

// Constructs a jar name from components...(ModuleID/Artifact)
def makeJarName(org: String, name: String, revision: String, artifactName: String): String =
(org + "." +
name + "-" +
Option(artifactName.replace(name, "")).filterNot(_.isEmpty).map(_ + "-").getOrElse("") +
revision + ".jar")
name + "-" +
Option(artifactName.replace(name, "")).filterNot(_.isEmpty).map(_ + "-").getOrElse("") +
revision + ".jar")

// Determines a nicer filename for an attributed jar file, using the
// ivy metadata if available.
def getJarFullFilename(dep: Attributed[File]): String = {
Expand All @@ -109,19 +118,19 @@ object JavaAppPackaging {
} yield makeJarName(module.organization, module.name, module.revision, artifact.name)
filename.getOrElse(dep.data.getName)
}

// Here we grab the dependencies...
def dependencyProjectRefs(build: sbt.BuildDependencies, thisProject: ProjectRef): Seq[ProjectRef] =
def dependencyProjectRefs(build: sbt.BuildDependencies, thisProject: ProjectRef): Seq[ProjectRef] =
build.classpathTransitive.get(thisProject).getOrElse(Nil)

def filterArtifacts(artifacts: Seq[(Artifact, File)], config: Option[String]): Seq[(Artifact, File)] =
for {
(art, file) <- artifacts
// TODO - Default to compile or default?
if art.configurations.exists(_.name == config.getOrElse("default"))
} yield art -> file
def extractArtifacts(stateTask: Task[State], ref: ProjectRef): Task[Seq[Attributed[File]]] =

def extractArtifacts(stateTask: Task[State], ref: ProjectRef): Task[Seq[Attributed[File]]] =
stateTask flatMap { state =>
val extracted = Project extract state
// TODO - Is this correct?
Expand All @@ -134,25 +143,25 @@ object JavaAppPackaging {
(art, file) <- arts.toSeq // TODO -Filter!
} yield {
sbt.Attributed.blank(file).
put(sbt.Keys.moduleID.key, module).
put(sbt.Keys.artifact.key, art)
put(sbt.Keys.moduleID.key, module).
put(sbt.Keys.artifact.key, art)
}
}
}

// TODO - Should we pull in more than just JARs? How do native packages come in?
def isRuntimeArtifact(dep: Attributed[File]): Boolean =
dep.get(sbt.Keys.artifact.key).map(_.`type` == "jar").getOrElse {
val name = dep.data.getName
!(name.endsWith(".jar") || name.endsWith("-sources.jar") || name.endsWith("-javadoc.jar"))
}

def findProjectDependencyArtifacts: Initialize[Task[Seq[Attributed[File]]]] =
(sbt.Keys.buildDependencies, sbt.Keys.thisProjectRef, sbt.Keys.state) apply { (build, thisProject, stateTask) =>
val refs = thisProject +: dependencyProjectRefs(build, thisProject)
// Dynamic lookup of dependencies...
val artTasks = (refs) map { ref => extractArtifacts(stateTask, ref) }
val allArtifactsTask: Task[Seq[Attributed[File]]] =
val allArtifactsTask: Task[Seq[Attributed[File]]] =
artTasks.fold[Task[Seq[Attributed[File]]]](task(Nil)) { (previous, next) =>
for {
p <- previous
Expand All @@ -161,9 +170,9 @@ object JavaAppPackaging {
}
allArtifactsTask
}

def findRealDep(dep: Attributed[File], projectArts: Seq[Attributed[File]]): Option[Attributed[File]] = {
if(dep.data.isFile) Some(dep)
if (dep.data.isFile) Some(dep)
else {
projectArts.find { art =>
// TODO - Why is the module not showing up for project deps?
Expand All @@ -178,11 +187,11 @@ object JavaAppPackaging {
}
}
}

// Converts a managed classpath into a set of lib mappings.
def universalDepMappings(deps: Seq[Attributed[File]], projectArts: Seq[Attributed[File]]): Seq[(File,String)] =
def universalDepMappings(deps: Seq[Attributed[File]], projectArts: Seq[Attributed[File]]): Seq[(File, String)] =
for {
dep <- deps
realDep <- findRealDep(dep, projectArts)
} yield realDep.data-> ("lib/"+getJarFullFilename(realDep))
} yield realDep.data -> ("lib/" + getJarFullFilename(realDep))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.typesafe.sbt.packager.archetypes

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

private[this] def upstartTemplateSource = getClass.getResource("upstart-template")
private[this] def charset = java.nio.charset.Charset.forName("UTF-8")

/**
*
* @param author -
* @param description - short description
* @param execScript - name of the script in /usr/bin
* @param chdir - execution path of the script
* @param retries - on fail, how often should a restart be tried
* @param retryTimeout - pause between retries
* @return Seq of key,replacement pairs
*/
def makeReplacements(
author: String,
descr: String,
execScript: String,
chdir: String,
retries: Int = 0,
retryTimeout: Int = 60): Seq[(String, String)] = Seq(
"exec" -> execScript,
"author" -> author,
"descr" -> descr,
"chdir" -> chdir,
"retries" -> retries.toString,
"retryTimeout" -> retryTimeout.toString)

private def replace(line: String, replacements: Seq[(String, String)]): String = {
replacements.foldLeft(line) {
case (line, (key, value)) =>
("\\$\\{\\{" + key + "\\}\\}").r.replaceAllIn(line, java.util.regex.Matcher.quoteReplacement(value))
}
}

def generateScript(replacements: Seq[(String, String)]): String = {
val sb = new StringBuilder
for (line <- sbt.IO.readLinesURL(upstartTemplateSource, charset)) {
sb append replace(line, replacements)
sb append "\n"
}
sb toString
}

}
Loading

0 comments on commit 9f9e0e4

Please sign in to comment.