skip to content

How does jacoco-report-aggregation actually collect the .exec data, classes and sources from each subproject — what's the resolution mechanism?

level: seniorimportance: should knowfreq 30%

answer

  1. consumable vs resolvable vs bucket
  2. attributes Category=verification, VerificationType
  3. jacoco-results / main-source-element variants
  4. ArtifactView -> FileCollection
  5. transitive pull, graceful skip

basics

~10 s

It uses Gradle's variant-aware dependency resolution: contributing projects publish coverage data, classes and sources as outgoing variants, and the aggregation plugin declares resolvable configurations that select each variant by attributes.

solid answer

~40 s

The aggregation plugin doesn't hard-code file paths. Each contributing project (via `java` / `jvm-test-suite`) exposes **consumable** outgoing variants: one carrying the JaCoCo `.exec` execution data, one carrying compiled `classes` (class directories), and one carrying `sources`. The aggregation project declares three **resolvable** configurations that extend the `jacocoAggregation` dependency set and request each of those variants using attributes — notably `Category` (`verification` for coverage data, plus `DocsType`/`VerificationType` attributes like `jacoco-results`). Gradle resolves the graph, picks the matching variant from every project, and exposes the resulting file collections (artifact views) to the generated `JacocoReport` task as its `executionData`, `classDirectories`, and `sourceDirectories`. Because it's attribute-driven, it transitively includes projects you didn't list directly if they're on the graph and skips ones that don't expose coverage variants.

code

kotlin · 11 lines
kotlin
// A contributing project exposes coverage as a consumable variant (done
// automatically by jvm-test-suite). Conceptually:
configurations.consumable("coverageDataElements") {
    attributes {
        attribute(Category.CATEGORY_ATTRIBUTE,
            objects.named(Category.VERIFICATION))
        attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE,
            objects.named(VerificationType.JACOCO_RESULTS))
    }
    outgoing.artifact(layout.buildDirectory.file("jacoco/test.exec"))
}

go deeper

for a junior

Just know it gathers coverage data, classes, and sources from each module automatically.

for a middle

Recognize that projects publish coverage as variants and the plugin resolves them, no manual paths.

for a senior

Explain consumable vs resolvable configurations and attribute matching (Category=verification, VerificationType=jacoco-results) feeding the JacocoReport task.

for a principal

Position it as one case of Gradle's general verification-results aggregation pattern and reason about transitivity, graceful skipping, and refactor-safety org-wide.

## First principles: consumable vs. resolvable Every Gradle `Configuration` is one of three roles: - **Consumable** (`canBeConsumed = true`, `canBeResolved = false`) — an *outgoing* variant a project exposes to others. - **Resolvable** (`canBeResolved = true`, `canBeConsumed = false`) — an *incoming* request a project resolves. - **Dependency bucket** (`canBeConsumed = false`, `canBeResolved = false`) — where you `add` dependencies, e.g. `jacocoAggregation`. Gradle matches a resolvable configuration to a consumable variant by comparing **attributes** (typed key/value metadata), not by name. ## What contributing projects publish When a project applies `java`/`jvm-test-suite`, it exposes outgoing variants tagged with attributes such as: - `Category = verification` + `VerificationType = jacoco-results` -> the `.exec` execution data. - `Category = verification` + `VerificationType = main-source-element` -> the source directories. - A `classes`-type variant -> the compiled class output directories (not jars, so JaCoCo can map bytecode to lines). ## What the aggregation plugin requests The plugin creates internal **resolvable** configurations that `extendsFrom(jacocoAggregation)` and set matching attributes. Resolving each one yields a `FileCollection` (via an `ArtifactView`) of, respectively, every reachable project's exec data, classes, and sources. ## Wiring into the report task Those file collections are bound to the registered `JacocoReport` (`testCodeCoverageReport`): ```kotlin tasks.named<JacocoReport>("testCodeCoverageReport") { // executionData, classDirectories, sourceDirectories // are all populated from the resolved aggregation variants } ``` ## Consequences of being attribute-driven - **Transitivity**: if `:app` depends on `:lib` and you only list `:app` in `jacocoAggregation`, `:lib` is still pulled in because it's on the dependency graph and exposes the variants. - **Graceful skipping**: a project that doesn't apply jacoco/jvm-test-suite simply has no matching variant, so it contributes nothing rather than failing the build. - **No path fragility**: you never reference `build/jacoco/test.exec` by hand; refactors that move outputs don't break aggregation. This is the same machinery behind `java-test-fixtures` and the `jvm-test-suite` aggregation of test results — coverage aggregation is one instance of Gradle's general 'verification results aggregation' pattern.

  • Why does it need the class *directories* rather than the jars?
    JaCoCo instruments and maps coverage against the exact `.class` files; resolving the `classes` variant (directory output) avoids jar repackaging/relocation that would break line/branch mapping.
  • If a module isn't listed in jacocoAggregation but is a transitive dependency, is it included?
    Yes. Resolution walks the dependency graph, so any reachable project exposing the coverage variants contributes, even if not named directly.
  • What happens to a project that doesn't apply the jacoco/jvm-test-suite plugins?
    It exposes no matching variant, so it's silently skipped — the build doesn't fail, that module just isn't in the report.

saying these in an interview costs you the question

  • Saying it greps the filesystem for *.exec files — it's variant/attribute resolution, not path scanning.
  • Claiming you must list every project explicitly — transitive projects are pulled automatically.

context