Skip to content

Commit

Permalink
Enables compiling against direct dependencies only
Browse files Browse the repository at this point in the history
  • Loading branch information
nkoroste authored and rsalvador committed Mar 26, 2023
1 parent 66c390c commit a19d0d7
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,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<? extends TransitiveInfoCollection> deps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Label> optimizers = javaOptions.bytecodeOptimizers;
if (optimizers.size() > 1) {
Expand Down Expand Up @@ -505,4 +507,8 @@ public boolean experimentalEnableJspecify() {
public boolean requireJavaPluginInfo() {
return requireJavaPluginInfo;
}

public boolean experimentalPruneTransitiveDeps() {
return experimentalPruneTransitiveDeps;
}
}
291 changes: 291 additions & 0 deletions src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java
Original file line number Diff line number Diff line change
@@ -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<Artifact> srcJars = ImmutableList.of();
ImmutableList<Artifact> 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<TransitiveInfoCollection> targets =
ImmutableList.<TransitiveInfoCollection>builder()
.addAll(ruleContext.getPrerequisites("deps"))
.addAll(ruleContext.getPrerequisites("exports"))
.build();
final JavaCommon common =
new JavaCommon(
ruleContext,
semantics,
/* sources= */ ImmutableList.<Artifact>of(),
targets,
targets,
targets);
semantics.checkRule(ruleContext, common);

// No need for javac options - no compilation happening here.
ImmutableBiMap.Builder<Artifact, Artifact> compilationToRuntimeJarMapBuilder =
ImmutableBiMap.builder();
ImmutableList<Artifact> 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<Artifact> 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<Artifact> filesBuilder = NestedSetBuilder.stableOrder();
filesBuilder.addAll(jars);

ImmutableBiMap<Artifact, Artifact> compilationToRuntimeJarMap =
compilationToRuntimeJarMapBuilder.buildOrThrow();

NestedSet<Artifact> 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<Artifact> 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<Artifact> collectTransitiveJavaSourceJars(
RuleContext ruleContext, Artifact srcJar) {
NestedSetBuilder<Artifact> 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<Artifact> jars, ImmutableList<Artifact> interfaceJars) {
return new JavaCompilationArtifacts.Builder()
.addRuntimeJars(jars)
// interfaceJars Artifacts have proper owner labels
.addInterfaceJarsWithFullJars(interfaceJars, jars)
.build();
}

private ImmutableList<Artifact> collectJars(RuleContext ruleContext) {
Set<Artifact> 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<Artifact> processWithIjarIfNeeded(
ImmutableList<Artifact> jars,
RuleContext ruleContext,
ImmutableMap.Builder<Artifact, Artifact> compilationToRuntimeJarMap) {
ImmutableList.Builder<Artifact> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Artifact> localCompileTimeDeps = pruneTransitiveDeps
? mergedDeps.getDirectCompileTimeJars()
: mergedDeps.getTransitiveCompileTimeJars();

attributes.addCompileTimeClassPathEntries(localCompileTimeDeps);
attributes.addRuntimeClassPathEntries(mergedRuntimeDeps.getRuntimeJars());
attributes.addRuntimeClassPathEntries(mergedDeps.getRuntimeJars());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,20 @@ public ImportDepsCheckingLevelConverter() {
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 getExec() {
// Note validation actions don't run in exec config, so no need copying flags related to that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@ public Builder addSourceFiles(Iterable<Artifact> 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;
}
Expand Down

0 comments on commit a19d0d7

Please sign in to comment.