Inside a build, how can you programmatically inspect the full set of tasks Gradle has scheduled, and what should you be careful about when doing so?
answer
- taskGraph.allTasks ordered List<Task>
- valid only after whenReady
- match on .path not .name
- getDependencies for predecessors
- config-cache: capture plain values
basics
~10 sUse gradle.taskGraph.allTasks after the graph is ready (e.g. in whenReady) to get the ordered List<Task> of scheduled tasks. Match on task.path (absolute) for precision in multi-project builds.
solid answer
~40 s`gradle.taskGraph.allTasks` returns the full, dependency-ordered list of scheduled tasks, but it's only valid after the graph is populated — query it inside `gradle.taskGraph.whenReady` or during execution, not during configuration. From there you can compute things like 'is `publish` scheduled?' via `allTasks.any { it.path == ":lib:publish" }`, or inspect ordering and per-task dependencies with `getDependencies(task)`. Cautions: (1) accessing `allTasks` forces the entire graph to be realized — fine in `whenReady`, but don't trigger it prematurely. (2) Use `.path` (absolute, e.g. `:lib:test`) rather than `.name` to disambiguate same-named tasks across subprojects. (3) Under the configuration cache, avoid capturing the live `Task`/`Gradle` references into task actions; read what you need inside the callback and store plain values.
code
kotlin · 9 linesgradle.taskGraph.whenReady {
// ordered list of scheduled tasks
allTasks.forEach { t ->
val deps = getDependencies(t).map { it.path }
logger.lifecycle("\${t.path} <- \${deps}")
}
val willPublish = allTasks.any { it.path == ":lib:publish" }
logger.lifecycle("publish scheduled: \$willPublish")
}go deeper
Know that taskGraph.allTasks lists the scheduled tasks; mainly for logging/inspection.
Use it inside whenReady, match on path, and know getDependencies; avoid premature realization.
Reason about lazy configuration / configuration avoidance and configuration-cache-safe extraction of plain values.
Standardize graph-introspection patterns across shared plugins so they stay configuration-cache compatible and don't defeat configuration avoidance at scale.
## The TaskExecutionGraph API `gradle.taskGraph` is a `TaskExecutionGraph`. Once Gradle has finished configuration and **populated** the graph, it exposes the scheduled plan: - `allTasks: List<Task>` — every scheduled task, in execution order. - `hasTask(path: String)` / `hasTask(task: Task)` — membership checks. - `getDependencies(task: Task): Set<Task>` — the direct graph predecessors of a task (its scheduled dependencies). - `whenReady { ... }` / `addTaskExecutionGraphListener(...)` — hooks fired when the graph is ready. ### Reading the scheduled set ```kotlin gradle.taskGraph.whenReady { val scheduledPaths = allTasks.map { it.path } logger.lifecycle("Scheduled: \n" + scheduledPaths.joinToString("\n")) val publishing = allTasks.any { it.path == ":lib:publish" } if (publishing) logger.lifecycle("Release build detected") } ``` ### Timing: graph must be populated `allTasks` (and `hasTask`) require the graph to exist. Inside the configuration phase, before population, the graph isn't ready — querying it is invalid. The correct, single place to branch on the whole plan is `whenReady`, which Gradle fires exactly once after configuration and before any task executes. ### path vs name In a multi-project build, two subprojects can each have a `test` task. `allTasks` will contain both, distinguished by `path` (`:app:test`, `:lib:test`) while sharing a `name` (`test`). Always match on `path` for correctness. ### Performance / realization caveat Gradle uses **lazy task configuration** (`tasks.register`, `Provider`-based wiring) so tasks aren't configured until needed. Touching `allTasks` realizes the scheduled tasks. That's expected inside `whenReady` (the plan is already fixed), but doing graph-wide introspection eagerly elsewhere can defeat configuration avoidance. ### Configuration cache friendliness The configuration cache forbids capturing certain live objects (the `Gradle` instance, arbitrary `Task` references) into task actions. The safe pattern is to read what you need *inside* the `whenReady` callback and store plain serializable values (strings, booleans), rather than holding `Task` objects to inspect later at execution time. ### Summary `allTasks` is your window into the executed plan; treat it as read-after-ready, key off `path`, and extract plain data for any execution-time use.
- Why match on task.path rather than task.name?In multi-project builds different subprojects can share a task name (e.g. test). The path is absolute (:app:test vs :lib:test) and uniquely identifies the task.
- What does getDependencies return?The set of direct scheduled predecessor tasks of the given task in the graph — useful for understanding why something runs before something else.
- Any configuration-cache concern with allTasks?Yes — don't capture live Task or Gradle references into task actions. Read needed values (paths, booleans) inside whenReady and store plain data.
saying these in an interview costs you the question
- Querying allTasks during configuration before the graph is ready.
- Matching on .name and getting the wrong subproject's task.
- Capturing live Task/Gradle objects into actions under the configuration cache.