Full-fat SBT projects

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 resolverssequence. 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 = (
    http://www.eigengo.org/akka-extras
    ...
  )

  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.

This entry was posted in Jan's Blog and tagged , , . Bookmark the permalink.

One Response to Full-fat SBT projects

  1. Pingback: This week in #Scala (05/08/2013) | Cake Solutions Team Blog

Leave a Reply