How does include(':services:auth') build out the project tree, and what does it imply about intermediate projects?
answer
- include of deep path creates intermediates
- ':services' auto-created if absent
- intermediates have no build file, no outputs
- subprojects { } iterates over them too
- default mapping :a:b -> a/b directory
basics
~10 sinclude(':services:auth') registers the leaf and implicitly creates any missing intermediate projects on the path (here ':services'). Each segment becomes a project node; intermediate ones exist even without their own build file.
solid answer
~50 sWhen you call `include(":services:auth")` in `settings.gradle.kts`, Gradle parses the colon-separated path and ensures **every** segment exists as a project in the tree. If `:services` was never explicitly included, Gradle creates it implicitly as an intermediate (container) project. So a single `include` of a deep path can materialize several projects: the root, `:services`, and `:services:auth`. Intermediate projects are real `Project` instances — they appear in `subprojects`, can be configured, and map (by default) to the `services/` directory — but they typically have no `build.gradle.kts` of their own and produce no artifacts; they exist purely to give the leaf a place in the hierarchy. This has practical consequences: `subprojects { }` or `allprojects { }` blocks will iterate over those intermediate container projects too, so plugin-applying or dependency-adding logic must tolerate projects that are just structural. By default Gradle maps `:services:auth` to the directory `services/auth`, creating the implied folder structure expectation.
code
kotlin · 7 lines// settings.gradle.kts
rootProject.name = "platform"
include(":services:auth") // creates :services (container) + :services:auth
include(":services:gateway") // shares :services, adds :services:gateway
// :services has a path, a name ('services'), maps to services/ dir,
// is visited by subprojects { }, but typically has no build.gradle.kts.go deeper
Know that include(':services:auth') registers the leaf and that ':services' becomes a project too.
Explain implicit intermediate creation, that they're real but usually empty, and that subprojects { } iterates over them.
Discuss the maintenance hazard of blanket cross-project config over container projects and prefer convention plugins.
Decide hierarchy depth/shape as an architectural concern, balancing logical grouping against the cost of empty container nodes and broad configuration.
## What include actually does `include(path)` in `settings.gradle.kts` takes an absolute project path and **registers each segment** as a project, creating any that don't already exist. For `include(":services:auth")`: 1. Root project `:` already exists. 2. `:services` is created if absent (an **implicit intermediate** project). 3. `:services:auth` is created. You can `include` the deep path directly without a separate `include(":services")` — Gradle fills the gap. ## Intermediate (container) projects These auto-created nodes are full projects: - They appear in `rootProject.subprojects` and are visited by `subprojects { }` / `allprojects { }`. - They have a `projectDir` (default `services/`), a path, and a name. - They usually have **no build file** and produce **no outputs** — they're structural. ```kotlin // settings.gradle.kts include(":services:auth") // creates :services AND :services:auth include(":services:gateway") // reuses existing :services, adds :services:gateway ``` Here `:services` is shared as the common parent of both leaves. ## Why it matters If your root script does: ```kotlin subprojects { apply(plugin = "java-library") } ``` that lambda also runs against `:services`, the empty container. Applying `java-library` to a folder with no sources is harmless but wasteful; more dangerous is logic that assumes every subproject has a `jar` or a specific source set. Guard such logic (e.g. check for a build file or use convention plugins applied per-leaf) rather than blanket `subprojects`. ## Default directory mapping By default `:services:auth` → `services/auth` relative to the root. Gradle does **not** require `services/` to contain a build file, but it does expect the directory to exist for the leaf. You can override with `project(":services:auth").projectDir = file("...")` if the physical layout differs from the logical path. ## Takeaway A hierarchical `include` is a convenience that builds the whole path; just remember the intermediate nodes are real, configurable, and iterated over — design cross-project configuration with that in mind.
- Do you need a separate include(':services') if you already include(':services:auth')?No. Including the deep path implicitly creates the intermediate :services project. A separate include is only needed if you want it for clarity; it's idempotent.
- Why can a blanket subprojects { apply(plugin = ...) } cause surprises with deep paths?Because auto-created intermediate container projects are visited too. Logic assuming each subproject has sources/outputs may misbehave; prefer convention plugins applied per leaf.
saying these in an interview costs you the question
- Believing intermediate projects don't exist unless explicitly included — they're auto-created.
- Assuming every subproject visited by subprojects { } is a buildable module with outputs.