Walk through how `gradle.taskGraph.hasTask('publish')` works in a conditional signing predicate and why the task graph readiness matters.
answer
- init → configuration → execution
- taskGraph populated end of configuration
- whenReady { } fires at the boundary
- hasTask catches transitively-pulled publish
- eager read during config => 'graph not populated' error
basics
~20 sgradle.taskGraph is the resolved DAG of tasks Gradle will run. hasTask('publish') returns true only when a publish task is scheduled. The graph is built after configuration, so the predicate must be read lazily at execution time, not during configuration.
solid answer
~40 sGradle has three phases: initialization, configuration, and execution. The **task execution graph** (`gradle.taskGraph`, a `TaskExecutionGraph`) is fully populated only **after** configuration, just before execution. `hasTask('publish')` (or `hasTask(somePublishTaskProvider)`) asks whether a matching task is in that resolved graph — letting you require signatures only when you're actually publishing, not on a plain `build`. The catch is timing: if you query the graph during configuration it isn't ready and Gradle throws. The signing plugin sidesteps this by treating `isRequired` as a lazily-evaluated condition resolved when the `Sign` task runs, so the standard `signing { isRequired = gradle.taskGraph.hasTask("publish") }` idiom is safe. If you need the value earlier yourself, hook `gradle.taskGraph.whenReady { ... }`.
code
kotlin · 5 linesgradle.taskGraph.whenReady {
val publishing = hasTask(":publish")
val release = !version.toString().endsWith("-SNAPSHOT")
tasks.withType<Sign>().configureEach { isRequired = publishing && release }
}go deeper
Know that the predicate is true only when a publish task will run, so signing is required only for publishes.
Explain the three phases, when the graph is populated, and the whenReady boundary; contrast with startParameter.
Discuss eager-vs-lazy evaluation, the 'graph not populated' error, and configuration-cache caveats.
Advise teams to prefer graph-independent conditions for config-cache compatibility and to standardize the idiom in a convention plugin.
## The three phases and where the graph lives 1. **Initialization** — settings evaluated, projects determined. 2. **Configuration** — every project's build script runs; tasks are *registered/configured* but not executed. The full set of tasks to run isn't known yet. 3. **Execution** — Gradle resolves which requested tasks plus their dependencies form the **task execution graph** (a DAG), then runs them in order. `gradle.taskGraph` is the `TaskExecutionGraph`. It is only populated at the **end of configuration / start of execution**. `whenReady { }` fires exactly at that boundary. ## What `hasTask` checks `taskGraph.hasTask("publish")` returns true if a task whose path matches is in the resolved graph. So: - `./gradlew build` → graph has no `publish` → predicate false → signing not required. - `./gradlew publish` → graph contains `publish` (and its dependencies) → predicate true. This is strictly better than checking `gradle.startParameter.taskNames`, because the latter only lists what the user typed, missing cases where `publish` is pulled in transitively as a dependency of another task. ## Why readiness matters If you evaluate `gradle.taskGraph.hasTask(...)` **eagerly during configuration**, the graph isn't built yet and Gradle raises an error like 'Task information is not available, as this task execution graph has not been populated.' Two safe patterns: ```kotlin // Pattern A: signing plugin evaluates `isRequired` lazily, so this is fine signing { isRequired = gradle.taskGraph.hasTask("publish") && !version.toString().endsWith("-SNAPSHOT") } // Pattern B: explicit, if you need the value yourself gradle.taskGraph.whenReady { val isRelease = hasTask(":publish") && !version.toString().endsWith("-SNAPSHOT") // configure something based on isRelease } ``` Pattern A works because the signing extension stores the expression behind a provider/closure that it only resolves when the `Sign` task actually executes — by then the graph is ready. ## Subtlety: task name vs. task path `hasTask("publish")` matches by simple name across the build in older APIs, but the overload taking a `Task`/path (e.g. `:publish` or a `TaskProvider`) is unambiguous and configuration-cache friendlier. Prefer passing the concrete task when you can.
- Why is `taskGraph.hasTask` better than checking `gradle.startParameter.taskNames`?startParameter only reflects what the user typed on the command line. hasTask inspects the fully resolved graph, so it also catches a publish task pulled in as a transitive dependency of another requested task.
- When exactly does `gradle.taskGraph.whenReady` fire?At the boundary between configuration and execution, once the full execution graph has been computed but before any task runs.
- Does this pattern play well with the configuration cache?Reading the task graph at configuration time is discouraged under the configuration cache; prefer task-graph-independent conditions (e.g. a property/version check) or the lazily-resolved signing idiom, and avoid whenReady blocks that capture mutable state.
saying these in an interview costs you the question
- Claiming the task graph is available during configuration.
- Equating hasTask with reading the command-line task names — it inspects the resolved DAG, including transitive tasks.