Propagate `.jdeps` for `java_import` (#363)

Downstream Java compilation actions need accurate `.jdeps` files for their reduced classpath optimization. With this change, `java_import` now always runs `ImportDepsChecker` to generate this file, but possibly suppresses its outputs. Note that Bazel (but not Blaze) always silences the output.

Fixes #362

Closes #363

COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/rules_java/pull/363 from fmeum:java-import-reduced-classpath 5daac7d7ba1eeb7016d5a2921bdd58d93722c586
PiperOrigin-RevId: 974359803
Change-Id: I151170f6382202f8faae0c75c52741920d45c81e
diff --git a/java/common/rules/impl/bazel_java_import_impl.bzl b/java/common/rules/impl/bazel_java_import_impl.bzl
index c402fc4..aa829d1 100644
--- a/java/common/rules/impl/bazel_java_import_impl.bzl
+++ b/java/common/rules/impl/bazel_java_import_impl.bzl
@@ -115,16 +115,23 @@
     collected_jars = _collect_jars(ctx, jars)
     all_deps = _filter_provider(JavaInfo, deps, exports)
 
-    jdeps_artifact = None
     merged_java_info = java_common.merge(all_deps)
-    if not skip_incomplete_deps_check and "incomplete-deps" not in ctx.attr.tags:
-        jdeps_artifact = import_deps_check(
-            ctx,
-            collected_jars,
-            merged_java_info.compile_jars,
-            merged_java_info.transitive_compile_time_jars,
-            "java_import",
-        )
+
+    # import_deps_check is always run to generate the jdeps proto for downstream compile actions,
+    # but may be silenced.
+    has_incomplete_deps = "incomplete-deps" in ctx.attr.tags
+    if skip_incomplete_deps_check or has_incomplete_deps:
+        checking_mode = "silence"
+    else:
+        checking_mode = "error"
+    jdeps_artifact = import_deps_check(
+        ctx,
+        collected_jars,
+        merged_java_info.compile_jars,
+        merged_java_info.transitive_compile_time_jars,
+        "java_import",
+        checking_mode = checking_mode,
+    )
 
     compilation_to_runtime_jar_map = _process_with_ijars_if_needed(collected_jars, ctx)
     runtime_deps_list = [runtime_dep[JavaInfo] for runtime_dep in runtime_deps if JavaInfo in runtime_dep]
@@ -134,6 +141,7 @@
         java_infos.append(JavaInfo(
             output_jar = jar,
             compile_jar = compilation_to_runtime_jar_map[jar],
+            compile_jdeps = jdeps_artifact,
             deps = all_deps,
             runtime_deps = runtime_deps_list,
             neverlink = neverlink,
@@ -169,7 +177,7 @@
     output_group_src_jars = depset() if srcjar == None else depset([srcjar])
 
     validation_group = []
-    if jdeps_artifact != None:
+    if jdeps_artifact != None and checking_mode != "silence":
         validation_group.append(jdeps_artifact)
     if srcjar != None:
         validation_group.append(srcjar)
diff --git a/java/common/rules/impl/import_deps_check.bzl b/java/common/rules/impl/import_deps_check.bzl
index 69dec8b..f5a950f 100644
--- a/java/common/rules/impl/import_deps_check.bzl
+++ b/java/common/rules/impl/import_deps_check.bzl
@@ -23,7 +23,8 @@
         jars_to_check,
         declared_deps,
         transitive_deps,
-        rule_class):
+        rule_class,
+        checking_mode = "error"):
     """
     Creates actions that checks import deps for java rules.
 
@@ -33,6 +34,7 @@
       declared_deps: (list[File]) A list of direct dependencies.
       transitive_deps: (list[File]) A list of transitive dependencies.
       rule_class: (String) Rule class.
+      checking_mode: (String) One of "error", "warning" or "silence".
 
     Returns:
       (File) Output file of the created action.
@@ -53,9 +55,11 @@
         before_each = "--classpath_entry",
     )
     args.add_all(java_toolchain.bootclasspath, before_each = "--bootclasspath_entry")
-    args.add("--checking_mode=error")
+    args.add(checking_mode, format = "--checking_mode=%s")
     args.add("--jdeps_output", jdeps_output)
-    args.add("--rule_label", ctx.label)
+
+    # ctx.label can start with an @ and must not be understood as a flagfile.
+    args.add(ctx.label, format = "--rule_label=%s")
 
     semantics.update_args_for_import_deps(ctx, args)
     inputs = depset(
diff --git a/test/java/bazel/rules/BUILD.bazel b/test/java/bazel/rules/BUILD.bazel
index 6f97d15..09c1f1a 100644
--- a/test/java/bazel/rules/BUILD.bazel
+++ b/test/java/bazel/rules/BUILD.bazel
@@ -1,10 +1,13 @@
 load(":java_binary_tests.bzl", "java_binary_tests")
+load(":java_import_tests.bzl", "java_import_tests")
 load(":java_library_tests.bzl", "java_library_tests")
 load(":java_plugin_tests.bzl", "java_plugin_tests")
 load(":java_test_tests.bzl", "java_test_tests")
 
 java_binary_tests(name = "java_binary_tests")
 
+java_import_tests(name = "java_import_tests")
+
 java_library_tests(name = "java_library_tests")
 
 java_plugin_tests(name = "java_plugin_tests")
diff --git a/test/java/bazel/rules/java_import_tests.bzl b/test/java/bazel/rules/java_import_tests.bzl
new file mode 100644
index 0000000..8ea3ff2
--- /dev/null
+++ b/test/java/bazel/rules/java_import_tests.bzl
@@ -0,0 +1,42 @@
+"""Tests for the Bazel java_import rule"""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
+load("@rules_testing//lib:util.bzl", "util")
+load("//java:java_import.bzl", "java_import")
+
+def _test_import_deps_checker_checking_mode(name):
+    util.helper_target(
+        java_import,
+        name = name + "/import-jar",
+        jars = ["import.jar"],
+        deps = [name + "/depjar"],
+    )
+    util.helper_target(
+        java_import,
+        name = name + "/depjar",
+        jars = ["depjar.jar"],
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_import_deps_checker_checking_mode_impl,
+        target = name + "/import-jar",
+        # The rules_java Starlark implementation is used from Bazel 8 on.
+        attr_values = {"tags": ["min_bazel_8"]},
+    )
+
+def _test_import_deps_checker_checking_mode_impl(env, target):
+    # java_import only generates the jdeps proto, it never fails the build on incomplete deps.
+    assert_action = env.expect.that_target(target).action_named("ImportDepsChecker")
+    assert_action.contains_flag_values([
+        ("--checking_mode", "silence"),
+        ("--rule_label", "//{package}:{name}"),
+    ])
+
+def java_import_tests(name):
+    test_suite(
+        name = name,
+        tests = [
+            _test_import_deps_checker_checking_mode,
+        ],
+    )
diff --git a/test/java/common/rules/java_import_tests.bzl b/test/java/common/rules/java_import_tests.bzl
index 35b713d..c74bd3d 100644
--- a/test/java/common/rules/java_import_tests.bzl
+++ b/test/java/common/rules/java_import_tests.bzl
@@ -178,6 +178,51 @@
         "{package}/depjar.jar",
     ])
 
+# Regression test for https://github.com/bazelbuild/rules_java/issues/362: every java_import
+# records a jdeps proto as its compile_jdeps so that reduced classpaths of downstream compilations
+# are not missing its transitive dependencies.
+def _test_compile_jdeps_propagated_for_deps(name):
+    util.helper_target(
+        java_import,
+        name = name + "/import-jar",
+        jars = ["import.jar"],
+        deps = [name + "/depjar"],
+    )
+    util.helper_target(
+        java_import,
+        name = name + "/incomplete-jar",
+        jars = ["incomplete.jar"],
+        deps = [name + "/depjar"],
+        tags = ["incomplete-deps"],
+    )
+    util.helper_target(
+        java_import,
+        name = name + "/depjar",
+        jars = ["depjar.jar"],
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_compile_jdeps_propagated_for_deps_impl,
+        targets = {
+            "incomplete_deps": name + "/incomplete-jar",
+            "with_deps": name + "/import-jar",
+            "without_deps": name + "/depjar",
+        },
+        # The rules_java Starlark implementation is used from Bazel 8 on.
+        attr_values = {"tags": ["min_bazel_8"]},
+    )
+
+def _test_compile_jdeps_propagated_for_deps_impl(env, targets):
+    for name in ["with_deps", "without_deps", "incomplete_deps"]:
+        java_info_subject.from_target(
+            env,
+            getattr(targets, name),
+            expr = name,
+        ).compilation_args().compile_time_java_dependencies().contains_exactly([
+            "{package}/_java_import/{name}/jdeps.proto",
+        ])
+
 # Regression test for b/262751943.
 def _test_commandline_contains_target_label(name):
     util.helper_target(
@@ -959,6 +1004,7 @@
             _test_simple,
             _test_with_java_library,
             _test_deps,
+            _test_compile_jdeps_propagated_for_deps,
             _test_commandline_contains_target_label,
             _test_java_library_allows_import_in_deps,
             _test_module_flags,
diff --git a/test/java/testutil/java_info_subject.bzl b/test/java/testutil/java_info_subject.bzl
index 9a0fb53..8df4f78 100644
--- a/test/java/testutil/java_info_subject.bzl
+++ b/test/java/testutil/java_info_subject.bzl
@@ -31,8 +31,9 @@
     )
     return public
 
-def _java_info_subject_from_target(env, target):
+def _java_info_subject_from_target(env, target, expr = None):
     return _new_java_info_subject(target[JavaInfo], meta = truth.expect(env).meta.derive(
+        expr = expr,
         format_str_kwargs = {
             "name": target.label.name,
             "package": target.label.package,
diff --git a/toolchains/default_java_toolchain.bzl b/toolchains/default_java_toolchain.bzl
index e4097ae..f131a62 100644
--- a/toolchains/default_java_toolchain.bzl
+++ b/toolchains/default_java_toolchain.bzl
@@ -76,6 +76,7 @@
 
 # Default java_toolchain parameters
 _BASE_TOOLCHAIN_CONFIGURATION = dict(
+    deps_checker = Label("@remote_java_tools//:ImportDepsChecker"),
     forcibly_disable_header_compilation = False,
     genclass = Label("@remote_java_tools//:GenClass"),
     header_compiler = Label("@remote_java_tools//:TurbineDirect"),