skip to content

How can you implement conditional or computed plugin versioning in eachPlugin, and what are the caveats?

level: seniorimportance: nice to knowfreq 18%

answer

  1. closure = arbitrary code in settings
  2. branch on requested.version / env / property
  3. guard every branch with id check
  4. no non-deterministic inputs
  5. prefer providers API for cache-safety

basics

~10 s

Inside 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 s

Because `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
kotlin
// 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

for a junior

Not expected to author conditional logic; awareness that eachPlugin is code is enough.

for a middle

Show a simple guarded conditional and note it must be deterministic.

for a senior

Discuss reproducibility, configuration-cache impact, and when to choose catalogs vs imperative hooks.

for a principal

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.

context