How can you implement conditional or computed plugin versioning in eachPlugin, and what are the caveats?
answer
- closure = arbitrary code in settings
- branch on requested.version / env / property
- guard every branch with id check
- no non-deterministic inputs
- prefer providers API for cache-safety
basics
~10 sInside eachPlugin you have full code, so you can branch on requested.id, requested.version, properties, or environment to call useVersion/useModule differently. Keep it cheap and deterministic since it affects resolution caching and reproducibility.
solid answer
~40 sBecause `eachPlugin` runs arbitrary code in settings, you can compute versions: read a `gradle.properties` value, branch on `requested.version` (e.g. only override when null, or only raise a floor), or pick a version per environment. For example, override an internal plugin to a `-SNAPSHOT` build on CI but a release locally. The caveats are real: this logic runs during every settings evaluation, so it must be **fast and side-effect-free**; non-deterministic inputs (timestamps, network calls, `latest.release`) break reproducibility and the configuration cache; and because it fires for *every* plugin you must guard with id checks. For static needs prefer a version catalog — reserve imperative `eachPlugin` for genuinely dynamic decisions, and document them so the indirection doesn't surprise readers.
code
kotlin · 11 lines// settings.gradle.kts
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.acme.internal") {
val onCi = providers.environmentVariable("CI").isPresent
useVersion(if (onCi) "2.0.0-SNAPSHOT" else "1.9.0")
}
}
}
}go deeper
Not expected to author conditional logic; awareness that eachPlugin is code is enough.
Show a simple guarded conditional and note it must be deterministic.
Discuss reproducibility, configuration-cache impact, and when to choose catalogs vs imperative hooks.
Set guardrails: ban dynamic versions, mandate provider APIs, document dynamic hooks, and steer teams to catalogs/convention plugins by default.
## Why conditional logic is possible `eachPlugin` is a normal closure executed during settings evaluation, with access to project properties, system properties, and environment variables. That makes computed versioning straightforward — but also dangerous if misused. ## Common conditional patterns **Override only when no version is requested:** ``` eachPlugin { if (requested.id.id == 'com.example.foo' && requested.version == null) { useVersion(providers.gradleProperty('fooVersion').get()) } } ``` **Environment-driven version (e.g. CI vs local):** ``` eachPlugin { if (requested.id.id == 'com.acme.internal') { def ci = System.getenv('CI') != null useVersion(ci ? '2.0.0-SNAPSHOT' : '1.9.0') } } ``` **Floor enforcement** — raise versions below a minimum to that minimum, leaving newer requests untouched (compare `requested.version` and only call `useVersion` when below the floor). ## Caveats and risks 1. **Runs for every plugin** — always guard with `requested.id.id ==` checks; unguarded `useVersion` corrupts unrelated plugins. 2. **Reproducibility** — non-deterministic inputs (current time, random, network lookups, `latest.release`/`+` ranges) make builds non-reproducible and undermine caching. Keep inputs to stable properties. 3. **Configuration cache / performance** — settings evaluation is on the critical path; expensive or I/O-heavy logic slows every invocation and can defeat caching. 4. **Discoverability** — versions hidden behind imperative branches are harder to audit than a catalog. Favor catalogs for static pinning; use `eachPlugin` for the genuinely dynamic minority and document why. 5. **Provider API** — prefer `providers.gradleProperty(...)` / `providers.environmentVariable(...)` over direct `System.getenv` so inputs are tracked and cache-friendly. ## Decision guide - Static, shared across builds -> **version catalog**. - Encapsulate plugin application + config -> **convention plugin**. - Dynamic version decision or id-to-module remap -> **eachPlugin**, kept minimal.
- Why is using latest.release or '+' inside eachPlugin discouraged?Dynamic versions make resolution non-deterministic, break reproducible builds, and defeat caching by changing between runs; pin explicit versions instead.
- Why prefer providers.environmentVariable over System.getenv in build logic?The Provider API registers the input with Gradle so it participates in up-to-date and configuration-cache tracking; raw System.getenv is an untracked side input.
saying these in an interview costs you the question
- Performing network calls or reading mutable external state inside eachPlugin.
- Omitting the id guard so every plugin gets the override.
- Treating eachPlugin as the default for all version management instead of catalogs.