How do you make a task in your root build depend on a task in an included build programmatically?
answer
- `gradle.includedBuild("name")`
- .task(":absolute:path")
- returns lazy TaskReference
- path rooted in the included build
- works with dependsOn/finalizedBy/mustRunAfter
basics
~10 sUse the gradle.includedBuild('name') API to get a handle, then resolve the task: dependsOn(gradle.includedBuild("lib").task(":publishToMavenLocal")). The task path is relative to that included build's root.
solid answer
~40 sFrom the CLI you address included-build tasks with a `:name:task` prefix, but **inside build scripts** you wire dependencies through the `Gradle` object. `gradle.includedBuild("lib")` returns an `IncludedBuild` handle; `.task(":path")` returns a `TaskReference` you can pass to `dependsOn`, `finalizedBy`, or `mustRunAfter`: ```kotlin tasks.register("smokeTest") { dependsOn(gradle.includedBuild("lib").task(":publishToMavenLocal")) } ``` The task path passed to `.task()` is **absolute within the included build** — it starts with `:` and is rooted at *that* build's root project, not yours. The returned reference is lazy: Gradle resolves the actual task during configuration of the composite. This is how you orchestrate cross-build ordering (e.g. publish the lib locally before integration-testing the consumer) without publishing to a remote repository.
code
kotlin · 8 lines// build.gradle.kts in the root (consumer) build
tasks.register("integrationTest") {
// ensure the library is published locally first
dependsOn(gradle.includedBuild("lib").task(":publishToMavenLocal"))
doLast {
println("running integration tests against freshly published lib")
}
}go deeper
Recall that there is a gradle.includedBuild(...).task(...) API for cross-build dependencies.
Write the wiring correctly, knowing the path is rooted in the included build and the reference is lazy.
Distinguish orchestration via dependsOn from automatic dependency substitution, and choose the right mechanism.
Set conventions for cross-build orchestration so large composites stay deterministic and avoid hidden ordering coupling.
## Two ways to reach included-build tasks 1. **CLI addressing** — `./gradlew :lib:build` — for humans invoking tasks. 2. **Programmatic addressing** — inside a build script, you cannot use the `:lib:build` string with `tasks.named(...)` because that task lives in a *different* build. Instead Gradle gives you an API on the `Gradle` object. ## The IncludedBuild API The `gradle` property (type `Gradle`) exposes: - `gradle.includedBuilds` — a collection of all `IncludedBuild` handles. - `gradle.includedBuild(name)` — look up one by its build name. An `IncludedBuild` has: - `getName()` — the build name. - `getProjectDir()` — the build's directory. - `task(path)` — returns a `TaskReference` for a task **inside that build**, where `path` is an absolute path *within the included build* (`":sub:task"`). ## Wiring a dependency ```kotlin tasks.register("integrationTest") { dependsOn(gradle.includedBuild("lib").task(":publishToMavenLocal")) } ``` Key points: - The path `":publishToMavenLocal"` is rooted at the **included** build's root project. `":core:test"` would mean the `test` task in the `core` subproject of `lib`. - The reference is **lazy** — it's a `TaskReference`, resolved when the composite's task graph is assembled, so you don't have to worry about configuration ordering between the builds. - The same reference works with `finalizedBy(...)`, `mustRunAfter(...)`, etc. ## When to prefer dependency substitution instead If the goal is "my consumer should compile against the *source* of the included library," you usually rely on Gradle's automatic **dependency substitution** (matching `group:name` coordinates to the included build) rather than a manual task `dependsOn`. Manual `dependsOn` on an included-build task is for **orchestration** — running a publish, a code-gen, or a packaging step in the other build at the right time. (Dependency substitution is a separate concern from task addressing.) ## Gotcha: build name must match `gradle.includedBuild("lib")` throws if no build named `lib` is included. Keep the `name` you set in `includeBuild(...)` consistent with what you reference here.
- Is the path passed to `.task()` rooted in your build or the included build?In the included build. `":core:test"` means the `test` task of the `core` subproject of that included build, regardless of where the calling script lives.
- What happens if you pass a build name that isn't included?`gradle.includedBuild(name)` fails because no matching `IncludedBuild` exists. The name must match what was set (or defaulted) in `includeBuild`.
- Why not just use `tasks.named(":lib:build")`?`tasks` only addresses tasks in the *current* build. The included build is a separate build, so you must go through the `IncludedBuild` handle.
saying these in an interview costs you the question
- Passing a path rooted in the calling build instead of the included build.
- Trying to resolve included-build tasks via the local `tasks` container.
- Eagerly creating tasks across builds and hitting configuration-ordering issues — the API is intentionally lazy.