Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SBT Multi-Project Build with dynamic external projects?

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.

like image 631
Yevgeniy Brikman Avatar asked Dec 04 '13 03:12

Yevgeniy Brikman


People also ask

What is a subproject in sbt build?

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.

How to aggregate multiple projects in SBT?

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:

Is it possible to have multiple subprojects in a single build?

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.

Why is my project not in the root directory in SBT?

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.


1 Answers

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"  
)
like image 145
Yevgeniy Brikman Avatar answered Sep 30 '22 01:09

Yevgeniy Brikman