A teammate's plugin reads `extension.outputDir.get()` inside the plugin's `apply` method to configure a task, and reports that the value users set in the build script is being ignored. Diagnose the bug and fix it.
answer
- apply runs before user DSL
- .get() snapshots default
- set(provider) restores live link
- map/flatMap to transform lazily
- get() in plugin = smell
basics
~10 s.get() resolves the value during configuration, before the user's DSL block runs, so it captures the default. Fix it by wiring the provider lazily: task.outputDir.set(extension.outputDir) with no .get().
solid answer
~50 sThe plugin's `apply` runs early in project configuration; the user's `extension { outputDir = ... }` block runs afterward. Calling `extension.outputDir.get()` in `apply` forces the value *at that point*, which is the default (or unset), so the user's later assignment never reaches the task. The fix is to pass the provider through instead of resolving it: `task.outputDir.set(extension.outputDir)`. Now the task's property holds a live link back to the extension, and Gradle resolves it lazily — at execution, when the user's value is in place. If you must transform the value, use provider operators like `extension.outputDir.map { ... }` so the transformation also stays lazy. The rule of thumb: in plugin/configuration code, never call `get()` on a value you intend to wire into a task; only Gradle should call `get()`, and it does so at the right time.
code
kotlin · 8 lines// BUG: eager read during apply
task.outputDir.set(extension.outputDir.get()) // captures default
// FIX: lazy provider wiring
task.outputDir.set(extension.outputDir)
// FIX with transform, still lazy
task.reportFile.set(extension.outputDir.map { it.file("report.txt") })go deeper
Recognize that .get() in apply is the bug and that set(provider) is the fix.
Explain the configuration ordering precisely and how map/flatMap keep transforms lazy.
Generalize to a 'get() is a smell in plugin code' rule and reason about who may resolve values.
Tie eager reads to broken configuration-cache compatibility and unreliable builds at scale.
## Configuration timing in Gradle A project is configured top to bottom. Plugins are applied (their `apply` methods run) when `plugins { }` / `apply plugin:` is processed, which is **before** the body of the build script that configures those plugins' extensions. So the lifecycle is: 1. `apply` creates the extension (defaults in place) and registers tasks. 2. The build script's `myExt { outputDir = file("out") }` block runs, mutating the extension. 3. At execution, tasks read their inputs. ## The eager-read bug If step 1 does `task.outputDir = extension.outputDir.get()`, it reads the value at the end of step 1 — before step 2 has run. The user's assignment in step 2 lands on the extension property, but the task already copied the *old* value. The task therefore uses the default, and the user concludes their configuration is "ignored." ## The fix: keep it lazy Replace the eager copy with a provider wiring: ```kotlin task.outputDir.set(extension.outputDir) ``` `set(Provider)` stores the link, not the value. When Gradle reads `task.outputDir` at execution, it walks back to `extension.outputDir`, which now reflects the user's step-2 assignment. ## Transforming while staying lazy Sometimes you don't want the raw extension value but a derived one. Don't `get()` then transform — chain provider operators: - `provider.map { transform(it) }` — lazily transform a present value. - `provider.flatMap { otherProvider }` — chain to another provider. - `provider.orElse(default)` — supply a fallback lazily. ```kotlin task.reportFile.set(extension.outputDir.map { it.file("report.txt") }) ``` Everything stays deferred, so the user's later changes still flow through. ## Heuristic In plugin code, treat `get()` as a smell. The only legitimate `get()` callers are Gradle itself (at execution) and tests. If you find yourself resolving a value to wire it somewhere, you've broken laziness.
- How would you wire a *derived* value (extension dir + a fixed filename) without breaking laziness?Use a provider operator: `extension.outputDir.map { it.file("report.txt") }`, then `set` that provider into the task. `map` defers the transform until resolution.
- Who is allowed to call `.get()` legitimately?Gradle itself at task execution, and test code. Plugin/configuration code should pass providers through rather than resolving them.
saying these in an interview costs you the question
- Claiming the fix is to move the user's DSL block above the plugin apply.
- Resolving the value with `get()` and then transforming it eagerly.