skip to content

Explain precisely how includeFlat resolves a project's directory, and how that resolution differs from include. What is the equivalent you could write by hand?

level: middleimportance: must knowfreq 30%

answer

  1. include -> rootDir/name
  2. includeFlat -> rootDir.parentFile/name
  3. sugar for include + projectDir = file("../name")
  4. logical path unchanged (:name)
  5. directory != project path

basics

~10 s

include resolves the project dir under rootDir (rootDir/name); includeFlat resolves it under rootDir's parent (rootDir/../name). You can reproduce includeFlat by calling include then setting project(':name').projectDir to file('../name').

solid answer

~40 s

`include("name")` gives the project a default `projectDir` of `rootDir/name`. `includeFlat("name")` gives it `rootDir.parentFile/name` — one directory up, then into `name`. Both register the project as a direct child of the root in the build's logical hierarchy (`:name`); the only difference is the on-disk location. Because `includeFlat` is pure sugar, the hand-written equivalent is: ```kotlin include("name") project(":name").projectDir = file("../name") ``` This equivalence matters in interviews because it shows `includeFlat` doesn't change the project model — it only sets a non-default `projectDir`. Anything you can do with `includeFlat` you can do with `include` + an explicit `projectDir`, which is also how you'd handle layouts `includeFlat` can't express (e.g. a sibling two levels up, or a renamed directory).

code

kotlin · 5 lines
kotlin
// settings.gradle.kts
include("shared")
project(":shared").projectDir = file("../shared")
// is exactly equivalent to:
includeFlat("shared")

go deeper

for a junior

Recall the two default directories: rootDir/name vs ../name.

for a middle

State the exact resolution base (rootDir vs rootDir.parentFile) and give the include + projectDir equivalent.

for a senior

Explain why directory and project path are independent, and when to drop to explicit projectDir overrides.

for a principal

Define conventions for projectDir overrides across many teams to keep settings files predictable and reviewable.

## Two resolution rules Gradle builds the project tree from `settings.gradle.kts`. Each registered project gets a `projectDir`. The default depends on which method you used: | Method | Default projectDir | |---|---| | `include("name")` | `rootDir/name` | | `include("a:b")` | `rootDir/a/b` (colons map to nested dirs) | | `includeFlat("name")` | `rootDir.parentFile/name` (`../name`) | `rootDir` is the directory containing the root `settings.gradle.kts`. `includeFlat` walks up one level to `rootDir.parentFile` (the parent folder) and then into `name`. ## The logical path is unaffected A crucial subtlety: **directory != project path**. The project path (`:name`) is determined by how it's registered, not by where its files live. `includeFlat("shared")` produces project path `:shared` — a top-level child of root — even though `shared` is physically a sibling of the root directory. So `settings.findProject(":shared")` works exactly like for an `include`d project. ## Overriding the default Because both methods just set a *default* `projectDir`, you can override it afterwards: ```kotlin include("shared") project(":shared").projectDir = file("../../other/shared") // anything you want ``` This is the escape hatch when `includeFlat` is too restrictive — it only handles a single name resolved against `rootDir.parentFile`. ## Why the distinction trips people up People assume `includeFlat` changes the build's *structure* (e.g. flattens the hierarchy or creates separate builds). It does neither. It only changes one project's `projectDir` default. The 'flat' refers to the on-disk arrangement (projects side-by-side), not the logical project graph. ```kotlin // All three of these create project :shared at ../shared includeFlat("shared") // ---- equivalent ---- include("shared"); project(":shared").projectDir = file("../shared") ```

  • Can includeFlat point two directories up, e.g. ../../shared?
    No. It only resolves a single name against rootDir.parentFile. For anything else use include plus an explicit projectDir = file("../../shared").
  • Does includeFlat change the project's logical path?
    No. The project still has path :name as a direct child of root; only its projectDir differs from the default.
  • How do colons behave in include vs includeFlat?
    include('a:b') maps colons to nested directories (rootDir/a/b) and nested project paths. includeFlat takes a single directory name and always produces a top-level child.

saying these in an interview costs you the question

  • Saying includeFlat flattens or removes the hierarchy.
  • Claiming includeFlat changes the logical project path.
  • Believing includeFlat can express arbitrary relative paths like ../../x.

context