blob: 492db60c121c386c41bd108b106ac8af2c9d0d4a [file] [log] [blame]
// Copyright 2016 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.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType.UNQUOTED;
import static com.google.devtools.build.lib.rules.java.JavaCompileActionBuilder.UTF8_ENVIRONMENT;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
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.common.collect.Sets;
import com.google.devtools.build.lib.actions.AbstractAction;
import com.google.devtools.build.lib.actions.ActionEnvironment;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.CommandLines;
import com.google.devtools.build.lib.actions.EmptyRunfilesSupplier;
import com.google.devtools.build.lib.actions.ExecutionRequirements;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.StrictDepsMode;
import com.google.devtools.build.lib.cmdline.Label;
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.TargetUtils;
import com.google.devtools.build.lib.rules.java.JavaCompileAction.ProgressMessage;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaClasspathMode;
import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData;
import com.google.devtools.build.lib.util.OnDemandString;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.proto.Deps;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
* Action builder for Java header compilation, to be used if --java_header_compilation is enabled.
*
* <p>The header compiler consumes the inputs of a java compilation, and produces an interface jar
* that can be used as a compile-time jar by upstream targets. The header interface jar is
* equivalent to the output of ijar, but unlike ijar the header compiler operates directly on Java
* source files instead post-processing the class outputs of the compilation. Compiling the
* interface jar from source moves javac off the build's critical path.
*
* <p>The implementation of the header compiler tool can be found under {@code
* //src/java_tools/buildjar/java/com/google/devtools/build/java/turbine}.
*/
public class JavaHeaderCompileActionBuilder {
private static final ParamFileInfo PARAM_FILE_INFO =
ParamFileInfo.builder(UNQUOTED).setCharset(ISO_8859_1).build();
private final RuleContext ruleContext;
private Artifact outputJar;
// Only non-null before set.
private Artifact outputDepsProto;
@Nullable private Artifact manifestOutput;
@Nullable private Artifact gensrcOutputJar;
@Nullable private Artifact resourceOutputJar;
private ImmutableSet<Artifact> additionalOutputs = ImmutableSet.of();
private ImmutableSet<Artifact> sourceFiles = ImmutableSet.of();
private ImmutableList<Artifact> sourceJars = ImmutableList.of();
private NestedSet<Artifact> classpathEntries = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
private NestedSet<Artifact> bootclasspathEntries =
NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
@Nullable private Label targetLabel;
@Nullable private String injectingRuleKind;
private StrictDepsMode strictJavaDeps = StrictDepsMode.OFF;
private NestedSet<Artifact> directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
private NestedSet<Artifact> compileTimeDependencyArtifacts =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private final ImmutableList.Builder<String> javacOptsBuilder = ImmutableList.builder();
private JavaPluginData plugins = JavaPluginData.empty();
private ImmutableList<Artifact> additionalInputs = ImmutableList.of();
private NestedSet<Artifact> toolsJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
private boolean enableHeaderCompilerDirect = true;
private boolean enableDirectClasspath = true;
public JavaHeaderCompileActionBuilder(RuleContext ruleContext) {
this.ruleContext = ruleContext;
}
/** Sets the output jdeps file. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setOutputDepsProto(Artifact outputDepsProto) {
this.outputDepsProto = checkNotNull(outputDepsProto);
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setManifestOutput(@Nullable Artifact manifestOutput) {
this.manifestOutput = manifestOutput;
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setGensrcOutputJar(@Nullable Artifact gensrcOutputJar) {
this.gensrcOutputJar = gensrcOutputJar;
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setResourceOutputJar(@Nullable Artifact resourceOutputJar) {
this.resourceOutputJar = resourceOutputJar;
return this;
}
/** Sets the direct dependency artifacts. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setDirectJars(NestedSet<Artifact> directJars) {
checkNotNull(directJars, "directJars must not be null");
this.directJars = directJars;
return this;
}
/** Sets the .jdeps artifacts for direct dependencies. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setCompileTimeDependencyArtifacts(
NestedSet<Artifact> dependencyArtifacts) {
checkNotNull(dependencyArtifacts, "dependencyArtifacts must not be null");
this.compileTimeDependencyArtifacts = dependencyArtifacts;
return this;
}
/** Adds Java compiler flags. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder addAllJavacOpts(Iterable<String> javacOpts) {
this.javacOptsBuilder.addAll(javacOpts);
return this;
}
/** Adds a Java compiler flag. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder addJavacOpt(String javacOpt) {
this.javacOptsBuilder.add(javacOpt);
return this;
}
/** Sets the output jar. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setOutputJar(Artifact outputJar) {
checkNotNull(outputJar, "outputJar must not be null");
this.outputJar = outputJar;
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setAdditionalOutputs(ImmutableSet<Artifact> outputs) {
checkNotNull(outputs, "outputs must not be null");
this.additionalOutputs = outputs;
return this;
}
/** Adds Java source files to compile. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setSourceFiles(ImmutableSet<Artifact> sourceFiles) {
checkNotNull(sourceFiles, "sourceFiles must not be null");
this.sourceFiles = sourceFiles;
return this;
}
/** Adds a jar archive of Java sources to compile. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setSourceJars(ImmutableList<Artifact> sourceJars) {
checkNotNull(sourceJars, "sourceJars must not be null");
this.sourceJars = sourceJars;
return this;
}
/** Sets the compilation classpath entries. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setClasspathEntries(NestedSet<Artifact> classpathEntries) {
checkNotNull(classpathEntries, "classpathEntries must not be null");
this.classpathEntries = classpathEntries;
return this;
}
/** Sets the compilation bootclasspath entries. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setBootclasspathEntries(
NestedSet<Artifact> bootclasspathEntries) {
checkNotNull(bootclasspathEntries, "bootclasspathEntries must not be null");
this.bootclasspathEntries = bootclasspathEntries;
return this;
}
/** Sets the annotation processors classpath entries. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setPlugins(JavaPluginData plugins) {
checkNotNull(plugins, "plugins must not be null");
checkState(this.plugins.isEmpty());
this.plugins = plugins;
return this;
}
/** Sets the label of the target being compiled. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setTargetLabel(@Nullable Label targetLabel) {
this.targetLabel = targetLabel;
return this;
}
/** Sets the injecting rule kind of the target being compiled. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setInjectingRuleKind(@Nullable String injectingRuleKind) {
this.injectingRuleKind = injectingRuleKind;
return this;
}
/** Sets the Strict Java Deps mode. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setStrictJavaDeps(StrictDepsMode strictJavaDeps) {
checkNotNull(strictJavaDeps, "strictJavaDeps must not be null");
this.strictJavaDeps = strictJavaDeps;
return this;
}
/** Sets additional inputs, e.g. for databinding support. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setAdditionalInputs(
ImmutableList<Artifact> additionalInputs) {
checkNotNull(additionalInputs, "additionalInputs must not be null");
this.additionalInputs = additionalInputs;
return this;
}
/** Sets the tools jars. */
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder setToolsJars(NestedSet<Artifact> toolsJars) {
checkNotNull(toolsJars, "toolsJars must not be null");
this.toolsJars = toolsJars;
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder enableHeaderCompilerDirect(
boolean enableHeaderCompilerDirect) {
this.enableHeaderCompilerDirect = enableHeaderCompilerDirect;
return this;
}
@CanIgnoreReturnValue
public JavaHeaderCompileActionBuilder enableDirectClasspath(boolean enableDirectClasspath) {
this.enableDirectClasspath = enableDirectClasspath;
return this;
}
/** Builds and registers the action for a header compilation. */
public void build(JavaToolchainProvider javaToolchain) throws InterruptedException {
checkNotNull(outputDepsProto, "outputDepsProto must not be null");
checkNotNull(sourceFiles, "sourceFiles must not be null");
checkNotNull(sourceJars, "sourceJars must not be null");
checkNotNull(classpathEntries, "classpathEntries must not be null");
checkNotNull(bootclasspathEntries, "bootclasspathEntries must not be null");
checkNotNull(strictJavaDeps, "strictJavaDeps must not be null");
checkNotNull(directJars, "directJars must not be null");
checkNotNull(compileTimeDependencyArtifacts, "compileTimeDependencyArtifacts must not be null");
ImmutableList<String> javacOpts = javacOptsBuilder.build();
// Invariant: if strictJavaDeps is OFF, then directJars and
// dependencyArtifacts are ignored
if (strictJavaDeps == StrictDepsMode.OFF) {
directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
compileTimeDependencyArtifacts = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
}
// Enable the direct classpath optimization if there are no annotation processors.
// N.B. we only check if the processor classes are empty, we don't care if there is plugin
// data or dependencies if there are no annotation processors to run. This differs from
// javac where java_plugin may be used with processor_class unset to declare Error Prone
// plugins.
boolean useDirectClasspath = enableDirectClasspath && plugins.processorClasses().isEmpty();
// Use the optimized 'direct' implementation if it is available, and either there are no
// annotation processors or they are built in to the tool and listed in
// java_toolchain.header_compiler_direct_processors.
ImmutableSet<String> processorClasses = plugins.processorClasses().toSet();
boolean useHeaderCompilerDirect =
enableHeaderCompilerDirect
&& javaToolchain.getHeaderCompilerDirect() != null
&& javaToolchain.getHeaderCompilerBuiltinProcessors().containsAll(processorClasses);
JavaConfiguration javaConfiguration =
ruleContext.getConfiguration().getFragment(JavaConfiguration.class);
JavaClasspathMode classpathMode = javaConfiguration.getReduceJavaClasspath();
if (!Collections.disjoint(
processorClasses, javaToolchain.getReducedClasspathIncompatibleProcessors())) {
classpathMode = JavaClasspathMode.OFF;
}
ActionEnvironment actionEnvironment =
ruleContext
.getConfiguration()
.getActionEnvironment()
.withAdditionalFixedVariables(UTF8_ENVIRONMENT);
OnDemandString progressMessage =
new ProgressMessage(
/* prefix= */ "Compiling Java headers",
/* output= */ outputJar,
/* sourceFiles= */ sourceFiles,
/* sourceJars= */ sourceJars,
/* plugins= */ plugins);
ImmutableSet.Builder<Artifact> outputs =
ImmutableSet.<Artifact>builder()
.add(outputJar)
.add(outputDepsProto)
.addAll(additionalOutputs);
Stream.of(gensrcOutputJar, resourceOutputJar, manifestOutput)
.filter(x -> x != null)
.forEachOrdered(outputs::add);
NestedSetBuilder<Artifact> mandatoryInputsBuilder =
NestedSetBuilder.<Artifact>stableOrder()
.addAll(additionalInputs)
.addTransitive(bootclasspathEntries)
.addAll(sourceJars)
.addAll(sourceFiles)
.addTransitive(toolsJars);
JavaToolchainTool headerCompiler =
useHeaderCompilerDirect
? javaToolchain.getHeaderCompilerDirect()
: javaToolchain.getHeaderCompiler();
// The header compiler is either a jar file that needs to be executed using
// `java -jar <path>`, or an executable that can be run directly.
headerCompiler.addInputs(javaToolchain, mandatoryInputsBuilder);
CustomCommandLine.Builder commandLine =
CustomCommandLine.builder()
.addExecPath("--output", outputJar)
.addExecPath("--gensrc_output", gensrcOutputJar)
.addExecPath("--resource_output", resourceOutputJar)
.addExecPath("--output_manifest_proto", manifestOutput)
.addExecPath("--output_deps", outputDepsProto)
.addExecPaths("--bootclasspath", bootclasspathEntries)
.addExecPaths("--sources", sourceFiles)
.addExecPaths("--source_jars", sourceJars)
.add("--injecting_rule_kind", injectingRuleKind);
if (!javacOpts.isEmpty()) {
commandLine.addAll("--javacopts", javacOpts);
// terminate --javacopts with `--` to support javac flags that start with `--`
commandLine.add("--");
}
if (targetLabel != null) {
commandLine.add("--target_label");
if (targetLabel.getRepository().isMain()) {
commandLine.addLabel(targetLabel);
} else {
// @-prefixed strings will be assumed to be params filenames and expanded,
// so add an extra @ to escape it.
commandLine.addPrefixedLabel("@", targetLabel);
}
}
ImmutableMap<String, String> executionInfo =
TargetUtils.getExecutionInfo(ruleContext.getRule(), ruleContext.isAllowTagsPropagation());
if (javaConfiguration.inmemoryJdepsFiles()) {
executionInfo =
ImmutableMap.of(
ExecutionRequirements.REMOTE_EXECUTION_INLINE_OUTPUTS,
outputDepsProto.getExecPathString());
}
if (useDirectClasspath) {
NestedSet<Artifact> classpath;
if (!directJars.isEmpty() || classpathEntries.isEmpty()) {
classpath = directJars;
} else {
classpath = classpathEntries;
}
mandatoryInputsBuilder.addTransitive(classpath);
commandLine.addExecPaths("--classpath", classpath);
commandLine.add("--reduce_classpath_mode", "NONE");
NestedSet<Artifact> allInputs = mandatoryInputsBuilder.build();
boolean stripOutputPaths =
JavaCompilationHelper.stripOutputPaths(allInputs, ruleContext.getConfiguration());
@Nullable
PathFragment strippedOutputBase =
stripOutputPaths ? JavaCompilationHelper.outputBase(outputJar) : null;
CustomCommandLine executableLine =
headerCompiler.getCommandLine(javaToolchain, strippedOutputBase);
Consumer<Pair<ActionExecutionContext, List<SpawnResult>>> resultConsumer =
createResultConsumer(
outputDepsProto,
allInputs,
// If classPathMode == BAZEL, also make sure to inject the dependencies to be
// available to downstream actions. Else just do enough work to locally create the
// full .jdeps from the .stripped .jdeps produced on the executor.
/*insertDependencies=*/ classpathMode == JavaClasspathMode.BAZEL,
stripOutputPaths);
if (strippedOutputBase != null) {
commandLine.stripOutputPaths(strippedOutputBase);
}
ruleContext.registerAction(
new SpawnAction(
/* owner= */ ruleContext.getActionOwner(),
/* tools= */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* inputs= */ allInputs,
/* outputs= */ outputs.build(),
/* primaryOutput= */ outputJar,
/* resourceSetOrBuilder= */ AbstractAction.DEFAULT_RESOURCE_SET,
/* commandLines= */ CommandLines.builder()
.addCommandLine(executableLine)
.addCommandLine(commandLine.build(), PARAM_FILE_INFO)
.build(),
/* commandLineLimits= */ ruleContext.getConfiguration().getCommandLineLimits(),
/* isShellCommand= */ false,
/* env= */ actionEnvironment,
/* executionInfo= */ ruleContext
.getConfiguration()
.modifiedExecutionInfo(executionInfo, "Turbine"),
/* progressMessage= */ progressMessage,
/* runfilesSupplier= */ EmptyRunfilesSupplier.INSTANCE,
/* mnemonic= */ "Turbine",
/* executeUnconditionally= */ false,
/* extraActionInfoSupplier= */ null,
/* resultConsumer= */ resultConsumer,
/* stripOutputPaths= */ stripOutputPaths));
return;
}
// If we get here the action requires annotation processing, so add additional inputs and
// flags needed for the javac-based header compiler implementations that supports
// annotation processing.
if (!useHeaderCompilerDirect) {
mandatoryInputsBuilder.addTransitive(plugins.processorClasspath());
mandatoryInputsBuilder.addTransitive(plugins.data());
}
commandLine.addAll(
"--builtin_processors",
Sets.intersection(
plugins.processorClasses().toSet(),
javaToolchain.getHeaderCompilerBuiltinProcessors()));
commandLine.addAll("--processors", plugins.processorClasses());
if (!useHeaderCompilerDirect) {
commandLine.addExecPaths("--processorpath", plugins.processorClasspath());
}
if (strictJavaDeps != StrictDepsMode.OFF) {
commandLine.addExecPaths("--direct_dependencies", directJars);
}
NestedSet<Artifact> mandatoryInputs = mandatoryInputsBuilder.build();
boolean pathStrippingEnabled =
JavaCompilationHelper.stripOutputPaths(
NestedSetBuilder.<Artifact>stableOrder()
.addTransitive(mandatoryInputs)
.addTransitive(classpathEntries)
.addTransitive(compileTimeDependencyArtifacts)
.build(),
ruleContext.getConfiguration());
PathFragment strippedOutputBase =
pathStrippingEnabled ? JavaCompilationHelper.outputBase(outputJar) : null;
if (strippedOutputBase != null) {
commandLine.stripOutputPaths(strippedOutputBase);
}
CustomCommandLine executableLine =
headerCompiler.getCommandLine(javaToolchain, strippedOutputBase);
ruleContext.registerAction(
new JavaCompileAction(
/* compilationType= */ JavaCompileAction.CompilationType.TURBINE,
/* owner= */ ruleContext.getActionOwner(),
/* env= */ actionEnvironment,
/* tools= */ toolsJars,
/* runfilesSupplier= */ EmptyRunfilesSupplier.INSTANCE,
/* progressMessage= */ progressMessage,
/* mandatoryInputs= */ mandatoryInputs,
/* transitiveInputs= */ classpathEntries,
/* directJars= */ directJars,
/* outputs= */ outputs.build(),
/* executionInfo= */ executionInfo,
/* extraActionInfoSupplier= */ null,
/* executableLine= */ executableLine,
/* flagLine= */ commandLine.build(),
/* configuration= */ ruleContext.getConfiguration(),
/* dependencyArtifacts= */ compileTimeDependencyArtifacts,
/* outputDepsProto= */ outputDepsProto,
/* classpathMode= */ classpathMode));
}
/**
* Creates a consumer that reads the produced .jdeps file into memory. Pulled out into a separate
* function to avoid capturing a data member, which would keep the entire builder instance alive.
*/
private static Consumer<Pair<ActionExecutionContext, List<SpawnResult>>> createResultConsumer(
Artifact outputDepsProto,
NestedSet<Artifact> inputs,
boolean insertDependencies,
boolean stripOutputPaths) {
return (Consumer<Pair<ActionExecutionContext, List<SpawnResult>>> & Serializable)
contextAndResults -> {
SpawnResult spawnResult = Iterables.getOnlyElement(contextAndResults.getSecond());
ActionExecutionContext context = contextAndResults.getFirst();
try {
Deps.Dependencies fullOutputDeps =
JavaCompileAction.createFullOutputDeps(
spawnResult, outputDepsProto, inputs, context, stripOutputPaths);
JavaCompileActionContext javaContext =
context.getContext(JavaCompileActionContext.class);
if (insertDependencies && javaContext != null) {
javaContext.insertDependencies(outputDepsProto, fullOutputDeps);
}
} catch (IOException e) {
// Left empty. If we cannot read the .jdeps file now, we will read it later or throw
// an appropriate error then.
}
};
}
}