Using SBT to create an RPM (detour including Vagrant)
Okay, so I need to build proper RPMs rather than using FPM to create them. Our build toolchain uses SBT to build everything, and utilises sbt-assembly to generate a single executable JAR. Which we then wrap up in an RPM via FPM as I’ve mentioned. Great – except I’m on a Mac, which doesn’t have RPM tooling. So it’s Vagrant, Puppet and sbt-native-packager to the rescue!
Install VirtualBox and Vagrant on your Mac.
Create a new directory
1 2 |
mkdir vagrant cd vagrant |
Initialize Vagrant with a CentOS 6.4 box
1 |
vagrant init sbtrpm http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130731.box |
My Vagrantfile looks something like this (note the config.vm.provision line which I’ve added)
1 2 3 4 5 6 7 8 9 10 11 |
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "sbtrpm" config.vm.box_url = " http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130731.box" config.vm.provision :puppet end |
# Vagrantfile API/syntax version. Don’t touch unless you know what you’re doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "sbtrpm"
config.vm.box_url = " http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130731.box"
config.vm.provision :puppet
end
to enable Puppet configuration.
Create the Puppet manifest
1 2 |
mkdir manifests touch manifests/default.pp |
Edit the manifests/default.pp file to be
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
node 'default' { # Ensure Java is present (needed for SBT) package { "java-1.7.0-openjdk.x86_64": ensure => present, } # Ensure rpm-build tooling is installed so we can create RPMs package { "rpm-build": ensure => present, } # Ensure rpmlint is available (only needed if you want to rpmlint the produced RPM) package { "rpmlint": ensure => present, } # SBT isn't available via yum or rpm, so manually fetch it and set correct permissions exec { "/usr/bin/wget http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar -O /usr/local/bin/sbt-launch.jar && chmod 0755 /usr/local/bin/sbt-launch.jar": creates => "/usr/local/bin/sbt-launch.jar", } # Create a launch script for SBT file { '/usr/local/bin/sbt': ensure => file, content => "SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"njava $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@"n", mode => 0755, } # Force a system update #exec { "yum update -y": # path => "/usr/bin", #} } |
# Ensure rpm-build tooling is installed so we can create RPMs
package {
"rpm-build":
ensure => present,
}
# Ensure rpmlint is available (only needed if you want to rpmlint the produced RPM)
package {
"rpmlint":
ensure => present,
}
# SBT isn’t available via yum or rpm, so manually fetch it and set correct permissions
exec {
"/usr/bin/wget http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.1/sbt-launch.jar -O /usr/local/bin/sbt-launch.jar && chmod 0755 /usr/local/bin/sbt-launch.jar":
creates => "/usr/local/bin/sbt-launch.jar",
}
# Create a launch script for SBT
file {
‘/usr/local/bin/sbt’:
ensure => file,
content => "SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"njava $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@"n",
mode => 0755,
}
# Force a system update
#exec { "yum update -y":
# path => "/usr/bin",
#}
}
Create your Scala project underneath this Vagrant directory (so it’s automatically mounted in your guest machine), and make some other directories which the native packager uses
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
mkdir sbtproj mkdir sbtproj/src mkdir sbtproj/src/main mkdir sbtproj/src/main/scala touch sbtproj/src/main/scala/Main.scala mkdir sbtproj/project touch sbtproj/project/plugins.sbt touch sbtproj/build.sbt touch sbtproj/assembly.sbt mkdir sbtproj/src/universal mkdir sbtproj/src/universal/bin mkdir sbtproj/src/universal/conf mkdir sbtproj/src/universal/doc mkdir sbtproj/src/universal/lib echo Config > sbtproj/src/universal/conf/config.txt echo Doc > sbtproj/src/universal/doc/documentation.txt |
and files placed into these directories will end up correctly symlink’d when they are installed by RPM.
Edit your sbtproj/project/plugins.sbt file
1 2 3 4 5 |
logLevel := Level.Warn addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.6.3") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.2") |
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.6.3")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.2")
Edit your sbtproj/build.sbt file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import NativePackagerKeys._ packagerSettings mapGenericFilesToLinux name := "sbt-rpm" version := "1.0" maintainer := "Andrew <[email protected]>" packageSummary := "Test rpm package" packageDescription := """SBT built RPM.""" rpmRelease := "1" rpmVendor := "devsoup" rpmUrl := Some("http://www.devsoup.co.uk") rpmLicense := Some("BSD") rpmPre := Some("""echo "pre-install"""") rpmPost := Some("""echo "post-install"""") rpmPretrans := Some("""echo "pretrans"""") rpmPosttrans := Some("""echo "posttrans"""") rpmPreun := Some("""echo "pre-uninstall"""") rpmPostun := Some("""echo "post-uninstall"""") rpmRequirements := Seq("java-1.7.0-openjdk") |
packagerSettings
mapGenericFilesToLinux
name := "sbt-rpm"
version := "1.0"
maintainer := "Andrew <[email protected]>"
packageSummary := "Test rpm package"
packageDescription := """SBT built RPM."""
rpmRelease := "1"
rpmVendor := "devsoup"
rpmUrl := Some("http://www.devsoup.co.uk")
rpmLicense := Some("BSD")
rpmPre := Some("""echo "pre-install"""")
rpmPost := Some("""echo "post-install"""")
rpmPretrans := Some("""echo "pretrans"""")
rpmPosttrans := Some("""echo "posttrans"""")
rpmPreun := Some("""echo "pre-uninstall"""")
rpmPostun := Some("""echo "post-uninstall"""")
rpmRequirements := Seq("java-1.7.0-openjdk")
Change your sbtproj/src/main/scala/Main.scala file
1 2 3 4 5 |
package uk.co.devsoup.sbt.rpm object Hi { def main(args: Array[String]) = println("Hello world!") } |
object Hi {
def main(args: Array[String]) = println("Hello world!")
}
Edit your sbtproj/assembly.sbt file
1 2 3 4 5 6 7 |
import AssemblyKeys._ assemblySettings mainClass in assembly := Some("uk.co.devsoup.sbt.rpm.Hi") outputPath in assembly := new java.io.File(s"src/universal/lib/${name.value}-${version.value}.jar") |
assemblySettings
mainClass in assembly := Some("uk.co.devsoup.sbt.rpm.Hi")
outputPath in assembly := new java.io.File(s"src/universal/lib/${name.value}-${version.value}.jar")
You can then do
1 2 3 4 5 6 7 |
vagrant up vagrant ssh cd /vagrant/sbtproj sbt compile sbt package sbt assembly sbt rpm:package-bin |
This produces a single JAR in src/universal/lib, which the sbt-assembly plugin can then package up into an RPM which is installable.
1 2 3 4 |
sudo yum install -y target/rpm/RPMS/noarch/sbt-rpm-1.0-1.noarch.rpm ls -al /etc/sbt-rpm java -jar /usr/share/sbt-rpm/lib/sbt-rpm-1.0.jar sudo yum remove -y sbt-rpm |
When you’re done with the Linux box
1 2 |
exit vagrant destroy -f |