sbt/src/sphinx/Detailed-Topics/Launcher.rst

389 lines
17 KiB
ReStructuredText

======================
Launcher Specification
======================
The sbt launcher component is a self-contained jar that boots a Scala
application without Scala or the application already existing on the
system. The only prerequisites are the launcher jar itself, an optional
configuration file, and a java runtime version 1.6 or greater.
Overview
========
A user downloads the launcher jar and creates a script to run it. In
this documentation, the script will be assumed to be called ``launch``.
For unix, the script would look like:
``java -jar sbt-launcher.jar "$@"``
The user then downloads the configuration file for the application (call
it ``my.app.configuration``) and creates a script to launch it (call it
``myapp``): ``launch @my.app.configuration "$@"``
The user can then launch the application using ``myapp arg1 arg2 ...``
Like the launcher used to distribute ``sbt``, the downloaded launcher
jar will retrieve Scala and the application according to the provided
configuration file. The versions may be fixed or read from a different
configuration file (the location of which is also configurable). The
location to which the Scala and application jars are downloaded is
configurable as well. The repositories searched are configurable.
Optional initialization of a properties file on launch is configurable.
Once the launcher has downloaded the necessary jars, it loads the
application and calls its entry point. The application is passed
information about how it was called: command line arguments, current
working directory, Scala version, and application ID (organization,
name, version). In addition, the application can ask the launcher to
perform operations such as obtaining the Scala jars and a
``ClassLoader`` for any version of Scala retrievable from the
repositories specified in the configuration file. It can request that
other applications be downloaded and run. When the application
completes, it can tell the launcher to exit with a specific exit code or
to reload the application with a different version of Scala, a different
version of the application, or different arguments.
There are some other options for setup, such as putting the
configuration file inside the launcher jar and distributing that as a
single download. The rest of this documentation describes the details of
configuring, writing, distributing, and running the application.
Configuration
-------------
The launcher may be configured in one of the following ways in
increasing order of precedence:
- Replace the ``/sbt/sbt.boot.properties`` file in the jar
- Put a configuration file named ``sbt.boot.properties`` on the
classpath. Put it in the classpath root without the ``/sbt`` prefix.
- Specify the location of an alternate configuration on the command
line. This can be done by either specifying the location as the
system property ``sbt.boot.properties`` or as the first argument to
the launcher prefixed by ``'@'``. The system property has lower
precedence. Resolution of a relative path is first attempted against
the current working directory, then against the user's home
directory, and then against the directory containing the launcher
jar. An error is generated if none of these attempts succeed.
Syntax
~~~~~~
The configuration file is line-based, read as UTF-8 encoded, and defined
by the following grammar. ``'nl'`` is a newline or end of file and
``'text'`` is plain text without newlines or the surrounding delimiters
(such as parentheses or square brackets):
.. productionlist::
configuration: `scala` `app` `repositories` `boot` `log` `appProperties`
scala: "[" "scala" "]" `nl` `version` `nl` `classifiers` `nl`
app: "[" "app" "]" `nl` `org` `nl` `name` `nl` `version` `nl` `components` `nl` `class` `nl` `crossVersioned` `nl` `resources` `nl` `classifiers` `nl`
repositories: "[" "repositories" "]" `nl` (`repository` `nl`)*
boot: "[" "boot" "]" `nl` `directory` `nl` `bootProperties` `nl` `search` `nl` `promptCreate` `nl` `promptFill` `nl` `quickOption` `nl`
log: "["' "log" "]" `nl` `logLevel` `nl`
appProperties: "[" "app-properties" "]" nl (property nl)*
ivy: "[" "ivy" "]" `nl` `homeDirectory` `nl` `checksums` `nl` `overrideRepos` `nl` `repoConfig` `nl`
directory: "directory" ":" `path`
bootProperties: "properties" ":" `path`
search: "search" ":" ("none" | "nearest" | "root-first" | "only" ) ("," `path`)*
logLevel: "log-level" ":" ("debug" | "info" | "warn" | "error")
promptCreate: "prompt-create" ":" `label`
promptFill: "prompt-fill" ":" `boolean`
quickOption: "quick-option" ":" `boolean`
version: "version" ":" `versionSpecification`
versionSpecification: `readProperty` | `fixedVersion`
readProperty: "read" "(" `propertyName` ")" "[" `default` "]"
fixedVersion: text
classifiers: "classifiers" ":" text ("," text)*
homeDirectory: "ivy-home" ":" `path`
checksums: "checksums" ":" `checksum` ("," `checksum`)*
overrideRepos: "override-build-repos" ":" `boolean`
repoConfig: "repository-config" ":" `path`
org: "org" ":" text
name: "name" ":" text
class: "class" ":" text
components: "components" ":" `component` ("," `component`)*
crossVersioned: "cross-versioned" ":" ("true" | "false" | "none" | "binary" | "full")
resources: "resources" ":" `path` ("," `path`)*
repository: ( `predefinedRepository` | `customRepository` ) `nl`
predefinedRepository: "local" | "maven-local" | "maven-central"
customRepository: `label` ":" `url` [ ["," `ivyPattern`] ["," `artifactPattern`] [", mavenCompatible"] [", bootOnly"]]
property: `label` ":" `propertyDefinition` ("," `propertyDefinition`)*
propertyDefinition: `mode` "=" (`set` | `prompt`)
mode: "quick" | "new" | "fill"
set: "set" "(" value ")"
prompt: "prompt" "(" `label` ")" ("[" `default` "]")?
boolean: "true" | "false"
nl: "\r\n" | "\n" | "\r"
path: text
propertyName: text
label: text
default: text
checksum: text
ivyPattern: text
artifactPattern: text
url: text
component: text
In addition to the grammar specified here, property values may include
variable substitutions. A variable substitution has one of these forms:
- ``${variable.name}``
- ``${variable.name-default}``
where ``variable.name`` is the name of a system property. If a system
property by that name exists, the value is substituted. If it does not
exists and a default is specified, the default is substituted after
recursively substituting variables in it. If the system property does
not exist and no default is specified, the original string is not
substituted.
Example
~~~~~~~
The default configuration file for sbt looks like:
.. code-block:: ini
[scala]
version: ${sbt.scala.version-auto}
[app]
org: ${sbt.organization-org.scala-sbt}
name: sbt
version: ${sbt.version-read(sbt.version)[0.12.0]}
class: ${sbt.main.class-sbt.xMain}
components: xsbti,extra
cross-versioned: ${sbt.cross.versioned-false}
[repositories]
local
typesafe-ivy-releases: http://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly
maven-central
sonatype-snapshots: https://oss.sonatype.org/content/repositories/snapshots
[boot]
directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/}
[ivy]
ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/}
checksums: ${sbt.checksums-sha1,md5}
override-build-repos: ${sbt.override.build.repos-false}
repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories}
Semantics
~~~~~~~~~
The ``scala.version`` property specifies the version of Scala used to
run the application. If the application is not cross-built, this may be
set to ``auto`` and it will be auto-detected from the application's
dependencies. If specified, the ``scala.classifiers`` property defines
classifiers, such as 'sources', of extra Scala artifacts to retrieve.
The ``app.org``, ``app.name``, and ``app.version`` properties specify
the organization, module ID, and version of the application,
respectively. These are used to resolve and retrieve the application
from the repositories listed in ``[repositories]``. If
``app.cross-versioned`` is ``binary``, the resolved module ID is
``{app.name+'_'+CrossVersion.binaryScalaVersion(scala.version)}``.
If ``app.cross-versioned`` is ``true`` or ``full``, the resolved module ID is
``{app.name+'_'+scala.version}``. The ``scala.version`` property must be
specified and cannot be ``auto`` when cross-versioned. The paths given
in ``app.resources`` are added to the application's classpath. If the
path is relative, it is resolved against the application's working
directory. If specified, the ``app.classifiers`` property defines
classifiers, like 'sources', of extra artifacts to retrieve for the
application.
Jars are retrieved to the directory given by ``boot.directory``. By
default, this is an absolute path that is shared by all launched
instances on the machine. If multiple versions access it simultaneously.
, you might see messages like:
.. code-block:: console
Waiting for lock on <lock-file> to be available...
This boot directory may be relative to the current directory instead. In
this case, the launched application will have a separate boot directory
for each directory it is launched in.
The ``boot.properties`` property specifies the location of the
properties file to use if ``app.version`` or ``scala.version`` is
specified as ``read``. The ``prompt-create``, ``prompt-fill``, and
``quick-option`` properties together with the property definitions in
``[app.properties]`` can be used to initialize the ``boot.properties``
file.
The app.class property specifies the name of the entry point to the
application. An application entry point must be a public class with a
no-argument constructor that implements ``xsbti.AppMain``. The
``AppMain`` interface specifies the entry method signature 'run'. The
run method is passed an instance of AppConfiguration, which provides
access to the startup environment. ``AppConfiguration`` also provides an
interface to retrieve other versions of Scala or other applications.
Finally, the return type of the run method is ``xsbti.MainResult``,
which has two subtypes: ``xsbti.Reboot`` and ``xsbti.Exit``. To exit
with a specific code, return an instance of ``xsbti.Exit`` with the
requested code. To restart the application, return an instance of
Reboot. You can change some aspects of the configuration with a reboot,
such as the version of Scala, the application ID, and the arguments.
The ``ivy.cache-directory`` property provides an alternative location
for the Ivy cache used by the launcher. This does not automatically set
the Ivy cache for the application, but the application is provided this
location through the AppConfiguration instance. The ``checksums``
property selects the checksum algorithms (sha1 or md5) that are used to
verify artifacts downloaded by the launcher. ``override-build-repos`` is
a flag that can inform the application that the repositories configured
for the launcher should be used in the application. If
``repository-config`` is defined, the file it specifies should contain a
``[repositories]`` section that is used in place of the section in the
original configuration file.
Execution
---------
On startup, the launcher searches for its configuration in the order
described in the Configuration section and then parses it. If either the
Scala version or the application version are specified as 'read', the
launcher determines them in the following manner. The file given by the
'boot.properties' property is read as a Java properties file to obtain
the version. The expected property names are ``${app.name}.version`` for
the application version (where ``${app.name}`` is replaced with the
value of the ``app.name`` property from the boot configuration file) and
``scala.version`` for the Scala version. If the properties file does not
exist, the default value provided is used. If no default was provided,
an error is generated.
Once the final configuration is resolved, the launcher proceeds to
obtain the necessary jars to launch the application. The
``boot.directory`` property is used as a base directory to retrieve jars
to. Locking is done on the directory, so it can be shared system-wide.
The launcher retrieves the requested version of Scala to
.. code-block:: console
${boot.directory}/${scala.version}/lib/
If this directory already exists, the launcher takes a shortcut for
startup performance and assumes that the jars have already been
downloaded. If the directory does not exist, the launcher uses Apache
Ivy to resolve and retrieve the jars. A similar process occurs for the
application itself. It and its dependencies are retrieved to
.. code-block:: console
${boot.directory}/${scala.version}/${app.org}/${app.name}/.
Once all required code is downloaded, the class loaders are set up. The
launcher creates a class loader for the requested version of Scala. It
then creates a child class loader containing the jars for the requested
'app.components' and with the paths specified in ``app.resources``. An
application that does not use components will have all of its jars in
this class loader.
The main class for the application is then instantiated. It must be a
public class with a public no-argument constructor and must conform to
xsbti.AppMain. The ``run`` method is invoked and execution passes to the
application. The argument to the 'run' method provides configuration
information and a callback to obtain a class loader for any version of
Scala that can be obtained from a repository in [repositories]. The
return value of the run method determines what is done after the
application executes. It can specify that the launcher should restart
the application or that it should exit with the provided exit code.
Creating a Launched Application
-------------------------------
This section shows how to make an application that is launched by this
launcher. First, declare a dependency on the launcher-interface. Do not
declare a dependency on the launcher itself. The launcher interface
consists strictly of Java interfaces in order to avoid binary
incompatibility between the version of Scala used to compile the
launcher and the version used to compile your application. The launcher
interface class will be provided by the launcher, so it is only a
compile-time dependency. If you are building with sbt, your dependency
definition would be:
::
libraryDependencies += "org.scala-sbt" % "launcher-interface" % "0.12.0" % "provided"
resolvers += sbtResolver.value
Make the entry point to your class implement 'xsbti.AppMain'. An example
that uses some of the information:
::
package xsbt.test
class Main extends xsbti.AppMain
{
def run(configuration: xsbti.AppConfiguration) =
{
// get the version of Scala used to launch the application
val scalaVersion = configuration.provider.scalaProvider.version
// Print a message and the arguments to the application
println("Hello world! Running Scala " + scalaVersion)
configuration.arguments.foreach(println)
// demonstrate the ability to reboot the application into different versions of Scala
// and how to return the code to exit with
scalaVersion match
{
case "2.8.2" =>
new xsbti.Reboot {
def arguments = configuration.arguments
def baseDirectory = configuration.baseDirectory
def scalaVersion = "2.9.2
def app = configuration.provider.id
}
case "2.9.2" => new Exit(1)
case _ => new Exit(0)
}
}
class Exit(val code: Int) extends xsbti.Exit
}
Next, define a configuration file for the launcher. For the above class,
it might look like:
.. code-block:: ini
[scala]
version: 2.9.2
[app]
org: org.scala-sbt
name: xsbt-test
version: 0.12.0
class: xsbt.test.Main
cross-versioned: binary
[repositories]
local
maven-central
[boot]
directory: ${user.home}/.myapp/boot
Then, ``publishLocal`` or ``+publishLocal`` the application to make it
available.
Running an Application
----------------------
As mentioned above, there are a few options to actually run the
application. The first involves providing a modified jar for download.
The second two require providing a configuration file for download.
- Replace the /sbt/sbt.boot.properties file in the launcher jar and
distribute the modified jar. The user would need a script to run
``java -jar your-launcher.jar arg1 arg2 ...``.
- The user downloads the launcher jar and you provide the configuration
file.
- The user needs to run ``java -Dsbt.boot.properties=your.boot.properties -jar launcher.jar``.
- The user already has a script to run the launcher (call it
'launch'). The user needs to run ``launch @your.boot.properties your-arg-1 your-arg-2``