Opensource java_single_jar in rules_java

PiperOrigin-RevId: 565664709
Change-Id: Id6d082ebfd28af9db53aff8700d697cb4ae21426
diff --git a/java/BUILD b/java/BUILD
index 8e07ca3..9e6bd30 100644
--- a/java/BUILD
+++ b/java/BUILD
@@ -16,3 +16,9 @@
     visibility = ["//visibility:public"],
     deps = ["//java/private"],
 )
+
+bzl_library(
+    name = "java_single_jar",
+    srcs = ["java_single_jar.bzl"],
+    visibility = ["//visibility:public"],
+)
diff --git a/java/java_single_jar.bzl b/java/java_single_jar.bzl
new file mode 100644
index 0000000..6632c62
--- /dev/null
+++ b/java/java_single_jar.bzl
@@ -0,0 +1,117 @@
+""" Definition of _java_single_jar. """
+
+def _java_single_jar(ctx):
+    transitive_inputs = []
+    for dep in ctx.attr.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)
+
+    args = ctx.actions.args()
+    args.add_all("--sources", inputs)
+    args.use_param_file("@%s")
+    args.set_param_file_format("multiline")
+    args.add("--output", ctx.outputs.jar)
+    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")
+
+    ctx.actions.run(
+        inputs = inputs,
+        outputs = [ctx.outputs.jar],
+        arguments = [args],
+        progress_message = "Merging into %s" % ctx.outputs.jar.short_path,
+        mnemonic = "JavaSingleJar",
+        executable = ctx.executable._singlejar,
+    )
+
+    files = depset([ctx.outputs.jar])
+    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
+
+java_single_jar = rule(
+    attrs = {
+        "deps": attr.label_list(
+            providers = [JavaInfo],
+            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.""",
+        ),
+        "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."""),
+        "_singlejar": attr.label(
+            default = Label("//tools/jdk:singlejar"),
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+        ),
+    },
+    outputs = {
+        "jar": "%{name}.jar",
+    },
+    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.
+""",
+)