Fix `java_single_jar` compatibility with Bazel 7

Rule extension APIs weren't enabled by default in Bazel 7, so the rule was broken after https://github.com/bazelbuild/rules_java/commit/37b099cfd27de53495aea44763a16cd23ff929df

Also add a dummy target under `test/repo` to build as a regression test.

Fixes https://github.com/bazelbuild/rules_java/issues/350

PiperOrigin-RevId: 866808173
Change-Id: I594f0194be02f935154585e68d091172140301e1
diff --git a/java/BUILD b/java/BUILD
index 452c765..7888dab 100644
--- a/java/BUILD
+++ b/java/BUILD
@@ -65,7 +65,9 @@
     name = "java_single_jar",
     srcs = ["java_single_jar.bzl"],
     visibility = ["//visibility:public"],
-    deps = ["//java/common"],
+    deps = [
+        "//java/bazel/rules",  # copybara-use-repo-external-label
+    ],
 )
 
 bzl_library(
diff --git a/java/bazel/rules/bazel_java_single_jar.bzl b/java/bazel/rules/bazel_java_single_jar.bzl
new file mode 100644
index 0000000..922fb63
--- /dev/null
+++ b/java/bazel/rules/bazel_java_single_jar.bzl
@@ -0,0 +1,21 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Bazel java_single_jar rule"""
+
+load("//java/common/rules:java_single_jar.bzl", _bazel_java_single_jar = "bazel_java_single_jar")
+
+def java_single_jar(*, name, **kwargs):
+    if "output" not in kwargs:
+        kwargs["output"] = name + ".jar"
+    _bazel_java_single_jar(name = name, **kwargs)
diff --git a/java/common/rules/BUILD b/java/common/rules/BUILD
index 5bb5c2f..f6fde9d 100644
--- a/java/common/rules/BUILD
+++ b/java/common/rules/BUILD
@@ -32,6 +32,13 @@
 )
 
 bzl_library(
+    name = "java_single_jar_bzl",
+    srcs = ["java_single_jar.bzl"],
+    visibility = ["//java:__subpackages__"],
+    deps = ["//java/common"],
+)
+
+bzl_library(
     name = "core_rules",
     srcs = [
         "basic_java_library.bzl",
@@ -81,6 +88,7 @@
     srcs = [
         "BUILD",
         ":core_rules",
+        ":java_single_jar_bzl",
         ":toolchain_rules",
         "//java/common/rules/impl:for_bazel_tests",
         "@rules_cc//cc/private/rules_impl:srcs",
diff --git a/java/common/rules/java_single_jar.bzl b/java/common/rules/java_single_jar.bzl
new file mode 100644
index 0000000..95b7ca2
--- /dev/null
+++ b/java/common/rules/java_single_jar.bzl
@@ -0,0 +1,167 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Definition of  the java_single_jar rule."""
+
+load("//java/common:java_common.bzl", "java_common")
+load("//java/common:java_info.bzl", "JavaInfo")
+
+# copybara: default visibility
+
+def _single_jar_inputs(deps, deploy_env):
+    transitive_inputs = []
+    for dep in deps:
+        if JavaInfo in dep:
+            info = dep[JavaInfo]
+            transitive_inputs.append(info.transitive_runtime_jars)
+            if hasattr(info, "compilation_info"):
+                compilation_info = info.compilation_info
+                if hasattr(compilation_info, "runtime_classpath"):
+                    transitive_inputs.append(compilation_info.runtime_classpath)
+        else:
+            files = []
+            for f in dep[DefaultInfo].files.to_list():
+                if not f.extension == "jar":
+                    fail("unexpected file type in java_single_jar.deps: %s" % f.path)
+                files.append(f)
+            transitive_inputs.append(depset(files))
+    inputs = depset(transitive = transitive_inputs)
+
+    if hasattr(java_common, "JavaRuntimeClasspathInfo"):
+        deploy_env_jars = depset(transitive = [
+            dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
+            for dep in deploy_env
+        ])
+        excluded_jars = {jar: None for jar in deploy_env_jars.to_list()}
+        if excluded_jars:
+            inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars])
+    return inputs
+
+def _bazel_java_single_jar_impl(ctx):
+    inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env)
+
+    args = ctx.actions.args()
+    args.add_all("--sources", inputs)
+    args.use_param_file("@%s")
+    args.set_param_file_format("multiline")
+    args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines)
+    args.add("--output", ctx.outputs.output)
+    args.add("--normalize")
+
+    # Deal with limitation of singlejar flags: tool's default behavior is
+    # "no", but you get that behavior only by absence of compression flags.
+    if ctx.attr.compress == "preserve":
+        args.add("--dont_change_compression")
+    elif ctx.attr.compress == "yes":
+        args.add("--compression")
+    elif ctx.attr.compress == "no":
+        pass
+    else:
+        fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress)
+
+    if ctx.attr.exclude_build_data:
+        args.add("--exclude_build_data")
+    if ctx.attr.multi_release:
+        args.add("--multi_release")
+
+    if ctx.attr.exclude_pattern:
+        args.add("--exclude_pattern", ctx.attr.exclude_pattern)
+
+    ctx.actions.run(
+        inputs = inputs,
+        outputs = [ctx.outputs.output],
+        arguments = [args],
+        progress_message = "Merging into %s" % ctx.outputs.output.short_path,
+        mnemonic = "JavaSingleJar",
+        executable = ctx.executable._singlejar,
+        use_default_shell_env = True,
+    )
+
+    files = depset([ctx.outputs.output])
+    providers = [DefaultInfo(
+        files = files,
+        runfiles = ctx.runfiles(transitive_files = files),
+    )]
+    if hasattr(java_common, "JavaRuntimeClasspathInfo"):
+        providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs))
+    return providers
+
+bazel_java_single_jar = rule(
+    attrs = {
+        "deps": attr.label_list(
+            allow_files = True,
+            doc = """
+                The Java targets (including java_import and java_library) to collect
+                transitive dependencies from. Runtime dependencies are collected via
+                deps, exports, and runtime_deps. Resources are also collected.
+                Native cc_library or java_wrap_cc dependencies are not.""",
+        ),
+        "deploy_manifest_lines": attr.string_list(doc = """
+          A list of lines to add to the <code>META-INF/manifest.mf</code> file."""),
+        "deploy_env": attr.label_list(
+            providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [],
+            allow_files = False,
+            doc = """
+            A list of `java_binary` or `java_single_jar` targets which represent
+            the deployment environment for this binary.
+
+            Set this attribute when building a plugin which will be loaded by another
+            `java_binary`.
+
+            `deploy_env` dependencies are excluded from the jar built by this rule.""",
+        ),
+        "compress": attr.string(default = "preserve", doc = """
+            Whether to always deflate ("yes"), always store ("no"), or pass
+            through unmodified ("preserve"). The default is "preserve", and is the
+            most efficient option -- no extra work is done to inflate or deflate."""),
+        "exclude_build_data": attr.bool(default = True, doc = """
+            Whether to omit the build-data.properties file generated
+            by default."""),
+        "multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""),
+        "exclude_pattern": attr.string(default = "", doc = """
+            A regex pattern of files to exclude from the jar.
+        """),
+        "_singlejar": attr.label(
+            default = Label("//toolchains:singlejar"),
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+        ),
+        "output": attr.output(),
+    },
+    implementation = _bazel_java_single_jar_impl,
+    doc = """
+Collects Java dependencies and jar files into a single jar
+
+`java_single_jar` collects Java dependencies and jar files into a single jar.
+This is similar to java_binary with everything related to executables disabled,
+and provides an alternative to the java_binary "deploy jar hack".
+
+## Example
+
+```skylark
+load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar")
+
+java_single_jar(
+    name = "my_single_jar",
+    deps = [
+        "//java/com/google/foo",
+        "//java/com/google/bar",
+    ],
+)
+```
+
+Outputs:
+  {name}.jar: A single jar containing all of the inputs.
+""",
+)
diff --git a/java/java_single_jar.bzl b/java/java_single_jar.bzl
index bfdd21c..8477161 100644
--- a/java/java_single_jar.bzl
+++ b/java/java_single_jar.bzl
@@ -1,158 +1,5 @@
-""" Definition of _java_single_jar. """
+"""The java_single_jar rule"""
 
