Before jacoco-report-aggregation, teams hand-rolled a root JacocoReport that pointed at every module's exec/classes/sources. What are the concrete advantages of the plugin over that approach?
answer
- old: subprojects{} + fileTree + hard paths
- breaks project isolation / config cache
- manual dependsOn on test tasks
- plugin: variant resolution, transitive, auto-wired
- supported first-party API
basics
~10 sThe plugin resolves coverage via variants instead of hard-coded paths, so it's refactor-safe, auto-transitive, runs the right test tasks for you, and avoids cross-project configuration that breaks isolation and the configuration cache.
solid answer
~40 sThe legacy pattern applied `jacoco` at the root and built a `JacocoReport` whose `executionData`, `classDirectories`, and `sourceDirectories` were assembled by iterating subprojects and reaching into their internals — `subprojects { }` blocks, `fileTree` over `build/jacoco`, references to other projects' source sets. That's brittle: it hard-codes output paths, reaches across project boundaries (hostile to project isolation and the configuration cache), needs manual `dependsOn` on each test task, and silently misses or double-counts modules. `jacoco-report-aggregation` replaces all of that with attribute-based variant resolution: paths are derived, contributing projects are pulled transitively, the report task automatically depends on the producing test tasks, and there's no cross-project configuration. It's also future-proofed as the officially supported API.
code
kotlin · 11 lines// NEW way replaces all the subprojects{} glue:
plugins {
id("base")
id("jacoco-report-aggregation")
}
dependencies {
jacocoAggregation(project(":app"))
jacocoAggregation(project(":lib"))
}
// testCodeCoverageReport now auto-depends on each module's test task
// and resolves exec/classes/sources by variant — no hard-coded paths.go deeper
Just know the plugin replaces fragile manual setup with a supported, simpler one.
Name a couple of concrete pains (hard-coded paths, manual dependsOn) the plugin removes.
Articulate project isolation / configuration-cache incompatibility of the old approach and how variant resolution fixes it.
Decide migration strategy across many legacy builds and weigh the rare custom-merge cases that still justify hand-rolling.
## The hand-rolled pattern (what it looked like) ```kotlin // root build.gradle.kts — the OLD way tasks.register<JacocoReport>("codeCoverageReport") { subprojects { plugins.withType<JacocoPlugin> { executionData(fileTree("$buildDir/jacoco").include("*.exec")) sourceSets(the<SourceSetContainer>()["main"]) } } } ``` ## Why it's problematic 1. **Hard-coded paths** — `build/jacoco/*.exec`, `build/classes/...`. Any output-location change or plugin upgrade can silently break the report. 2. **Cross-project reach** — accessing another project's `SourceSetContainer`/build dir violates **project isolation** and breaks the **configuration cache** (Gradle forbids one project mutating/reading another's model at configuration time). 3. **Manual ordering** — you must remember to `dependsOn` each subproject's `test` task or you aggregate stale/empty exec data. 4. **Membership drift** — `subprojects { }` either grabs everything (including modules you didn't want) or you maintain an allowlist by hand; new modules are easy to forget. 5. **Eager configuration** — `subprojects { }` configures every project eagerly, hurting configuration time. ## What the plugin does instead - **Variant resolution, not paths.** It requests coverage/classes/sources variants by attribute; outputs are wherever the producing plugin says they are. - **Transitive, attribute-gated membership.** List entry points; reachable projects exposing coverage variants join automatically, others are skipped. - **Automatic task wiring.** Resolving the producer variants makes `testCodeCoverageReport` depend on the producing test tasks, so data is always fresh. - **Isolation/CC friendly.** No project reaches into another's mutable model; it's all dependency resolution, compatible with the configuration cache and project isolation. - **Supported API.** It's a first-party plugin that tracks Gradle's evolution, so you're not maintaining glue against internal layouts. ## When the old way still appears Legacy builds, or exotic cases needing custom merge logic, may still hand-roll. But for standard JVM multi-project coverage, the plugin is the recommended, lower-maintenance choice.
- Why specifically does the hand-rolled version conflict with the configuration cache?Because it reads/mutates other projects' models at configuration time (their source sets, build dirs). Project isolation, which the configuration cache enforces, disallows cross-project model access.
- Does the plugin still let you set excludes (e.g. generated code)?Yes — you can configure the underlying `JacocoReport` task's `classDirectories` with `exclude` filters, the same as a normal jacoco report, without giving up variant resolution.
saying these in an interview costs you the question
- Claiming the plugin is just sugar over `subprojects { }` — it fundamentally changes the mechanism to variant resolution.
- Saying the old pattern is configuration-cache compatible — cross-project model access is exactly what CC forbids.