Add runtime JAR overlap check helper for plugins

Introduce reusable tooling to ensure Gerrit plugins do not bundle
libraries that are already shipped by Gerrit.

- diff_overlap.sh: fails with the intersection of two normalized jar-ID
  lists
- runtime_jars_overlap_test: generates a plugin runtime jar manifest and
  checks it for overlap with a provided manifest, e.g.
  //:release.war.jars.txt

The overlap check is intended for Gerrit-tree builds where the Gerrit runtime
manifest is available; plugins can gate it via target_compatible_with so
standalone builds report it as SKIPPED rather than failing.

Change-Id: If77e8a4026f48afd304e9f486ebc35e37f9925bf
diff --git a/README.md b/README.md
index 92fee68..7261f9c 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
   <ul>
     <li><a href="#gerrit_plugin">gerrit_plugin</a></li>
     <li><a href="#runtime_jars_allowlist_test">runtime_jars_allowlist_test</a></li>
+    <li><a href="#runtime_jars_overlap_test">runtime_jars_overlap_test</a></li>
   </ul>
 </div>
 
@@ -271,3 +272,47 @@
 - normalize (default: True) — strip version suffixes from jar basenames.
 - exclude_self (default: True) — omit the target's own output jar(s).
 - size (default: "small") — Bazel test size classification.
+
+<a name="runtime_jars_overlap_test"></a>
+## runtime_jars_overlap_test
+
+This macro helps plugins detect accidental bundling of third-party runtime JARs
+that are already shipped by Gerrit. It compares the plugin's packaged runtime
+JAR list against a provided manifest (for example Gerrit's
+`//:release.war.jars.txt`) and fails on overlap.
+
+This check is typically meaningful only when the plugin is built inside the
+Gerrit source tree where Gerrit's runtime manifest exists. Standalone plugin
+workspaces should gate the test via `target_compatible_with` so it is reported
+as **SKIPPED** rather than failing.
+
+Example usage in a plugin BUILD file:
+
+```python
+load(
+    "@com_googlesource_gerrit_bazlets//tools:runtime_jars_overlap.bzl",
+    "runtime_jars_overlap_test",
+)
+load(
+    "@com_googlesource_gerrit_bazlets//tools:in_gerrit_tree.bzl",
+    "in_gerrit_tree_enabled",
+)
+
+runtime_jars_overlap_test(
+    name = "no_overlap_with_gerrit",
+    target = ":my_plugin__plugin",
+    against = "//:release.war.jars.txt",
+    hint = "Exclude overlaps via maven.install(excluded_artifacts=[...]) and re-run this test.",
+    target_compatible_with = in_gerrit_tree_enabled(),
+)
+```
+
+Optional arguments:
+
+- normalize (default: True) — strip version suffixes from jar basenames.
+- exclude_self (default: True) — omit the target's own output jar(s).
+- size (default: "small") — Bazel test size classification.
+- hint (default: "") — optional guidance printed on failure.
+
+On failure, the test prints the overlapping normalized jar IDs and exits
+non-zero.
diff --git a/tools/BUILD b/tools/BUILD
index 068e8b0..617d077 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -1,5 +1,7 @@
 exports_files([
     "diff_allowlist.sh",
+    "diff_overlap.sh",
     "java_runtime_jars_manifest.bzl",
     "runtime_jars_allowlist.bzl",
+    "runtime_jars_overlap.bzl",
 ])
diff --git a/tools/diff_overlap.sh b/tools/diff_overlap.sh
new file mode 100755
index 0000000..1476b2d
--- /dev/null
+++ b/tools/diff_overlap.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+LEFT="${1:?missing left list}"
+RIGHT="${2:?missing right list}"
+# Bazel sh_test args are not guaranteed to preserve spaces as a single argv element
+# across all wrappers/platforms. The hint is free-form text, so treat it as "all
+# remaining args" (argv[3..]) to avoid truncating it to the first word.
+HINT="${*:3}"
+
+tmpdir="$(mktemp -d)"
+trap 'rm -rf "${tmpdir}"' EXIT
+
+# Normalize: drop comments/blank lines, sort unique
+grep -vE '^\s*(#|$)' "${LEFT}"  | sort -u > "${tmpdir}/left.norm"
+grep -vE '^\s*(#|$)' "${RIGHT}" | sort -u > "${tmpdir}/right.norm"
+
+# Intersection (overlap)
+comm -12 "${tmpdir}/left.norm" "${tmpdir}/right.norm" > "${tmpdir}/overlap"
+
+if [[ -s "${tmpdir}/overlap" ]]; then
+  echo "" >&2
+  echo "FAIL: Plugin bundles third-party JARs that are also provided by Gerrit:" >&2
+  cat "${tmpdir}/overlap" >&2
+  echo "" >&2
+  echo "Exclude these artifacts from the plugin to avoid duplicate classes and version skew at runtime." >&2
+  echo "" >&2
+
+  if [[ -n "${HINT}" ]]; then
+    echo "Fix:" >&2
+    echo "  ${HINT}" >&2
+  else
+    echo "Fix:" >&2
+    echo "  Exclude the overlapping artifacts from maven.install(excluded_artifacts = [...])" >&2
+    echo "  (or treat them as provided_deps if they come from Gerrit)." >&2
+  fi
+
+  echo "" >&2
+  exit 1
+fi
diff --git a/tools/runtime_jars_overlap.bzl b/tools/runtime_jars_overlap.bzl
new file mode 100644
index 0000000..3c4d8b7
--- /dev/null
+++ b/tools/runtime_jars_overlap.bzl
@@ -0,0 +1,48 @@
+load("//tools:java_runtime_jars_manifest.bzl", "java_runtime_jars_manifest")
+
+def runtime_jars_overlap_test(
+        name,
+        target,
+        against,
+        normalize = True,
+        exclude_self = True,
+        size = "small",
+        hint = "",
+        **kwargs):
+    """Fail if `target`'s runtime jar IDs overlap with `against` list.
+
+    Args:
+      name: sh_test name.
+      target: Java target providing JavaInfo (e.g. ":myplugin__plugin").
+      against: label of a generated text manifest (e.g. "//:release.war.jars.txt").
+      normalize/exclude_self: controls jar ID normalization and self-jar exclusion.
+      hint: optional help printed on failure.
+      **kwargs: forwarded to native.sh_test (e.g. tags, target_compatible_with).
+    """
+    plugin_manifest = name + "_manifest"
+
+    java_runtime_jars_manifest(
+        name = plugin_manifest,
+        target = target,
+        normalize = normalize,
+        exclude_self = exclude_self,
+    )
+
+    args = [
+        "$(location :%s.txt)" % plugin_manifest,
+        "$(location %s)" % against,
+    ]
+    if hint:
+        args.append(hint)
+
+    native.sh_test(
+        name = name,
+        size = size,
+        srcs = ["@com_googlesource_gerrit_bazlets//tools:diff_overlap.sh"],
+        args = args,
+        data = [
+            ":%s.txt" % plugin_manifest,
+            against,
+        ],
+        **kwargs
+    )