Gradle Footguns: Don't add potentially-empty providers to collection properties
After pairing with Tony Robalik on a recent weird behavior I was seeing in Gradle, we encountered a deliciously evil bug in Gradle. It's documented a little in Gradle 8.7's release notes (see Better API for updating collection properties), but the basic premise is below.
abstract class MyTask : DefaultTask() {
@get:Input
abstract val listProperty: ListProperty<String>
}
listProperty.add("one")
listProperty.add(providers.gradleProperty("hi"))
listProperty
will be "empty" if there is no "hi"
Gradle property, clearing the previously added elements.Where this gets worse is that Gradle's docs kinda regularly conflate empty and absent when dealing with collections, so my understanding was that it just cleared the elements. Turns out, it actually clears the value entirely.
So - doing this...
listProperty
.add("one")
.add(providers.gradleProperty("hi"))
.get()
... actually throws a MissingValueException
!
Even better, it also completely destroys your conventions
listProperty
.convention(emptyList())
.add("one")
.add(providers.gradleProperty("hi"))
.get()
... also throws an exception!
Finally, if you hide it behind a non-managed-property type like the Kotlin Gradle plugin does in its legacy API, you get something like this later.
kotlinOptions.freeCompilerArgs += listOf("hi")
This quietly becomes an inert action later if the underlying provider was added to with an absent provider, and the property is rendered unusable unless you call set()
on it again with a fresh collection.
TL;DR don't add providers to collections without also using orElse()
listProperty
.convention(emptyList())
.add("one")
.add(
providers.gradleProperty("hi")
.map { listOf(it) }
.orElse(emptyList())
)
.get()