SBT Multi-Project Build with dynamic external projects?
Asked Answered
S

2

7

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.

Surcease answered 4/12, 2013 at 3:59 Comment(1)
S
2

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"  
)
Surcease answered 14/12, 2013 at 9:8 Comment(0)
A
0

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

Alfy answered 24/1, 2015 at 9:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.