Allow java_binary to exclude build metadata (#376)

When enabled, omit build-info inputs and pass `--exclude_build_data` to singlejar.

Reject explicit stamping with build data excluded.

Closes #376

COPYBARA_INTEGRATE_REVIEW=No public description
PiperOrigin-RevId: 970499598
Change-Id: I79bac9327c7f6637ad2ba5f54db18e47d1907c5f
diff --git a/java/bazel/rules/bazel_java_binary.bzl b/java/bazel/rules/bazel_java_binary.bzl
index 4b2a4b5..0611e06 100644
--- a/java/bazel/rules/bazel_java_binary.bzl
+++ b/java/bazel/rules/bazel_java_binary.bzl
@@ -122,6 +122,7 @@
         main_class,
         coverage_main_class,
         info.strip_as_default,
+        exclude_build_data = ctx.attr.exclude_build_data,
         add_exports = info.add_exports,
         add_opens = info.add_opens,
     )
@@ -346,6 +347,13 @@
 BASE_BINARY_ATTRS = merge_attrs(
     BASIC_JAVA_BINARY_ATTRIBUTES,
     {
+        "exclude_build_data": attr.bool(
+            default = False,
+            doc = """
+Whether to omit the <code>build-data.properties</code> file from the
+<code>*_deploy.jar</code> output.
+            """,
+        ),
         "resource_strip_prefix": attr.string(
             doc = """
 The path prefix to strip from Java resources.
diff --git a/java/common/rules/impl/java_binary_deploy_jar.bzl b/java/common/rules/impl/java_binary_deploy_jar.bzl
index 2600e4e..b315bc3 100644
--- a/java/common/rules/impl/java_binary_deploy_jar.bzl
+++ b/java/common/rules/impl/java_binary_deploy_jar.bzl
@@ -32,7 +32,8 @@
         one_version_level = "OFF",
         one_version_allowlist = None,
         extra_args = [],
-        extra_manifest_lines = []):
+        extra_manifest_lines = [],
+        exclude_build_data = False):
     """ Registers actions for _deploy.jar and _deploy.jar.unstripped
 
     Args:
@@ -49,6 +50,7 @@
         one_version_allowlist: (File) Optional allowlist for one version check
         extra_args: (list[Args]) Optional arguments for the deploy jar action
         extra_manifest_lines: (list[String]) Optional lines added to the jar manifest
+        exclude_build_data: (bool) Whether to omit build-data.properties
     """
     classpath_resources = java_attrs.classpath_resources
 
@@ -61,7 +63,10 @@
         order = "preorder",
     )
     multi_release = ctx.fragments.java.multi_release_deploy_jars
-    build_info_files = helper.get_build_info(ctx, ctx.attr.stamp)
+    if exclude_build_data and ctx.attr.stamp == 1:
+        fail("Enabling stamping has no effect with exclude_build_data enabled")
+
+    build_info_files = [] if exclude_build_data else helper.get_build_info(ctx, ctx.attr.stamp)
     build_target = str(ctx.label)
     manifest_lines = ctx.attr.deploy_manifest_lines + extra_manifest_lines
     create_deploy_archive(
@@ -75,6 +80,7 @@
         build_info_files,
         build_target,
         output = ctx.outputs.deployjar,
+        exclude_build_data = exclude_build_data,
         one_version_level = one_version_level,
         one_version_allowlist = one_version_allowlist,
         multi_release = multi_release,
@@ -96,6 +102,7 @@
             build_info_files,
             build_target,
             output = ctx.outputs.unstrippeddeployjar,
+            exclude_build_data = exclude_build_data,
             multi_release = multi_release,
             hermetic = hermetic,
             add_exports = add_exports,
@@ -122,7 +129,8 @@
         hermetic = False,
         add_exports = [],
         add_opens = [],
-        extra_args = []):
+        extra_args = [],
+        exclude_build_data = False):
     """ Creates a deploy jar
 
     Requires a Java runtime toolchain if and only if hermetic is True.
@@ -146,6 +154,7 @@
         add_exports: (depset)
         add_opens: (depset)
         extra_args: (list[Args]) Optional arguments for the deploy jar action
+        exclude_build_data: (bool) Whether to omit build-data.properties
     """
     input_files = []
     input_files.extend(build_info_files)
@@ -172,7 +181,10 @@
     if main_class:
         args.add("--main_class", main_class)
     args.add_all("--deploy_manifest_lines", manifest_lines)
-    args.add_all(build_info_files, before_each = "--build_info_file")
+    if exclude_build_data:
+        args.add("--exclude_build_data")
+    else:
+        args.add_all(build_info_files, before_each = "--build_info_file")
     if launcher:
         input_files.append(launcher)
         args.add("--java_launcher", launcher)
diff --git a/test/java/bazel/rules/java_binary_tests.bzl b/test/java/bazel/rules/java_binary_tests.bzl
index 8436e0e..107f63f 100644
--- a/test/java/bazel/rules/java_binary_tests.bzl
+++ b/test/java/bazel/rules/java_binary_tests.bzl
@@ -1,9 +1,11 @@
 """Tests for the Bazel java_binary rule"""
 
+load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge")
 load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
 load("@rules_testing//lib:truth.bzl", "matching")
 load("@rules_testing//lib:util.bzl", "util")
 load("//java:java_binary.bzl", "java_binary")
+load("//test/java/testutil:helper.bzl", "always_passes")
 load("//test/java/testutil:java_info_subject.bzl", "java_info_subject")
 load("//test/java/testutil:rules/template_var_info_rule.bzl", "template_var_info_rule")
 
@@ -49,6 +51,66 @@
     assert_action.substitutions().keys().contains("%jvm_flags%")
     assert_action.inputs().contains_exactly(["java/bazel/rules/java_stub_template.txt"])
 
+def _test_java_binary_excludes_build_data(name):
+    if not _bazel_version_ge("8.0.0"):
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_binary,
+        name = name + "/bin",
+        srcs = ["Main.java"],
+        exclude_build_data = True,
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_java_binary_excludes_build_data_impl,
+        target = name + "/bin",
+        attr_values = {"tags": ["min_bazel_8"]},
+    )
+
+def _test_java_binary_excludes_build_data_impl(env, target):
+    assert_deploy_jar_action = env.expect.that_target(target).action_generating(
+        "{package}/{name}_deploy.jar",
+    )
+
+    assert_deploy_jar_action.argv().contains("--normalize")
+    assert_deploy_jar_action.argv().contains("--exclude_build_data")
+    assert_deploy_jar_action.argv().not_contains("--build_info_file")
+    assert_deploy_jar_action.inputs().not_contains_predicate(
+        matching.file_basename_equals("non_volatile_file.properties"),
+    )
+    assert_deploy_jar_action.inputs().not_contains_predicate(
+        matching.file_basename_equals("redacted_file.properties"),
+    )
+
+def _test_java_binary_stamping_enabled_build_data_excluded_fails(name):
+    if not _bazel_version_ge("8.0.0"):
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_binary,
+        name = name + "/bin",
+        srcs = ["Main.java"],
+        exclude_build_data = True,
+        stamp = 1,
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_java_binary_stamping_enabled_build_data_excluded_fails_impl,
+        target = name + "/bin",
+        expect_failure = True,
+        attr_values = {"tags": ["min_bazel_8"]},
+    )
+
+def _test_java_binary_stamping_enabled_build_data_excluded_fails_impl(env, target):
+    env.expect.that_target(target).failures().contains_predicate(
+        matching.str_matches("Enabling stamping has no effect with exclude_build_data enabled"),
+    )
+
 def _test_java_binary_javacopts_make_variable_expansion(name):
     util.helper_target(
         template_var_info_rule,
@@ -137,6 +199,8 @@
         name = name,
         tests = [
             _test_java_binary_cross_compilation_to_unix,
+            _test_java_binary_excludes_build_data,
+            _test_java_binary_stamping_enabled_build_data_excluded_fails,
             _test_java_binary_javacopts_make_variable_expansion,
             _test_java_binary_javacopts_location_expansion,
             _test_java_binary_resource_strip_prefix,
diff --git a/test/java/common/rules/java_binary_tests.bzl b/test/java/common/rules/java_binary_tests.bzl
index 496a005..b8add39 100644
--- a/test/java/common/rules/java_binary_tests.bzl
+++ b/test/java/common/rules/java_binary_tests.bzl
@@ -56,6 +56,9 @@
         "{package}/{name}_deploy.jar",
     )
 
+    assert_deploy_jar_action.argv().contains("--normalize")
+    assert_deploy_jar_action.argv().not_contains("--exclude_build_data")
+    assert_deploy_jar_action.argv().contains("--build_info_file")
     assert_deploy_jar_action.inputs().not_contains_predicate(
         matching.file_basename_equals("non_volatile_file.properties"),
     )