Explain tasks.withType<Test>().configureEach { } in the Kotlin DSL. How does it differ from tasks.withType<Test> { } and tasks.withType<Test>().all { }?
answer
- configureEach = lazy, per-task at realization
- all / block overload = eager
- covers existing + future tasks
- ideal for cross-cutting config
- shows up in configuration-time scans
basics
~10 sconfigureEach applies a configuration lazily to all current and future tasks of a type, realizing each only when needed. withType { } and .all { } apply eagerly, realizing every matching task now.
solid answer
~40 s`tasks.withType<Test>()` returns a lazy `TaskCollection<Test>`. Calling `.configureEach { ... }` on it registers a configuration action that runs **per task, only when that task is realized**, and it covers both already-registered and *future* tasks of that type. By contrast, `tasks.withType<Test> { ... }` (the block overload) and `tasks.withType<Test>().all { ... }` are **eager**: they iterate the collection and realize every matching task immediately to run the block, defeating configuration avoidance. `configureEach` is the idiomatic way to apply cross-cutting config — e.g. `useJUnitPlatform()` on every `Test` task, or a common JVM arg on every `JavaExec` — without forcing them all into existence. Use it whenever you'd otherwise loop over a task type.
code
kotlin · 9 linestasks.withType<Test>().configureEach {
useJUnitPlatform()
maxHeapSize = "1g"
}
// Eager equivalent to AVOID:
tasks.withType<Test>().all {
useJUnitPlatform()
}go deeper
Recognize configureEach applies config to all tasks of a type lazily.
Distinguish configureEach (lazy) from all/block overload (eager) and note it covers future tasks.
Use it for cross-cutting policy in convention plugins and explain the realization semantics and scan impact.
Standardize convention-plugin patterns (register + configureEach) and audit the codebase for eager withType/all that inflate configuration time.
## Cross-cutting task configuration Sometimes you want to configure *every* task of a given type — all `Test` tasks use JUnit Platform, all `JavaCompile` tasks get a compiler arg, all `Jar` tasks get reproducible settings. The Kotlin DSL exposes typed collections via `withType<T>()`. ## The collection is lazy; how you traverse it decides eagerness `tasks.withType<Test>()` itself returns a `TaskCollection<Test>` — a live, lazy view. What you call on it determines realization: - `.configureEach { ... }` — **lazy**. Registers an action invoked once per task **at realization time**, for both existing and future matching tasks. Nothing is realized just by registering. - `.all { ... }` — **eager**. Realizes every current matching task immediately to run the block, and also runs for future ones as they are created (also realizing them). - `tasks.withType<Test> { ... }` (block overload) — sugar that is effectively **eager** like `.all`, realizing matching tasks now. ## Future tasks A crucial property of both `configureEach` and `all` is that they also apply to tasks registered *later*. This is why plugins use them: a plugin can say "every Test task ever created should do X" before any test task exists. ```kotlin // Lazy cross-cutting config — preferred tasks.withType<Test>().configureEach { useJUnitPlatform() testLogging { events("passed", "failed", "skipped") } } tasks.withType<JavaCompile>().configureEach { options.compilerArgs.add("-Xlint:all") } ``` ## Why not just loop? Writing `tasks.withType<Test> { ... }` or `forEach { }` forces realization of every `Test` task even on invocations that never run tests. On big builds this shows up directly in configuration time on a build scan. `configureEach` defers each task's configuration to the moment it's actually needed. ## Combining with register `configureEach` composes with `register`: a registered-but-unrealized task that matches the type will receive the `configureEach` action *if and when* it's realized. So you get a clean separation — `register` declares the task lazily, `configureEach` declares cross-cutting policy lazily, and neither forces the other to realize. ## Quick reference | Call | Lazy? | Covers future tasks? | |------|-------|----------------------| | `withType<T>().configureEach { }` | yes | yes | | `withType<T>().all { }` | no (eager) | yes | | `withType<T> { }` (block) | no (eager) | yes |
- Does configureEach apply to tasks registered after the configureEach call?Yes. It applies to both already-registered and future matching tasks, running the action lazily when each is realized.
- Is tasks.withType<Test> { } (the block overload) lazy?No. The block overload is eager — equivalent to .all — and realizes all current matching tasks immediately.
- How does configureEach interact with a task registered via register but never run?The action is attached but only fires if the task is realized. If it never runs, the configureEach block never executes for it.
saying these in an interview costs you the question
- Claiming withType<Test> { } and configureEach are equivalent in eagerness.
- Saying configureEach only affects tasks that already exist at the time of the call.