Skip to content

Commit

Permalink
Merge pull request #208 from sbt/wip/getting-started-guide
Browse files Browse the repository at this point in the history
First cut at a getting started guide which is more useful than what we had
  • Loading branch information
muuki88 committed Apr 2, 2014
2 parents 92ddf6a + 2e358d9 commit 2ccecc9
Show file tree
Hide file tree
Showing 21 changed files with 716 additions and 13 deletions.
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.2")

addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.6.2")

addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.7.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.7.2")

libraryDependencies <+= (sbtVersion) { sv =>
"org.scala-sbt" % "scripted-plugin" % sv
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ Debian requires the following specific settings:
are placed in the ``DEBIAN`` file when building. Some of these files can be autogenerated,
for example when using a package archetype, like server_application. Howeve, any autogenerated file
can be overridden by placing your own files in the ``src/debian/DEBIAN`` directory.

``


Tasks
Expand Down
14 changes: 14 additions & 0 deletions src/sphinx/DetailedTopics/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Advanced Topics
###############



.. toctree::
:maxdepth: 2

archetypes.rst
universal.rst
linux.rst
redhat.rst
debian.rst
windows.rst
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ Mapping a complete directory.
}
This maps the ``api`` folder directly to the generate universal zip. ``dir.***`` is a short way for
``dir ** "*"``, which means _select all files including dir_. ``relativeTo(dir.getParentFile)``
``dir ** "*"``, which means _select all files including *dir*. ``relativeTo(dir.getParentFile)``
generates a function with a ``file -> Option[String]`` mapping, which tries to generate a relative
string path from ``dir.getParentFile`` to the passed in file. ``pair`` uses the ``relativeTo``
function to generate a mapping ``File -> String``, which is _your file_ to _relative destination_.
function to generate a mapping ``File -> String``, which is *your file* to *relative destination*.

It exists some helper methods to map a complete directory in more human readable way.

Expand Down
File renamed without changes.
134 changes: 134 additions & 0 deletions src/sphinx/GettingStartedApplications/AddingConfiguration.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
Adding configuration
####################

After :doc:`creating a package <MyFirstProject>`, the very next thing needed, usually, is the ability for users/ops to customize the application once it's deployed. Let's add some configuration to the newly deployed application.

There are generally two types of configurations:

* Configuring the JVM and the process
* Configuring the Application itself.

The native packager provides a direct hook into the generated scripts for JVM configuration. Let's make use of this. First, add the following to the ``src/universal/conf/jvmopts`` file in the project ::

-DsomeProperty=true

Now, if we run the ``stage`` task, we'll see this file show up in the distribution ::

$ sbt stage
$ ls target/universal/stage
bin/
conf/
lib/
$ ls target/unviersal/stage/conf
jvmopts

By default, any file in the ``src/universal`` directory is packaged. This is a convenient way to include things like licenses, and readmes.

Now, we need to modify the script templates to load this configuration. To do so, add the following
to ``build.sbt`` ::

bashScriptConfigLocation := Some("${app_home}/../conf/jvmopts")

Here, we define the configuration location for the BASH script too look for the ``conf/jvmopts`` file. Now, let's run ``sbt stage`` and then execute the script in debug mode to see what command line it executes ::

./target/universal/stage/bin/example-cli -d
# Executing command line:
java
-Xms1024m
-Xmx1024m
-XX:MaxPermSize=256m
-XX:ReservedCodeCacheSize=128m
-DsomeProperty=true
-cp
/home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/universal/stage/lib/example-cli.example-cli-1.0.jar:/home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/universal/stage/lib/org.scala-lang.scala-library-2.10.3.jar:/home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/universal/stage/lib/com.typesafe.config-1.2.0.jar
TestApp


The configuration file for bash scripts takes arguments for the BASH file on each line, and allows comments which start with the ``#`` character. Essentially, this provides a set of default arguments when calling the script.

Now that we have ability to configure the JVM, let's add in a more robust method of customizing the applciation. We'll be using the `Typesafe Config <https://github.com/typesafehub/config>`_ library for this purpose.

First, let's add it as a dependency in ``build.sbt`` ::

libraryDependencies += "com.typesafe" % "config" % "1.2.0"

Next, let's create the configuration file itself. Add the following to ``src/universal/conf/app.config`` ::

example {
greeting = "Hello, World!"
}

Now, we need a means of telling the typesafe config library where to find our configuration. The library supports
a JVM property "``config.file``" which it will use to look for configuration. Let's expose this file
in the startup BASH script. To do so, add the following to ``build.sbt`` ::

bashScriptExtraDefines += """addJava "-Dconfig.file=${app_home}/../conf/app.config""""

This line modifies the generated BASH script to add the JVM options the location of the application configuration on disk. Now, let's modify the application (``src/main/scala/TestApp.scala``) to read this configuration ::

