This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is a short recipe, Recipe 18.6, “How to create an SBT project with subprojects.”
Problem
You want to configure sbt to work with a main Scala project that depends on other subprojects you’re developing.
Solution
Create your subproject as a regular sbt project, but without a project subdirectory. Then, in your main project, define a project/Build.scala file that defines the dependencies between the main project and subprojects.
This is demonstrated in the following example, which I created based on the sbt Multi-Project documentation:
import sbt._ import Keys._ /** * based on http://www.scala-sbt.org/release/docs/Getting-Started/Multi-Project */ object HelloBuild extends Build { // aggregate: running a task on the aggregate project will also run it // on the aggregated projects. // dependsOn: a project depends on code in another project. // without dependsOn, you'll get a compiler error: "object bar is not a // member of package com.alvinalexander". lazy val root = Project(id = "hello", base = file(".")) aggregate(foo, bar) dependsOn(foo, bar) // sub-project in the Foo subdirectory lazy val foo = Project(id = "hello-foo", base = file("Foo")) // sub-project in the Bar subdirectory lazy val bar = Project(id = "hello-bar", base = file("Bar")) }
To create your own example, you can either follow the instructions in the SBT Multi-Project documentation to create a main project with subprojects, or clone my SBT Subproject Example on GitHub, which I created to help you get started quickly.
Discussion
Creating a main project with subprojects is well documented on the sbt website, and the primary glue that defines the relationships between projects is the project/Build.scala file you create in your main project.
In the example shown, my main project depends on two subprojects, which are in directories named Foo and Bar beneath my project’s main directory. I reference these projects in the following code in my main project, so it’s necessary to tell sbt about the relationship between the projects:
package com.alvinalexander.subprojecttests import com.alvinalexander.bar._ import com.alvinalexander.foo._ object Hello extends App { println(Bar("I'm a Bar")) println(Bar("I'm a Foo")) }
The following output from the Unix tree
command shows what the directory structure for my project looks like, including the files and directories for the main project, and the two subprojects:
|-- Bar | |-- build.sbt | +-- src | |-- main | | |-- java | | |-- resources | | +-- scala | | +-- Bar.scala | +-- test | |-- java | +-- resources |-- Foo | |-- build.sbt | +-- src | |-- main | | |-- java | | |-- resources | | +-- scala | | +-- Foo.scala | +-- test | |-- java | +-- resources |-- build.sbt |-- project | |-- Build.scala | +-- src |-- main | |-- java | |-- resources | +-- scala | +-- Hello.scala +-- test |-- java |-- resources +-- scala +-- HelloTest.scala
To experiment with this yourself, I encourage you to clone my GitHub project.
Extra sbt sub-project 'run main' example
As an added note, I’ll show how to run a main
method that’s in a sub-project.
Given this project:
Which contains this build.sbt file:
ThisBuild / scalaVersion := "2.13.3"
ThisBuild / organization := "com.innerproduct"
ThisBuild / version := "0.0.1-SNAPSHOT"
ThisBuild / fork := true
val CatsVersion = "2.2.0"
val CatsEffectVersion = "2.2.0"
val CatsTaglessVersion = "0.11"
val CirceVersion = "0.13.0"
val Http4sVersion = "0.21.4"
val LogbackVersion = "1.2.3"
val MunitVersion = "0.7.8"
val commonSettings =
Seq(
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
),
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % MunitVersion % Test
),
testFrameworks += new TestFramework("munit.Framework")
)
lazy val exercises = (project in file("exercises"))
.settings(commonSettings)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % CatsEffectVersion,
"org.typelevel" %% "cats-effect-laws" % CatsEffectVersion % Test
),
// remove fatal warnings since exercises have unused and dead code blocks
scalacOptions --= Seq(
"-Xfatal-warnings"
)
)
lazy val petstore = (project in file("case-studies") / "petstore")
.dependsOn(exercises % "test->test;compile->compile")
.settings(commonSettings)
.settings(
scalacOptions += "-Ymacro-annotations", // required by cats-tagless-macros
libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % LogbackVersion,
"io.circe" %% "circe-generic" % CirceVersion,
"org.http4s" %% "http4s-blaze-server" % Http4sVersion,
"org.http4s" %% "http4s-blaze-client" % Http4sVersion,
"org.http4s" %% "http4s-circe" % Http4sVersion,
"org.http4s" %% "http4s-dsl" % Http4sVersion,
"org.typelevel" %% "cats-tagless-macros" % CatsTaglessVersion,
"org.scalameta" %% "munit-scalacheck" % MunitVersion % Test
)
)
Start sbt
in the root directory of the project. Then, to run the HelloWorld
example that’s under the exercises/src/main/scala/com.innerproduct.ee/resources directory, run this command from inside the sbt shell:
sbt> exercises/runMain com.innerproduct.ee.resources.HelloWorld
When you do that you’ll see output that actually looks like this:
sbt:essential-effects-code> exercises/runMain com.innerproduct.ee.resources.HelloWorld [warn] multiple main classes detected: run 'show discoveredMainClasses' to see the list [info] running (fork) com.innerproduct.ee.resources.HelloWorld [info] Hello world!
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |