Bazel: Use plugin-scoped Maven repositories for in-tree plugin builds

When Bazel module support (Bzlmod) is enabled, in-tree plugins may
contribute `maven.install()` tags that are merged by the
rules_jvm_external module extension.

Plugins have been updated to declare their bundled runtime dependencies
in plugin-scoped repositories (for example `oauth_plugin_deps`) that
own their lock files.

This avoids conflicts when multiple in-tree plugins contribute pinned
runtime dependencies: if plugins share the same Maven repository name,
rules_jvm_external merges their `maven.install()` tags and rejects
multiple `lock_file` declarations for the same repository.

Using plugin-scoped Maven repositories allows multiple plugins with
independent dependency lock files to coexist in a single in-tree build.

Document the required configuration for plugin modules and the
workflow for updating Maven and Bzlmod lock files.

Gerrit provides the file `plugins/external_plugin_deps.MODULE.bazel`
which is included from the root `MODULE.bazel` and can be used to
wire plugin Bazel modules into the in-tree build.

Release-Notes: skip
Change-Id: Ib6566007f6ebbbf328f9a80bf3c2892a9c50a5f4
diff --git a/Documentation/dev-build-plugins.txt b/Documentation/dev-build-plugins.txt
index 3b4a259..cddade7 100644
--- a/Documentation/dev-build-plugins.txt
+++ b/Documentation/dev-build-plugins.txt
@@ -90,10 +90,12 @@
 below. The documentation of the deprecated `external_plugin_deps.bzl`
 functionality has been moved to a dedicated section below.
 
-If a plugin requires external Java dependencies, they can install those in its
-`MODULE.bazel` using rules_jvm_external. The bazel repository for the maven
-dependencies has to be plugin specific, e.g. `oauth_plugin_deps`.
-Such a `MODULE.bazel`-file might look as follows:
+If a plugin requires external Java dependencies, it can install them in its
+`MODULE.bazel` using `rules_jvm_external`. The Maven repository containing the
+plugin runtime dependencies must be plugin-scoped (for example
+`<plugin>_plugin_deps`) and must not use a shared repository name across
+plugins when built in-tree with Bzlmod. Such a `MODULE.bazel` file
+might look as follows:
 
 ----
 module(name = "gerrit-oauth-provider")
@@ -124,22 +126,16 @@
     ],
     version_conflict_policy = "pinned",
 )
+
 use_repo(maven, "oauth_plugin_deps")
 ----
 
-After creating the `MODULE.bazel`-file create and update the lock-file for the
-plugin:
-
-----
-cd plugins/oauth
-touch oauth_plugin_deps.lock.json
-bazelisk run @oauth_plugin_deps//:pin
-bazelisk mod deps --lockfile_mode=update
-----
-
-If the plugin has external dependencies, then they must be included from Gerrit's
-own MODULE.bazel file. This can be achieved by loading the plugin's bazel module
-in Gerrit's own module (`MODULE.bazel`-file), e.g.:
+If the plugin has external dependencies, its Bazel module must be loaded from
+Gerrit's own `MODULE.bazel` file and the plugin-scoped Maven repository
+imported via `use_repo()`. Gerrit must not define a `maven.install()` for
+plugin runtime dependencies, as plugins may contribute their own lock files.
+This can be achieved by loading the plugin's Bazel module in Gerrit's own
+module (`MODULE.bazel` file), e.g.:
 
 ----
 bazel_dep(name = "gerrit-plugin-oauth")
@@ -151,6 +147,76 @@
 use_repo(maven, "oauth_plugin_deps")
 ----
 
+After creating the `MODULE.bazel` file, create and update the plugin’s Maven
+lock file:
+
+----
+cd plugins/oauth
+touch oauth_plugin_deps.lock.json
+bazelisk run @oauth_plugin_deps//:pin
+----
+
+The generated Maven lock file (for example `oauth_plugin_deps.lock.json`) is owned
+by the plugin and must be checked into the plugin repository.
+
+When the plugin is built in-tree, Gerrit imports the plugin-scoped Maven
+repository via `use_repo()`, but does not resolve or repin its
+dependencies. The plugin Maven lock file remains owned and maintained by
+the plugin in both standalone and in-tree build modes.
+
+If Bazel module lockfile mode is enabled (for example via
+`--lockfile_mode=error`), the Bzlmod module graph lock file
+(`MODULE.bazel.lock`) must be updated from the workspace root:
+
+----
+cd plugins/oauth
+bazelisk mod deps --lockfile_mode=update
+----
+
+==== Wiring plugin modules into the in-tree build
+
+Plugins that declare external dependencies via `rules_jvm_external`
+must expose their Maven repository to Gerrit's root Bazel module when
+built in-tree.
+
+Gerrit provides the file `plugins/external_plugin_deps.MODULE.bazel`
+which is included from the root `MODULE.bazel`. Plugin modules can be
+wired into the in-tree build by referencing their module and importing
+their plugin-scoped Maven repository.
+
+Example for the `oauth` plugin:
+
+----
+bazel_dep(name = "gerrit-plugin-oauth")
+local_path_override(
+    module_name = "gerrit-plugin-oauth",
+    path = "plugins/oauth",
+)
+
+use_repo(maven, "oauth_plugin_deps")
+----
+
+Plugins may provide their own `external_plugin_deps.MODULE.bazel`
+fragment containing these declarations. When building locally, the
+fragment can be linked into the Gerrit tree:
+
+----
+cd gerrit/plugins
+rm external_plugin_deps.MODULE.bazel
+ln -s oauth/external_plugin_deps.MODULE.bazel external_plugin_deps.MODULE.bazel
+----
+
+This makes the plugin's Bazel module and its plugin-scoped Maven
+repository visible to Gerrit's root module.
+
+[NOTE]
+When multiple plugins are built in-tree, `rules_jvm_external` merges
+`maven.install()` tags with the same name across all modules. Plugin runtime
+dependencies must therefore be declared in plugin-scoped repositories
+(e.g. `<plugin>_plugin_deps`) that own their lock files. Shared repository
+names must not define a `lock_file` in more than one module, otherwise the
+build will fail during module extension evaluation.
+
 === Bundle custom plugin in release.war ===
 
 To bundle custom plugin(s) in the link:dev-bazel.html#release[release.war] artifact,
diff --git a/MODULE.bazel b/MODULE.bazel
index a0e4b79..2debe0b 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -41,3 +41,6 @@
 
 # External dependency wiring is split out.
 include("//tools:java_deps.MODULE.bazel")
+
+# Wiring for external dependencies contributed by in-tree plugins.
+include("//plugins:external_plugin_deps.MODULE.bazel")
diff --git a/plugins/external_plugin_deps.MODULE.bazel b/plugins/external_plugin_deps.MODULE.bazel
new file mode 100644
index 0000000..4c07678
--- /dev/null
+++ b/plugins/external_plugin_deps.MODULE.bazel
@@ -0,0 +1,4 @@
+# Module fragment used to wire plugin Bazel modules into the Gerrit
+# in-tree build. Plugins that declare external dependencies can expose
+# their repositories (e.g. repositories created via rules_jvm_external)
+# through this file.