sbt-native-packager offers a comprehensive approach to packaging artifacts with SBT. The user describes a generic layout, which can then be extended for different types of software and deployments. For example, it is flexible enough to describe both a Zip-based archive format, and an RPM package with appropriate Systemd configuration for a service.
This talk will cover the essentials needed to understand the design of sbt-native-packager, and how to extend its structure to create custom layouts and deployments.
3. $
bin/my-‐first-‐app
-‐h
Usage:
[options]
-‐h
|
-‐help
print
this
message
-‐v
|
-‐verbose
this
runner
is
chattier
-‐d
|
-‐debug
set
sbt
log
level
to
debug
-‐no-‐version-‐check
Don't
run
the
java
version
check.
-‐main
<classname>
Define
a
custom
main
class
-‐jvm-‐debug
<port>
Turn
on
JVM
debugging,
open
at
the
given
port.
#
java
version
(default:
java
from
PATH,
currently
java
version
"1.8.0_45")
-‐java-‐home
<path>
alternate
JAVA_HOME
#
jvm
options
and
output
control
JAVA_OPTS
environment
variable,
if
unset
uses
""
-‐Dkey=val
pass
-‐Dkey=val
directly
to
the
java
runtime
-‐J-‐X
pass
option
-‐X
directly
to
the
java
runtime
(-‐J
is
stripped)
#
special
option
-‐-‐
To
stop
parsing
built-‐in
commands
from
the
rest
of
the
command-‐line.
e.g.)
enabling
debug
and
sending
-‐d
as
app
argument
$
./start-‐script
-‐d
-‐-‐
-‐d
In
the
case
of
duplicated
or
conflicting
options,
basically
the
order
above
shows
precedence:
JAVA_OPTS
lowest,
command
line
options
highest
except
"-‐-‐".
5. Setting[T]
Computation run once, returning T
>
set
name
:=
{
println("hello
world!”);
"name"
}
[info]
Defining
*:name
[info]
Reapplying
settings...
hello
world!
[info]
Set
current
project
to
name
(in
build
file:/Users/gcoady/my-‐project/)
>
name
[info]
name
6. Task[T]
Computation run every time value is needed, returning T
>
set
run
:=
{
println("hello
world");
()
}
[info]
Defining
*:run
[info]
The
new
value
will
be
used
by
no
settings
or
tasks.
[info]
Reapplying
settings...
blah
[info]
Set
current
project
to
name
(in
build
file:/Users/gcoady/my-‐project/)
>
run
hello
world
[success]
Total
time:
0
s,
completed
11-‐Feb-‐2016
14:59:18
>
run
hello
world
[success]
Total
time:
0
s,
completed
11-‐Feb-‐2016
14:59:19
7. Dependencies
• Tasks can depend on other tasks and settings
• Settings can depend on other settings
8. Keys
Typed identifier & documentation for settings/tasks
>
inspect
name
[info]
Setting:
java.lang.String
=
name
[info]
Description:
[info]
Project
name.
[info]
Provided
by:
[info]
{file:/Users/gcoady/my-‐project/}my-‐project/*:name
11. Universal Configuration
• Provided by Universal Plugin
• Platform-independent layout
• Staging
• Package zip & tgz files
• Depended on by other deployment formats
12. Universal Configuration
val
mappings
=
TaskKey[Seq[(File,
String)]](
"mappings",
"Defines
the
mappings
from
a
file
to
a
path,
used
by
packaging,
for
example."
)
13. Starting your plugin
sbtPlugin := true
// The Typesafe repository
resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/"
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.3" % "provided")
14. Starting your plugin
object MyPlugin extends AutoPlugin {
val MyPluginConfig = config("myplugin") extend Universal
object autoImport {
val myPluginSetting = settingKey[Seq[String]](“A custom setting")
val myPluginTask = taskKey[File](“A custom task")
}
import autoImport._
override val requires = JavaAppPackaging
override val projectSettings = inConfig(MyPluginConfig)(Seq(
// Configure settings for project here
))
}
15. Case Study
• YourKit Profiler:
An awesome CPU and memory Java Profiler
• Lots of annoying steps to install
16. Plugin Requirements
• Add platform-specific shared library
• Add flags to use library as Java agent
• Allow changes to configuration at deployment time
18. Adding resources
val
stream
=
Option(getClass.getResourceAsStream(path))
stream
match
{
case
Some(s)
=>
val
tempFile
=
targetDir
/
"yourkit"
/
p
/
s"yourkit.$ext"
tempFile.getParentFile.mkdirs()
IO.transferAndClose(s,
new
java.io.FileOutputStream(tempFile))
20. Java entry point generation
• Provided by JavaAppPackaging
• Creates start script for Java applications
• Arbitrary bash code can be injected
• Adding Java agents to Java binaries
• Environment discovery & injection
21. Injecting arbitrary code
val
bashScriptExtraDefines
=
TaskKey[Seq[String]](
“bashScriptExtraDefines",
"A
list
of
extra
definitions
that
should
be
written
to
the
bash
file
template.")
22. Bash script helpers
• ${app_path}/…/ — root directory of distribution
• addJava — adds a Java argument
• addApp — adds an argument to your application
• addResidual — adds an argument to your
application, goes after non-residuals
23. Injecting bash code
def
startYourKitScript(defaultStartupOptions:
String):
String
=
"""
if
[[
-‐z
"$YOURKIT_AGENT_DISABLED"
]];
then
if
[[
-‐z
"$YOURKIT_AGENT_STARTUP_OPTIONS"
]];
then
YOURKIT_AGENT_STARTUP_OPTIONS=""""
+
defaultStartupOptions
+
""""
export
YOURKIT_AGENT_STARTUP_OPTIONS
fi
"""
bashScriptExtraDefines
+=
"""addJava
"-‐agentpath:${app_home}/../"""
+
mapping
+
"""=${YOURKIT_AGENT_STARTUP_OPTIONS}""""