How would you make build logic run only when a specific task (say `test`) is actually part of the current build, and why not just check the command-line arguments?
answer
- taskGraph.hasTask(path) in whenReady
- args miss transitive/abbreviated/default tasks
- use absolute path :project:task
- graph must be ready before querying
- allTasks for the full set
basics
~10 sUse gradle.taskGraph.whenReady { if (hasTask(":test")) { ... } }. Checking command-line args fails because tasks like test are often pulled in transitively (by build, check), not typed directly.
solid answer
~40 sThe robust approach is `gradle.taskGraph.hasTask(path)` evaluated after the graph is built. Register a `whenReady` callback (or call `hasTask` later), then branch on whether the task is in the scheduled set: ```kotlin gradle.taskGraph.whenReady { if (hasTask(":app:test")) { // enable extra config only when tests will run } } ``` Inspecting `startParameter.taskNames` is fragile: a user running `gradle build` never types `test`, yet `test` is scheduled. The string list also misses task-name abbreviations (`gradle b` → `build`), default tasks, and tasks added via `finalizedBy`/`dependsOn` in other plugins. `hasTask` works on the *resolved* graph, so it sees the true scheduled set regardless of how a task got there. Use the full path (`:project:taskName`) in multi-project builds to avoid ambiguity.
code
kotlin · 11 lines// build.gradle.kts (root)
gradle.taskGraph.whenReady {
if (hasTask(":app:test")) {
// configure something only when tests are actually scheduled
tasks.named("test") {
(this as Test).systemProperty("extra.reporting", "true")
}
} else {
logger.lifecycle("No test task in this build — skipping reporting setup")
}
}go deeper
Know that you can ask the task graph whether a task is scheduled, rather than parsing the command line.
Use gradle.taskGraph.whenReady + hasTask(absolutePath); explain the failure modes of checking startParameter.taskNames.
Discuss timing (graph must be ready), absolute paths, allTasks/getDependencies, and configuration-cache-friendly capture.
Set conventions for plugins so cross-cutting conditional logic keys off the resolved graph; avoid coupling shared build logic to command-line strings across many modules.
## The problem: 'do X only if task Y will run' A common build-logic need is conditional configuration: enable verbose reporting only when `test` runs, skip an expensive setup unless `publish` is scheduled, etc. There are two tempting sources of truth, and only one is correct. ### Wrong source: the command line `gradle.startParameter.taskNames` gives you the literal strings the user typed. This breaks in many ordinary cases: - **Transitive tasks**: `gradle build` schedules `test` but the string list contains only `build`. - **Abbreviations**: Gradle accepts camel-case abbreviations — `gradle cT` may map to `compileTestJava`. The raw string is `cT`, not the resolved name. - **Default tasks** declared via `defaultTasks(...)` — nothing is typed at all. - **Plugin-injected tasks** via `dependsOn`, `finalizedBy`, lifecycle wiring. String-matching `taskNames` therefore gives false negatives constantly. ### Right source: the resolved task graph `gradle.taskGraph` is the `TaskExecutionGraph`. After Gradle finishes configuration it *populates* the graph, then fires `whenReady`. Inside (or any time after population) you can query: - `hasTask(path: String)` — is a task with this path scheduled? Use the fully-qualified path `:project:taskName`. - `hasTask(task: Task)` — same check by instance. - `allTasks: List<Task>` — the full ordered scheduled set. - `getDependencies(task)` — direct graph predecessors of a task. ```kotlin gradle.taskGraph.whenReady { val willRunTests = hasTask(":app:test") val willPublish = allTasks.any { it.name == "publish" } tasks.named("compileJava") { // mutate config that depends on whether tests/publish are scheduled } } ``` ### Timing matters `hasTask` is only meaningful **after** the graph is ready — querying it during configuration (before population) throws or gives wrong answers. `whenReady` is the safe hook; it runs once, after configuration, before execution. (Configuration-cache caveat: prefer querying via the graph inside this callback rather than capturing the whole `gradle` object into a task action.) ### Path correctness In a multi-project build, `hasTask("test")` without a leading colon is ambiguous/relative. Always pass an absolute path like `:app:test` so the check resolves to exactly the task you mean. `allTasks` entries expose `.path` (absolute) vs `.name` (short) — match on `.path` to be precise. ### Summary React to the *scheduled* graph, never the *requested* strings. `hasTask(path)` inside `whenReady` is the idiomatic, correct primitive.
- Why is matching against startParameter.taskNames unreliable?It only sees the literal strings typed. It misses transitively-pulled tasks (build → test), name abbreviations, defaultTasks, and tasks added by other plugins via dependsOn/finalizedBy.
- When is it safe to call hasTask?After the graph is populated — i.e. inside gradle.taskGraph.whenReady or later, during the execution phase. Calling it during configuration before population is wrong.
- Why use the path :app:test instead of test?In multi-project builds a bare name is ambiguous. The absolute path uniquely identifies the task in the correct subproject.
saying these in an interview costs you the question
- Recommending string-matching gradle.startParameter.taskNames for conditional logic.
- Calling hasTask during configuration before the graph is ready.
- Using bare task names in multi-project builds where paths are ambiguous.