blob: da741d8b0a9df43b53266832667fa78cbb732958 [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.rules.java.JavaCompileActionBuilder.UTF8_ENVIRONMENT;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
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.BuildConfiguration;
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.rules.java.JavaPluginInfoProvider.JavaPluginInfo;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.LazyString;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
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 final RuleContext ruleContext;
private Artifact outputJar;
@Nullable private Artifact outputDepsProto;
private ImmutableSet<Artifact> sourceFiles = ImmutableSet.of();
private final Collection<Artifact> sourceJars = new ArrayList<>();
private NestedSet<Artifact> classpathEntries = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
private ImmutableList<Artifact> bootclasspathEntries = ImmutableList.of();
@Nullable private Label targetLabel;
@Nullable private String injectingRuleKind;
private PathFragment tempDirectory;
private BuildConfiguration.StrictDepsMode strictJavaDeps = BuildConfiguration.StrictDepsMode.OFF;
private boolean reduceClasspath = true;
private NestedSet<Artifact> directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
private NestedSet<Artifact> compileTimeDependencyArtifacts =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private ImmutableList<String> javacOpts;
private JavaPluginInfo plugins = JavaPluginInfo.empty();
private NestedSet<Artifact> additionalInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private Artifact javacJar;
private NestedSet<Artifact> toolsJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
public JavaHeaderCompileActionBuilder(RuleContext ruleContext) {
this.ruleContext = ruleContext;
}
/** Sets the output jdeps file. */
public JavaHeaderCompileActionBuilder setOutputDepsProto(@Nullable Artifact outputDepsProto) {
this.outputDepsProto = outputDepsProto;
return this;
}
/** Sets the direct dependency artifacts. */
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. */
public JavaHeaderCompileActionBuilder setCompileTimeDependencyArtifacts(
NestedSet<Artifact> dependencyArtifacts) {
checkNotNull(dependencyArtifacts, "dependencyArtifacts must not be null");
this.compileTimeDependencyArtifacts = dependencyArtifacts;
return this;
}
/** Sets Java compiler flags. */
public JavaHeaderCompileActionBuilder setJavacOpts(ImmutableList<String> javacOpts) {
checkNotNull(javacOpts, "javacOpts must not be null");
this.javacOpts = javacOpts;
return this;
}
/** Sets the output jar. */
public JavaHeaderCompileActionBuilder setOutputJar(Artifact outputJar) {
checkNotNull(outputJar, "outputJar must not be null");
this.outputJar = outputJar;
return this;
}
/** Adds Java source files to compile. */
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. */
public JavaHeaderCompileActionBuilder addSourceJars(Collection<Artifact> sourceJars) {
checkNotNull(sourceJars, "sourceJars must not be null");
this.sourceJars.addAll(sourceJars);
return this;
}
/** Sets the compilation classpath entries. */
public JavaHeaderCompileActionBuilder setClasspathEntries(NestedSet<Artifact> classpathEntries) {
checkNotNull(classpathEntries, "classpathEntries must not be null");
this.classpathEntries = classpathEntries;
return this;
}
/** Sets the compilation bootclasspath entries. */
public JavaHeaderCompileActionBuilder setBootclasspathEntries(
ImmutableList<Artifact> bootclasspathEntries) {
checkNotNull(bootclasspathEntries, "bootclasspathEntries must not be null");
this.bootclasspathEntries = bootclasspathEntries;
return this;
}
/** Sets the annotation processors classpath entries. */
public JavaHeaderCompileActionBuilder setPlugins(JavaPluginInfo 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. */
public JavaHeaderCompileActionBuilder setTargetLabel(@Nullable Label targetLabel) {
this.targetLabel = targetLabel;
return this;
}
/** Sets the injecting rule kind of the target being compiled. */
public JavaHeaderCompileActionBuilder setInjectingRuleKind(@Nullable String injectingRuleKind) {
this.injectingRuleKind = injectingRuleKind;
return this;
}
/**
* Sets the path to a temporary directory, e.g. for extracting sourcejar entries to before
* compilation.
*/
public JavaHeaderCompileActionBuilder setTempDirectory(PathFragment tempDirectory) {
checkNotNull(tempDirectory, "tempDirectory must not be null");
this.tempDirectory = tempDirectory;
return this;
}
/** Sets the Strict Java Deps mode. */
public JavaHeaderCompileActionBuilder setStrictJavaDeps(
BuildConfiguration.StrictDepsMode strictJavaDeps) {
checkNotNull(strictJavaDeps, "strictJavaDeps must not be null");
this.strictJavaDeps = strictJavaDeps;
return this;
}
/** Enables reduced classpaths. */
public JavaHeaderCompileActionBuilder setReduceClasspath(boolean reduceClasspath) {
this.reduceClasspath = reduceClasspath;
return this;
}
/** Sets the javabase inputs. */
public JavaHeaderCompileActionBuilder setAdditionalInputs(NestedSet<Artifact> additionalInputs) {
checkNotNull(additionalInputs, "additionalInputs must not be null");
this.additionalInputs = additionalInputs;
return this;
}
/** Sets the javac jar. */
public JavaHeaderCompileActionBuilder setJavacJar(Artifact javacJar) {
checkNotNull(javacJar, "javacJar must not be null");
this.javacJar = javacJar;
return this;
}
/** Sets the tools jars. */
public JavaHeaderCompileActionBuilder setToolsJars(NestedSet<Artifact> toolsJars) {
checkNotNull(toolsJars, "toolsJars must not be null");
this.toolsJars = toolsJars;
return this;
}
/** Builds and registers the action for a header compilation. */
public void build(JavaToolchainProvider javaToolchain, JavaRuntimeInfo hostJavabase) {
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(tempDirectory, "tempDirectory 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");
checkNotNull(javacOpts, "javacOpts must not be null");
// Invariant: if strictJavaDeps is OFF, then directJars and
// dependencyArtifacts are ignored
if (strictJavaDeps == BuildConfiguration.StrictDepsMode.OFF) {
directJars = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
compileTimeDependencyArtifacts = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
}
// The compilation uses API-generating annotation processors and has to fall back to
// javac-turbine.
boolean requiresAnnotationProcessing = !plugins.isEmpty();
SpawnAction.Builder builder = new SpawnAction.Builder();
builder.setEnvironment(
ruleContext.getConfiguration().getActionEnvironment().addFixedVariables(UTF8_ENVIRONMENT));
builder.setProgressMessage(
new ProgressMessage(
this.outputJar, sourceFiles.size() + sourceJars.size(), plugins.processorClasses()));
builder.addTool(javacJar);
builder.addTransitiveTools(toolsJars);
builder.addOutput(outputJar);
builder.addOutput(outputDepsProto);
builder.addTransitiveInputs(additionalInputs);
builder.addInputs(bootclasspathEntries);
builder.addInputs(sourceJars);
builder.addInputs(sourceFiles);
FilesToRunProvider headerCompiler =
(!requiresAnnotationProcessing && javaToolchain.getHeaderCompilerDirect() != null)
? 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.
if (!headerCompiler.getExecutable().getExtension().equals("jar")) {
builder.setExecutable(headerCompiler);
} else {
builder.addTransitiveInputs(hostJavabase.javaBaseInputsMiddleman());
builder.setJarExecutable(
hostJavabase.javaBinaryExecPath(),
headerCompiler.getExecutable(),
javaToolchain.getJvmOptions());
}
CustomCommandLine.Builder commandLine =
CustomCommandLine.builder()
.addExecPath("--output", outputJar)
.addExecPath("--output_deps", outputDepsProto)
.addPath("--temp_dir", tempDirectory)
.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.getPackageIdentifier().getRepository().isDefault()
|| targetLabel.getPackageIdentifier().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);
}
}
// The action doesn't require annotation processing, so use the non-javac-based turbine
// implementation.
if (!requiresAnnotationProcessing) {
NestedSet<Artifact> classpath;
if (!directJars.isEmpty() || classpathEntries.isEmpty()) {
classpath = directJars;
} else {
classpath = classpathEntries;
}
builder.addTransitiveInputs(classpath);
commandLine.addExecPaths("--classpath", classpath);
commandLine.add("--nojavac_fallback");
ruleContext.registerAction(
builder
.addCommandLine(
commandLine.build(), ParamFileInfo.builder(ParameterFileType.UNQUOTED).build())
.setMnemonic("Turbine")
.build(ruleContext));
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.
builder.addTransitiveInputs(classpathEntries);
builder.addTransitiveInputs(plugins.processorClasspath());
builder.addTransitiveInputs(plugins.data());
builder.addTransitiveInputs(compileTimeDependencyArtifacts);
commandLine.addExecPaths("--classpath", classpathEntries);
commandLine.addAll("--processors", plugins.processorClasses());
commandLine.addExecPaths("--processorpath", plugins.processorClasspath());
if (strictJavaDeps != BuildConfiguration.StrictDepsMode.OFF) {
commandLine.addExecPaths("--direct_dependencies", directJars);
if (!compileTimeDependencyArtifacts.isEmpty()) {
commandLine.addExecPaths("--deps_artifacts", compileTimeDependencyArtifacts);
}
}
if (reduceClasspath && strictJavaDeps != BuildConfiguration.StrictDepsMode.OFF) {
commandLine.add("--reduce_classpath");
} else {
commandLine.add("--noreduce_classpath");
}
ruleContext.registerAction(
builder
.addCommandLine(
commandLine.build(),
ParamFileInfo.builder(ParameterFileType.UNQUOTED).setCharset(ISO_8859_1).build())
.setMnemonic("JavacTurbine")
.build(ruleContext));
}
/** Static class to avoid keeping a reference to this builder after build() is called. */
@AutoCodec.VisibleForSerialization
@AutoCodec
static class ProgressMessage extends LazyString {
private final Artifact outputJar;
private final int fileCount;
private final NestedSet<String> processorClasses;
public ProgressMessage(Artifact outputJar, int fileCount, NestedSet<String> processorClasses) {
this.outputJar = outputJar;
this.fileCount = fileCount;
this.processorClasses = processorClasses;
}
@Override
public String toString() {
return String.format(
"Compiling Java headers %s (%d files)%s",
outputJar.prettyPrint(),
fileCount,
processorClasses.isEmpty()
? ""
: processorClasses.toCollection().stream()
.map(name -> name.substring(name.lastIndexOf('.') + 1))
.collect(joining(", ", " and running annotation processors (", ")")));
}
}
}