-load("//java/common:java_common.bzl", "java_common")
-load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/bazel/rules:bazel_java_single_jar.bzl", _java_single_jar = "java_single_jar")
 
-def _single_jar_inputs(deps, deploy_env):
-    transitive_inputs = []
-    for dep in deps:
-        if JavaInfo in dep:
-            info = dep[JavaInfo]
-            transitive_inputs.append(info.transitive_runtime_jars)
-            if hasattr(info, "compilation_info"):
-                compilation_info = info.compilation_info
-                if hasattr(compilation_info, "runtime_classpath"):
-                    transitive_inputs.append(compilation_info.runtime_classpath)
-        else:
-            files = []
-            for f in dep[DefaultInfo].files.to_list():
-                if not f.extension == "jar":
-                    fail("unexpected file type in java_single_jar.deps: %s" % f.path)
-                files.append(f)
-            transitive_inputs.append(depset(files))
-    inputs = depset(transitive = transitive_inputs)
-
-    if hasattr(java_common, "JavaRuntimeClasspathInfo"):
-        deploy_env_jars = depset(transitive = [
-            dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
-            for dep in deploy_env
-        ])
-        excluded_jars = {jar: None for jar in deploy_env_jars.to_list()}
-        if excluded_jars:
-            inputs = depset([jar for jar in inputs.to_list() if jar not in excluded_jars])
-    return inputs
-
-def _java_single_jar(ctx):
-    inputs = _single_jar_inputs(ctx.attr.deps, ctx.attr.deploy_env)
-
-    args = ctx.actions.args()
-    args.add_all("--sources", inputs)
-    args.use_param_file("@%s")
-    args.set_param_file_format("multiline")
-    args.add_all("--deploy_manifest_lines", ctx.attr.deploy_manifest_lines)
-    args.add("--output", ctx.outputs.output)
-    args.add("--normalize")
-
-    # Deal with limitation of singlejar flags: tool's default behavior is
-    # "no", but you get that behavior only by absence of compression flags.
-    if ctx.attr.compress == "preserve":
-        args.add("--dont_change_compression")
-    elif ctx.attr.compress == "yes":
-        args.add("--compression")
-    elif ctx.attr.compress == "no":
-        pass
-    else:
-        fail("\"compress\" attribute (%s) must be: yes, no, preserve." % ctx.attr.compress)
-
-    if ctx.attr.exclude_build_data:
-        args.add("--exclude_build_data")
-    if ctx.attr.multi_release:
-        args.add("--multi_release")
-
-    if ctx.attr.exclude_pattern:
-        args.add("--exclude_pattern", ctx.attr.exclude_pattern)
-
-    ctx.actions.run(
-        inputs = inputs,
-        outputs = [ctx.outputs.output],
-        arguments = [args],
-        progress_message = "Merging into %s" % ctx.outputs.output.short_path,
-        mnemonic = "JavaSingleJar",
-        executable = ctx.executable._singlejar,
-        use_default_shell_env = True,
-    )
-
-    files = depset([ctx.outputs.output])
-    providers = [DefaultInfo(
-        files = files,
-        runfiles = ctx.runfiles(transitive_files = files),
-    )]
-    if hasattr(java_common, "JavaRuntimeClasspathInfo"):
-        providers.append(java_common.JavaRuntimeClasspathInfo(runtime_classpath = inputs))
-    return providers
-
-def _init(name, **kwargs):
-    if "output" not in kwargs:
-        kwargs["output"] = name + ".jar"
-    return kwargs
-
-java_single_jar = rule(
-    attrs = {
-        "deps": attr.label_list(
-            allow_files = True,
-            doc = """
-                The Java targets (including java_import and java_library) to collect
-                transitive dependencies from. Runtime dependencies are collected via
-                deps, exports, and runtime_deps. Resources are also collected.
-                Native cc_library or java_wrap_cc dependencies are not.""",
-        ),
-        "deploy_manifest_lines": attr.string_list(doc = """
-          A list of lines to add to the <code>META-INF/manifest.mf</code> file."""),
-        "deploy_env": attr.label_list(
-            providers = [java_common.JavaRuntimeClasspathInfo] if hasattr(java_common, "JavaRuntimeClasspathInfo") else [],
-            allow_files = False,
-            doc = """
-            A list of `java_binary` or `java_single_jar` targets which represent
-            the deployment environment for this binary.
-
-            Set this attribute when building a plugin which will be loaded by another
-            `java_binary`.
-
-            `deploy_env` dependencies are excluded from the jar built by this rule.""",
-        ),
-        "compress": attr.string(default = "preserve", doc = """
-            Whether to always deflate ("yes"), always store ("no"), or pass
-            through unmodified ("preserve"). The default is "preserve", and is the
-            most efficient option -- no extra work is done to inflate or deflate."""),
-        "exclude_build_data": attr.bool(default = True, doc = """
-            Whether to omit the build-data.properties file generated
-            by default."""),
-        "multi_release": attr.bool(default = True, doc = """Whether to enable Multi-Release output jars."""),
-        "exclude_pattern": attr.string(default = "", doc = """
-            A regex pattern of files to exclude from the jar.
-        """),
-        "_singlejar": attr.label(
-            default = Label("//toolchains:singlejar"),
-            cfg = "exec",
-            allow_single_file = True,
-            executable = True,
-        ),
-        "output": attr.output(),
-    },
-    initializer = _init,
-    implementation = _java_single_jar,
-    doc = """
-Collects Java dependencies and jar files into a single jar
-
-`java_single_jar` collects Java dependencies and jar files into a single jar.
-This is similar to java_binary with everything related to executables disabled,
-and provides an alternative to the java_binary "deploy jar hack".
-
-## Example
-
-```skylark
-load("//tools/build_defs/java_single_jar:java_single_jar.bzl", "java_single_jar")
-
-java_single_jar(
-    name = "my_single_jar",
-    deps = [
-        "//java/com/google/foo",
-        "//java/com/google/bar",
-    ],
-)
-```
-
-Outputs:
-  {name}.jar: A single jar containing all of the inputs.
-""",
-)
+java_single_jar = _java_single_jar
diff --git a/test/repo/BUILD.bazel b/test/repo/BUILD.bazel
index b0fb612..a251c7b 100644
--- a/test/repo/BUILD.bazel
+++ b/test/repo/BUILD.bazel
@@ -1,4 +1,5 @@
 load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test")  # copybara-use-repo-external-label
+load("@rules_java//java:java_single_jar.bzl", "java_single_jar")  # copybara-use-repo-external-label
 load("@rules_java//toolchains:default_java_toolchain.bzl", "NONPREBUILT_TOOLCHAIN_CONFIGURATION", "default_java_toolchain")  # copybara-use-repo-external-label
 load("@rules_shell//shell:sh_test.bzl", "sh_test")
 
@@ -27,6 +28,15 @@
     ],
 )
 
+java_single_jar(
+    name = "uber",
+    testonly = True,
+    deps = [
+        ":MyTest",
+        ":bin",
+    ],
+)
+
 genrule(
     name = "MakeVarGenruleTest",
     outs = ["MakeVarGenruleTestSuccess"],