import com.typesafe.config.ConfigFactory
object TestApp extends App {
val config = ConfigFactory.load()
println(config.getString("example.greeting"))
}

Now, let's try it out on the command line ::

$ sbt stage
$ ./target/universal/stage/bin/example-cli
Hello, World!


Finally, let's see what this configuration looks like in a linux distribution. Let's run the debian packaging again ::

$ sbt debian:packageBin

The resulting structure is the following ::

/usr/
share/example-cli/
conf/
app.config
jvmopts
bin/
example-cli
lib/
example-cli.example-cli-1.0.jar
org.scala-lang.scala-library-2.10.3.jar
bin/
example-cli -> ../share/example-cli/bin/example-cli
/etc/
example-cli -> /usr/share/example-cli/conf

Here, we can see that the entire ``conf`` directory for the application is exposed on ``/etc`` as is standard for other linux applications. By convention, all files in the universal ``conf`` directory are marked as configuration files when packaged, allowing users to modify them.

Configuring for Windows
~~~~~~~~~~~~~~~~~~~~~~~
While we just covered how to do configuration for linux/mac, windows offers some subtle differences.

First, while the BASH file allows you to configure where to load JVM options and default arguments, in
windows we can only configure JVM options. The path is hardcoded, as well to:

``<install directory>/@@APP_ENV_NAME@@_config.txt``

where ``@@APP_ENV_NAME@@`` is replaced with an environment friendly name for your app. In this example, that would be: ``EXAMPLE_CLI``.

We can provide a configuration for JVM options on windows by creating a ``src/universal/EXAMPLE_CLI_config.txt`` file with the following contents ::

-Xmx512M
-Xms128M

This will add each line of the file as arguments to the JVM when running your application.


Now, if we want to add the typesafe config library again, we need to write the ``config.file`` property into the JVM options again.

One means of doing this is hooking the ``batScriptExtraDefines`` key. This allows us to insert various BAT settings/commands into the script. Let's use this to hook the config file location, using the other variables in the BASH script. Modify your ``build.sbt`` as follows ::

batScriptExtraDefines += """set _JAVA_OPTS=%_JAVA_OPTS% -Dconfig.file=%EXAMPLE_CLI_HOME%\\conf\\app.config"""

Now, the windows version will also load the configuration from the ``conf/`` directory of the package.


Next, let's :doc:`add some generated files <GeneratingFiles>`.
51 changes: 51 additions & 0 deletions src/sphinx/GettingStartedApplications/GeneratingFiles.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Generating files for the package
################################

Let's dynamically (in the build) construct some files that should be included in the package.


For the example, let's download a license file for our application and add it to the distribution. First,
let's create a task which will download a license file. Add the following to build.sbt ::

val downloadLicense = taskKey[File]("Downloads the latest license file.")

downloadLicense := {
val location = target.value / "downloads" / "LICENSE"
location.getParentFile.mkdirs()
IO.download(url("http://www.schillmania.com/projects/soundmanager2/license.txt?txt"), location)
location
}

Now, we have a taks that will download the BSD license when run. Note: We assume that the license file is
something you host on your own website and keep up to date separately form the package.

Next, let's wire this license into the package. The native package, by default, works with **mappings**.
In sbt, a **mappings** object is a grouping of files and relative locations, e.g ::

/home/jsuereth/projects/example/src/universal/conf/app.config -> conf/app.config
/home/jsuereth/projects/example/src/universal/conf/jvmopts -> conf/jvmopts

shows the mapping of the configuration files we set up :doc:`previously <AddingConfiguration>`. We can directly
append files to the mappings rather than relying on the native packager to find things. Let's add
the license in the root of the package we're creating. Add the following to the ``build.sbt`` ::

mappings in Universal += downloadLicense.value -> "LICENSE"

This is appending a new mapping to those used for packaging. In this case, we reference the file returned by
the ``downloadLicense`` task and put it in the root directory of the package, calling it ``LICENSE``. We
can verify this by checking the ``stage`` task ::

$ sbt stage
$ ls target/universal/stage
bin conf lib LICENSE

You can see the license file is now included in the distribution.


TODO - Describe linuxPackageMappings

With control over mappings, you can rework any aspect of the native packager defaults just be overriding
which files are used. However, sometimes the defaults don't need to be completely replaced, just altered a bit.

Next, let's look at :doc:`how to provide our own BASH template <OverridingTemplates>` that the native packager will use when generating
the script.
136 changes: 136 additions & 0 deletions src/sphinx/GettingStartedApplications/MyFirstProject.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
My First Packaged Project
#########################

After installing the native packager, let's set up a raw sbt project to experiment with bundling things. First, let's create a
``project/build.properties`` file to save the sbt version ::

sbt.version=0.13.1

sbt builds should always specify which version of sbt they are designed to use. This helps keeps builds consistent between developers,
and documents to users which version of sbt you require for the build.

