You wrote a custom task that packages a distribution. How do you make `gradle build` run it, and where should it hook?
answer
- artifact -> assemble
- verification -> check
- build = assemble + check
- use tasks.named (lazy)
- archives configuration also wires assemble
basics
~10 sMake assemble depend on your packaging task: tasks.named("assemble") { dependsOn(myTask) }. Since build depends on assemble, gradle build will then run it.
solid answer
~40 sHook it into the correct lifecycle task by **type of work**. A packaging/artifact task produces output, so wire it into `assemble`: `tasks.named("assemble") { dependsOn(packageDist) }`. Because `build` depends on `assemble`, running `gradle build` (or `gradle assemble`) now runs your task. Conversely, a *verification* task (a custom lint, a schema validator) should hook into `check`, not `assemble`. The key rule is: never hardcode `myTask.dependsOn` chains that bypass the lifecycle — always attach to the standard lifecycle task so your work participates in the conventional `build` invocation. Prefer `tasks.named(...)` (lazy, configuration-avoidance) over `tasks.getByName(...)` so the lifecycle task isn't eagerly realized. For artifact tasks specifically you can alternatively publish to the `archives` configuration via `artifacts { archives(packageDist) }`, which also wires it into `assemble`.
code
kotlin · 7 linesval packageDist by tasks.registering(Zip::class) {
from(layout.buildDirectory.dir("staged"))
archiveFileName.set("dist.zip")
}
// Hook the producer into the lifecycle, lazily:
tasks.named("assemble") { dependsOn(packageDist) }go deeper
Knowing tasks.named("assemble") { dependsOn(myTask) } makes build run it is enough.
Distinguish assemble vs check by work type and use lazy named. Mention build = assemble + check.
Bring up configuration avoidance, the archives-configuration alternative, and why convention participation matters for CI.
Frame lifecycle hooking as the contract that keeps gradle build uniform org-wide; design custom plugins to attach correctly rather than expose bespoke entry points.
## The decision: which lifecycle task? The base/lifecycle-base plugins give you three umbrella tasks to attach to. Pick by the nature of the work: - Produces an artifact/output (zip, jar, docker image, generated site) -> **`assemble`**. - Verifies something (tests, linters, format checks, schema validation) -> **`check`**. - Need it for both -> attach to both, or rely on `build` (which depends on both). Do **not** invent your own top-level task and tell users to run it; participate in the convention so `gradle build` Just Works. ## The idiomatic wiring ```kotlin val packageDist by tasks.registering(Zip::class) { from(layout.buildDirectory.dir("staged")) archiveFileName.set("dist.zip") destinationDirectory.set(layout.buildDirectory.dir("distributions")) } tasks.named("assemble") { dependsOn(packageDist) } ``` `tasks.named` returns a `TaskProvider` and does **not** realize the task until it's needed — this is *configuration avoidance*, important for build performance. Using `tasks.getByName("assemble")` would eagerly create/configure the task at configuration time. ## Alternative: the `archives` configuration The `base` plugin's `archives` configuration is the legacy hook. Adding an artifact to it also makes `assemble` depend on the producing task: ```kotlin artifacts { add("archives", packageDist) } ``` Modern builds usually prefer the explicit `dependsOn` on `assemble`, but the `archives` route additionally exposes the artifact for consumption/publishing. ## Verification example ```kotlin val validateSchema by tasks.registering { /* ... */ } tasks.named("check") { dependsOn(validateSchema) } ``` Now `gradle check` and `gradle build` both run your validation. ## Why this matters Consistent hooking is what lets CI pipelines just call `gradle build` regardless of project specifics. A task that's only runnable by its own name is invisible to the convention and frequently gets skipped in CI.
- Why prefer `tasks.named` over `tasks.getByName`?`named` returns a TaskProvider and defers realization (configuration avoidance), so the lifecycle task isn't eagerly configured. `getByName` forces it at configuration time, hurting performance.
- Where would you hook a custom code-coverage verification task?Into `check`, since coverage gating is verification, not artifact production. Then `gradle build` runs it via build -> check.
- What does adding the task to the `archives` configuration give you beyond `dependsOn`?It also exposes the produced artifact for consumption/publishing, while still making `assemble` depend on the producing task.
saying these in an interview costs you the question
- Hooking an artifact task into `check` (or a verification task into `assemble`).
- Using `tasks.getByName` and realizing tasks eagerly when `named` would do.
- Creating a standalone top-level task that bypasses the lifecycle entirely.