As a build platform owner, how do you leverage the base/lifecycle-base convention so heterogeneous modules (JVM, JS, docs) all respond uniformly to `gradle build` in CI?
answer
- lifecycle = language-agnostic contract
- centralize in convention plugin
- assemble=artifacts, check=verification, even for JS/docs
- CI: build on main, check on PR
- one dependsOn propagates org-wide
basics
~10 sEnsure every module applies a plugin in the base family so it has assemble/check/build, then hook each module's real work into assemble (artifacts) or check (verification). CI then just runs gradle build everywhere.
solid answer
~50 sThe base/lifecycle-base lifecycle is a cross-language *contract*: any module with these tasks responds to `gradle build` predictably. As platform owner you exploit that by encoding it in a **convention plugin**. Make your convention plugin apply `lifecycle-base` (or `base`) so the umbrella tasks exist, then wire each technology's work consistently — JVM jars and JS bundles into `assemble`, tests/linters/format-checks into `check`. For non-JVM tools (a webpack build, a docs generator) you write small tasks and `dependsOn` them from `assemble`/`check`. CI then calls a single `gradle build` (or `gradle check` for fast PR gates) at the root, and every module — regardless of language — does the right thing. The lifecycle's action-less aggregation means you can add a new verification (e.g. a security scan) org-wide by hooking it into `check` in the convention plugin, and it instantly participates in every existing pipeline without touching CI YAML.
code
kotlin · 8 lines// my.lifecycle-conventions.gradle.kts — applied by every module
plugins { base }
val sbom = tasks.register("generateSbom") { /* ... */ }
tasks.named("assemble") { dependsOn(sbom) } // artifact-side, org-wide
val depScan = tasks.register("dependencyScan") { /* ... */ }
tasks.named("check") { dependsOn(depScan) } // verification, org-widego deeper
Likely beyond junior scope; at most, note that every module getting assemble/check lets CI run one command.
Explain hooking each language's work into assemble/check so gradle build is uniform; mention convention plugins.
Design the convention-plugin centralization, the PR-check vs main-build CI mapping, and configuration-avoidance at scale.
Treat the lifecycle as a governance contract: a single hook point to roll out org-wide controls, with explicit attention to PR latency, caching, and avoiding CI-invisible bespoke tasks.
## The lifecycle as an organizational contract The value of `assemble`/`check`/`build` is that they're a **language-agnostic vocabulary**. A CI system that knows only `gradle build` can drive a JVM service, a TypeScript frontend wrapped in Gradle, and a documentation module — because each has applied something in the base family and hooked its work into the right umbrella task. ## Encode it in a convention plugin Don't scatter lifecycle wiring across modules. Centralize it in a precompiled script plugin or binary convention plugin under `buildSrc`/an included build: ```kotlin // my.lifecycle-conventions.gradle.kts plugins { base } // lifecycle + archives for every consumer // Org-wide verification hooked once, applies everywhere this plugin is applied: val licenseCheck = tasks.register("licenseCheck") { /* ... */ } tasks.named("check") { dependsOn(licenseCheck) } ``` Every module applies `my.lifecycle-conventions`, so they uniformly gain the lifecycle plus the org verifications. ## Wiring non-JVM work For a JS module driven through Gradle: ```kotlin val buildFrontend = tasks.register<Exec>("buildFrontend") { commandLine("pnpm", "build") } val lintFrontend = tasks.register<Exec>("lintFrontend") { commandLine("pnpm", "lint") } tasks.named("assemble") { dependsOn(buildFrontend) } // artifact tasks.named("check") { dependsOn(lintFrontend) } // verification ``` Now `gradle build` builds and lints the frontend with no CI changes. ## CI strategy that falls out of it - **PR gate (fast):** `gradle check` — runs all verification across every module, no artifact production. - **Main/release:** `gradle build` — assemble + check everywhere. - **Caching:** because tasks declare inputs/outputs and hook the lifecycle, the build cache and incremental build apply uniformly; CI just calls one command. ## Governance leverage Adding a new org-wide control (SBOM generation, dependency-vulnerability scan) is a one-line `dependsOn` from `check`/`assemble` in the convention plugin. It propagates to every module instantly — a strong centralization point, but also a place to be careful: hooking a slow task into `check` slows every PR, so gate expensive scans behind separate lifecycle-attached tasks or a dedicated `verification`-style task you opt modules into. ## Pitfalls - Modules that define bespoke top-level tasks instead of hooking the lifecycle become CI-invisible. - Eagerly realizing lifecycle tasks (`getByName`) across many modules hurts configuration time at scale; prefer `named`/configuration avoidance. - Over-stuffing `check` makes PRs slow; balance coverage against feedback latency.
- Why hook an org-wide security scan into `check` rather than a custom task?Because `check` (and thus `build`) is what CI already runs everywhere. Hooking into it makes the scan participate in every pipeline with zero CI changes — though you must watch PR latency.
- What CI commands map to `assemble` vs `check`?Fast PR gates run `gradle check` (verification only, no artifacts). Main/release runs `gradle build` (assemble + check), producing artifacts and verifying.
- How do you keep a slow scan from slowing every PR?Don't hook it into `check`. Attach it to a separate lifecycle-attached task (or opt-in convention) run only on a nightly/release pipeline, keeping the PR `check` fast.
The lifecycle is like a universal power outlet standard: any appliance (language) that fits the plug works with the same wall socket (gradle build) — CI is the socket, not the appliance.
saying these in an interview costs you the question
- Letting modules define bespoke top-level tasks that bypass the lifecycle (CI-invisible).
- Stuffing slow scans into `check` and tanking PR feedback time.
- Duplicating lifecycle wiring per-module instead of centralizing in a convention plugin.