Walk through migrating shared build logic from buildSrc to an included build-logic build. What are the concrete steps and pitfalls?
answer
- scaffold build-logic + own settings
- move convention plugin sources
- includeBuild in root settings
- switch consumers to plugins { id() }
- watch: catalog access, plugin-id match, lost global imports
basics
~10 sCreate build-logic/ with its own settings.gradle.kts, move convention plugin sources in, apply kotlin-dsl, add includeBuild('build-logic') to the root settings, then change subprojects to apply the plugins by id. Delete buildSrc once migrated.
solid answer
~40 sSteps: (1) Create a `build-logic/` directory with its own `settings.gradle.kts` (`rootProject.name = "build-logic"`). (2) Add a plugin project applying `kotlin-dsl` (or `java-gradle-plugin`), and move your precompiled script plugins (e.g. `com.acme.java-conventions.gradle.kts`) and any custom tasks from `buildSrc/src/main/kotlin` into it. (3) In the **root** `settings.gradle.kts`, add `includeBuild("build-logic")`. (4) Change every consuming subproject from relying on the implicit classpath to applying the plugins **by id**: `plugins { id("com.acme.java-conventions") }`. (5) Delete `buildSrc`. Pitfalls: forgetting the included build's own settings file; expecting helper classes that were globally visible via buildSrc to still be importable (now they must be exposed through plugins or proper APIs); version-catalog access inside build-logic needs the catalog wired (typeUnsafe accessors or `versionCatalogs`); and plugin ids must match the precompiled script file names/package. Validate with `--configuration-cache` and a build scan.
code
kotlin · 13 lines// 1. build-logic/settings.gradle.kts
rootProject.name = "build-logic"
// 2. build-logic/build.gradle.kts
plugins { `kotlin-dsl` }
// 3. root settings.gradle.kts
includeBuild("build-logic")
// 4. app/build.gradle.kts (was relying on buildSrc classpath)
plugins { id("com.acme.java-conventions") }
// 5. remove buildSrc/ once greengo deeper
List the happy-path steps: make build-logic, includeBuild it, apply by id, delete buildSrc.
Add the precompiled-script-plugin id mechanics and the consumer plugins{} change.
Call out the real pitfalls — catalog access, lost global visibility, id matching — and how to verify with config cache + build scans.
Plan a staged rollout across many subprojects/repos, keep the diff reviewable, and decide whether to split build-logic into modules and later publish it.
## Goal Replace the implicit, global `buildSrc` with an explicit `includeBuild("build-logic")` composite so conventions become opt-in, isolated, testable, and potentially publishable — without changing what the conventions *do*. ## Step-by-step 1. **Scaffold build-logic.** ```kotlin // build-logic/settings.gradle.kts rootProject.name = "build-logic" ``` ```kotlin // build-logic/build.gradle.kts plugins { `kotlin-dsl` } ``` 2. **Move sources.** Relocate `buildSrc/src/main/kotlin/com.acme.java-conventions.gradle.kts` and custom task/extension classes into `build-logic/src/main/kotlin`. Precompiled script plugins keep their plugin id derived from the file name. 3. **Include it.** In the *root* `settings.gradle.kts`: ```kotlin includeBuild("build-logic") ``` 4. **Switch consumers to by-id application.** ```kotlin // app/build.gradle.kts plugins { id("com.acme.java-conventions") } ``` 5. **Delete buildSrc** once everything compiles and applies. ## Pitfalls - **Missing settings file.** An included build *must* have its own `settings.gradle.kts`; without it the build isn't recognized. - **Lost global visibility.** Helper classes that build scripts used directly (because buildSrc was on the classpath) are no longer auto-visible. Expose behavior through the convention plugins, custom tasks, or a published API — don't expect free imports. - **Version catalog access.** Inside build-logic, the root `libs` catalog isn't automatically available to precompiled script plugins. Common fixes: reference the generated catalog accessor via a dependency on the catalog, or read it through `extensions.getByType<VersionCatalogsExtension>()`. Plan this before moving dependency declarations. - **Plugin id mismatch.** The id consumers apply must match the precompiled script file name (including any package). A rename breaks `plugins { id(...) }`. - **gradle.properties / toolchain settings** that lived implicitly for buildSrc may need to be set in build-logic too. ## Verify Run the build with `--configuration-cache` and capture a build scan before and after. Confirm: conventions still apply, configuration cache reuses across runs, and editing one convention plugin reconfigures only its consumers. Keep the diff scoped — migrate, prove green, then optionally split build-logic into multiple plugin modules.
- Why might a precompiled script plugin in build-logic fail to see the libs version catalog?The root catalog isn't auto-exposed to an included build's precompiled scripts; you must wire it — e.g. depend on the generated catalog accessor or read VersionCatalogsExtension — otherwise libs.* won't resolve.
- What proves the migration didn't regress caching?Run with --configuration-cache before and after and compare build scans; confirm the cache is reused and that changing one convention plugin reconfigures only its consumers.
- What commonly breaks when buildSrc is deleted?Build scripts that referenced buildSrc helper classes directly via the global classpath; those references must now go through plugins, tasks, or a published API.
saying these in an interview costs you the question
- Forgetting build-logic needs its own settings.gradle.kts.
- Assuming helper classes stay globally importable after dropping buildSrc.
- Renaming the precompiled script file and breaking the plugin id consumers apply.