Your org mirrors plugins through an internal repository instead of the public Plugin Portal. What has to be true about markers and pluginManagement for `id("…")` resolution to keep working, and what breaks if you only mirror implementation jars?
answer
- two hops: marker POM then impl jar
- mirror BOTH layers or id resolution fails
- virtual proxy fronts the Portal, preserves redirect
- central pluginManagement via init script / settings convention
- jar-only mirror forces legacy buildscript classpath
basics
~20 sThe internal repo must serve the marker POMs (<id>:<id>.gradle.plugin) as well as the implementation jars, and pluginManagement.repositories must point at it. If you mirror only the implementation jars, id(...) resolution fails because Gradle can't find the marker to translate the id into a coordinate.
solid answer
~50 s`id("…")` resolution needs the **marker artifact** to translate id → implementation coordinate. So an internal mirror must host **both** layers: the per-id marker POMs *and* the implementation jars they redirect to. Then you redirect lookups by configuring `pluginManagement { repositories { maven(internalUrl) } }` in `settings.gradle(.kts)` — typically pushed everywhere via an **init script** or a **settings convention plugin** so teams don't each re-add the Portal. If you mirror **only implementation jars**, id-based `plugins {}` breaks: there's no marker, so Gradle can't resolve the id. Teams would be forced into the legacy `buildscript classpath` path (naming the real GAV), losing classloader isolation, `apply false`, and version-catalog integration. Proxy repos like Artifactory/Nexus can virtual-proxy the Plugin Portal so markers pass through transparently — that's the clean fix. Governance-wise, you also want to mirror marker + impl together at a pinned version set, and forbid `gradlePluginPortal()` in build scripts via the central settings convention.
code
kotlin · 9 lines// settings convention plugin or settings.gradle.kts
pluginManagement {
repositories {
maven("https://nexus.acme.io/repository/gradle-plugins") {
name = "acmeInternal"
}
// intentionally NOT gradlePluginPortal() — markers come via the proxy
}
}go deeper
Recognize that an internal repo must still provide the marker, not just the jar.
Explain that pluginManagement must point at the mirror and the mirror must serve both marker and impl layers.
Detail the failure mode of a jar-only mirror and the proxy-repo solution that preserves the redirect.
Design org governance: centralized pluginManagement via init script/settings convention, repositoriesMode enforcement, pinned version sets, and supply-chain treatment of both marker and impl artifacts.
## Why markers matter for mirroring Id-based resolution is a **two-hop** lookup: id → marker POM → implementation jar. A mirror that only carries the second hop (jars) cannot satisfy the first hop. Gradle, given `id("com.acme.x")`, must first find `com.acme.x:com.acme.x.gradle.plugin:<v>`; absent that marker, resolution fails with a 'plugin not found in any repositories' error even though the implementation jar physically exists in the mirror. ## What a correct internal mirror provides 1. **Marker POMs** for every plugin id you allow, at the versions you allow. 2. **Implementation jars** (and their transitive deps) each marker redirects to. 3. A stable repository URL declared in **`pluginManagement.repositories`**. The cleanest way to get (1)+(2) without hand-maintaining markers is a **virtual/proxy repository** (Artifactory, Nexus) that fronts `https://plugins.gradle.org/m2/`. The proxy fetches and caches both the marker POM and the implementation jar on first request, so the redirect is preserved end-to-end. ## Pushing pluginManagement org-wide You don't want every `settings.gradle.kts` to re-list repos. Two mechanisms: - **Init script** (`~/.gradle/init.d/*.gradle.kts`) using `settingsEvaluated { … }` to inject `pluginManagement.repositories` and even `repositoriesMode` to *override* per-project repos. - **Settings convention plugin** applied in `settings.gradle.kts` via `plugins { id("com.acme.settings") }`, centralizing pluginManagement, version catalogs, and Develocity. ```kotlin // init.gradle.kts — force all builds through the internal mirror settingsEvaluated { pluginManagement { repositories { clear() maven("https://nexus.acme.io/repository/gradle-plugins") } } } ``` ## What breaks if you only mirror jars - `plugins { id("…") version "…" }` fails (no marker). - Teams fall back to `buildscript { classpath("group:impl:ver") }` + `apply(plugin=…)`, which: - loses **per-plugin classloader isolation** (shared buildscript classpath → class leakage), - cannot use **`apply false`** (breaks multi-project version alignment), - bypasses **version-catalog `plugins {}` aliases**. - Reproducibility and air-gapped builds suffer because the id→coordinate translation is no longer self-contained in your repo. ## Governance recommendations - Mirror **marker + implementation together**, pinned to an approved version set. - Use a **virtual proxy** so markers come through automatically. - Centralize **pluginManagement** (init script or settings convention plugin); optionally set `repositoriesMode` to forbid ad-hoc `gradlePluginPortal()`. - Treat plugin ids as part of your **supply-chain inventory** — both layers are artifacts to scan and approve.
- Why does mirroring only implementation jars break `id(...)` but not `buildscript classpath`?`id(...)` needs the marker to translate id → coordinate, which a jar-only mirror lacks. `buildscript classpath` names the implementation GAV directly, so it never consults a marker and still resolves.
- How do you enforce that every project uses the internal mirror and not the public Portal?Inject pluginManagement.repositories via an init script (`settingsEvaluated`) and/or a settings convention plugin; optionally set repositoriesMode to FAIL_ON_PROJECT_REPOS-style overrides so ad-hoc Portal declarations are ignored or rejected.
- What's the simplest way to keep markers flowing without hand-maintaining marker POMs?A virtual/proxy repository (Artifactory/Nexus) fronting the Plugin Portal. It transparently caches the marker POM and implementation jar on demand, preserving the redirect.
saying these in an interview costs you the question
- Assuming mirroring implementation jars alone is sufficient — id resolution still needs the marker.
- Configuring the mirror only in project `repositories {}` — markers are resolved from pluginManagement.
- Believing the Portal is mandatory — a proxy that preserves marker + jar fully replaces it.