diff --git a/src/main/scala/com/typesafe/sbt/packager/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/Keys.scala index 8cb7ad1ac..ebc2842c8 100644 --- a/src/main/scala/com/typesafe/sbt/packager/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/Keys.scala @@ -16,6 +16,7 @@ object Keys extends linux.Keys val makeBashScript = TaskKey[Option[File]]("makeBashScript", "Creates or discovers the bash script used by this project.") val bashScriptDefines = TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.") val bashScriptExtraDefines = TaskKey[Seq[String]]("bashScriptExtraDefines", "A list of extra definitions that should be written to the bash file template.") + val bashScriptConfigLocation = TaskKey[Option[String]]("bashScriptConfigLocation", "The location where the bash script will load default argument configuration from.") val batScriptExtraDefines = TaskKey[Seq[String]]("batScriptExtraDefines", "A list of extra definitions that should be written to the bat file template.") val scriptClasspathOrdering = TaskKey[Seq[(File, String)]]("scriptClasspathOrdering", "The order of the classpath used at runtime for the bat/bash scripts.") val projectDependencyArtifacts = TaskKey[Seq[Attributed[File]]]("projectDependencyArtifacts", "The set of exported artifacts from our dependent projects.") diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala index daf507c77..278da4a95 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala @@ -5,7 +5,7 @@ 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, sourceDirectory } import linux.LinuxPackageMapping import SbtNativePackager._ @@ -36,14 +36,16 @@ object JavaAppPackaging { mappings in Universal <++= scriptClasspathOrdering, scriptClasspath <<= scriptClasspathOrdering map makeRelativeClasspathNames, bashScriptExtraDefines := Nil, - bashScriptDefines <<= (Keys.mainClass in Compile, scriptClasspath, bashScriptExtraDefines) map { (mainClass, cp, extras) => + bashScriptConfigLocation <<= bashScriptConfigLocation ?? None, + bashScriptDefines <<= (Keys.mainClass in Compile, scriptClasspath, bashScriptExtraDefines, bashScriptConfigLocation) map { (mainClass, cp, extras, config) => val hasMain = for { cn <- mainClass - } yield JavaAppBashScript.makeDefines(cn, appClasspath = cp, extras = extras) + } yield JavaAppBashScript.makeDefines(cn, appClasspath = cp, extras = extras, configFile = config) hasMain getOrElse Nil }, - makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName) map makeUniversalBinScript, + // TODO - Overridable bash template. + makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName, sourceDirectory) map makeUniversalBinScript, batScriptExtraDefines := Nil, batScriptReplacements <<= (normalizedName, Keys.mainClass in Compile, scriptClasspath, batScriptExtraDefines) map { (name, mainClass, cp, extras) => mainClass map { mc => @@ -51,7 +53,7 @@ object JavaAppPackaging { } getOrElse Nil }, - makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName) map makeUniversalBatScript, + makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName, sourceDirectory) map makeUniversalBatScript, mappings in Universal <++= (makeBashScript, normalizedName) map { (script, name) => for { s <- script.toSeq @@ -73,10 +75,13 @@ object JavaAppPackaging { else "../" + name } - def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String): Option[File] = + def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String, sourceDir: File): Option[File] = if (defines.isEmpty) None else { - val scriptBits = JavaAppBashScript.generateScript(defines) + val defaultTemplateLocation = sourceDir / "templates" / "bash-template" + val scriptBits = + if(defaultTemplateLocation.exists) JavaAppBashScript.generateScript(defines, defaultTemplateLocation.toURI.toURL) + else JavaAppBashScript.generateScript(defines) val script = tmpDir / "tmp" / "bin" / name IO.write(script, scriptBits) // TODO - Better control over this! @@ -84,10 +89,13 @@ object JavaAppPackaging { Some(script) } - def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] = + def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String, sourceDir: File): Option[File] = if (replacements.isEmpty) None else { - val scriptBits = JavaAppBatScript.generateScript(replacements) + val defaultTemplateLocation = sourceDir / "templates" / "bat-template" + val scriptBits = + if(defaultTemplateLocation.exists) JavaAppBatScript.generateScript(replacements, defaultTemplateLocation.toURI.toURL) + else JavaAppBatScript.generateScript(replacements) val script = tmpDir / "tmp" / "bin" / (name + ".bat") IO.write(script, scriptBits) Some(script) diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBashScript.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBashScript.scala index d5a71b6da..1f1f506c9 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBashScript.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBashScript.scala @@ -1,5 +1,7 @@ package com.typesafe.sbt.packager.archetypes +import java.net.URL + /** * Constructs a bash script for running a java application. * @@ -33,10 +35,10 @@ object JavaAppBashScript { val fullString = cp map (n => "$lib_dir/"+n) mkString ":" "declare -r app_classpath=\""+fullString+"\"\n" } - def generateScript(defines: Seq[String]): String = { + def generateScript(defines: Seq[String], template: URL = bashTemplateSource): String = { val defineString = defines mkString "\n" val replacements = Seq("template_declares" -> defineString) - TemplateWriter.generateScript(bashTemplateSource, replacements) + TemplateWriter.generateScript(template, replacements) } def configFileDefine(configFile: String) = diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBatScript.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBatScript.scala index a128c93b0..6f22a35e3 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBatScript.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppBatScript.scala @@ -50,7 +50,7 @@ object JavaAppBatScript { } def generateScript( - replacements: Seq[(String,String)]): String = - TemplateWriter.generateScript(bashTemplateSource, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround) + replacements: Seq[(String,String)], template: java.net.URL = bashTemplateSource): String = + TemplateWriter.generateScript(template, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround) } \ No newline at end of file diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala index 3c22bca89..81c8634c2 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaAppUpstartScript.scala @@ -1,5 +1,8 @@ package com.typesafe.sbt.packager.archetypes +import java.io.File +import java.net.URL + /** * Constructs an start script for running a java application. * @@ -8,31 +11,32 @@ object JavaAppStartScript { import ServerLoader._ - protected def upstartTemplateSource: java.net.URL = getClass.getResource("upstart-template") - protected def sysvinitTemplateSource: java.net.URL = getClass.getResource("sysvinit-template") - protected def postinstTemplateSource: java.net.URL = getClass.getResource("postinst-template") - protected def postinstSysvinitTemplateSource: java.net.URL = getClass.getResource("postinst-sysvinit-template") - protected def preremTemplateSource: java.net.URL = getClass.getResource("prerem-template") - - - def generateScript(replacements: Seq[(String, String)], loader: ServerLoader): String = - loader match { - case Upstart => - TemplateWriter.generateScript(upstartTemplateSource, replacements) - case SystemV => - TemplateWriter.generateScript(sysvinitTemplateSource, replacements) + protected def upstartTemplateSource: URL = getClass.getResource("upstart-template") + protected def sysvinitTemplateSource: URL = getClass.getResource("sysvinit-template") + protected def postinstTemplateSource: URL = getClass.getResource("postinst-template") + protected def postinstSysvinitTemplateSource: URL = getClass.getResource("postinst-sysvinit-template") + protected def preremTemplateSource: URL = getClass.getResource("prerem-template") + + + def defaultStartScriptTemplate(loader: ServerLoader, defaultLocation: File): URL = + if(defaultLocation.exists) defaultLocation.toURI.toURL + else loader match { + case Upstart => upstartTemplateSource + case SystemV => sysvinitTemplateSource } - def generatePrerm(appName: String): String = - TemplateWriter.generateScript(preremTemplateSource, Seq("app_name" -> appName)) + def generatePrerm(appName: String, template: java.net.URL = preremTemplateSource): String = + TemplateWriter.generateScript(template, Seq("app_name" -> appName)) - def generatePostinst(appName: String, loader: ServerLoader): String = - loader match { - case Upstart => + def generatePostinst(appName: String, loader: ServerLoader, template: Option[java.net.URL] = None): String = + (template, loader) match { + // User has overriden the default. + case (Some(template), _) => TemplateWriter.generateScript(template, Seq("app_name" -> appName)) + case (_, Upstart) => TemplateWriter.generateScript(postinstTemplateSource, Seq("app_name" -> appName)) - case SystemV => + case (_, SystemV) => TemplateWriter.generateScript(postinstSysvinitTemplateSource, Seq("app_name" -> appName)) } diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala index 7cebe30fb..b9139b129 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala @@ -4,7 +4,7 @@ package archetypes import Keys._ import sbt._ -import sbt.Keys.{ target, mainClass, normalizedName } +import sbt.Keys.{ target, mainClass, normalizedName, sourceDirectory } import SbtNativePackager._ import com.typesafe.sbt.packager.linux.{ LinuxFileMetaData, LinuxPackageMapping, LinuxSymlink, LinuxPlugin } @@ -28,6 +28,7 @@ object JavaServerAppPackaging { Seq( serverLoading := Upstart, daemonUser := Users.Root, + // This one is begging for sbt 0.13 syntax... debianStartScriptReplacements <<= ( maintainer in Debian, packageSummary in Debian, serverLoading in Debian, daemonUser in Debian, normalizedName, sbt.Keys.version, defaultLinuxInstallLocation, mainClass in Compile, scriptClasspath) @@ -45,9 +46,18 @@ object JavaServerAppPackaging { appMainClass = mainClass.get, daemonUser = daemonUser) }, - debianMakeStartScript <<= (debianStartScriptReplacements, normalizedName, target in Universal, serverLoading in Debian) + // TODO - Default locations shouldn't be so hacky. + linuxStartScriptTemplate in Debian <<= (serverLoading in Debian, sourceDirectory) map { (loader, dir) => + JavaAppStartScript.defaultStartScriptTemplate(loader, dir / "templates" / "start") + }, + debianMakeStartScript <<= (debianStartScriptReplacements, normalizedName, target in Universal, linuxStartScriptTemplate in Debian) map makeDebianStartScript, - debianMakeEtcDefault <<= (normalizedName, target in Universal, serverLoading in Debian) + linuxEtcDefaultTemplate in Debian <<= sourceDirectory map { dir => + val overrideScript = dir / "templates" / "etc-default" + if(overrideScript.exists) overrideScript.toURI.toURL + else etcDefaultTemplateSource + }, + debianMakeEtcDefault <<= (normalizedName, target in Universal, serverLoading in Debian, linuxEtcDefaultTemplate in Debian) map makeEtcDefaultScript, linuxPackageMappings in Debian <++= (debianMakeEtcDefault, normalizedName) map { (conf, name) => conf.map(c => LinuxPackageMapping(Seq(c -> ("/etc/default/" + name))).withConfig()).toSeq @@ -58,7 +68,6 @@ object JavaServerAppPackaging { case Upstart => ("/etc/init/" + name + ".conf", "0644") case SystemV => ("/etc/init.d/" + name, "0755") } - for { s <- script.toSeq } yield LinuxPackageMapping(Seq(s -> path)).withPerms(permissions) @@ -79,10 +88,10 @@ object JavaServerAppPackaging { debianMakePostinstScript <<= (normalizedName, target in Universal, serverLoading in Debian) map makeDebianPostinstScript) private def makeDebianStartScript( - replacements: Seq[(String, String)], name: String, tmpDir: File, loader: ServerLoader): Option[File] = + replacements: Seq[(String, String)], name: String, tmpDir: File, template: URL): Option[File] = if (replacements.isEmpty) None else { - val scriptBits = JavaAppStartScript.generateScript(replacements, loader) + val scriptBits = TemplateWriter.generateScript(template, replacements) val script = tmpDir / "tmp" / "init" / name IO.write(script, scriptBits) Some(script) @@ -102,11 +111,11 @@ object JavaServerAppPackaging { Some(script) } - protected def makeEtcDefaultScript(name: String, tmpDir: File, loader: ServerLoader): Option[File] = { + protected def makeEtcDefaultScript(name: String, tmpDir: File, loader: ServerLoader, source: java.net.URL): Option[File] = { loader match { case Upstart => None case SystemV => { - val scriptBits = TemplateWriter.generateScript(etcDefaultTemplateSource, Seq.empty) + val scriptBits = TemplateWriter.generateScript(source, Seq.empty) val script = tmpDir / "tmp" / "etc" / "default" / name IO.write(script, scriptBits) Some(script) diff --git a/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala index a91868cbe..c4b8e4820 100644 --- a/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala @@ -16,6 +16,9 @@ trait Keys { val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.") val linuxPackageSymlinks = TaskKey[Seq[LinuxSymlink]]("linux-package-symlinks", "Symlinks we should produce in the underlying package.") val generateManPages = TaskKey[Unit]("generate-man-pages", "Shows all the man files in the current project") + + val linuxStartScriptTemplate = TaskKey[URL]("linuxStartScriptTemplate", "The location of the template start script file we use for debian (upstart or init.d") + val linuxEtcDefaultTemplate = TaskKey[URL]("linuxEtcDefaultTemplate", "The location of the /etc/default/ template script.") } object Keys extends Keys { diff --git a/src/sphinx/archetypes.rst b/src/sphinx/archetypes.rst index 1bb1302d2..9ce0c2017 100644 --- a/src/sphinx/archetypes.rst +++ b/src/sphinx/archetypes.rst @@ -14,7 +14,8 @@ still leveraging the default configuration for other platforms. Curently, in the nativepackager these archetypes are available: - * Java Command Line Application (Experimental) + * Java Command Line Application + * Java Server Application (Experimental - Debian Only) Java Command Line Application @@ -24,6 +25,7 @@ A Java Command Line application is a Java application that consists of a set of custom start scripts, or services. It is just a bash/bat script that starts up a Java project. To use this archetype in your build, do the following in your ``build.sbt``: +.. code-block:: scala packageArchetype.java_application @@ -47,6 +49,7 @@ this archetype in your build, do the following in your ``build.sbt``: This archetype will use the ``mainClass`` setting of sbt (automatically discovers your main class) to generate ``bat`` and ``bin`` scripts for your project. It produces a universal layout that looks like the following: +.. code-block:: none bin/ <- BASH script @@ -56,3 +59,130 @@ produces a universal layout that looks like the following: You can add additional files to the project by placing things in ``src/windows``, ``src/universal`` or ``src/linux`` as needed. + +The default bash script also supports having a configuration file. This config file can be used to specify default arguments to the BASH script. +To define a config location for your bash script, you can manually override the template defines: + +.. code-block:: scala + + bashScriptConfigLocation := "$app_home/conf/my.conf" + + +This string can include any variable defines in the BASH script. In this case, ``app_home`` refers to the install location of the script. + +Java Server +----------- + +This archetype is designed for Java applications that are intended to run as +servers or services. This archetype includes wiring an application to start +immediately upon startup. + +Currently supported operating systems: + +* Ubuntu 12.04 LTS - Upstart +* Ubuntu 12.04 LTS - init.d + + +The Java Server archetype has a similar installation layout as the java +application archetype. The primary differneces are: + +* Linux + + * ``/var/log/`` is symlinked from ``/log`` + + * Creates a start script in ``/etc/init.d`` or ``/etc/init/`` + + * Creates a startup config file in ``/etc/default/`` + + +For Debian servers, you can select to either use SystemV or Upstart for your servers. By default, Upstart (the current Ubuntu LTS default), is used. To switch to SystemV, add the following: + +.. code-block:: scala + + import NativePackagerKeys._ + import com.typesafe.sbt.packager.archetypes.ServerLoader + + serverLoading in Debian := ServerLoader.SystemV + +By default, the native packager will install and run services using the ``root`` user and group. This is not a good default for services, which should not be exposed to root access. You can change the installation and usage user via the ``daemonUser`` key: + +.. code-block:: scala + + daemonUser in Debian := "my_app_user" + +The archetype will automatically append/prepend the creation/deletion of the user +to your packaging for Debian. *Note:* All specified users are **deleted** on an ``apt-get purge ``. + + + + +Overriding Templates +-------------------- + +You can override the default template used to generate any of the scripts in +any archetype. Listed below are the overridable files and variables that +you can use when generating scripts. + +``src/templates/bat-template`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creating a file here will override the default template used to +generate the ``.bat`` script for windows distributions. + +**Syntax** + +``@@APP_ENV_NAME@@`` - will be replaced with the script friendly name of your package. + +``@@APP_NAME@@`` - will be replaced with user friendly name of your package. + +``@APP_DEFINES@@`` - will be replaced with a set of variable definitions, like + ``APP_MAIN_CLASS``, ``APP_MAIN_CLASS``. + +You can define addiitonal variable definitions using ``batScriptExtraDefines``. + +``src/templates/bash-template`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creating a file here will override the default template used to +generate the BASH start script found in ``bin/`` in the +universal distribution + +**Syntax** + +``${{template_declares}}`` - Will be replaced with a series of ``declare `` +lines based on the ``bashScriptDefines`` key. You can add more defines to +the ``bashScriptExtraDefines`` that will be used in addition to the default set: + +* ``app_mainclass`` - The main class entry point for the application. +* ``app_classpath`` - The complete classpath for the application (in order). + + + +``src/templates/start`` +~~~~~~~~~~~~~~~~~~~~~~~ + +Creating a file here will override either the init.d startup script or +the upstart start script. It will either be located at +``/etc/init/`` or ``/etc/init.d/`` depending on which +serverLoader is being used. + +**Syntax** + +You can use ``${{variable_name}}`` to reference variables when writing your scirpt. The default set of variables is: + +* ``descr`` - The description of the server. +* ``author`` - The configured author name. +* ``exec`` - The script/binary to execute when starting the server +* ``chdir`` - The working directory for the server. +* ``retries`` - The number of times to retry starting the server. +* ``retryTimeout`` - The amount of time to wait before trying to run the server. +* ``app_name`` - The name of the application (linux friendly) +* ``app_main_class`` - The main class / entry point of the application. +* ``app_classpath`` - The (ordered) classpath of the application. +* ``daemon_user`` - The user that the server should run as. + +``src/templates/etc-default`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creating a file here will override the ``/etc/default/`` template +used when SystemV is the server loader. diff --git a/src/sphinx/installation.rst b/src/sphinx/installation.rst index a058877c3..acbe619d0 100644 --- a/src/sphinx/installation.rst +++ b/src/sphinx/installation.rst @@ -5,7 +5,7 @@ Installation The sbt-native-packager is a plugin. To use it, first create a ``project/plugins.sbt`` file with the following. -:: +.. code-block:: scala addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.6.3") @@ -45,9 +45,4 @@ Creating ``tgz`` or ``txz`` requires the use of the following command line tools - ``gzip`` - ``xz`` -- ``tar`` - - - - - +- ``tar`` \ No newline at end of file diff --git a/src/sphinx/linux.rst b/src/sphinx/linux.rst index ccc724fb7..a62a0278a 100644 --- a/src/sphinx/linux.rst +++ b/src/sphinx/linux.rst @@ -33,7 +33,9 @@ Most of the work in generating a linux package is constructing package mappings. Note that while the ``sbt-native-packager`` plugin allows you to specify all of this information, not all platforms will make use of the information. It's best to be specific about how you want files handled and run tests on each platform you wish to deploy to. -A package mapping takes this general form :: +A package mapping takes this general form + +.. code-block:: scala (packageMapping( file -> "/usr/share/man/man1/sbt.1.gz" diff --git a/src/sphinx/redhat.rst b/src/sphinx/redhat.rst index 183cdd8fa..409d4c63d 100644 --- a/src/sphinx/redhat.rst +++ b/src/sphinx/redhat.rst @@ -8,7 +8,9 @@ Settings Rpms require the following specific settings: -name in Rpm := "sbt", +.. code-block:: scala + + name in Rpm := "sbt", version in Rpm <<= sbtVersion.identity, rpmRelease := "1", rpmVendor := "typesafe",