skip to content

With configuration on demand enabled, how does Gradle decide which projects to configure for a given set of requested tasks?

level: middleimportance: must knowfreq 30%

answer

  1. root always configured
  2. task owners + project deps (transitive)
  3. explicit cross-project dependsOn pulls project in
  4. allprojects/subprojects side effects skipped
  5. reachability over declared graph

basics

~10 s

Gradle always configures the root project, plus the projects that own the requested tasks, plus any projects reachable through project dependencies. Unrelated subprojects are skipped.

solid answer

~40 s

Configure-on-demand evaluates projects **lazily** rather than configuring all of them up front. The configured set is: the **root project** (always), the **projects that own the requested tasks** (e.g. `:app` for `:app:assemble`), and any projects reachable via **project dependencies** declared in those projects (`implementation(project(":core"))` brings in `:core`, transitively pulling in `:core`'s own project dependencies). Tasks declared with explicit cross-project `dependsOn` on another project's task also force that project to be configured. Everything outside this reachable set is left unconfigured, which is exactly where the time savings come from. The catch is that **implicit** cross-project wiring — `allprojects {}`, `subprojects {}`, or one project mutating another by reaching into `project(":other")` — isn't part of this reachability analysis, so those side effects may not run, producing an incomplete or incorrect task graph.

code

kotlin · 9 lines
kotlin
// app/build.gradle.kts
dependencies {
    implementation(project(":core")) // configures :core under configure-on-demand
}

// a cross-project task dependency also forces configuration of :other
tasks.register("pack") {
    dependsOn(":other:jar") // :other gets configured so :jar exists
}

go deeper

for a junior

State that root, the task-owning projects, and their project dependencies get configured.

for a middle

Explain the transitive reachability walk and that explicit cross-project task dependencies also pull projects in.

for a senior

Articulate why imperative cross-project configuration breaks the reachability assumption and how convention plugins / isolation fix it.

for a principal

Connect to a monorepo strategy: enforce self-contained project configuration so configure-on-demand (and Project Isolation / configuration cache) stay correct.

## The reachability set Under configuration on demand, Gradle starts from the requested tasks and configures **only** the projects it can reach through *declared* relationships: 1. **Root project** — always configured, because root scripts often define shared conventions. 2. **Owning projects of requested tasks** — `gradle :a:build :b:test` configures `:a` and `:b`. 3. **Project dependencies** — if `:a` declares `implementation(project(":lib"))`, then `:lib` is configured; if `:lib` in turn depends on `:base`, `:base` is configured too. This is a transitive walk over the project dependency graph. 4. **Explicit cross-project task dependencies** — `tasks.named("x") { dependsOn(":other:y") }` forces `:other` to be configured so task `y` exists. ## What is NOT pulled in - Projects you didn't request and that nothing in the reachable set depends on. - Side effects from **cross-project configuration blocks**: `allprojects {}`, `subprojects {}`, or a project that imperatively reaches into another (`project(":other").extensions…`, `rootProject.subprojects.forEach { … }`). Because the *source* project of that side effect may itself never be configured, the mutation never happens. ## Worked example ```kotlin // settings.gradle.kts include(":app", ":core", ":util", ":reports") // app/build.gradle.kts dependencies { implementation(project(":core")) } // core/build.gradle.kts dependencies { implementation(project(":util")) } ``` Running `gradle :app:assemble --configure-on-demand` configures: **root, :app, :core, :util**. `:reports` is **skipped** — nothing reachable from `:app` references it. If `:reports` had a `subprojects {}` block in root that other projects relied on, you'd have a problem only if that wiring lived in an unconfigured project; root itself is always configured, so root-level `subprojects {}` is a particularly dangerous pattern here. ## Why cross-project config breaks it The whole optimization assumes a project's contribution to the graph is fully captured by its **own** script plus its **declared** dependencies. Imperative cross-project configuration violates that assumption: project A's script changing project B means B's final state depends on whether A was configured — which configure-on-demand doesn't guarantee. This is precisely why Gradle pushed toward **convention plugins** and isolated project models (and later **Project Isolation**), where each project's configuration is self-contained. ## Verifying Use `--info` and inspect which projects Gradle reports configuring; compare against your expectation. A surprising 'all projects configured' usually means an `allprojects/subprojects` block (often in root) is forcing eager evaluation.

  • Why can an `allprojects {}` block in the root script defeat the benefit of configure-on-demand?
    Such a block typically forces Gradle to touch every project to apply the shared configuration, so the reachable set effectively becomes all projects and nothing is skipped — and it can also break correctness if the wiring isn't captured by declared dependencies.
  • Does a `testImplementation(project(":testfixtures"))` dependency get the fixtures project configured?
    Yes — any project dependency in a configured project pulls the target project into the configured set, regardless of which configuration declared it.

saying these in an interview costs you the question

  • Saying only the single requested project is configured (ignores project dependencies and the root).
  • Claiming cross-project `allprojects {}`/`subprojects {}` side effects are honored — they often are not.
  • Assuming an arbitrary `project(":x")` lookup is enough to guarantee `:x` is configured.

context