How do you include a subproject whose directory does not match its Gradle path, and when is that useful?
answer
- include(':x') then project(':x').projectDir = file(...)
- default: colon → directory under root
- ProjectDescriptor handle in settings
- path = identity, projectDir = location
- buildFileName for non-standard scripts
basics
~10 sCall include(":logical-name") to register the project, then override its location with project(":logical-name").projectDir = file("actual/path"). This decouples the Gradle project path from the on-disk directory layout.
solid answer
~40 sBy default `include(":app")` maps the Gradle path `:app` to the directory `<root>/app`. When the directory layout differs from the logical path, you relocate after including: `include(":app")` then `project(":app").projectDir = file("subdir/app")`. You can also override `project(":app").buildFileName` if the build script isn't named `build.gradle(.kts)`. This is useful for flat or unconventional layouts — e.g. a legacy repo where modules live under `modules/`, or when you want short logical names (`:web`) but nested physical folders, or when migrating directory structure without renaming Gradle paths (which would break every `project(":web")` reference and dependency declaration). The key idea: the project *path* (`:web`) is the stable identity used in dependencies and task addressing; the *projectDir* is just where its files happen to live.
code
kotlin · 9 lines// settings.gradle.kts
rootProject.name = "legacy-app"
include(":auth")
project(":auth").projectDir = file("modules/authentication-service")
include(":web")
project(":web").projectDir = file("subprojects/web-frontend")
// other projects now depend on project(":auth"), not the long folder namego deeper
Know the default colon-to-directory mapping; awareness that projectDir can override it is a bonus.
Show include + project(...).projectDir, explain the ProjectDescriptor, and give a real use case.
Articulate path-as-identity vs dir-as-location, and how relocation enables low-churn directory migrations.
Define a repo-wide convention for module layout vs logical naming so relocation is consistent and discoverable across teams.
## Default path-to-directory mapping When you write `include(":web:server")`, Gradle derives the directory by replacing each colon with a directory separator relative to the build root: `<root>/web/server`. This convention covers the common case where logical structure mirrors physical layout. ## Breaking the convention with projectDir When they must differ, register the project first, then relocate it: ```kotlin include(":web") project(":web").projectDir = file("subprojects/web-frontend") ``` `project(":web")` returns a `ProjectDescriptor` — a lightweight, init-phase handle to the not-yet-created project. Setting `projectDir` tells Gradle where that project's files (its `build.gradle`, sources) live. You can similarly set: - `buildFileName` — if the build script has a non-standard name. - `name` — though usually you set the name via the `include` path segment. ## When relocation is useful - **Legacy/migrating layouts:** the repo already nests modules under `modules/` or `subprojects/`, but you want clean logical paths like `:auth` instead of `:modules:auth`. - **Short stable identities:** dependencies are declared as `implementation(project(":web"))`. Keeping the path short and stable while the physical folder is long/descriptive keeps build scripts readable and refactors cheap. - **Renaming folders without churn:** you can move a directory on disk and just update one `projectDir` line, instead of renaming the Gradle path (which would force edits to every dependency reference). ## The identity vs. location distinction The crucial mental model: the **project path** (`:web`) is the project's *identity* — it is what other projects reference in `project(":web")`, what the task addressing `:web:build` uses, and what feeds default artifact naming. The **projectDir** is merely *where the bytes live*. Relocation lets you vary the second without disturbing the first. ## Caveats - You must `include` the project before relocating it; `project(":x")` for an un-included path fails. - Relocation happens entirely in the settings script (initialization phase) — you cannot move a project from a build script. - Two projects cannot share the same `projectDir`.
- What object does project(":auth") return inside the settings script?A ProjectDescriptor — a metadata handle for a project that has not been created yet (initialization phase). You can set its projectDir, buildFileName, and name; the actual Project instance is created at the end of initialization.
- Why prefer a short logical path over the physical folder name in dependency declarations?Dependencies use the project path: implementation(project(":auth")). A short, stable path keeps references concise and lets you relocate or rename the on-disk folder with a one-line projectDir change instead of editing every dependency.
saying these in an interview costs you the question
- Saying you must rename the directory to match the Gradle path — projectDir exists precisely to avoid that.
- Trying to relocate a project that was never include()d first.
- Confusing project name/path (identity) with projectDir (location).