skip to content

Addressing Tasks in Included Builds

Invoking and depending on tasks that live in an included build, from the command line and from a task definition. Interviewers ask because cross-build task wiring is the part of composites people never stumble onto by accident.

on this pageshow

questions

5

How do you run a task that lives in an included build from the command line in a Gradle composite build?

level: juniorimportance: must knowfreq 55%

answer

  1. `:buildName:task` prefix
  2. build name = dir name unless overridden
  3. name set in includeBuild { name = ... }
  4. no special CLI flag
  5. root `build` skips included builds

basics

~10 s

Prefix the task path with the included build's name: ./gradlew :includedBuildName:some:task. The leading segment is the build name (its directory name unless overridden), not a project in the root build.

solid answer

~40 s

A composite build wires another standalone Gradle build into the main build via `includeBuild`. To invoke a task that physically lives inside that included build from the CLI, you address it with the included build's **name** as the first path segment: `./gradlew :my-lib:build` or `./gradlew :my-lib:sub:test`. The build name defaults to the included build's root directory name but can be set with `includeBuild('../my-lib') { name = 'lib' }`. This differs from ordinary project paths in the same build, which start with `:` and reference subprojects of the *current* build. Note that only the **root** of the composite (the build doing the including) exposes this addressing; you generally run from there. Use `./gradlew :build --include-build` is not needed — inclusion is configured in `settings.gradle(.kts)`.

code

bash · 8 lines
bash
# settings.gradle.kts of root build contains:
#   includeBuild("../my-lib") { name = "lib" }

# run a task in the included build 'lib'
./gradlew :lib:build

# run a task in a subproject of the included build
./gradlew :lib:core:test

go deeper

for a junior

Recall the :buildName:task syntax and that the name defaults to the directory name.

for a middle

Explain that included builds keep their own project tree and that root build does not cascade into them.

for a senior

Contrast build-name addressing with ordinary project paths and explain on-demand execution of included-build tasks.

for a principal

Discuss how this addressing underpins develop-together workflows and how naming/conventions keep large composites navigable.

## What an included build is A **composite build** is a build that includes other, otherwise-independent Gradle builds. You configure it in `settings.gradle(.kts)` of the *including* (root) build: ```kotlin // settings.gradle.kts includeBuild("../my-lib") ``` Each included build is a complete Gradle build with its own `settings.gradle(.kts)`, its own subprojects, and its own task graph. It is **not** flattened into the root build's project tree — it keeps its own identity. ## Build name vs. project path Every build in a composite has a **name**. By default the name is the included build's root directory name (`my-lib` above). You can override it: ```kotlin includeBuild("../my-lib") { name = "lib" } ``` Within a single build, task paths are `:subproject:task` (a leading `:` is the build's root project). In a **composite**, you reach into an included build by prefixing with that build's name: ``` :<buildName>:<subproject-path>:<task> ``` So `:lib:build` runs the `build` task of the root project of the included build named `lib`, and `:lib:core:test` runs `test` in its `core` subproject. ## Running it From the root build directory: ```bash ./gradlew :lib:build ./gradlew :lib:core:test ``` There is no extra CLI flag — the inclusion is already declared in settings, so Gradle resolves the leading segment as a build name. If you omit the build prefix (`./gradlew build`), Gradle runs the task only in the **root** build (and its subprojects), *not* in included builds — included-build tasks are run on demand, when addressed or when something depends on them. ## Why this matters Composite builds let you develop a library and its consumer together without publishing to a repository. Being able to address the library's tasks (`:lib:publishToMavenLocal`, `:lib:test`) directly from the consumer's command line is the everyday workflow.

  • What determines the build name used in the task path?
    By default it is the included build's root directory name. You can override it in the `includeBuild(...) { name = "..." }` configuration block in settings.
  • If you run `./gradlew build` at the root, do included builds' `build` tasks run?
    No. Included-build tasks only execute when explicitly addressed (`:lib:build`) or when a root-build task depends on them. A bare `build` runs only the root build and its subprojects.

saying these in an interview costs you the question

  • Thinking the leading segment is a subproject of the root build rather than a separate build's name.
  • Believing `./gradlew build` automatically runs all included builds' equivalent tasks.

context

open as a page

How do you make a task in your root build depend on a task in an included build programmatically?

level: middleimportance: must knowfreq 50%

basics

~10 s

Use 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.

open as a page

How does Gradle disambiguate task addressing when the root build and an included build both have a project or task with the same name?

level: middleimportance: should knowfreq 35%

basics

~20 s

The first path segment is the build name. :lib:app:run targets the included build lib; :app:run targets the root build's app subproject. The build-name prefix removes ambiguity, so equal subproject names in different builds don't clash.

open as a page

How would you iterate over all included builds and wire a common task across each of them, for example an aggregate `cleanAll`?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Iterate gradle.includedBuilds, and for each handle call .task(":clean"), adding it to an aggregate task: tasks.register("cleanAll") { dependsOn(gradle.includedBuilds.map { it.task(":clean") }) }.

open as a page

Why can you address tasks in an included build but not in `buildSrc`, and what does that imply about how composites expose tasks?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

buildSrc is an implicit, automatically-built helper that contributes classes/plugins — its tasks aren't part of the addressable composite graph. An included build is a first-class member of the composite, so its tasks are reachable via :buildName:task and gradle.includedBuild(...).

open as a page