Build the same package with different configs¶
If you want to build your application with different settings, e.g. for test, staging and production, then you have three ways to do this.
Tip
All examples are shown in a simple build.sbt
. We recommend using AutoPlugins to encapsulate certain aspects
of your build.
All examples can also be found in the native-packager examples,
SBT sub modules¶
The main idea is to create a submodule per configuration. We start with a simple project build.sbt
.
name := "my-app"
enablePlugins(JavaAppPackaging)
In the end we want to create three different packages (test, stage, prod) with the respective configurations. We do this by creating an application module and three packaging submodules.
// the application
lazy val app = project
.in(file("."))
.settings(
name := "my-app",
libraryDependencies += "com.typesafe" % "config" % "1.3.0"
)
Now that our application is defined in a module, we can add the three packaging submodules. We will override the resourceDirectory
setting with our app
resource directory to gain easy access to the applications resources.
lazy val testPackage = project
// we put the results in a build folder
.in(file("build/test"))
.enablePlugins(JavaAppPackaging)
.settings(
// override the resource directory
Compile / resourceDirectory := (app / compile / resourceDirectory).value,
Universal / mappings += {
((Compile / resourceDirectory).value / "test.conf") -> "conf/application.conf"
}
)
.dependsOn(app)
// bascially identical despite the configuration differences
lazy val stagePackage = project
.in(file("build/stage"))
.enablePlugins(JavaAppPackaging)
.settings(
Compile / resourceDirectory := (app / Compile / resourceDirectory).value,
Universal / mappings += {
((Compile / resourceDirectory).value / "stage.conf") -> "conf/application.conf"
}
)
.dependsOn(app)
lazy val prodPackage = project
.in(file("build/prod"))
.enablePlugins(JavaAppPackaging)
.settings(
Compile / resourceDirectory := (app / Compile / resourceDirectory).value,
Universal / mappings += {
((Compile / resourceDirectory).value / "prod.conf") -> "conf/application.conf"
}
)
.dependsOn(app)
Now that you have your build.sbt
set up, you can try building packages.
# stages a test build in build/test/target/universal/stage
testPackage/stage
# creates a zip with the test configuration
sbt testPackage/Universal/packageBin
This technique is a bit verbose, but communicates very clear what is being built and why.
SBT parameters and Build Environment¶
SBT is a java process, which means you can start it with system properties and use these in your build. This pattern may be useful in other scopes as well. First we define an AutoPlugin that sets a build environment.
This plugin allows you to start sbt for example like
sbt -Denv=prod
[info] Set current project to my-app (in build file: ...)
[info] Running in build environment: Production
> show buildEnv
[info] Production
Now we can use this buildEnv
setting to change things. For example the mappings
. We recommend doing this in a
plugin as it involves quite some logic. In this case we decide which configuration file to map as application.conf
.
Universal / mappings += {
val confFile = buildEnv.value match {
case BuildEnv.Developement => "dev.conf"
case BuildEnv.Test => "test.conf"
case BuildEnv.Stage => "stage.conf"
case BuildEnv.Production => "prod.conf"
}
((Compile / resourceDirectory).value / confFile) -> "conf/application.conf"
}
Ofcourse you can change all other settings, package names, etc. as well. Building different output packages would look like this
sbt -Denv=test Universal/packageBin
sbt -Denv=stage Universal/packageBin
sbt -Denv=prod Universal/packageBin
SBT configuration scope (not recommended)¶
The other option is to generate additional scopes in order to build a package like Prod / packageBin
. Scopes behave
counter intuitive sometimes, why we don’t recommend this technique.
Error
This example is work in progress and doesn’t work. Unless you are not very familiar with sbt we highly recommend using another technique.
A simple start may look like this
lazy val Prod = config("prod") extend(Universal) describedAs("scope to build production packages")
lazy val Stage = config("stage") extend(Universal) describedAs("scope to build staging packages")
lazy val app = project
.in(file("."))
.enablePlugins(JavaAppPackaging)
.configs(Prod, Stage)
.settings(
name := "my-app",
libraryDependencies += "com.typesafe" % "config" % "1.3.0"
)
You would expect Prod / packageBin
to work, but extending scopes doesn’t imply inheriting tasks and settings. This
needs to be done manually. Append this to the app
project.
// inheriting tasks and settings
.settings(inConfig(Prod)(UniversalPlugin.projectSettings))
.settings(inConfig(Prod)(JavaAppPackaging.projectSettings))
// define custom settings
.settings(inConfig(Prod)(Seq(
// you have to override everything carefully
packageName := "my-prod-app",
executableScriptName := "my-prod-app",
// this is what we acutally want to change
mappings += ((Compile / resourceDirectory).value / "prod.conf") -> "conf/application.conf"
)))
Note that you have to know more on native-packager internals than you should, because you override all the necessary settings with the intended values. Still this doesn’t work as the universal plugin picks up the wrong mappings to build the package.