Next, let's add the native packager to our build by created a ``project/plguins.sbt`` file with the following contents ::

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.0-RC1")

Now, the build needs to be configured for packaging. Let's define the ``build.sbt`` file as follows

.. code-block:: scala
name := "example-cli"
version := "1.0"
packageArchetype.java_application
The third line of ``build.sbt`` adds the default packaging settings for java applications. The native packager includes two
"batteries included" options for packaging applications:

* ``java_application`` - Defines packaging of your project with a start script and automatic PATH additions
* ``java_server`` - Defines packaging of your project with automatic service start scripts (supports System V + init.d).

In addition to these, you can always directly configure all packaging by hand. For now, we're using one of the built-in options
as these are pretty robust and configurable.

Now that the build is set up, Let's create an application that we can run on the command line. Create the following file
``src/main/scala/TestApp.scala``

.. code-block:: scala
object TestApp extends App {
println("IT LIVES!")
}
Once this is created, start ``sbt`` on the console and run the ``stage`` command ::

$ sbt
> stage

Now, in another terminal, let's look at what was generated ::

target/universal/stage/
bin/
example-cli
example-cli.bat
lib/
example-cli.example-cli-1.0.jar
org.scala-lang.scala-library-2.10.3.jar

By default, the plugin has created both a windows BAT file and a linux/mac bash script for running the application.
In addition, all the dependent jars are added into the ``lib/`` folder. Let's try out the script in a terminal ::

$ ./target/universal/stage/bin/example-cli
IT LIVES!
$

Now that the package has been verified, let's work on the generic or "universal" packaging. This is when
the plugin packages your application in a simple format that should be consumable from most operating systems or
platforms. There are two ways to do this in the sbt console ::

> universal:packageBin
[info] /home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/universal/example-cli-1.0.zip

> universal:packageZipTarabell
[info] /home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/universal/example-cli-1.0.tgz

This task simple constructs either a tgz or zip file with the exact same contents we found in the staged directory.

While this is a great first step towards deploying our application, we'd like to make it even simpler. Our target
deployment platform is Ubuntu. The command line tool should be usable by all our developers with a very simple
installation and update mechanism. So, let's try to make a debian out of our package. Try the ``debian:packageBin`` task in the sbt console ::

> debian:packageBin
[trace] Stack trace suppressed: run last debian:debianControlFile for the full output.
[error] (debian:debianControlFile) packageDescription in Debian cannot be empty. Use
[error] packageDescription in Debian := "My package Description"
[error] Total time: 0 s, completed Apr 1, 2014 10:21:13 AM

Here, the native packager is warning that we haven't fully configured all the information required to genreate a valid debian file. In particular, the packageDescription needs to be filled out for debian, in addition to a few other settings. Let's add the debian configuration to ``build.sbt`` ::

name := "example-cli"

version := "1.0"

packageArchetype.java_application

packageDescription in Debian := "Example Cli"

maintainer in Debian := "Josh Suereth"

Now, let's try to run the ``debian:packageBin`` command in the sbt console again ::

$ sbt
> debian:PacakgeBin
[info] Altering postrm/postinst files to add user example-cli and group example-cli
[info] dpkg-deb: building package `example-cli' in `/home/jsuereth/projects/sbt/sbt-native-packager/tutorial-example/target/example-cli-1.0.deb'

This generates a debian file that will install the following owners and files ::

root:root /usr/
examplecli:examplecli share/example-cli/
examplecli:examplecli bin/
examplecli:examplecli example-cli
examplecli:examplecli lib/
examplecli:examplecli example-cli.example-cli-1.0.jar
examplecli:examplecli org.scala-lang.scala-library-2.10.3.jar
root:root bin/
root:root example-cli -> ../share/example-cli/bin/example-cli

So, the default packaing takes the "universal" distribution and places it inside a ``/usr/share`` directory, owned by a user for the application. In addition, there is a a symlink in ``/usr/bin`` to the distributed bin script. This allows users on the platform to run the ``example-cli`` as a native install.

We can generate other packages via the following tasks. Here's a complete list of current options.

* ``universal:packageBin`` - Generates a universal zip file
* ``universal:packageZipTarball`` - Generates a universal tgz file
* ``debian:packageBin`` - Generates a deb
* ``rpm:packageBin`` - Generates an rpm
* ``universal::packageOsxDmg`` - Generates a DMG file with the same contents as the universal zip/tgz.
* ``windows:packageBin`` - Generates an MSI

While we only covered the necessary configuration for ``debian``, each package type beyond ``universal`` requires some additonal
configuration relative to that packager. For example, windows MSIs require UUIDs for all packages which are used to uniquely
identifiy two packages that may have the same name.

Next, let's look at how to :doc:`Add configuration files <AddingConfiguration>` to use with our script.


Loading

0 comments on commit 2ccecc9

Please sign in to comment.