From 580722590d7d312b525fe8db4f67161715fe49bc Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Wed, 26 Aug 2026 12:53:58 +0100 Subject: [PATCH 1/2] Support extra_args in java_common.create_compilation_action --- .../lib/rules/java/JavaCompilationHelper.java | 7 +++ .../lib/rules/java/JavaCompileAction.java | 39 ++++++++---- .../rules/java/JavaCompileActionBuilder.java | 10 ++++ .../rules/java/JavaHeaderCompileAction.java | 1 + .../lib/rules/java/JavaStarlarkCommon.java | 14 ++++- .../starlarkbuildapi/java/JavaCommonApi.java | 13 +++- .../devtools/build/lib/rules/java/BUILD | 1 + .../java/JavaCompileActionBuilderTest.java | 60 +++++++++++++++++++ 8 files changed, 132 insertions(+), 13 deletions(-) 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 d924b1a65a35ab..d449f7be52e1fe 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 @@ public final class JavaCompilationHelper { private final ImmutableList additionalInputsForDatabinding; private boolean enableJspecify = true; private boolean enableDirectClasspath = true; + private ImmutableList extraCommandLineArgs = ImmutableList.of(); private final String execGroup; public JavaCompilationHelper( @@ -104,6 +106,10 @@ public void enableJspecify(boolean enableJspecify) { this.enableJspecify = enableJspecify; } + public void setExtraCommandLineArgs(ImmutableList extraCommandLineArgs) { + this.extraCommandLineArgs = extraCommandLineArgs; + } + JavaTargetAttributes getAttributes() { if (builtAttributes == null) { builtAttributes = attributes.build(); @@ -298,6 +304,7 @@ && getJavaConfiguration().experimentalEnableJspecify() .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 ce638e13a1355f..1075ba8a8af8c4 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 @@ enum CompilationType { private final ImmutableMap executionInfo; private final CommandLine executableLine; private final CommandLine flagLine; + private final ImmutableList extraCommandLineArgs; private final BuildConfigurationValue configuration; private final OnDemandString progressMessage; @@ -149,6 +150,7 @@ public JavaCompileAction( ExtraActionInfoSupplier extraActionInfoSupplier, CommandLine executableLine, CommandLine flagLine, + ImmutableList extraCommandLineArgs, BuildConfigurationValue configuration, NestedSet dependencyArtifacts, Artifact outputDepsProto, @@ -171,6 +173,7 @@ public JavaCompileAction( 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 @@ protected void computeKey( 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 @@ private JavaSpawn getReducedSpawn( 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,14 @@ private static boolean appendCount(StringBuilder sb, boolean first, int count, S public ExtraActionInfo.Builder getExtraActionInfo(ActionKeyContext actionKeyContext) throws CommandLineExpansionException, InterruptedException { ExtraActionInfo.Builder builder = super.getExtraActionInfo(actionKeyContext); - CommandLines commandLinesWithoutExecutable = + CommandLines.Builder commandLinesBuilder = CommandLines.builder() .addCommandLine(flagLine) - .addCommandLine(getFullClasspathLine()) - .build(); + .addCommandLine(getFullClasspathLine()); + for (CommandLine extraCommandLine : extraCommandLineArgs) { + commandLinesBuilder.addCommandLine(extraCommandLine); + } + CommandLines commandLinesWithoutExecutable = commandLinesBuilder.build(); if (extraActionInfoSupplier != null) { extraActionInfoSupplier.extend(builder, commandLinesWithoutExecutable.allArguments()); } @@ -604,11 +617,15 @@ public PathMapper getPathMapper() { @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 5b0d37636ee5be..6f731ecb3c3589 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 @@ public void extend(ExtraActionInfo.Builder builder, ImmutableList argume private NestedSet extraData = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER); private Label targetLabel; @Nullable private String injectingRuleKind; + private ImmutableList extraCommandLineArgs = ImmutableList.of(); private ImmutableList additionalInputs = ImmutableList.of(); private Artifact genSourceOutput; private JavaCompileOutputs outputs; @@ -260,6 +262,7 @@ public JavaCompileAction build() throws RuleErrorException, InterruptedException /* extraActionInfoSupplier= */ extraActionInfoSupplier, /* executableLine= */ executableLine, /* flagLine= */ buildParamFileContents(javacOpts), + /* extraCommandLineArgs= */ extraCommandLineArgs, /* configuration= */ ruleContext.getConfiguration(), /* dependencyArtifacts= */ compileTimeDependencyArtifacts, /* outputDepsProto= */ outputs.depsProto(), @@ -378,6 +381,13 @@ public JavaCompileActionBuilder setCompileTimeDependencyArtifacts( return this; } + @CanIgnoreReturnValue + public JavaCompileActionBuilder setExtraCommandLineArgs( + ImmutableList extraCommandLineArgs) { + this.extraCommandLineArgs = extraCommandLineArgs; + return this; + } + @CanIgnoreReturnValue public JavaCompileActionBuilder setJavacOpts(ImmutableList copts) { this.javacOpts = Preconditions.checkNotNull(copts); 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 52f9d7114e4748..1b9dfb4913324a 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 @@ public void build(JavaToolchainProvider javaToolchain) /* 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 18b18494682ec7..0bf8ccb1b3ca45 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 @@ -20,8 +20,12 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; 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.starlark.Args; +import com.google.devtools.build.lib.starlarkbuildapi.CommandLineArgsApi; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.Expander; import com.google.devtools.build.lib.analysis.RuleContext; @@ -51,6 +55,7 @@ import com.google.devtools.build.lib.rules.cpp.CppFileTypes; import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi; import com.google.devtools.build.lib.starlarkbuildapi.java.JavaCommonApi; +import net.starlark.java.eval.Dict; import net.starlark.java.eval.EvalException; import net.starlark.java.eval.Sequence; import net.starlark.java.eval.Starlark; @@ -201,7 +206,8 @@ public void createCompilationAction( boolean enableJSpecify, boolean enableDirectClasspath, Sequence additionalInputs, - Sequence additionalOutputs) + Sequence additionalOutputs, + Sequence extraArgs) throws EvalException, TypeException, RuleErrorException, @@ -217,6 +223,11 @@ public void createCompilationAction( .nativeHeader(nativeHeader == Starlark.NONE ? null : (Artifact) nativeHeader) .manifestProto(manifestProto) .build(); + ImmutableList.Builder 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 +272,7 @@ public void createCompilationAction( 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 8867bf9b446f0e..0f53698d68d329 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; @@ -31,6 +32,7 @@ import net.starlark.java.annot.ParamType; import net.starlark.java.annot.StarlarkBuiltin; import net.starlark.java.annot.StarlarkMethod; +import net.starlark.java.eval.Dict; import net.starlark.java.eval.EvalException; import net.starlark.java.eval.NoneType; import net.starlark.java.eval.Sequence; @@ -524,6 +526,14 @@ void createHeaderCompilationAction( @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 +563,8 @@ void createCompilationAction( boolean enableJSpecify, boolean enableDirectClasspath, Sequence additionalInputs, - Sequence additionalOutputs) + Sequence additionalOutputs, + Sequence extraArgs) throws EvalException, TypeException, RuleErrorException, diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/BUILD b/src/test/java/com/google/devtools/build/lib/rules/java/BUILD index 687ea4fbf69783..823042e6137653 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/java/BUILD +++ b/src/test/java/com/google/devtools/build/lib/rules/java/BUILD @@ -63,6 +63,7 @@ java_test( "//src/main/java/com/google/devtools/build/lib/actions", "//src/main/java/com/google/devtools/build/lib/actions:action_input_prefetcher", "//src/main/java/com/google/devtools/build/lib/actions:artifacts", + "//src/main/java/com/google/devtools/build/lib/analysis:configured_target", "//src/main/java/com/google/devtools/build/lib/actions:file_metadata", "//src/main/java/com/google/devtools/build/lib/cmdline", "//src/main/java/com/google/devtools/build/lib/remote:remote_action_file_system", 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 d0b7b6fe328a86..721c764107e3d8 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,64 @@ private static BulkTransferException lostInputException(Artifact artifact, Strin cacheMiss.setFilename(artifact.getExecPathString()); return new BulkTransferException(cacheMiss); } + + @Test + public void testExtraArgsPropagated() throws Exception { + scratch.file("third_party/bazel_rules/rules_java/BUILD"); + scratch.file( + "third_party/bazel_rules/rules_java/rule.bzl", + """ + load("@rules_java//java:defs.bzl", "JavaPluginInfo", rules_java_common = "java_common") + + def _my_rule_impl(ctx): + output = ctx.outputs.jar + manifest = ctx.actions.declare_file(ctx.label.name + ".manifest") + internal_common = java_common.internal_DO_NOT_USE() + args = ctx.actions.args() + args.add("--foo=bar") + internal_common.create_compilation_action( + ctx, + ctx.attr._java_toolchain[rules_java_common.JavaToolchainInfo], + output, + 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", + }, + attrs = { + "_java_toolchain": attr.label(default = "@bazel_tools//tools/jdk:current_java_toolchain"), + }, + fragments = ["java"], + toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], + ) + """); + scratch.file( + "java/com/google/test/BUILD", + """ + load("//third_party/bazel_rules/rules_java:rule.bzl", "my_rule") + + my_rule( + name = "a", + ) + """); + + JavaCompileAction compileAction = + (JavaCompileAction) getGeneratingActionForLabel("//java/com/google/test:a.jar"); + List command = getJavacArguments(compileAction); + assertThat(command).contains("--foo=bar"); + } } From 487d3eb8d7d87662069c2b5c609c6878e280be9a Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Wed, 26 Aug 2026 12:53:58 +0100 Subject: [PATCH 2/2] Add conditional end-to-end integration test for unused deps --- src/test/shell/bazel/bazel_java_test.sh | 320 ++++++++++++++++++++++++ 1 file changed, 320 insertions(+) diff --git a/src/test/shell/bazel/bazel_java_test.sh b/src/test/shell/bazel/bazel_java_test.sh index 0f515a530a9769..38015f60285097 100755 --- a/src/test/shell/bazel/bazel_java_test.sh +++ b/src/test/shell/bazel/bazel_java_test.sh @@ -2531,4 +2531,324 @@ EOF expect_log "foo.txt" } +function test_unused_deps() { + if [[ -n "${RULES_JAVA_OVERRIDE_PATH:-}" ]]; then + cat >> MODULE.bazel < pkg/BUILD </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 <> ext/MODULE.bazel < ext/BUILD < ext/Ext.java + echo "public class ExtDep {}" > ext/ExtDep.java + + cat >> MODULE.bazel <& $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 third_party/bazel_rules/rules_java pkg + touch third_party/bazel_rules/rules_java/BUILD + + cat > third_party/bazel_rules/rules_java/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("//third_party/bazel_rules/rules_java: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("//third_party/bazel_rules/rules_java: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"