From 770001e603ca0b6c360f847dae02980c72a04f12 Mon Sep 17 00:00:00 2001 From: Nick Korostelev Date: Fri, 7 Oct 2022 16:33:35 -0700 Subject: [PATCH] Enables compiling against direct dependencies only --- .../lib/rules/java/JavaCompilationHelper.java | 4 +- .../lib/rules/java/JavaConfiguration.java | 6 + .../build/lib/rules/java/JavaImport.java | 291 ++++++++++++++++++ .../lib/rules/java/JavaLibraryHelper.java | 10 +- .../build/lib/rules/java/JavaOptions.java | 15 + .../lib/rules/java/JavaTargetAttributes.java | 6 +- 6 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java 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 8ae3fad9cf94f2..fe3256ff11f51a 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 @@ -777,7 +777,9 @@ private void addArgsAndJarsToAttributes( attributes.addDirectJars(directJars); } - attributes.merge(args); + boolean pruneTransitiveDeps = ruleContext.getFragment(JavaConfiguration.class) + .experimentalPruneTransitiveDeps(); + attributes.merge(args, pruneTransitiveDeps); } private void addLibrariesToAttributesInternal(Iterable deps) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java index 7bebefb95f383c..92599573d1d2c8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java @@ -113,6 +113,7 @@ public enum ImportDepsCheckingLevel { private final boolean multiReleaseDeployJars; private final boolean disallowJavaImportExports; private final boolean disallowJavaImportEmptyJars; + private final boolean experimentalPruneTransitiveDeps; // TODO(dmarting): remove once we have a proper solution for #2539 private final boolean useLegacyBazelJavaTest; @@ -156,6 +157,7 @@ public JavaConfiguration(BuildOptions buildOptions) throws InvalidConfigurationE this.multiReleaseDeployJars = javaOptions.multiReleaseDeployJars; this.disallowJavaImportExports = javaOptions.disallowJavaImportExports; this.disallowJavaImportEmptyJars = javaOptions.disallowJavaImportEmptyJars; + this.experimentalPruneTransitiveDeps = javaOptions.experimentalPruneTransitiveDeps; Map optimizers = javaOptions.bytecodeOptimizers; if (optimizers.size() > 1) { @@ -511,4 +513,8 @@ public boolean experimentalEnableJspecify() { public boolean requireJavaPluginInfo() { return requireJavaPluginInfo; } + + public boolean experimentalPruneTransitiveDeps() { + return experimentalPruneTransitiveDeps; + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java new file mode 100644 index 00000000000000..375aa35e1ab7eb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java @@ -0,0 +1,291 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.rules.java; + +import static com.google.devtools.build.lib.packages.Type.STRING_LIST; + +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; +import com.google.devtools.build.lib.analysis.Allowlist; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.OutputGroupInfo; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; +import com.google.devtools.build.lib.rules.java.JavaConfiguration.ImportDepsCheckingLevel; +import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.JavaOutput; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.Nullable; + +/** An implementation for the "java_import" rule. */ +public class JavaImport implements RuleConfiguredTargetFactory { + private final JavaSemantics semantics; + + protected JavaImport(JavaSemantics semantics) { + this.semantics = semantics; + } + + @Override + @Nullable + public ConfiguredTarget create(RuleContext ruleContext) + throws InterruptedException, RuleErrorException, ActionConflictException { + ImmutableList srcJars = ImmutableList.of(); + ImmutableList jars = collectJars(ruleContext); + Artifact srcJar = ruleContext.getPrerequisiteArtifact("srcjar"); + + if (ruleContext.hasErrors()) { + return null; + } + + checkJarsAttributeEmpty(ruleContext); + + if (exportError(ruleContext)) { + ruleContext.ruleError( + "java_import.exports is no longer supported; use java_import.deps instead"); + } + + ImmutableList targets = + ImmutableList.builder() + .addAll(ruleContext.getPrerequisites("deps")) + .addAll(ruleContext.getPrerequisites("exports")) + .build(); + final JavaCommon common = + new JavaCommon( + ruleContext, + semantics, + /* sources= */ ImmutableList.of(), + targets, + targets, + targets); + semantics.checkRule(ruleContext, common); + + // No need for javac options - no compilation happening here. + ImmutableBiMap.Builder compilationToRuntimeJarMapBuilder = + ImmutableBiMap.builder(); + ImmutableList interfaceJars = + processWithIjarIfNeeded(jars, ruleContext, compilationToRuntimeJarMapBuilder); + + JavaCompilationArtifacts javaArtifacts = collectJavaArtifacts(jars, interfaceJars); + common.setJavaCompilationArtifacts(javaArtifacts); + + boolean neverLink = JavaCommon.isNeverLink(ruleContext); + JavaCompilationArgsProvider javaCompilationArgs = + common.collectJavaCompilationArgs(neverLink, false); + NestedSet transitiveJavaSourceJars = + collectTransitiveJavaSourceJars(ruleContext, srcJar); + if (srcJar != null) { + srcJars = ImmutableList.of(srcJar); + } + + Artifact jdepsArtifact = null; + JavaToolchainProvider toolchain = JavaToolchainProvider.from(ruleContext); + Artifact depsChecker = toolchain.depsChecker(); + if (Allowlist.hasAllowlist(ruleContext, "java_import_deps_checking") + && !Allowlist.isAvailable(ruleContext, "java_import_deps_checking") + && !ruleContext.attributes().get("tags", STRING_LIST).contains("incomplete-deps") + && !jars.isEmpty() + && depsChecker != null) { + jdepsArtifact = + ruleContext.getUniqueDirectoryArtifact( + "_java_import", "jdeps.proto", ruleContext.getBinOrGenfilesDirectory()); + JavaCompilationArgsProvider provider = JavaCompilationArgsProvider.legacyFromTargets(targets); + boolean pruneTransitiveDeps = ruleContext.getFragment(JavaConfiguration.class) + .experimentalPruneTransitiveDeps(); + JavaTargetAttributes attributes = + new JavaTargetAttributes.Builder(semantics) + .merge(provider, pruneTransitiveDeps) + .addDirectJars(provider.getDirectCompileTimeJars()) + .build(); + ImportDepsCheckActionBuilder.newBuilder() + .importDepsChecker(depsChecker) + .bootclasspath(toolchain.getBootclasspath().bootclasspath()) + .declareDeps(attributes.getDirectJars()) + .transitiveDeps(attributes.getCompileTimeClassPath()) + .checkJars(NestedSetBuilder.wrap(Order.STABLE_ORDER, jars)) + .importDepsCheckingLevel(ImportDepsCheckingLevel.ERROR) + .jdepsOutputArtifact(jdepsArtifact) + .ruleLabel(ruleContext.getLabel()) + .buildAndRegister(ruleContext); + } + + // The "neverlink" attribute is transitive, so if it is enabled, we don't add any + // runfiles from this target or its dependencies. + Runfiles runfiles = + neverLink + ? Runfiles.EMPTY + : new Runfiles.Builder( + ruleContext.getWorkspaceName(), + ruleContext.getConfiguration().legacyExternalRunfiles()) + // add the jars to the runfiles + .addArtifacts(javaArtifacts.getRuntimeJars()) + .addTargets(targets, RunfilesProvider.DEFAULT_RUNFILES) + .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES) + .build(); + + RuleConfiguredTargetBuilder ruleBuilder = new RuleConfiguredTargetBuilder(ruleContext); + NestedSetBuilder filesBuilder = NestedSetBuilder.stableOrder(); + filesBuilder.addAll(jars); + + ImmutableBiMap compilationToRuntimeJarMap = + compilationToRuntimeJarMapBuilder.buildOrThrow(); + + NestedSet filesToBuild = filesBuilder.build(); + + JavaRuleOutputJarsProvider.Builder ruleOutputJarsProviderBuilder = + JavaRuleOutputJarsProvider.builder(); + for (Artifact jar : jars) { + ruleOutputJarsProviderBuilder.addJavaOutput( + JavaOutput.builder() + .setClassJar(jar) + .setCompileJar(compilationToRuntimeJarMap.inverse().get(jar)) + .addSourceJar(srcJar) + .build()); + } + + NestedSet proguardSpecs = new ProguardLibrary(ruleContext).collectProguardSpecs(); + + JavaRuleOutputJarsProvider ruleOutputJarsProvider = ruleOutputJarsProviderBuilder.build(); + JavaSourceJarsProvider sourceJarsProvider = + JavaSourceJarsProvider.create(transitiveJavaSourceJars, srcJars); + JavaCompilationArgsProvider compilationArgsProvider = javaCompilationArgs; + + JavaInfo.Builder javaInfoBuilder = JavaInfo.Builder.create(); + common.addTransitiveInfoProviders(ruleBuilder, javaInfoBuilder, filesToBuild, null); + + JavaInfo javaInfo = + javaInfoBuilder + .addProvider(JavaCompilationArgsProvider.class, compilationArgsProvider) + .addProvider(JavaRuleOutputJarsProvider.class, ruleOutputJarsProvider) + .addProvider(JavaSourceJarsProvider.class, sourceJarsProvider) + .setRuntimeJars(javaArtifacts.getRuntimeJars()) + .setJavaConstraints(JavaCommon.getConstraints(ruleContext)) + .setNeverlink(neverLink) + .build(); + + if (jdepsArtifact != null) { + ruleBuilder.addOutputGroup(OutputGroupInfo.VALIDATION, jdepsArtifact); + } + + return ruleBuilder + .setFilesToBuild(filesToBuild) + .addNativeDeclaredProvider(javaInfo) + .add(RunfilesProvider.class, RunfilesProvider.simple(runfiles)) + .addNativeDeclaredProvider(new ProguardSpecProvider(proguardSpecs)) + .addOutputGroup(JavaSemantics.SOURCE_JARS_OUTPUT_GROUP, transitiveJavaSourceJars) + .addOutputGroup( + JavaSemantics.DIRECT_SOURCE_JARS_OUTPUT_GROUP, + NestedSetBuilder.wrap(Order.STABLE_ORDER, sourceJarsProvider.getSourceJars())) + .addOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL, proguardSpecs) + .build(); + } + + private static boolean exportError(RuleContext ruleContext) { + if (!ruleContext.attributes().isAttributeValueExplicitlySpecified("exports")) { + return false; + } + if (ruleContext.getFragment(JavaConfiguration.class).disallowJavaImportExports()) { + return true; + } + return Allowlist.hasAllowlist(ruleContext, "java_import_exports") + && !Allowlist.isAvailable(ruleContext, "java_import_exports"); + } + + private static void checkJarsAttributeEmpty(RuleContext ruleContext) { + if (ruleContext.getPrerequisites("jars").isEmpty() + && ruleContext.getFragment(JavaConfiguration.class).disallowJavaImportEmptyJars() + && Allowlist.hasAllowlist(ruleContext, "java_import_empty_jars") + && !Allowlist.isAvailable(ruleContext, "java_import_empty_jars")) { + ruleContext.ruleError( + "empty java_import.jars is no longer supported " + ruleContext.getLabel()); + } + } + + private NestedSet collectTransitiveJavaSourceJars( + RuleContext ruleContext, Artifact srcJar) { + NestedSetBuilder transitiveJavaSourceJarBuilder = NestedSetBuilder.stableOrder(); + if (srcJar != null) { + transitiveJavaSourceJarBuilder.add(srcJar); + } + for (JavaSourceJarsProvider other : + JavaInfo.getProvidersFromListOfTargets( + JavaSourceJarsProvider.class, ruleContext.getPrerequisites("exports"))) { + transitiveJavaSourceJarBuilder.addTransitive(other.getTransitiveSourceJars()); + } + return transitiveJavaSourceJarBuilder.build(); + } + + private JavaCompilationArtifacts collectJavaArtifacts( + ImmutableList jars, ImmutableList interfaceJars) { + return new JavaCompilationArtifacts.Builder() + .addRuntimeJars(jars) + // interfaceJars Artifacts have proper owner labels + .addInterfaceJarsWithFullJars(interfaceJars, jars) + .build(); + } + + private ImmutableList collectJars(RuleContext ruleContext) { + Set jars = new LinkedHashSet<>(); + for (TransitiveInfoCollection info : ruleContext.getPrerequisites("jars")) { + if (JavaInfo.getProvider(JavaCompilationArgsProvider.class, info) != null) { + ruleContext.attributeError("jars", "should not refer to Java rules"); + } + for (Artifact jar : info.getProvider(FileProvider.class).getFilesToBuild().toList()) { + if (!JavaSemantics.JAR.matches(jar.getFilename())) { + ruleContext.attributeError("jars", jar.getFilename() + " is not a .jar file"); + } else { + if (!jars.add(jar)) { + ruleContext.attributeError("jars", jar.getFilename() + " is a duplicate"); + } + } + } + } + return ImmutableList.copyOf(jars); + } + + private ImmutableList processWithIjarIfNeeded( + ImmutableList jars, + RuleContext ruleContext, + ImmutableMap.Builder compilationToRuntimeJarMap) { + ImmutableList.Builder interfaceJarsBuilder = ImmutableList.builder(); + boolean useIjar = ruleContext.getFragment(JavaConfiguration.class).getUseIjars(); + for (Artifact jar : jars) { + Artifact interfaceJar = + useIjar + ? JavaCompilationHelper.createIjarAction( + ruleContext, + JavaToolchainProvider.from(ruleContext), + jar, + ruleContext.getLabel(), + /* injectingRuleKind */ null, + true) + : jar; + interfaceJarsBuilder.add(interfaceJar); + compilationToRuntimeJarMap.put(interfaceJar, jar); + } + return interfaceJarsBuilder.build(); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java index 41bba1b73f433d..d25d069d2e32a0 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaLibraryHelper.java @@ -24,6 +24,7 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.StrictDepsMode; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaClasspathMode; @@ -404,7 +405,14 @@ private void addDepsToAttributes(JavaTargetAttributes.Builder attributes) { attributes.addDirectJars(mergedDeps.getDirectCompileTimeJars()); } - attributes.addCompileTimeClassPathEntries(mergedDeps.getTransitiveCompileTimeJars()); + boolean pruneTransitiveDeps = ruleContext.getFragment(JavaConfiguration.class) + .experimentalPruneTransitiveDeps(); + + NestedSet localCompileTimeDeps = pruneTransitiveDeps + ? mergedDeps.getDirectCompileTimeJars() + : mergedDeps.getTransitiveCompileTimeJars(); + + attributes.addCompileTimeClassPathEntries(localCompileTimeDeps); attributes.addRuntimeClassPathEntries(mergedRuntimeDeps.getRuntimeJars()); attributes.addRuntimeClassPathEntries(mergedDeps.getRuntimeJars()); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java index a332e90153fb1a..d384082a2874d5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java @@ -618,6 +618,21 @@ public ImportDepsCheckingLevelConverter() { effectTags = {OptionEffectTag.UNKNOWN}, help = "Enable experimental jspecify integration.") public boolean experimentalEnableJspecify; + + @Option( + name = "experimental_prune_transitive_deps", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION, + effectTags = { + OptionEffectTag.LOADING_AND_ANALYSIS, + OptionEffectTag.EXECUTION, + OptionEffectTag.AFFECTS_OUTPUTS}, + metadataTags = {OptionMetadataTag.EXPERIMENTAL}, + help = + "If enabled, compilation is performed against only direct dependencies. Transitive deps " + + "required for compilation must be explicitly added") + public boolean experimentalPruneTransitiveDeps; + @Override public FragmentOptions getHost() { // Note validation actions don't run in host config, so no need copying flags related to that. diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java index fedee3356aa4ef..c60931f6f10fa5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java @@ -129,9 +129,11 @@ public Builder addSourceFiles(Iterable sourceFiles) { } @CanIgnoreReturnValue - public Builder merge(JavaCompilationArgsProvider context) { - Preconditions.checkArgument(!built); + public Builder merge(JavaCompilationArgsProvider context, boolean pruneTransitiveDeps) { addCompileTimeClassPathEntries(context.getTransitiveCompileTimeJars()); + addCompileTimeClassPathEntries( + pruneTransitiveDeps ? context.getDirectCompileTimeJars() + : context.getTransitiveCompileTimeJars()); addRuntimeClassPathEntries(context.getRuntimeJars()); return this; }