Scala SBT building and publishing April 30, 2013
Akka Extras use the full .scala
SBT syntax. This is different from the .sbt
syntax, so let us explore how to construct the Scala syntax build, including publishing to Sonatype. We will explore this on the real EigengoBuild
object.
The build object
The EigengoBuild
extends the Build
object, which is the project settings. In this file, we will define the modules as well as the root
module, which aggregates the child modules. Let's explore the overall stucture.
object EigengoBuild extends Build {
// contains the modules and build settings
}
object Publish {
// contains settings for publishing
}
object Dependencies {
// contains dependencies
}
Let's explore the EigengoBuild
's main structure. We begin with the settings and the modules. The settings
and defaultSettings
hold the global and module-specific SBT settings. We then refer and use these settings sequences.
The override val settings
holds the global settings for the project. We set the organisation
, version
and scalaVersion
SBT settings. The defaultSettings
holds the settings that will be used in each module. These include the compiler options (scalac and javac), the threading behaviour and the artefact resolvers. If you have a local/enterprise repository, you will need to add it to the resolvers
sequence. Notice that some of the keys in the settings are pulled from our Publish
object as well as objects that the plugins define. (Ex gratia graphSettings
, defined in net.virtualvoid.sbt.graph.Plugin
.)
object EigengoBuild extends Build {
override val settings = super.settings ++ Seq(
organization := "org.eigengo.akka-extras",
version := "0.1.0",
scalaVersion := "2.10.1"
)
lazy val defaultSettings = Defaults.defaultSettings ++ Publish.settings ++ graphSettings ++ Seq(
scalacOptions in Compile ++= Seq("-encoding", "UTF-8",
"-target:jvm-1.6",
"-deprecation",
"-unchecked"),
javacOptions in Compile ++= Seq("-source", "1.6",
"-target", "1.6",
"-Xlint:unchecked",
"-Xlint:deprecation",
"-Xlint:-options"),
// https://github.com/sbt/sbt/issues/702
javaOptions += "-Djava.util.logging.config.file=logging.properties",
javaOptions += "-Xmx2G",
outputStrategy := Some(StdoutOutput),
fork := true,
maxErrors := 1,
resolvers ++= Seq(
Resolver.mavenLocal,
Resolver.sonatypeRepo("releases"),
Resolver.typesafeRepo("releases"),
"Spray Releases" at "http://repo.spray.io",
Resolver.typesafeRepo("snapshots"),
Resolver.sonatypeRepo("snapshots")
),
parallelExecution in Test := false
) ++ ScctPlugin.instrumentSettings // ++ ScalastylePlugin.Settings
...
}
Now that we have the settings, we can define the modules and pull in the settings we have defined above. Here, we have all the modules that make up Akka Extras. We use underscore for the variable name and dash for the directory name. So, the apple_push
variable defines a module that lives in the apple-push
directory.
The modules include settings
, which usually define the libraryDependencies
. The variables that hold the depndencies themselves are defined further down, in the Dependencies
object. (Hence the import Dependencies._
above.) Under each directory, we have the usual Maven-esque structure src/main/scala
,
src/main/resources
; src/test/scala
and so on.
object EigengoBuild extends Build {
override val settings = ...
lazy val defaultSettings = Defaults.defaultSettings ++ Publish.settings ++ graphSettings ++ ...
def module(dir: String) = Project(id = dir, base = file(dir), settings = defaultSettings)
import Dependencies._
lazy val apple_push = module("apple-push") settings(
libraryDependencies += akka,
libraryDependencies += specs2 % "test"
)
lazy val freemarker_templating = module("freemarker-templating") settings (
libraryDependencies += freemarker,
libraryDependencies += specs2 % "test"
)
lazy val javamail = module("javamail") settings (
libraryDependencies += mail,
libraryDependencies += scalaz_core,
libraryDependencies += typesafe_config,
libraryDependencies += akka,
libraryDependencies += specs2 % "test",
libraryDependencies += dumbster % "test",
libraryDependencies += akka_testkit % "test",
publishArtifact in Compile := true
)
lazy val main = module("main") dependsOn(apple_push, freemarker_templating, javamail)
...
}
We are nearly at the end. The last thing we need to do is to aggregate all these projects into the root
project. It does not have any further libraryDependencies
, but it includes all other modules.
object EigengoBuild extends Build {
...
lazy val root = Project(
id = "parent",
base = file("."),
settings = defaultSettings ++ ScctPlugin.mergeReportSettings ++ Seq(publishArtifact in Compile := false),
aggregate = Seq(apple_push, freemarker_templating, javamail)
)
}
Dependencies
In the full-blown Scala builds, we define the individual dependencies as variables; it makes sense to separate them out to their own object. And that's exactly what we've done in the Dependencies
object.
object Dependencies {
val akka_version = "2.1.2"
val akka = "com.typesafe.akka" %% "akka-actor" % akka_version
val scalaz_core = "org.scalaz" %% "scalaz-core" % "7.0.0"
val typesafe_config = "com.typesafe" % "config" % "1.0.0"
val akka_testkit = "com.typesafe.akka" %% "akka-testkit" % akka_version
val specs2 = "org.specs2" %% "specs2" % "1.14"
val mail = "javax.mail" % "mail" % "1.4.2"
val freemarker = "org.freemarker" % "freemarker" % "2.3.19"
val dumbster = "dumbster" % "dumbster" % "1.6"
}
Publishing and the Publish object
We are publishing our artefacts to Sonatype OSS hosting; and we are using the sbt-release
plugin. The plugin needs some settings that we define in the Publish
object. The code follows the Publishing guide, but uses the full-blown .scala
syntax instead of the .sbt
syntax.
We define the settings
variable: the sequence of SBT settings that is the Scala-syntax equivalent of writing
pomExtra := ...
publishTo := ...
and others in the .sbt
syntax. To keep things readable, we have pulled out the actual values like akkaExtrasPomExtra
, which is a proper variable in the Publish
object; and we refer to it when we construct the SBT settings in this sequence. (Viz pomExtra := akkaExtrasPomExtra
.)
The akkaExtrasCredentials
is loaded from the ~/.sonatype
file. (You don't want to publish your credentials to GitHub--remember the sorry episode with people pushing their keyphrase-less private keys?)
This file must contain the realm
, host
, username
and password
property-like lines.
realm=Sonatype Nexus Repository Manager
host=oss.sonatype.org
user=yeahright
password=likeidtellyou
So, onwards to the Publish
object.
object Publish {
lazy val settings = Seq(
crossPaths := false,
pomExtra := akkaExtrasPomExtra,
publishTo <<= version { v: String =>
val nexus = "https://oss.sonatype.org/"
if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
},
credentials ++= akkaExtrasCredentials,
organizationName := "Eigengo",
organizationHomepage := Some(url("http://www.eigengo.com")),
publishMavenStyle := true,
// Maven central cannot allow other repos.
// TODO - Make sure all artifacts are on central.
pomIncludeRepository := { x => false }
)
val akkaExtrasPomExtra = (
<url>http://www.eigengo.org/akka-extras</url>
...
)
val akkaExtrasCredentials = Seq(Credentials(Path.userHome / ".sonatype"))
}
Summary
This completes the post about how we build the multi-module Akka Extras project. I hope you will find it useful for your own projects. The source code is at https://github.com/eigengo/akka-extras.