Support pass-through `Args` for `JavaBuilder` in `java_common.create_compilation_action` (https://github.com/bazelbuild/bazel/pull/30808) This allows faster iteration on the `rules_java` <> `JavaBuilder` contract without having to wait for Bazel changes/releases. In the immediate future, this will be used for the unused-deps checking. Longer term this should also help in migrating off the native compilation code. Note: the added integration shell test is currently a no-op on CI (since we need the corresponding rules_java changes) but does allow for local testing with an overridden `@rules_java`. Closes #30808. PiperOrigin-RevId: 971826913 Change-Id: I8047e057716475a7a4d8bff48b8c7bfe6d79ba1c
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java index d924b1a..4e4b44e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -23,6 +23,7 @@ import com.google.common.collect.Interner; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.actions.ExecutionRequirements; import com.google.devtools.build.lib.actions.ParamFileInfo; import com.google.devtools.build.lib.actions.ParameterFile; @@ -73,6 +74,7 @@ private final ImmutableList<Artifact> additionalInputsForDatabinding; private boolean enableJspecify = true; private boolean enableDirectClasspath = true; + private ImmutableList<CommandLine> extraCommandLineArgs = ImmutableList.of(); private final String execGroup; public JavaCompilationHelper( @@ -104,6 +106,10 @@ this.enableJspecify = enableJspecify; } + public void setExtraCommandLineArgs(ImmutableList<CommandLine> extraCommandLineArgs) { + this.extraCommandLineArgs = Preconditions.checkNotNull(extraCommandLineArgs); + } + JavaTargetAttributes getAttributes() { if (builtAttributes == null) { builtAttributes = attributes.build(); @@ -298,6 +304,7 @@ .getFixDepsTool(ruleContext.getRule(), getJavaConfiguration()) .ifPresent(builder::setFixDepsTool); builder.setCompileTimeDependencyArtifacts(attributes.getCompileTimeDependencyArtifacts()); + builder.setExtraCommandLineArgs(extraCommandLineArgs); builder.setTargetLabel( attributes.getTargetLabel() == null ? label : attributes.getTargetLabel()); builder.setInjectingRuleKind(attributes.getInjectingRuleKind());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java index ce638e1..9a867a2 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
@@ -123,6 +123,7 @@ private final ImmutableMap<String, String> executionInfo; private final CommandLine executableLine; private final CommandLine flagLine; + private final ImmutableList<CommandLine> extraCommandLineArgs; private final BuildConfigurationValue configuration; private final OnDemandString progressMessage; @@ -149,6 +150,7 @@ ExtraActionInfoSupplier extraActionInfoSupplier, CommandLine executableLine, CommandLine flagLine, + ImmutableList<CommandLine> extraCommandLineArgs, BuildConfigurationValue configuration, NestedSet<Artifact> dependencyArtifacts, Artifact outputDepsProto, @@ -171,6 +173,7 @@ configuration.modifiedExecutionInfo(executionInfo, compilationType.mnemonic); this.executableLine = executableLine; this.flagLine = flagLine; + this.extraCommandLineArgs = extraCommandLineArgs; this.configuration = configuration; this.progressMessage = progressMessage; this.extraActionInfoSupplier = extraActionInfoSupplier; @@ -230,6 +233,10 @@ actionKeyContext, inputMetadataProvider, effectiveOutputPathsMode, fp); flagLine.addToFingerprint( actionKeyContext, inputMetadataProvider, effectiveOutputPathsMode, fp); + for (CommandLine extraCommandLine : extraCommandLineArgs) { + extraCommandLine.addToFingerprint( + actionKeyContext, inputMetadataProvider, effectiveOutputPathsMode, fp); + } // As the classpath is no longer part of commandLines implicitly, we need to explicitly add // the transitive inputs to the key here. actionKeyContext.addNestedSetToFingerprint(fp, transitiveInputs); @@ -309,12 +316,15 @@ classpathLine.add("--reduce_classpath_mode", fallback ? "BAZEL_FALLBACK" : "BAZEL_REDUCED"); } - CommandLines reducedCommandLine = + CommandLines.Builder commandLinesBuilder = CommandLines.builder() .addCommandLine(executableLine) .addCommandLine(flagLine, PARAM_FILE_INFO) - .addCommandLine(classpathLine.build(), PARAM_FILE_INFO) - .build(); + .addCommandLine(classpathLine.build(), PARAM_FILE_INFO); + for (CommandLine extraCommandLine : extraCommandLineArgs) { + commandLinesBuilder.addCommandLine(extraCommandLine, PARAM_FILE_INFO); + } + CommandLines reducedCommandLine = commandLinesBuilder.build(); CommandLines.ExpandedCommandLines expandedCommandLines = reducedCommandLine.expand( actionExecutionContext.getInputMetadataProvider(), @@ -552,11 +562,12 @@ public ExtraActionInfo.Builder getExtraActionInfo(ActionKeyContext actionKeyContext) throws CommandLineExpansionException, InterruptedException { ExtraActionInfo.Builder builder = super.getExtraActionInfo(actionKeyContext); - CommandLines commandLinesWithoutExecutable = - CommandLines.builder() - .addCommandLine(flagLine) - .addCommandLine(getFullClasspathLine()) - .build(); + CommandLines.Builder commandLinesBuilder = + CommandLines.builder().addCommandLine(flagLine).addCommandLine(getFullClasspathLine()); + for (CommandLine extraCommandLine : extraCommandLineArgs) { + commandLinesBuilder.addCommandLine(extraCommandLine); + } + CommandLines commandLinesWithoutExecutable = commandLinesBuilder.build(); if (extraActionInfoSupplier != null) { extraActionInfoSupplier.extend(builder, commandLinesWithoutExecutable.allArguments()); } @@ -604,11 +615,15 @@ @VisibleForTesting public CommandLines getCommandLines() { - return CommandLines.builder() - .addCommandLine(executableLine) - .addCommandLine(flagLine, PARAM_FILE_INFO) - .addCommandLine(getFullClasspathLine(), PARAM_FILE_INFO) - .build(); + CommandLines.Builder builder = + CommandLines.builder() + .addCommandLine(executableLine) + .addCommandLine(flagLine, PARAM_FILE_INFO) + .addCommandLine(getFullClasspathLine(), PARAM_FILE_INFO); + for (CommandLine extraCommandLine : extraCommandLineArgs) { + builder.addCommandLine(extraCommandLine, PARAM_FILE_INFO); + } + return builder.build(); } private CommandLine getFullClasspathLine() {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java index 5b0d376..fae5300 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilder.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.ActionEnvironment; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; import com.google.devtools.build.lib.actions.extra.JavaCompileInfo; import com.google.devtools.build.lib.analysis.RuleContext; @@ -157,6 +158,7 @@ private NestedSet<Artifact> extraData = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); private Label targetLabel; @Nullable private String injectingRuleKind; + private ImmutableList<CommandLine> extraCommandLineArgs = ImmutableList.of(); private ImmutableList<Artifact> additionalInputs = ImmutableList.of(); private Artifact genSourceOutput; private JavaCompileOutputs<Artifact> outputs; @@ -260,6 +262,7 @@ /* extraActionInfoSupplier= */ extraActionInfoSupplier, /* executableLine= */ executableLine, /* flagLine= */ buildParamFileContents(javacOpts), + /* extraCommandLineArgs= */ extraCommandLineArgs, /* configuration= */ ruleContext.getConfiguration(), /* dependencyArtifacts= */ compileTimeDependencyArtifacts, /* outputDepsProto= */ outputs.depsProto(), @@ -379,6 +382,13 @@ } @CanIgnoreReturnValue + public JavaCompileActionBuilder setExtraCommandLineArgs( + ImmutableList<CommandLine> extraCommandLineArgs) { + this.extraCommandLineArgs = checkNotNull(extraCommandLineArgs); + return this; + } + + @CanIgnoreReturnValue public JavaCompileActionBuilder setJavacOpts(ImmutableList<String> copts) { this.javacOpts = Preconditions.checkNotNull(copts); return this;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java index 52f9d71..1b9dfb4 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaHeaderCompileAction.java
@@ -655,6 +655,7 @@ /* extraActionInfoSupplier= */ null, /* executableLine= */ executableLine, /* flagLine= */ commandLine.build(), + /* extraCommandLineArgs= */ ImmutableList.of(), /* configuration= */ ruleContext.getConfiguration(), /* dependencyArtifacts= */ compileTimeDependencyArtifacts, /* outputDepsProto= */ outputDepsProto,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java index 18b1849..944fec1 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.CommandLine; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.Expander; import com.google.devtools.build.lib.analysis.RuleContext; @@ -30,6 +31,7 @@ import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget; import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo; import com.google.devtools.build.lib.analysis.platform.ToolchainInfo; +import com.google.devtools.build.lib.analysis.starlark.Args; import com.google.devtools.build.lib.analysis.starlark.StarlarkActionFactory; import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext; import com.google.devtools.build.lib.cmdline.Label; @@ -201,7 +203,8 @@ boolean enableJSpecify, boolean enableDirectClasspath, Sequence<?> additionalInputs, - Sequence<?> additionalOutputs) + Sequence<?> additionalOutputs, + Sequence<?> extraArgs) throws EvalException, TypeException, RuleErrorException, @@ -217,6 +220,11 @@ .nativeHeader(nativeHeader == Starlark.NONE ? null : (Artifact) nativeHeader) .manifestProto(manifestProto) .build(); + ImmutableList.Builder<CommandLine> extraCommandLineArgs = ImmutableList.builder(); + for (Args args : Sequence.cast(extraArgs, Args.class, "extra_args")) { + extraCommandLineArgs.add( + args.build(ctx.getRuleContext().getAnalysisEnvironment()::getMainRepoMapping)); + } JavaTargetAttributes.Builder attributesBuilder = new JavaTargetAttributes.Builder() .addSourceJars(Sequence.cast(sourceJars, Artifact.class, "source_jars")) @@ -261,6 +269,7 @@ Depset.cast(javaBuilderJvmFlags, String.class, "javabuilder_jvm_flags")); compilationHelper.enableJspecify(enableJSpecify); compilationHelper.enableDirectClasspath(enableDirectClasspath); + compilationHelper.setExtraCommandLineArgs(extraCommandLineArgs.build()); compilationHelper.createCompileAction(outputs); }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java index 8867bf9..5052f4f 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java
@@ -21,6 +21,7 @@ import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException; import com.google.devtools.build.lib.packages.Info; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; +import com.google.devtools.build.lib.starlarkbuildapi.CommandLineArgsApi; import com.google.devtools.build.lib.starlarkbuildapi.FileApi; import com.google.devtools.build.lib.starlarkbuildapi.StarlarkActionFactoryApi; import com.google.devtools.build.lib.starlarkbuildapi.StarlarkRuleContextApi; @@ -524,6 +525,14 @@ @Param(name = "enable_direct_classpath", defaultValue = "True", named = true), @Param(name = "additional_inputs", defaultValue = "[]", named = true), @Param(name = "additional_outputs", defaultValue = "[]", named = true), + @Param( + name = "extra_args", + allowedTypes = { + @ParamType(type = Sequence.class, generic1 = CommandLineArgsApi.class), + }, + defaultValue = "[]", + named = true, + positional = false), }) void createCompilationAction( StarlarkRuleContextT ctx, @@ -553,7 +562,8 @@ boolean enableJSpecify, boolean enableDirectClasspath, Sequence<?> additionalInputs, - Sequence<?> additionalOutputs) + Sequence<?> additionalOutputs, + Sequence<?> extraArgs) throws EvalException, TypeException, RuleErrorException,
diff --git a/src/main/starlark/builtins_bzl/common/java/java_common.bzl b/src/main/starlark/builtins_bzl/common/java/java_common.bzl index 33fefbc..fcc08e9 100644 --- a/src/main/starlark/builtins_bzl/common/java/java_common.bzl +++ b/src/main/starlark/builtins_bzl/common/java/java_common.bzl
@@ -17,6 +17,7 @@ _java_common_internal = _builtins.internal.java_common_internal_do_not_use _ALLOWLIST = [ + ("", "bazel_internal/test_rules"), ("", "third_party/bazel_rules/rules_java"), ("", "javatests/com/google/devtools/grok/kythe/analyzers/build/testdata/pkg"), ("rules_java", ""),
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilderTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilderTest.java index d0b7b6f..6dd7b57 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilderTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionBuilderTest.java
@@ -426,4 +426,58 @@ cacheMiss.setFilename(artifact.getExecPathString()); return new BulkTransferException(cacheMiss); } + + @Test + public void testExtraArgsPropagated() throws Exception { + scratch.file("bazel_internal/test_rules/BUILD"); + scratch.file( + "bazel_internal/test_rules/rule.bzl", + String.format( + """ + load("@rules_java//java:defs.bzl", "JavaPluginInfo", rules_java_common = "java_common") + internal_common = java_common.internal_DO_NOT_USE() + def _my_rule_impl(ctx): + output = ctx.outputs.jar + args = ctx.actions.args() + args.add("--foo=bar") + internal_common.create_compilation_action( + ctx, + ctx.toolchains["%1$s"].java, + output, + ctx.actions.declare_file(ctx.label.name + ".manifest"), + JavaPluginInfo(runtime_deps = []), + depset(), + depset(), + depset(), + depset(), + depset(), + depset(), + "ERROR", + ctx.label, + extra_args = [args], + ) + return [DefaultInfo(files = depset([output]))] + + my_rule = rule( + implementation = _my_rule_impl, + outputs = { + "jar": "%%{name}.jar", + }, + fragments = ["java"], + toolchains = ["%1$s"], + ) + """, + TestConstants.JAVA_TOOLCHAIN_TYPE)); + scratch.file( + "java/com/google/test/BUILD", + """ + load("//bazel_internal/test_rules:rule.bzl", "my_rule") + my_rule(name = "a") + """); + + JavaCompileAction compileAction = + (JavaCompileAction) getGeneratingActionForLabel("//java/com/google/test:a.jar"); + List<String> command = getJavacArguments(compileAction); + assertThat(command).contains("--foo=bar"); + } }
diff --git a/src/test/shell/bazel/bazel_java_test.sh b/src/test/shell/bazel/bazel_java_test.sh index 0f515a5..a02e4f15 100755 --- a/src/test/shell/bazel/bazel_java_test.sh +++ b/src/test/shell/bazel/bazel_java_test.sh
@@ -2531,4 +2531,325 @@ expect_log "foo.txt" } +# TODO: Once ready to be enabled on CI, make these separate tests with shared setup +function test_unused_deps() { + if [[ -n "${RULES_JAVA_OVERRIDE_PATH:-}" ]]; then + cat >> MODULE.bazel <<EOF +local_path_override( + module_name = "rules_java", + path = "${RULES_JAVA_OVERRIDE_PATH}", +) +EOF + fi + + # Check if rules_java has the toolchain-driven unused_deps support + # We check this by querying the attributes of java_package_configuration + # or attempting to build a package configuration target with 'unused_deps'. + # If rules_java does not have it, we skip the test. + + mkdir -p pkg + cat > pkg/BUILD <<EOF +load("@rules_java//java:defs.bzl", "java_package_configuration") +java_package_configuration( + name = "test_config_query", + package_specs = [":spec_query"], + unused_deps = "error", +) +package_group( + name = "spec_query", + packages = ["//pkg/..."], +) +EOF + + if ! bazel query //pkg:test_config_query >/dev/null 2>&1; then + echo "Skipping test_unused_deps: rules_java does not support unused_deps configuration yet" + return 0 + fi + + # Write the common toolchain definition for main repo targets + cat << 'EOF' > pkg/BUILD +load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain") +load("@rules_java//java:defs.bzl", "java_package_configuration") + +package_group( + name = "my_package_spec", + packages = ["//pkg/..."], +) + +java_package_configuration( + name = "unused_deps_error_config", + package_specs = [":my_package_spec"], + unused_deps = "error", +) + +default_java_toolchain( + name = "java_toolchain", + package_configuration = [":unused_deps_error_config"], +) +EOF + + # ------------------------------------------------------------- + # Scenario 1: Simple Unused (Error) + # ------------------------------------------------------------- + cat << 'EOF' >> pkg/BUILD +load("@rules_java//java:java_library.bzl", "java_library") +java_library(name = "simple_unused", srcs = ["SimpleUnused.java"], deps = [":b"]) +java_library(name = "b", srcs = ["B.java"]) +EOF + echo "public class SimpleUnused {}" > pkg/SimpleUnused.java + echo "public class B {}" > pkg/B.java + + bazel build //pkg:simple_unused \ + --extra_toolchains=//pkg:java_toolchain_definition \ + >& $TEST_log && fail "build succeeded, but expected it to fail due to unused dependency (Simple Unused)" + + expect_log "Target '//pkg:b' is declared as a direct dependency of '//pkg:simple_unused' but is unused" + + # ------------------------------------------------------------- + # Scenario 2: Simple Used (Success) + # ------------------------------------------------------------- + echo "public class SimpleUnused { B b; }" > pkg/SimpleUnused.java + bazel build //pkg:simple_unused \ + --extra_toolchains=//pkg:java_toolchain_definition \ + >& $TEST_log || fail "build failed, but expected Simple Used to succeed" + + # ------------------------------------------------------------- + # Scenario 3: Exported Only (Error) + # ------------------------------------------------------------- + cat << 'EOF' >> pkg/BUILD +java_library(name = "exported_only", srcs = ["ExportedOnly.java"], deps = [":exports_c"]) +java_library(name = "exports_c", exports = [":c"]) +java_library(name = "c", srcs = ["C.java"]) +EOF + echo "public class ExportedOnly { C c; }" > pkg/ExportedOnly.java + echo "public class C {}" > pkg/C.java + + bazel build //pkg:exported_only \ + --extra_toolchains=//pkg:java_toolchain_definition \ + >& $TEST_log && fail "build succeeded, but expected it to fail due to unused dependency (Exported Only)" + + expect_log "Target '//pkg:exports_c' is declared as a direct dependency of '//pkg:exported_only' but is unused" + + # ------------------------------------------------------------- + # Scenario 4: Exported & Direct Used (Success) + # ------------------------------------------------------------- + # Define exports_c_with_srcs that exports C but also has its own class + cat << 'EOF' >> pkg/BUILD +java_library(name = "exported_and_direct", srcs = ["ExportedAndDirect.java"], deps = [":exports_c_with_srcs"]) +java_library(name = "exports_c_with_srcs", srcs = ["B2.java"], exports = [":c"]) +EOF + echo "public class B2 {}" > pkg/B2.java + echo "public class ExportedAndDirect { B2 b; C c; }" > pkg/ExportedAndDirect.java + + bazel build //pkg:exported_and_direct \ + --extra_toolchains=//pkg:java_toolchain_definition \ + >& $TEST_log || fail "build failed, but expected Exported & Direct Used to succeed" + + # ------------------------------------------------------------- + # Scenario 5: Indirect Used (Error - Strict Deps violation) + # ------------------------------------------------------------- + cat << 'EOF' >> pkg/BUILD +java_library(name = "indirect_used", srcs = ["IndirectUsed.java"], deps = [":dep_no_exports"]) +java_library(name = "dep_no_exports", deps = [":c"]) +EOF + echo "public class IndirectUsed { C c; }" > pkg/IndirectUsed.java + + bazel build //pkg:indirect_used \ + --extra_toolchains=//pkg:java_toolchain_definition \ + >& $TEST_log && fail "build succeeded, but expected it to fail due to strict deps violation" + + # Check that it's a strict deps error, not an unused deps error + expect_log "is not visible from target '//pkg:indirect_used'" + + # ------------------------------------------------------------- + # Scenario 6: Non-Main Repository Target (Success) + # ------------------------------------------------------------- + mkdir -p ext + cat > ext/MODULE.bazel <<EOF +module(name = "ext") +bazel_dep(name = "rules_java") +EOF + if [[ -n "${RULES_JAVA_OVERRIDE_PATH:-}" ]]; then + cat >> ext/MODULE.bazel <<EOF +local_path_override( + module_name = "rules_java", + path = "${RULES_JAVA_OVERRIDE_PATH}", +) +EOF + fi + + cat > ext/BUILD <<EOF +load("@rules_java//java:java_library.bzl", "java_library") +load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain") +load("@rules_java//java:defs.bzl", "java_package_configuration") + +package_group( + name = "ext_package_spec", + packages = ["//..."], +) + +java_package_configuration( + name = "unused_deps_error_config", + package_specs = [":ext_package_spec"], + unused_deps = "error", +) + +default_java_toolchain( + name = "java_toolchain", + package_configuration = [":unused_deps_error_config"], +) + +java_library(name = "ext_lib", srcs = ["Ext.java"], deps = [":ext_dep"]) +java_library(name = "ext_dep", srcs = ["ExtDep.java"]) +EOF + + echo "public class Ext {}" > ext/Ext.java + echo "public class ExtDep {}" > ext/ExtDep.java + + cat >> MODULE.bazel <<EOF +bazel_dep(name = "ext") +local_path_override( + module_name = "ext", + path = "ext", +) +EOF + + # Build the target in the external repo. Even though ext_dep is unused, + # it should compile successfully because unused deps checking is disabled for non-main repos. + bazel build @ext//:ext_lib \ + --extra_toolchains=@ext//:java_toolchain_definition \ + >& $TEST_log || fail "build of external target failed, but expected to succeed (unused check disabled for external repos)" +} + +function test_extra_args_unused_deps_execution() { + if [[ "${JAVA_TOOLS_ZIP}" == "released" ]]; then + echo "Skipping test_extra_args_unused_deps_execution: released java_tools does not support --experimental_check_unused_deps" + return 0 + fi + + mkdir -p bazel_internal/test_rules pkg + touch bazel_internal/test_rules/BUILD + + cat > bazel_internal/test_rules/rule.bzl <<'EOF' +load("@rules_java//java:defs.bzl", "JavaInfo", "JavaPluginInfo", rules_java_common = "java_common") + +def _custom_compile_rule_impl(ctx): + internal_common = java_common.internal_DO_NOT_USE() + output = ctx.outputs.jar + manifest = ctx.actions.declare_file(ctx.label.name + ".manifest") + + compile_jars = [] + args = ctx.actions.args() + args.add("--experimental_check_unused_deps", "ERROR") + for dep in ctx.attr.deps: + for jar in dep[JavaInfo].compile_jars.to_list(): + compile_jars.append(jar) + args.add("--target_declared_deps", str(dep.label)) + + compile_jars_depset = depset(compile_jars) + + internal_common.create_compilation_action( + ctx, + ctx.attr._java_toolchain[rules_java_common.JavaToolchainInfo], + output, + manifest, + JavaPluginInfo(runtime_deps = []), + compile_jars_depset, + compile_jars_depset, + depset(), + depset(), + depset(), + depset(), + "ERROR", + ctx.label, + sources = depset(ctx.files.srcs), + extra_args = [args], + ) + return [ + DefaultInfo(files = depset([output])), + JavaInfo( + output_jar = output, + compile_jar = output, + ), + ] + +custom_compile_rule = rule( + implementation = _custom_compile_rule_impl, + outputs = { + "jar": "%{name}.jar", + }, + attrs = { + "srcs": attr.label_list(allow_files = [".java"]), + "deps": attr.label_list(), + "_java_toolchain": attr.label(default = "@bazel_tools//tools/jdk:current_java_toolchain"), + }, + fragments = ["java"], + toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], +) +EOF + + cat > pkg/BUILD <<'EOF' +load("@rules_java//java:java_library.bzl", "java_library") +load("//bazel_internal/test_rules:rule.bzl", "custom_compile_rule") + +java_library( + name = "dep", + srcs = ["Dep.java"], +) + +custom_compile_rule( + name = "main", + srcs = ["Main.java"], + deps = [":dep"], +) +EOF + + cat > pkg/Dep.java <<'EOF' +package pkg; +public class Dep {} +EOF + + # 1. Main does not use Dep -> MUST FAIL with unused-deps error + cat > pkg/Main.java <<'EOF' +package pkg; +public class Main {} +EOF + + bazel build //pkg:main >& $TEST_log && fail "Build succeeded, but expected failure due to unused dependency" + expect_log "\[unused-deps\] Dependency '.*:dep' is declared as a direct dependency but is not referenced in jdeps" + + # 2. Main uses Dep -> MUST SUCCEED + cat > pkg/Main.java <<'EOF' +package pkg; +public class Main { + Dep d = new Dep(); +} +EOF + + bazel build //pkg:main >& $TEST_log || fail "Build failed, but expected success when dependency is used" + + # 3. Main does not use Dep, and Dep is removed from deps -> MUST SUCCEED + cat > pkg/Main.java <<'EOF' +package pkg; +public class Main {} +EOF + cat > pkg/BUILD <<'EOF' +load("@rules_java//java:java_library.bzl", "java_library") +load("//bazel_internal/test_rules:rule.bzl", "custom_compile_rule") + +java_library( + name = "dep", + srcs = ["Dep.java"], +) + +custom_compile_rule( + name = "main", + srcs = ["Main.java"], + deps = [], +) +EOF + + bazel build //pkg:main >& $TEST_log || fail "Build failed, but expected success with no unused dependencies" +} + run_suite "Java integration tests"