Let's say we have an SBT project bar
with a dependency on some artifact foo
:
val bar = Project('bar', file('.')).settings(
libraryDependencies += "com.foo" % "foo" % "1.0.0"
)
However, in certain cases, I want to checkout the source of foo
and have SBT load the source from my file system instead of the published artifact; that way, I could make local changes to foo
and immediately test them with bar
without having to publish anything.
val foo = Project('foo', file('foo'))
val bar = Project('bar', file('.')).dependsOn(foo)
We have a spec.json
file in the root folder of bar
that already specifies if foo
should be used from source or as an artifact. Is there any way to setup my build to read this file and add dependsOn
or libraryDependencies
based on the value in spec.json
? '
It's easy enough to do this for libraryDependencies
:
val bar = Project('bar', file('.')).settings(
libraryDependencies ++=
if (containsFoo(baseDirectory.value / "spec.json")) {
Seq()
} else {
Seq("com.foo" % "foo" % "1.0.0")
}
)
However, we can't find any way to set do anything "dynamic" in dependsOn
, such as reading the baseDirectory
SettingKey
.
Each subproject in a build has its own source directories, generates its own jar file when you run package, and in general works like any other project. A project is defined by declaring a lazy val of type Project. For example, : The name of the val is used as the subproject’s ID, which is used to refer to the subproject at the sbt shell.
Start up sbt with two subprojects as in the example, and try compile. You should see that all three projects are compiled. In the project doing the aggregating, the root project in this case, you can control aggregation per-task. For example, to avoid aggregating the update task:
It can be useful to keep multiple related subprojects in a single build, especially if they depend on one another and you tend to modify them together. Each subproject in a build has its own source directories, generates its own jar file when you run package, and in general works like any other 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. Because project hello-foo is defined with base = file ("foo"), it will be contained in the subdirectory foo.
We tried a few approaches, but the only one we could get to work and that didn't feel like an incomprehensible/unmaintainable hack was to add an implicit class
that adds a method to Project
that can add a dependency either locally or as an artifact.
Pseudo-code outline of the implementation:
implicit class RichProject(val project: Project) extends AnyVal {
def withSpecDependencies(moduleIds: ModuleID*): Project = {
// Read the spec.json file that tells us which modules are on the local file system
val localModuleIds = loadSpec(project.base / "spec.json")
// Partition the passed in moduleIds into those that are local and those to be downloaded as artifacts
val (localModules, artifactModules) = moduleIds.partition(localModuleIds.contains)
val localClasspathDependencies = toClasspathDependencies(localModules)
project
.dependsOn(localClasspathDependencies: _*)
.settings(libraryDependencies ++= artifactDependencies)
}
}
The usage pattern in an actual SBT build is pretty simple:
val foo = Project("foo", file("foo")).withSpecDependencies(
"com.foo" % "bar" % "1.0.0",
"org.foo" % "bar" % "2.0.0"
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With