skip to content

How can build logic make decisions based on whether a particular task will run, and why must this be done at the right phase?

level: seniorimportance: nice to knowfreq 25%

answer

  1. taskGraph.whenReady { graph -> }
  2. fires after resolution, before execution
  3. graph.hasTask / allTasks
  4. still runs under --dry-run
  5. read the plan, don't mutate edges here

basics

~10 s

Use gradle.taskGraph.whenReady { graph -> } (or graph.hasTask(...)). It fires after the full task graph is resolved but before execution, so logic can branch on whether, say, a release task is in the plan.

solid answer

~50 s

Register a callback with `gradle.taskGraph.whenReady { graph -> ... }`. It runs **once the complete task graph is resolved** — end of configuration, before any task executes — so `graph.hasTask(":publish")` or iterating `graph.allTasks` reliably reflects the *entire* plan. This is the correct place to make plan-dependent decisions: e.g. only require a signing credential when a publishing task is actually scheduled, or tighten settings for a release graph. Doing the check too early (during configuration of an individual task) is unreliable because the rest of the graph may not be built yet. Note that `whenReady` runs even with `--dry-run` (the graph is still resolved), so guard against side effects you don't want in a preview. Avoid the anti-pattern of *reacting* to the graph by mutating task wiring at this point — the plan is already fixed; use it to set values/flags, not to add dependencies.

code

kotlin · 5 lines
kotlin
gradle.taskGraph.whenReady {
    if (hasTask(":publish") && System.getenv("SIGNING_KEY") == null) {
        throw GradleException("Refusing to publish without SIGNING_KEY")
    }
}

go deeper

for a junior

Know that taskGraph.whenReady lets you check whether a task will run before execution.

for a middle

Explain the phase guarantee (after resolution, before execution) and hasTask/allTasks usage.

for a senior

Cover dry-run firing, immutability of the plan at that point, and preferring lazy wiring over imperative mutation.

for a principal

Set conventions for plan-aware build logic (release gating, credential checks) without coupling or graph mutation, and keep it configuration-cache compatible.

## The hook `gradle.taskGraph` is the `TaskExecutionGraph`. Its `whenReady { graph -> }` callback fires exactly when graph resolution completes — **after configuration, before execution**. At that point the plan is final, so queries are trustworthy: - `graph.hasTask(":app:publish")` — is this task in the plan? - `graph.allTasks` — the full ordered list. ## Why phase matters The task graph is only complete at the end of configuration. If you ask 'will publish run?' while configuring some unrelated task, the answer may be wrong because publish (or its dependency edges) may not be registered yet. `whenReady` guarantees the **whole** graph exists. ## Typical uses - **Conditional requirements**: only fail-fast on a missing signing key if a signing/publishing task is actually in the plan. - **Mode flags**: detect a 'release' plan and flip a project-wide flag read by other tasks' inputs. ## Pitfalls - It **still fires under `--dry-run`**, since the graph is resolved either way — keep side effects idempotent/preview-safe. - The plan is **immutable** here: this is for *reading* the graph and setting values, not for adding `dependsOn` edges (too late, and it fights the model). - Prefer lazy `Provider`/`Property` wiring for inputs over imperative `whenReady` mutation where possible. ```kotlin gradle.taskGraph.whenReady { val publishing = hasTask(":publish") if (publishing && System.getenv("SIGNING_KEY") == null) { throw GradleException("Refusing to publish without SIGNING_KEY") } } ```

  • Why is whenReady safer than checking hasTask during a task's configuration closure?
    Because the full graph isn't built until configuration ends; querying earlier may miss tasks or edges not yet registered. whenReady guarantees the complete plan.
  • Does whenReady fire during a --dry-run?
    Yes — the graph is still resolved under --dry-run, so the callback runs; keep its side effects preview-safe.

saying these in an interview costs you the question

  • Adding dependsOn edges inside whenReady (the plan is already fixed)
  • Assuming whenReady is skipped during --dry-run
  • Querying hasTask too early and trusting an incomplete graph

context