Skip to content

Instantly share code, notes, and snippets.

@raevilman
Last active October 6, 2017 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raevilman/3bd1106259f11e4a045153239a85160d to your computer and use it in GitHub Desktop.
Save raevilman/3bd1106259f11e4a045153239a85160d to your computer and use it in GitHub Desktop.
SBT Multi-Module Project

Notes on building multi-module project with sbt in intellij

Configuring multi-module projects with sbt is simple.
In Maven, we need to write a parent pom.xml and child pom.xml files for all of the sub modules.
In sbt you only need to prepare one project file (build.sbt or projecct/Build.scala).

Suppose we have the following folder structure with a parent module (root folder) and two sub modules core and util:

project/Build.sbt
src
core/src
util/src

Follwing is a simple multi-module project setting example.

project/Build.scala

import sbt._
import Keys._

object MyBuild extends Build {

  lazy val root = project.in(file(".")).aggregates(core, util)

  lazy val core = project
      .settings(
          libraryDependencies += "org.fluentd" % "fluent-logger" % "0.2.10"
      )

  lazy val util = project
}

Project core depends on fluent-logger library, while project util has no library dependency.
Projects are defined by using variables: root, core and util. You can use these variable names to reference projects in the build file and sbt console.

For Scala newbies:
object means a definition of a singleton class.
lazy val is a variable defintion that will be evaluated when it is used for the first time.

You can specify module directories with project.in(file("...")) syntax. The default directory becomes the same with the variable name. A project with the root directory file(".") will be the root project, which will be selected by default when lauching sbt.

In the above example, the root project aggregates util and core projects. Enter the sbt console, and try test command. All test codes in three projects, including root, util and core, will be tested.

> test
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for root/test:test
[info] No tests to run for util/test:test
[info] No tests to run for core/test:test
[success] Total time: 0 s, completed 2017/03/27 11:52:12

To run the test cases in a specific module, select a project name in sbt:

> project core
[info] Set current project to core (in build file:/Users/leo/work/tmp/mproj/)
> test
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for core/test:test
[success] Total time: 0 s, completed 2017/03/27 11:53:50

You can see the project settings with show commands:

> show version
[info] 0.1-SNAPSHOT
> show dependencyClasspath
[info] List(Attributed(/Users/leo/.sbt/boot/scala-2.10.3/lib/scala-library.jar), Attributed(/Users/leo/.ivy2/cache/org.fluentd/fluent-logger/jars/fluent-logger-0.2.10.jar), Attributed(/Users/leo/.ivy2/cache/org.msgpack/msgpack/bundles/msgpack-0.6.7.jar), Attributed(/Users/leo/.ivy2/cache/com.googlecode.json-simple/json-simple/bundles/json-simple-1.1.1.jar), Attributed(/Users/leo/.ivy2/cache/junit/junit/jars/junit-4.10.jar), Attributed(/Users/leo/.ivy2/cache/org.hamcrest/hamcrest-core/jars/hamcrest-core-1.1.jar), Attributed(/Users/leo/.ivy2/cache/org.javassist/javassist/jars/javassist-3.16.1-GA.jar))

If you are not sure names of settings keys, use completion.


Building jar files

Now, let’s go back to the root project:

