To summarise your question: There are two ways of capturing user input:
- A normal property
var foo: String
- Using a property,
var foo: Property<String>
What's the difference? And why are there different ways of setting these values?
tl;dr:
Avoiding work
Gradle uses tasks to perform work, and Gradle likes avoiding work.
To avoid work, there are two aspects to consider:
Gradle must be able to check if inputs or outputs have changed to perform up-to-date checks.
To avoid unnecessary work, inputs and outputs should only be evaluated on demand, using Lazy Configuration.
Up-to-date checks
Gradle checks if tasks are up-to-date by storing checksums of all inputs and outputs task properties. All properties must therefore be serialisable.
This is usually not relevant, because Gradle can serialise most properties. All primitives are serialisable (in your example, foo
, bar
, and baz
are all strings), and if not, it can implement Java Serializable
.
The Provider API
Note that up-to-date checks are only used for tasks. There are no up-to-date checks on Gradle extensions. So why shouldn't an extension use a normal property?
interface MyCoolPluginExtension {
var version: String // what's wrong with a simple variable property?
}
A simple property might work for a lot of cases, but there are some edge cases:
- What if computing the value of
version
is, in some way, expensive or slow? For example, if it's contained within a file that's downloaded from the internet.
- What if
version
is dependent on another property that isn't known yet?
While computing the actual value of version
could be performed immediately, this could introduce unnecessary work if the value of version
isn't needed for the tasks that Gradle need to run. It could also introduce expensive work in the configuration phase - something to be avoided!
Using a Property
solves these problems:
interface MyCoolPluginExtension {
val version: Property<String> // use the Provider API for better work-avoidance
}
It does this in two ways
- The value will be lazily evaluated. Ideally, this evaluation only happens during the execution of a task - so if the task that needs the property never runs, the work is avoided.
- Properties can be chained together. This means that a property can lazily react to another property - again, avoiding work unless its necessary.
Best of both worlds?
Using the Gradle Provider API can sometimes be awkward to use. But we can create helper functions to make it more convenient!
When it comes to up-to-date checks, Gradle will only consider task properties that are annotated as inputs or outputs, and will ignore those annotated with @Internal
.
This means you could expose some helper functions to users - so long as the actual task property is correctly annotated as an input or output.
For example, you could mark the actual Property<String>
as internal
, and only expose a var
to plugin users:
import org.gradle.api.provider.Property
abstract class MyCoolPluginExtension {
internal abstract val versionProp: Property<String>
var version: String?
get() = versionProp.orNull
set(value) {
versionProp.set(value)
}
}
(This is a pretty silly example - in real life just use the Property setter, version.set("1.2.3")
!)
// build.gradle.kts
plugins {
id("my.cool.plugin")
}
myCoolPlugin {
version = "1.2.3"
}