Skip to content

Commit

Permalink
Add a post about artifact aggregation
Browse files Browse the repository at this point in the history
  • Loading branch information
liutikas committed Dec 11, 2024
1 parent 944c88a commit b5fa4b9
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ DEPENDENCIES
github-pages
jekyll-feed
tzinfo-data
webrick (~> 1.8.1)
webrick (~> 1.8.2)

BUNDLED WITH
2.4.7
91 changes: 91 additions & 0 deletions _posts/2024-12-11-Together-In-Isolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
layout: post
title: Together in Isolation - Isolated Project Safe Way to Aggregate Optional Artifacts
header: header-eng
---

One common thing to do in a Gradle project is collect artifacts from a subset of projects to produce an aggregate
artifact. An example of it might be collecting test reports from each project that have tests to create a combined
test report for all projects. Historically, someone might have done this with:

Root project `build.gradle.kts`:
```kotlin
abstract class AggregatingTask : DefaultTask() {
@get:InputFiles
abstract val inputs: ListProperty<File>
@get:OutputFile
abstract val output: RegularFile
@TaskAction fun aggregate() { /* process inputs */ }
}

tasks.register<AggregatingTask>("aggregatingTask")
```

Each project that has an artifact in their `build.gradle.kts`:
```kotlin
rootProject.tasks.named<AggregatingTask>(
"aggregatingTask"
).configure {
inputs.add(reportTask.flatmap { it.reportFile })
}
```

Sadly, in addition to being fragile with hardcoding of the task name/type, it is also not valid with Gradle
[Isolated Project](https://docs.gradle.org/current/userguide/isolated_projects.html) feature enabled. Is there a better
way? Yes (but it is a tad verbose)!

Root project `build.gradle.kts`:
```kotlin
val reportConsumer = configurations.create("reportConsumer") {
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(
Category.CATEGORY_ATTRIBUTE,
objects.named("my-reports")
)
}
}
// Add all subprojects to dependencies
subprojects {
reportConsumer.dependencies.add(dependencies.create(this))
}
// Be lenient if not all projects have this artifact
val reportCollection = reportConsumer.incoming.artifactView {
lenient(true)
}.files

abstract class AggregatingTask : DefaultTask() {
@get:InputFiles
abstract val inputs: ConfigurableFileCollection
@get:OutputFile
abstract val output: RegularFile
@TaskAction fun aggregate() { /* process inputs */ }
}
tasks.register<AggregatingTask>("aggregatingTask") {
inputs.from(reportCollection)
}

```

Each project that has an artifact in their `build.gradle.kts`:
```kotlin
configurations.create("reportPublisher") {
// Note, consumed and resolved are opposite of root project
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(
Category.CATEGORY_ATTRIBUTE,
objects.named("my-reports")
)
}
}
artifacts.add("reportPublisher", reportTask)
```

With this set up, root project still has the aggregating task that is able to build the report from all the artifacts
from each project, but instead of each subproject contributing to that task directly, everything flows through Gradle
artifact publishing and attribute resolution mechanisms.

Pro-tip: to visualize what you have added to artifacts, you can use `./gradlew :projectA:outgoingVariants`

0 comments on commit b5fa4b9

Please sign in to comment.