Why can you address tasks in an included build but not in `buildSrc`, and what does that imply about how composites expose tasks?
answer
- buildSrc = implicit classpath build
- included build = named composite member
- only included builds are addressable
- convert buildSrc → includeBuild(build-logic)
- addressability is the dividing line
basics
~20 sbuildSrc is an implicit, automatically-built helper that contributes classes/plugins — its tasks aren't part of the addressable composite graph. An included build is a first-class member of the composite, so its tasks are reachable via :buildName:task and gradle.includedBuild(...).
solid answer
~40 sBoth `buildSrc` and `includeBuild` pull external Gradle code into your build, but they participate differently. `buildSrc` is a **special, implicit** build that Gradle compiles *before* everything else and puts on the build-script classpath; you never address its tasks (`:buildSrc:test` isn't how you run them) — it's an implementation detail. An **included build** is a genuine member of the **composite**: it has a build name, its tasks are addressable from the CLI (`:lib:build`) and programmatically (`gradle.includedBuild("lib").task(":build")`), and you can wire cross-build dependencies to them. The modern guidance even is to convert `buildSrc` into an included build (`includeBuild("build-logic")`) precisely so its tasks become addressable, independently testable, and not rebuilt-on-every-invocation. So 'addressability' is the dividing line: composites expose included builds' task graphs as named, reachable nodes; `buildSrc` deliberately does not.
code
kotlin · 4 lines// settings.gradle.kts — make build logic addressable
includeBuild("build-logic")
// now you can run: ./gradlew :build-logic:test
// and wire: dependsOn(gradle.includedBuild("build-logic").task(":check"))go deeper
Know buildSrc is automatic and its tasks aren't addressed like an included build's.
Explain that included builds are named composite members with reachable task graphs, unlike buildSrc.
Articulate the addressability contract and why converting buildSrc to an included build unlocks it.
Decide org-wide whether build logic lives in buildSrc or an included build-logic, factoring testability, caching, and addressability.
## Two ways to share build logic - **`buildSrc/`** — a conventionally-named directory at the root. Gradle *automatically* treats it as a build, compiles it first, and adds its output to the build-script classpath of every project. It exists to hold plugins, conventions, and helper classes. - **`includeBuild(...)`** — explicitly wires a standalone build into a **composite build**. ## The addressability difference The key distinction for *task addressing*: | Aspect | `buildSrc` | Included build | |---|---|---| | Has a build name you address by | No (implicit) | Yes (`lib`, default = dir name) | | CLI: `:name:task` | Not the intended interface | `./gradlew :lib:build` | | Programmatic: `gradle.includedBuild(name)` | Not listed | Yes, returns an `IncludedBuild` | | Rebuilt | On essentially every invocation | Only when its inputs change / when addressed | `buildSrc` is an **implementation detail of the build's classpath**, not a participant whose tasks you orchestrate. Its tasks run as part of building the classpath, not as addressable nodes you depend on with `gradle.includedBuild(...)`. ## Why convert buildSrc → included build Because an included build's tasks *are* addressable, you gain: ```kotlin // settings.gradle.kts includeBuild("build-logic") ``` ```bash ./gradlew :build-logic:test # run the convention plugins' tests directly ./gradlew :build-logic:check ``` That is impossible with `buildSrc`, where you can't cleanly target `:buildSrc:test` as part of your normal task addressing. You also avoid `buildSrc`'s tendency to invalidate caches and be reconfigured frequently. ## Implication for composites A composite build's contract is: **each included build contributes a named, fully-addressable task graph**. That's what makes `:lib:publishToMavenLocal` and `gradle.includedBuild("lib").task(":publishToMavenLocal")` work. `buildSrc` opts out of that contract by design — it's there to *provide build logic*, not to be *driven* as a build. ## Nuance This isn't about capability of the code inside; both can host plugins. It's specifically about whether the build's **tasks are exposed as reachable composite nodes**. Only included builds are.
- Practically, why do teams migrate from `buildSrc` to an included `build-logic`?To make the build logic's own tasks (tests, checks) addressable and independently runnable, to avoid frequent reconfiguration/cache invalidation, and to gain composite-style orchestration.
- Does `gradle.includedBuilds` ever contain `buildSrc`?No. `buildSrc` is an implicit special build, not part of the included-builds collection, so it isn't addressable through that API.
saying these in an interview costs you the question
- Claiming you address `buildSrc` tasks the same way as included builds.
- Believing `buildSrc` appears in `gradle.includedBuilds`.