skip to content

How would you emit a custom build report or CI notification at the end of every build across a developer team, and where would you register that completion logic?

level: seniorimportance: should knowfreq 22%

answer

  1. init script = runs for every build, register here
  2. init.d / --init-script / custom distribution
  3. FlowAction in an init plugin for cache-safety
  4. buildWorkResult → failed flag → notify CI
  5. Build Scan/Develocity for real telemetry

basics

~10 s

Register completion logic in an init script (~/.gradle/init.gradle.kts or distributed via an init plugin) so it applies to every build. Use a FlowAction reading buildWorkResult to report pass/fail to CI or a dashboard, config-cache-safe.

solid answer

~50 s

Two decisions: **where to register** and **what hook to use**. *Where:* an **init script** runs for every build on a machine, before settings/projects, so it's the natural home for org-wide completion logic. Distribute it via `~/.gradle/init.d/`, `--init-script`, or a published **init plugin** applied through a Gradle distribution / wrapper, so the whole team gets it consistently. *What hook:* historically `gradle.buildFinished { result -> postToCi(result.failure) }`. For modern builds use the **Flow API**: a `FlowAction` registered with `flowScope.always(...)`, pulling the outcome from `flowProviders.buildWorkResult`. That keeps the report compatible with the configuration cache, which is essential because teams enable it for speed. Keep the action's parameters serializable (the failure flag, build id, timing) and avoid capturing the build model. For richer telemetry, Gradle's own Build Scan / Develocity is usually preferable to hand-rolled reporting, but a FlowAction is the right primitive when you must roll your own.

go deeper

for a junior

Know completion logic can live in an init script that runs for every build.

for a middle

Explain init script sources and a basic buildFinished or FlowAction report.

for a senior

Design the distribution mechanism (custom distribution/init plugin) and pick the cache-safe FlowAction; know when Develocity is better.

for a principal

Set the org policy: telemetry strategy, cache-safety mandate, and governance of init scripts across many teams.

## Registration scope: init scripts Gradle evaluates **init scripts** at the very start of every build, before the settings and project scripts. Sources, in order: 1. `--init-script <file>` on the command line. 2. `~/.gradle/init.gradle(.kts)`. 3. Every `*.gradle(.kts)` in `~/.gradle/init.d/`. 4. Init scripts in the Gradle distribution's `init.d/` (lets you ship them with a custom wrapper distribution). Because they run for *every* build on the machine, init scripts are the canonical place for cross-cutting, org-wide concerns: completion reporting, enforced repository mirrors, auditing. ## Distributing to a team - **Custom Gradle distribution**: point the wrapper at a distribution that bundles your `init.d` script — every clone gets it automatically. - **Published init plugin**: an init script can `apply plugin:` a binary plugin from an internal repo, so logic lives in versioned code, not a copy-pasted script. ## The completion hook itself Inside the init script, register a completion action. Legacy: ```kotlin gradle.buildFinished { result -> notifyCi(failed = result.failure != null) } ``` Modern, configuration-cache-safe: ```kotlin abstract class CiReportAction : FlowAction<CiReportAction.Params> { interface Params : FlowParameters { @get:Input val failed: Property<Boolean> } override fun execute(p: Params) { notifyCi(p.failed.get()) } } // flowScope/flowProviders injected into the init plugin flowScope.always(CiReportAction::class.java) { parameters.failed.set(flowProviders.buildWorkResult.map { it.failure.isPresent }) } ``` ## Why cache-safety matters here especially Team builds enable the configuration cache for speed. If your org-wide report uses `buildFinished`, every developer's build prints configuration-cache problems and may fall back to no caching. A `FlowAction` keeps the report invisible to the cache. ## When not to hand-roll For real fleet telemetry (build times, failure causes, flaky tasks), **Build Scans / Develocity** capture far more than a custom hook and centralize it. Reserve custom completion actions for lightweight, specific signals (e.g., a Slack ping on `master` build failure).

  • Why an init script rather than a per-project build.gradle for org-wide completion logic?
    Init scripts apply to every build on the machine without each project opting in, and can be distributed via a custom Gradle distribution so the whole team gets identical behavior.
  • What goes wrong if the team enables the configuration cache and your report uses buildFinished?
    Every build reports configuration-cache problems for the buildFinished closure, undermining the cache; a FlowAction avoids this.

saying these in an interview costs you the question

  • Hand-rolling heavy telemetry when Build Scan/Develocity already does it better.
  • Copy-pasting an init script per developer instead of distributing via a wrapper/plugin.
  • Using buildFinished in shared infra while teams run the configuration cache.

context