skip to content

Flat Layouts with includeFlat

includeFlat for pulling in sibling directories that live outside the root tree, and how its path resolution differs from include. A legacy-shaped question that surfaces around multi-repository checkouts.

on this pageshow

questions

5

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

open as a page

After includeFlat('shared'), how do you reference that project as a dependency from another module, and does its sibling directory location change the dependency syntax?

level: juniorimportance: should knowfreq 18%

basics

~10 s

You reference it by its project path, project(':shared'), exactly like any included project. The sibling directory on disk does not change the dependency syntax — only its projectDir differs.

open as a page

What does includeFlat('sibling-project') do in a Gradle settings file, and how does the resulting project layout differ from a normal include?

level: middleimportance: should knowfreq 35%

basics

~10 s

includeFlat adds a project that lives in a sibling directory next to the root project (../sibling), instead of beneath the root like include does.

open as a page

A teammate added includeFlat('common') and now the build fails on a fresh CI checkout with 'project directory does not exist'. What is going on and how do you fix or harden it?

level: seniorimportance: should knowfreq 20%

basics

~20 s

includeFlat expects ../common to exist next to the root. On CI only the root repo was cloned, so the sibling is missing. Fix by checking out the sibling beside the root, or restructure to include/composite build.

open as a page

When would you choose includeFlat over a hierarchical include layout or a composite build (includeBuild)? What are the trade-offs?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Use includeFlat when projects are checked out side-by-side as separate folders but must share one build. Use hierarchical include for a single repo, and composite builds (includeBuild) for independent builds with their own settings.

open as a page