This lab teaches the basics of the Simple Build Tool.
Specifically:
- multiple sub-project build
- dependency resolution
- reducing code duplication in your SBT build configuration
- setting the version of SBT
- setting the version of Scala
- Java/Scala package hierarchy
- how repositories are organized
There is no implementation required Parts 1, 2, and 3. Your tasks are in Parts 4 and 5.
scala-class/sbtTutorial/minimal/src/main/scala
Directory minimal
contains an SBT build definition that is close to minimal complexity, and some very simple Scala code in minimal/src/main/scala/Hello.scala
.
Start SBT inside directory minimal
:
bash-3.2$ pwd
../sbtTutorial/minimal
bash-3.2$ sbt
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=384m; support was removed in 8.0
[info] Loading global plugins from /Users/peterbecich/dotfiles/sbt/0.13/plugins
[info] Loading project definition from ../sbtTutorial/minimal/project
[info] Set current project to hello (in build file:../sbtTutorial/minimal/)
>
Two runnable main classes are defined in Hello.scala
. This means that run
will prompt you to choose a main class to run:
> run
run
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] Hello
[2] Hello2
^JEnter number: 1
1
[info] Running Hello
hello
[success] Total time: 4 s, completed May 21, 2016 5:14:34 PM
>
minimal/build.sbt
is a build definition.
It is not an absolutely minimal build definition, as shown here:
name := "Test 1"
version := "1.0"
scalaVersion := "2.9.1"
The reason we skip over this absolutely minimal build definition is that such a build definition is not suitable to be expanded to multiple sub-projects, as show in Part 2. Our Scala code, and supporting code like build definitions, will keep scalability in mind whenever possible.
The version of SBT is set outside minimal/build.sbt
, in minimal/project/build.properties
.
scala-class-private/sbtTutorial/multi
Directory multi
contains an SBT build definition with multiple SBT sub-projects.
Start SBT inside directory multi
.
You can list the sub-projects with projects
:
> projects
projects
[info] * backend
[info] frontend
[info] root
>
backend
and frontend
are defined in multi/build.sbt
. The root
sub-project will exist in any SBT project.
Enter project frontend
:
> project frontend
project frontend
[info] Set current project to frontend (in build file:/Users/peterbecich/misc-private/sbtTutorial/multi/)
>
Type run
:
> run
run
[info] Running com.datascience.reporting.PrintPi
Pi is estimated to equal 3.1484375 with 4096 Monte Carlo iterations
[success] Total time: 1 s, completed May 21, 2016 5:45:53 PM
>
This SBT project demonstrates a dependency of frontend
upon backend
.
Simple code re-use is demonstrated. commonSettings
is shared by backend
and frontend
.
Settings can be stacked. Settings specific to sub-project frontEnd
are stacked with commonSettings
:
settings(
name := "frontend",
version := "1.0"
).settings(commonSettings:_*)
lazy val frontend = (project in file("frontend")).
settings(
...
).settings(commonSettings:_*).aggregates(backend)
aggregates(backend)
means that whenever sub-project frontend
is compiled, sub-project backend
will be compiled. It does not bring backend
's code into frontend
's scope. It is one-directional.
lazy val backend = (project in file("backend")).
....aggregates(frontend)
would be necessary for bi-directional dependence. With such tight coupling, the use of multiple sub-projects is called into question.
By default, root
aggregates all other sub-projects.
aggregates
is a subset of the functionality of dependsOn
.
lazy val frontend = (project in file("frontend")).
settings(
...
).settings(commonSettings:_*).dependsOn(backend)
dependsOn(backend)
gives frontend
access to backend
's code.
Informally, code can be located by filesystem location (multi/backend/src/main/scala/MonteCarlo.scala
) or by Java package (com.datascience.approximations.MonteCarlo
).
I suggest referring to these separate systems as the filesystem hierarchy and the class hierarchy, respectively. Either one of these will be useful to refer a colleague to a particular piece of code.
Java and Scala code only cares about class hierarchy, though. Outside of the REPL, you cannot import Scala code from a file (import src/main/scala/Foo.scala
) -- rather, import from a class (import backend.Foo
).
Because of the importance of the class hierarchy, I recommend thinking in terms of packages and classes more often than .scala
files.
You can navigate by package or class in any IDE:
You can find two pieces of code in
-
com.datascience.approximations.MonteCarlo
- located in
multi/backend/src/main/scala/MonteCarlo.scala
- located in
-
com.datascience.reporting.PrintPi
- located in
multi/frontend/src/main/scala/PrintPi.scala
- located in
Note that the class and filesystem hierarchies do not necessarily correspond. PrintPi
's class hierarchy does not incorparate frontend
. This demonstrates how these two hierarchies can be "orthogonal".
IntelliJ's Navigate by Name tool or a similar tool will hunt down the class for you. This is the way to find a class of ambiguous filesystem location.
scala-class/sbtTutorial/usingCats
usingCats
shows how to add an external dependency. This SBT project contains only a single sub-project.
Start SBT inside directory usingCats
.
The Cats library is a dependency:
lazy val root = (project in file(".")).
settings(
name := "hello",
version := "1.0",
scalaVersion := "2.11.8",
libraryDependencies += "org.typelevel" % "cats-core_2.11" % "0.5.0"
)
usingCats
contains a warm-up exercise for Monads week, based upon this resource.
Look at class com.datascience.storage.UserExperiment
, located in usingCats/src/main/scala/UserRepository.scala
.
Many things are imported from the Scala standard library and the Cats library:
import scala.util.Try
import cats._
import cats.std.all._
import cats.syntax.functor._
import cats.data.Kleisli
Let's hone in on import cats.data.Kleisli
.
Cats objects and classes are organized by package/class hierarchy. Kleisli
exists in package cats.data
.
ScalaDoc is organized by package/class hierarchy. ScalaDoc for cats.data.Kleisli
Some class hierarchies begin with a company name, like org.typelevel.discipline.Laws
or com.datascience.storage.UserExperiment
. Others do not, like cats.data.Kleisli
.
The example, revisited in Week 11, demonstrates the use of a Reader Monad (Kleisli) for dependency injection. The dependency is a repository of mafiosos. Proper use of dependency injection assures us that our logic is not "hard-wired" to any particular mafioso repository. We can swap alternative repositories in and out and re-use the logic we have implemented.
> run
run
[info] Running com.datascience.storage.UserExample
describe Fredo
Fredo's boss is Michael
describe Sonny
None
the absence of Sonny in the repository is handled safely
[success] Total time: 2 s, completed May 21, 2016 6:01:20 PM
>
scala-class/sbtTutorial/usingBreeze
Use minimal
, multi
, and usingCats
to set up the SBT project in directory usingBreeze
.
Start SBT inside directory usingBreeze
.
usingBreeze
depends on 3 Breeze libraries, found here:
https://github.com/scalanlp/breeze#sbt
- Breeze
- Breeze Natives
- Breeze Viz
usingBreeze
has two runnable sub-projects:
-
distributions
distributions
depends on Breeze and Breeze Natives.
-
plotting
plotting
depends ondistributions
. This is an internal dependency.plotting
also depends on Breeze, Breeze Natives, and Breeze Viz. These are external dependencies.
Set up these two sub-projects in usingBreeze/build.sbt
and try to reduce code duplication.
distributions
is not dependent on plotting
. That means plotting
can remain uncompilable and unrunnable while distributions
is runnable.
Resolve the dependencies of distributions
. Do not add dependencies that distributions
does not need -- Breeze Viz.
At the SBT prompt, enter the distributions
sub-project.
reload
at the SBT prompt to take in changes made to build.sbt
.
run
to see some outputs of various statistical distributions.
plotting
depends on distributions
, so will not be compilable or runnable if distributions
is not set up correctly.
Resolve the dependencies of plotting
.
reload
at the SBT prompt to take in changes made to build.sbt
.
run
to produce plots of two distributions. The rendered PNGs will appear in usingBreeze/
.
Skim the documentation on the default root
sub-project.
If a project is not defined for the root directory in the build, sbt creates a default one that aggregates all other projects in the build.
Define root
explicitly instead of letting SBT create it implicitly.
root
should aggregate distributions
and plotting
.
plotting
and distributions
share two external dependencies.
Take another look at multi/build.sbt
from Part 2. Define commonSettings
so that these two dependencies only appear once in usingBreeze.sbt
. Use stacking, as described in Part 2, to resolve plotting
's third external dependency -- Breeze Viz.
There will be no difference in the behavior of plotting
or distributions
, but you have eliminated some obvious code duplication and room for error. Come time to change the version of these Breeze dependencies, there will be less room for error.
Use How build.sbt
defines settings as a guide:
lazy val commonSettings = Seq(
organization := "com.example",
version := "0.1.0",
scalaVersion := "2.11.8"
)
###Terminal commands you will use
-
sbt
in the root ofminimal
,multi
,usingCats
orusingBreeze
to enter SBT. -
sbt
in a sub-folder an SBT project will not load the project correctly.
###SBT commands you will use
-
projects
- list the sub-projects. The one with an asterix is the sub-project you're currently in. -
project X
- enter a sub-project -
reload
- reloadbuild.sbt
. Equivalent to exiting SBT and starting it again. -
compile
- fetches dependencies if necessary -
run
- run the main method in an SBT sub-project, or pick a main method if there is more than one is an SBT sub-project -
exit
-
help
- Displays this help message or prints detailed help on requested commands (runhelp <command>
)
scala-class/sbtTutorial/usingBreezeRefactored
In this Part we will refactor our build configuration from Part 4 in the style of the MLeap library.
We will split our build.sbt
into multiple files: build.sbt
, project/Common.scala
and project/Dependencies.scala
.
The extra imports in the *.scala
files
import sbt._
import Keys._
are explained in What is the difference between build.sbt
and build.scala
?.
scala-class/sbtTutorial/usingBreezeRefactored/project/Common.scala
Emulate mleap/project/Common.scala
Refactor commonSettings
from scala-class/sbtTutorial/usingBreeze/build.sbt
in Part 4.
import sbt._
import Keys._
object Common {
lazy val commonSettings: Seq[Def.Setting[_]] = ???
}
Define the organization as com.datascience.education
.
Include
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
scala-class/sbtTutorial/usingBreezeRefactored/project/Common.scala
Emulate mleap/project/Common.scala
Factor out the string containing the version of Scala from commonSettings
.
import sbt._
import Keys._
object Common {
// use Scala 2.11.8
val scalaVer: String = ???
lazy val commonSettings: Seq[Def.Setting[_]] = ???
}
scala-class/sbtTutorial/usingBreezeRefactored/project/Common.scala
Emulate mleap/project/Common.scala
Skim over SBT's documentation on Resolvers.
Fill in
lazy val otherResolvers: Seq[Resolver] = ???
with
"bintray/non" at "http://dl.bintray.com/non/maven",
and
"Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/"
.
Put otherResolvers
into commonSettings
with, right below the organization
:
resolvers ++= otherResolvers
scala-class/sbtTutorial/usingBreezeRefactored/project/Dependencies.scala
Emulate mleap/project/Dependencies.scala
.
Refactor libraryDependencies
from scala-class/sbtTutorial/usingBreeze/build.sbt
in Part 4.
Fill in
lazy val commonDependencies: Seq[ModuleID] = ???
and
lazy val plottingDependencies: Seq[ModuleID] = ???
. plottingDependencies
should not include the commonDependencies
. They can be combined, if need be, with commonDependencies.union(plottingDependencies)
. You have all the methods of Seq
at your disposal.
scala-class/sbtTutorial/usingBreezeRefactored/project/Dependencies.scala
Factor out the version of Breeze into a val
:
val breezeVersion: String = ???
scala-class/sbtTutorial/usingBreezeRefactored/project/Dependencies.scala
Skim the SBT documentation on compiler plugins.
si2712fix-plugin and kind-projector are compiler plugins. They are dependencies, but must be wrapped with compilerPlugin(...)
.
The necessity of these two particular compiler plugins is a topic for another day.
val kindProjector = compilerPlugin("org.spire-math" % "kind-projector" % "0.9.0" cross CrossVersion.binary)
val si2712 = compilerPlugin("com.milessabin" % "si2712fix-plugin" % "1.2.0" cross CrossVersion.full)
Append these two dependencies to commonDependencies: Seq[ModuleID]
.
scala-class/sbtTutorial/usingBreezeRefactored/project/Dependencies.scala
Include Dependencies.commonDependencies
with scalaVersion
, organization
, scalacOptions
and resolvers
in:
lazy val commonSettings: Seq[Def.Setting[_]] = ???
scala-class/sbtTutorial/usingBreezeRefactored/build.sbt
Copy and refactor
lazy val root = ???
lazy val distributions = ???
lazy val plotting = ???
from scala-class/sbtTutorial/usingBreeze/build.sbt
in Part 4.
root
should aggregate distributions
and plotting
.
plotting
should depend on distributions
.
Ensure the plotting
sub-project has access to Dependencies.plottingDependencies
.
This build.sbt
will be much more concise than scala-class/sbtTutorial/usingBreeze/build.sbt
in Part 4.
ModuleID
Scaladoc - the type that holds a dependency