skip to content

How does Gradle build the project hierarchy from include() declarations, and how do nested project paths map to directories?

level: middleimportance: should knowfreq 35%

answer

  1. colon path = tree node
  2. :web:api creates :web too
  3. default dir mirrors path
  4. project(":x").projectDir override
  5. rootProject.name = build identity

basics

~10 s

include(":web:api") creates intermediate projects (:web and :web:api), each a Project node under rootProject. By default a project path maps to a matching directory; you can override the location with project(":api").projectDir.

solid answer

~40 s

During initialization, each `include(...)` path becomes a node in the project tree rooted at `rootProject`. A colon-separated path like `:web:api` is hierarchical: Gradle ensures both `:web` and `:web:api` exist, with `:web:api` as a child of `:web`. By **convention**, the project directory mirrors the path — `:web:api` maps to `<root>/web/api`. When your layout differs, override it in settings: ```kotlin include(":api") project(":api").projectDir = file("modules/api-service") ``` The `rootProject.name` is the build's identity (independent of folder name). Each created `Project` is a placeholder during initialization; its `build.gradle.kts` runs later in configuration. You can also rename or relocate the root via `rootProject.projectDir`/`rootProject.buildFileName`. The hierarchy you build here is what `project(":web:api")` references and what configuration walks when evaluating build scripts.

code

kotlin · 8 lines
kotlin
// settings.gradle.kts
rootProject.name = "app"

include(":web:api")   // materializes :web and :web:api
include(":data")

// Relocate :data to a non-conventional folder:
project(":data").projectDir = file("libs/data-access")

go deeper

for a junior

Know include adds a project and that folders usually match the path.

for a middle

Explain colon-path hierarchy, intermediate-project materialization, and overriding projectDir.

for a senior

Discuss stable project paths vs. directory reorganization and rootProject identity.

for a principal

Standardize module layout/path conventions across many teams to keep references stable as repos evolve.

## From include to a tree The project hierarchy is a tree of `Project` objects with a single `rootProject` at the top. Each `include` path adds a node: ```kotlin rootProject.name = "app" include("core") // :core include(":web:api") // creates :web AND :web:api ``` Paths use `:` as the separator. `:web:api` is read as "project `api`, child of project `web`, child of root." If `:web` wasn't explicitly included, Gradle still materializes it as an intermediate (possibly empty) project so the path is valid. ## Path-to-directory mapping By default, the project's directory equals its path translated to folders: - `:core` -> `<root>/core` - `:web:api` -> `<root>/web/api` The leaf segment is also the default project name. When the on-disk layout doesn't match the logical path, override in settings: ```kotlin include(":api") project(":api").projectDir = file("services/api") // optionally: project(":api").buildFileName = "api.gradle.kts" ``` ## rootProject `rootProject.name` defines the build identity and is independent of the root folder name (good for reproducibility — the folder can be checked out under any name). You can also relocate or rename root scripts via `rootProject.projectDir` / `rootProject.buildFileName`, though that's rare. ## What exists after initialization At the end of initialization you have a fully-formed tree of `Project` objects, but **no build script has run**. References like `project(":web:api")` resolve against this tree. Configuration then visits the projects and evaluates each `build.gradle.kts`. Understanding this mapping matters when modules live in non-standard folders or when a project path must be stable even though directories are reorganized.

  • If you include(":web:api") but never include(":web"), does :web exist?
    Yes — Gradle materializes the intermediate :web project automatically so the path is valid, even if it has no build script of its own.
  • How do you point a project at a directory that doesn't match its path?
    Set project(":path").projectDir = file("custom/dir") in settings.gradle.kts.

saying these in an interview costs you the question

  • Assuming rootProject.name must equal the checkout folder name.
  • Thinking a non-standard module folder works without setting projectDir.
  • Believing the build script of an included project runs during initialization.

context