Yevgeniy Brikman
Yevgeniy Brikman

Reputation: 9361

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.

Upvotes: 7

Views: 557

Answers (2)

axel22
axel22

Reputation: 32335

The Mecha build automation SBT plugin does this depending on whether there are other projects on the local file system. It's a new project, so docs are scarce, but you can take a look at its source: https://github.com/storm-enroute/mecha

Upvotes: 0

Yevgeniy Brikman
Yevgeniy Brikman

Reputation: 9361

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"  
)

Upvotes: 2

Related Questions