Show how to wire providers.gradleProperty into a custom task input the lazy way, and explain what goes wrong if you call .get() in the configuration block.
answer
- Property<T> input + provider wiring
- who.set(provider), not provider.get()
- .get() only in @TaskAction/doLast
- eager get = lost input tracking + crash on absent
- config-cache staleness
basics
~10 sDeclare a Property<String> task input and set it to the provider: input.set(providers.gradleProperty("x").orElse("d")). Calling .get() in the configuration block resolves eagerly, loses laziness, and can break config-cache or up-to-date checks.
solid answer
~40 sGive the task a lazy input field — `abstract val flag: Property<String>` annotated `@Input` — and wire the provider without resolving it: ```kotlin tasks.register<MyTask>("run") { flag.set(providers.gradleProperty("flag").orElse("default")) } ``` Gradle now resolves `flag` at execution time and records the gradle property as a tracked input, so up-to-date checks, the build cache, and the configuration cache all behave correctly. If instead you write `flag.set(providers.gradleProperty("flag").get())`, you resolve the value *at configuration time*: the property read is no longer tracked as a live input, a missing property throws during configuration for everyone, and the configuration cache may store a stale snapshot. The rule: pass *providers* into `Property.set`, and only call `.get()` inside `@TaskAction`/`doLast` execution code where eager resolution is expected.
code
kotlin · 10 linesabstract class GreetTask : DefaultTask() {
@get:Input abstract val who: Property<String>
@TaskAction fun run() = println("hello ${who.get()}") // execution time
}
tasks.register<GreetTask>("greet") {
// GOOD: wire the provider lazily
who.set(providers.gradleProperty("who").orElse("world"))
// BAD: who.set(providers.gradleProperty("who").get())
}go deeper
Recognize that task inputs are Property<T> and you set them from providers.
Show the correct wiring and name that .get() belongs in @TaskAction, not configuration.
Explain the cache/up-to-date/config-cache consequences of eager resolution and the absence-crash failure mode.
Establish lint/review rules banning .get() in configuration blocks and codify lazy-wiring patterns across the plugin/build codebase.
## Lazy task inputs in modern Gradle A well-formed Gradle task declares its inputs as lazy containers: ```kotlin abstract class GreetTask : DefaultTask() { @get:Input abstract val who: Property<String> @TaskAction fun run() = println("hello ${who.get()}") // .get() here = execution time, OK } ``` `Property<T>` is a settable, lazy input. You feed it either a literal or, crucially, **a provider**: ```kotlin tasks.register<GreetTask>("greet") { who.set(providers.gradleProperty("who").orElse("world")) } ``` ## Why wiring the provider is correct When you call `who.set(provider)`, Gradle stores the *provider*, not a value. Several machineries depend on this: - **Up-to-date checks / build cache**: the `@Input` value is read at execution time and hashed. If the gradle property changes between builds, the input hash changes and the task re-runs. Wiring the provider makes this automatic. - **Configuration cache**: the serialized task carries "read gradle property who, else 'world'" as its input. On a later build Gradle can detect the property changed and invalidate. No `Project` is captured. - **Laziness / ordering**: the value is read when needed, so even values configured later in the build still take effect. ## What breaks with eager .get() at configuration time ```kotlin tasks.register<GreetTask>("greet") { who.set(providers.gradleProperty("who").get()) // BAD } ``` Problems: 1. **Crashes on absence**: if `who` isn't defined, `.get()` throws `MissingValueException` during configuration — and it throws for *every* invocation that configures this task, even tasks the user never runs (unless configuration avoidance saves you). 2. **Lost input tracking**: you stored a fixed `String`, not a provider. The link to the gradle property source is gone; the property is no longer the tracked thing. 3. **Configuration-cache staleness/eagerness**: the value is captured at store time and not re-derived from the property on cache reuse; combined with eager configuration this fights the cache's design. 4. **Configuration-time work**: you've done resolution during configuration, undermining configuration avoidance and config-cache performance. ## The discipline - Pass **providers** into `Property.set(...)`; build defaults with `.orElse(...)` and transforms with `.map(...)` while still in provider form. - Call `.get()`/`.getOrElse(...)` **only** inside `@TaskAction` or `doLast { }` execution code. - Treat any `.get()` in a `tasks.register { }` / `tasks.named { }` configuration block as a smell. This keeps the build lazy, cacheable, and config-cache-compatible — the whole reason the provider API exists for reading gradle/system/env properties.
- Where is it acceptable to call .get() on the provider?Inside execution-time code — @TaskAction methods or doLast/doFirst closures — where the value is genuinely needed; not in the task configuration block.
- How does wiring the provider help up-to-date checks?Gradle reads the @Input value at execution time and hashes it; if the underlying gradle property changes, the hash changes and the task re-runs automatically.
- Does eager .get() always crash?Only when the property is absent (MissingValueException). Even when it succeeds it still loses input tracking and works against the configuration cache, so it's an anti-pattern regardless.
Setting the input to a provider is like giving a recipe step 'measure the flour at bake time'; calling .get() at config time is measuring the flour now and writing the number on the recipe — if the bag is empty you fail immediately, and if someone refills it later your number is wrong.
saying these in an interview costs you the question
- Calling provider.get() inside tasks.register { } / tasks.named { } configuration blocks.
- Storing a resolved String as a task input instead of wiring the provider, then wondering why the task isn't re-running on property change.
- Claiming .get() at config time is fine because 'it works on my machine' where the property is always set.