When do you need aggregate over dependsOn

Viewed 487

When doing a multiproject build, you can list the projects you depend upon in dependsOn, and tasks will run on the dependencies first, so you can depend on their results.

There also is an aggregate task that, eh, aggregates subprojects. How does aggregating and depending on subprojects differ, and in what cases should you use aggregrate instead of dependsOn

1 Answers

The key difference is aggregate does not modify classpath and does not establish ordering between sub-projects. Consider the following multi-project build consisting of root, core and util projects:

├── build.sbt
├── core
│   ├── src
│   └── target
├── project
│   ├── build.properties
│   └── target
├── src
│   ├── main
│   └── test
├── target
│   ├── scala-2.13
│   └── streams
└── util
├── src
└── target

where

core/src/main/scala/example/Core.scala:

package example

object Core {
  def foo = "Core.foo"
}

util/src/main/scala/example/Util.scala

package example

object Util {
  def foo =
    "Util.foo" + Core.foo // note how we depend on source from another project here
}

src/main/scala/example/Hello.scala:

package example

object Hello extends App {
  println(42)
}

Note how Util.foo has a classpath dependency on Core.foo from core project. If we now try to establish "dependency" using aggregate like so

lazy val root = (project in file(".")).aggregate(core, util)
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))

and then execute compile from root project

root/compile

it will indeed attempt to compile all the aggregated projects however Util will fail compilation because it is missing a classpath dependency:

sbt:aggregate-vs-dependsOn> root/compile
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/target/scala-2.13/classes ...
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/core/target/scala-2.13/classes ...
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/util/target/scala-2.13/classes ...
[error] /Users/mario/IdeaProjects/aggregate-vs-dependson/util/src/main/scala/example/Util.scala:5:18: not found: value Core
[error]     "Util.foo" + Core.foo // note how we depend on source from another project here
[error]                  ^
[error] one error found
[error] (util / Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed 13-Oct-2019 12:35:51

Another way of seeing this is to execute show util/dependencyClasspath which should have Core dependency missing from output.

On the other hand, dependsOn will modify classpath and establish appropriate ordering between projects

lazy val root = (project in file(".")).aggregate(core, util)
lazy val util = (project in file("util")).dependsOn(core)
lazy val core = (project in file("core"))

Now root/compile gives

sbt:aggregate-vs-dependsOn> root/compile
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/target/scala-2.13/classes ...
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/core/target/scala-2.13/classes ...
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/aggregate-vs-dependson/util/target/scala-2.13/classes ...
[success] Total time: 1 s, completed 13-Oct-2019 12:40:25

and show util/dependencyClasspath shows Core on the classpath:

sbt:aggregate-vs-dependsOn> show util/dependencyClasspath
[info] * Attributed(/Users/mario/IdeaProjects/aggregate-vs-dependson/core/target/scala-2.13/classes)

Finally, aggregate and dependsOn are not mutually exclusive, in fact, it is common practice to use both at the same time, often aggregate on the root all sub-projects to aid building, whilst using dependsOn to handcraft particular orderings for particular sub-projects.

Related