> project root
[info] Set current project to root (in build file:/Users/leo/work/tmp/mproj/)
> show version
[info] core/*:version
[info]  0.1-SNAPSHOT
[info] util/*:version
[info]  0.1-SNAPSHOT
[info] root/*:version
[info]  0.1-SNAPSHOT

package command creates jar files of projects:

> package
[info] Updating {file:/Users/leo/work/tmp/mproj/}root...
[info] Updating {file:/Users/leo/work/tmp/mproj/}core...
[info] Updating {file:/Users/leo/work/tmp/mproj/}util...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Resolving org.fluentd#fluent-logger;0.2.10 ...
[info] Packaging /Users/leo/work/tmp/mproj/target/scala-2.10/root_2.10-0.1-SNAPSHOT.jar ...
[info] Done packaging.
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Resolving org.scala-lang#scala-library;2.10.3 ...
[info] Packaging /Users/leo/work/tmp/mproj/core/target/scala-2.10/core_2.10-0.1-SNAPSHOT.jar ...
[info] Done packaging.
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Packaging /Users/leo/work/tmp/mproj/util/target/scala-2.10/util_2.10-0.1-SNAPSHOT.jar ...
[info] Done packaging.

Publishing to Maven Repository

publishLocal command creates .jar, -source.jar and -javadoc.jar of your projects, then install them to your local ivy repository $HOME/.ivy2/local. While publishM2 commands install them to your local Maven repository $HOME/.m2/repository.

To deploy jars to a remote repository, use publish command. This is equivalent to mvn deploy command in Maven.


Project dependencies

aggregate settings is just for convenience of running commands in multiple projects. If some project actually depends on another project’s code, use dependsOn:

lazy val core = project.settings(...).dependsOn(util)

Now core project can use classes in util project and its dependent libraries. Try publishLocal:

> publishLocal
...
[info]  published core_2.10 to /Users/leo/.ivy2/local/core/core_2.10/0.1-SNAPSHOT/poms/core_2.10.pom
...

This creates .pom xml files under the target folder of each module. Looking at the generated pom.xml file, you can confirm the dependency to util package is properly set:

core_2.10.pom

<?xml version='1.0' encoding='UTF-8'?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi\
="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>core</groupId>
    <artifactId>core_2.10</artifactId>
    <packaging>jar</packaging>
    <description>core</description>
    <version>0.1-SNAPSHOT</version>
    <name>core</name>
    <organization>
        <name>core</name>
    </organization>
    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.10.3</version>
        </dependency>
        <dependency>
            <groupId>util</groupId>
            <artifactId>util_2.10</artifactId>
            <version>0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.fluentd</groupId>
            <artifactId>fluent-logger</artifactId>
            <version>0.2.10</version>
        </dependency>
    </dependencies>
</project>

If you do not want include Scala library in the dependency, especially for building pure-java projects, set autoScalaLibrary to false in the project settings. And also to remove Scala versions appended to the artifactId, set crossPaths to false.

Settings these keys simplifies your pom.xml:

core-0.1-SNAPSHOT.pom

<?xml version='1.0' encoding='UTF-8'?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi\
="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>core</groupId>
    <artifactId>core</artifactId>
    <packaging>jar</packaging>
    <description>core</description>
    <version>0.1-SNAPSHOT</version>
    <name>core</name>
    <organization>
        <name>core</name>
    </organization>
    <dependencies>
        <dependency>
            <groupId>util</groupId>
            <artifactId>util</artifactId>
            <version>0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.fluentd</groupId>
            <artifactId>fluent-logger</artifactId>
            <version>0.2.10</version>
        </dependency>
    </dependencies>
</project>

Use dependencies from local Maven repo

Need to add mavenLocal resolver to sbt build file as follows
resolvers += Resolver.mavenLocal,

Then add the dependency
libraryDependencies += "com.example.common" % "DbUtil" % "1.0-SNAPSHOT"

Note : If you have defined projects in build file, then you need to add the resolver to each project's setting or just include it in commonSettings like below

lazy val commonSettings = Seq(
  resolvers += Resolver.mavenLocal,
  organization := "com.example",
  version := "0.1.0-SNAPSHOT",
  scalaVersion := "2.12.3"
)

Then add above commonSettings to each project definition

val Commons = project.settings(
  commonSettings,
  libraryDependencies ++= Seq(
    "com.example.common" % "DbUtil" % "1.0-SNAPSHOT"
  ))

Copied from here
Good read: Basics of .sbt build definition
Good read: Scala-SBT Multi-Project Documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment