skip to content

What does customizing a subproject's projectDir in settings.gradle.kts let you do, and how do you set it?

level: juniorimportance: must knowfreq 45%

answer

  1. logical path vs physical dir
  2. project(":lib").projectDir = file(...)
  3. ProjectDescriptor at settings time
  4. include first, then customize
  5. path unchanged, only folder moves

basics

~10 s

It decouples a project's logical path (like ':lib') from its folder on disk. In settings.gradle.kts you write project(":lib").projectDir = file("libs/lib") so ':lib' lives in a different directory than the default ./lib.

solid answer

~30 s

By default Gradle derives a subproject's directory from its logical path: `include(":lib")` expects the folder `./lib`. Sometimes the disk layout you want differs from the project path you want. In `settings.gradle.kts` you can override the location after declaring the project: ```kotlin include(":lib") project(":lib").projectDir = file("libs/lib") ``` Now the logical project `:lib` (used in `dependencies { implementation(project(":lib")) }`) maps to the physical folder `libs/lib`. The `file(...)` path is resolved relative to the settings file's directory (the root). This is the standard knob for keeping clean Gradle paths while organising source on disk however your team prefers.

code

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

include(":lib")
project(":lib").projectDir = file("libs/lib")

// build.gradle.kts (anywhere)
dependencies {
    implementation(project(":lib")) // still referenced by logical path
}

go deeper

for a junior

Know that include(":lib") defaults to folder ./lib and that project(":lib").projectDir = file(...) overrides that folder.

for a middle

Explain the descriptor type, that it's settings-time, and that the logical path is untouched while only the directory changes.

for a senior

Discuss when to decouple (flat paths over grouped folders, migrations) and how file(...) resolves against rootDir.

for a principal

Frame it as a structural governance lever — keeping a stable logical project graph independent of how the monorepo's directories are organised.

## The default mapping: path → directory Every Gradle project has a **logical path** — a colon-separated identifier like `:lib` or `:services:auth` — and a **physical directory** on disk. When you write `include(":lib")` in `settings.gradle.kts`, Gradle by default assumes the project lives in a folder whose location is derived from the path: `:lib` → `<rootDir>/lib`, and `:services:auth` → `<rootDir>/services/auth`. Each path segment becomes a nested directory. ## Why you'd override it The default ties your **build structure** to your **folder structure**. Teams often want them to differ: keep short, flat logical paths (`:lib`) while grouping folders on disk (`libs/lib`), reuse an externally-located directory, or migrate a legacy layout without renaming every reference. The `projectDir` property lets you set the directory explicitly. ## Setting projectDir In `settings.gradle.kts`, after declaring the project, access its descriptor via `project("<path>")` and assign its `projectDir`: ```kotlin include(":lib") project(":lib").projectDir = file("libs/lib") ``` Key facts: - `project(":lib")` returns a `ProjectDescriptor` — a settings-time handle, NOT the `Project` object you get in `build.gradle.kts`. - `projectDir` is of type `File`. Use `file("...")` so the relative path resolves against the settings directory (the root). - You must `include` the project first; `project(...)` looks up an already-declared descriptor. ## What it does and doesn't change Changing `projectDir` changes ONLY where Gradle looks for that project's build file and its default source/output roots. It does NOT change the logical path: dependencies still reference `project(":lib")`, the task path is still `:lib:build`, and `settings.gradle.kts` is still where it's all wired. This separation — logical path stable, physical directory flexible — is the whole point. ## ProjectDescriptor properties The descriptor exposes `name`, `projectDir`, and `buildFileName`, all mutable at settings time. So alongside relocating the directory you can also rename the project or point it at a non-default build file. Everything happens during settings evaluation, before any project is configured.

  • What type is returned by project(":lib") in settings.gradle.kts?
    A ProjectDescriptor — a settings-time handle exposing name, projectDir, and buildFileName. It is not the runtime Project object available in build scripts.
  • Does changing projectDir change the logical path used in dependencies?
    No. The path :lib and task paths like :lib:build are unchanged; only the directory Gradle resolves for that project moves.

Like a URL rewrite: the public address (:lib) stays stable while the file it actually serves can live anywhere on the server (libs/lib).

saying these in an interview costs you the question

  • Thinking projectDir also renames the project's logical path
  • Trying to set projectDir in build.gradle.kts instead of settings.gradle.kts

context