blob: df755da7a98f24b0d19e7eb19cfe01c9aa4e2021 [file] [log] [blame]
// 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.buildjar;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.MoreFiles;
import com.google.devtools.build.buildjar.instrumentation.JacocoInstrumentationProcessor;
import com.google.devtools.build.buildjar.javac.BlazeJavacArguments;
import com.google.devtools.build.buildjar.javac.JavacOptions;
import com.google.devtools.build.buildjar.javac.JavacOptions.FilteredJavacopts;
import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin;
import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule;
import com.google.devtools.build.buildjar.javac.plugins.processing.AnnotationProcessingModule;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import javax.annotation.Nullable;
/** All the information needed to perform a single Java library build operation. */
public final class JavaLibraryBuildRequest {
private final ImmutableList<String> javacOpts;
/** The directory relative to which all other paths should be resolved. */
private final Path workDir;
/** Where to store source files generated by annotation processors. */
private final Path sourceGenDir;
/** The path to an output jar for source files generated by annotation processors. */
private final Path generatedSourcesOutputJar;
private final ArrayList<Path> sourceFiles;
private final ImmutableList<Path> sourceJars;
private final ImmutableList<Path> sourcePath;
private final ImmutableList<Path> classPath;
private final ImmutableList<Path> bootClassPath;
private final Path system;
private final ImmutableList<Path> processorPath;
private final List<String> processorNames;
private final Path outputJar;
private final Path nativeHeaderOutput;
@Nullable private final String targetLabel;
@Nullable private final String injectingRuleKind;
private final Path classDir;
private final Path tempDir;
private JacocoInstrumentationProcessor jacocoInstrumentationProcessor;
private final boolean compressJar;
private final OptionsParser.ReduceClasspathMode reduceClasspathMode;
private final int fullClasspathLength;
private final int reducedClasspathLength;
/** Repository for all dependency-related information. */
private final DependencyModule dependencyModule;
/** Repository for information about annotation processor-generated symbols. */
private final AnnotationProcessingModule processingModule;
/** List of plugins that are given to javac. */
private final ImmutableList<BlazeJavaCompilerPlugin> plugins;
private final OptionalInt requestId;
/**
* Constructs a build from a list of command args. Sets the same JavacRunner for both compilation
* and annotation processing.
*
* @param optionsParser the parsed command line args.
* @param extraPlugins extraneous plugins to use in addition to the strict dependency module.
* @throws InvalidCommandLineException on any command line error
*/
public JavaLibraryBuildRequest(
OptionsParser optionsParser, List<BlazeJavaCompilerPlugin> extraPlugins)
throws InvalidCommandLineException, IOException {
this(optionsParser, extraPlugins, new DependencyModule.Builder(), Path.of(""));
}
/**
* Constructs a build from a list of command args. Sets the same JavacRunner for both compilation
* and annotation processing.
*
* @param optionsParser the parsed command line args.
* @param extraPlugins extraneous plugins to use in addition to the strict dependency module.
* @param depsBuilder a preconstructed dependency module builder.
* @param workDir the directory relative to which all other paths should be resolved.
* @throws InvalidCommandLineException on any command line error
*/
public JavaLibraryBuildRequest(
OptionsParser optionsParser,
List<BlazeJavaCompilerPlugin> extraPlugins,
DependencyModule.Builder depsBuilder,
Path workDir)
throws InvalidCommandLineException, IOException {
this(optionsParser, extraPlugins, depsBuilder, workDir, OptionalInt.empty());
}
/**
* Constructs a build from a list of command args. Sets the same JavacRunner for both compilation
* and annotation processing.
*
* @param optionsParser the parsed command line args.
* @param extraPlugins extraneous plugins to use in addition to the strict dependency module.
* @param depsBuilder a preconstructed dependency module builder.
* @param requestId the id of executed request to builder.
* @throws InvalidCommandLineException on any command line error
*/
public JavaLibraryBuildRequest(
OptionsParser optionsParser,
List<BlazeJavaCompilerPlugin> extraPlugins,
DependencyModule.Builder depsBuilder,
OptionalInt requestId)
throws InvalidCommandLineException, IOException {
this(optionsParser, extraPlugins, depsBuilder, Path.of(""), requestId);
}
/**
* Constructs a build from a list of command args. Sets the same JavacRunner for both compilation
* and annotation processing.
*
* @param optionsParser the parsed command line args.
* @param extraPlugins extraneous plugins to use in addition to the strict dependency module.
* @param depsBuilder a preconstructed dependency module builder.
* @throws InvalidCommandLineException on any command line error
*/
public JavaLibraryBuildRequest(
OptionsParser optionsParser,
List<BlazeJavaCompilerPlugin> extraPlugins,
DependencyModule.Builder depsBuilder,
Path workDir,
OptionalInt requestId)
throws InvalidCommandLineException, IOException {
// Keep this first as it is used by asPath.
this.workDir = workDir;
depsBuilder.setDirectJars(
optionsParser.directJars().stream().map(this::asPath).collect(toImmutableSet()));
if (optionsParser.getStrictJavaDeps() != null) {
depsBuilder.setStrictJavaDeps(optionsParser.getStrictJavaDeps());
}
if (optionsParser.getOutputDepsProtoFile() != null) {
depsBuilder.setOutputDepsProtoFile(asPath(optionsParser.getOutputDepsProtoFile()));
}
depsBuilder.addDepsArtifacts(asPaths(optionsParser.getDepsArtifacts()));
depsBuilder.setPlatformJars(
optionsParser.getBootClassPath().stream().map(this::asPath).collect(toImmutableSet()));
if (optionsParser.reduceClasspathMode() != OptionsParser.ReduceClasspathMode.NONE) {
depsBuilder.setReduceClasspath();
}
if (optionsParser.getTargetLabel() != null) {
depsBuilder.setTargetLabel(optionsParser.getTargetLabel());
}
this.dependencyModule = depsBuilder.build();
this.sourceGenDir =
deriveDirectory(optionsParser.getTargetLabel(), optionsParser.getOutputJar(), "_sources");
AnnotationProcessingModule.Builder processingBuilder = AnnotationProcessingModule.builder();
processingBuilder.setSourceGenDir(sourceGenDir);
if (optionsParser.getManifestProtoPath() != null) {
processingBuilder.setManifestProtoPath(asPath(optionsParser.getManifestProtoPath()));
}
this.processingModule = processingBuilder.build();
ImmutableList.Builder<BlazeJavaCompilerPlugin> pluginsBuilder =
ImmutableList.<BlazeJavaCompilerPlugin>builder().add(dependencyModule.getPlugin());
processingModule.registerPlugin(pluginsBuilder);
pluginsBuilder.addAll(extraPlugins);
this.plugins = pluginsBuilder.build();
this.compressJar = optionsParser.compressJar();
this.reduceClasspathMode = optionsParser.reduceClasspathMode();
this.fullClasspathLength = optionsParser.fullClasspathLength();
this.reducedClasspathLength = optionsParser.reducedClasspathLength();
this.sourceFiles = new ArrayList<>(asPaths(optionsParser.getSourceFiles()));
this.sourceJars = asPaths(optionsParser.getSourceJars());
this.classPath = asPaths(optionsParser.getClassPath());
this.sourcePath = asPaths(optionsParser.getSourcePath());
this.bootClassPath = asPaths(optionsParser.getBootClassPath());
this.system = asPath(optionsParser.getSystem());
this.processorPath = asPaths(optionsParser.getProcessorPath());
this.processorNames = optionsParser.getProcessorNames();
this.classDir =
deriveDirectory(optionsParser.getTargetLabel(), optionsParser.getOutputJar(), "_classes");
this.tempDir =
deriveDirectory(optionsParser.getTargetLabel(), optionsParser.getOutputJar(), "_tmp");
this.outputJar = asPath(optionsParser.getOutputJar());
this.nativeHeaderOutput = asPath(optionsParser.getNativeHeaderOutput());
for (Map.Entry<String, List<String>> entry : optionsParser.getPostProcessors().entrySet()) {
switch (entry.getKey()) {
case "jacoco":
this.jacocoInstrumentationProcessor =
JacocoInstrumentationProcessor.create(entry.getValue());
break;
default:
throw new AssertionError("unsupported post-processor " + entry.getKey());
}
}
this.javacOpts = ImmutableList.copyOf(optionsParser.getJavacOpts());
this.generatedSourcesOutputJar = asPath(optionsParser.getGeneratedSourcesOutputJar());
this.targetLabel = optionsParser.getTargetLabel();
this.injectingRuleKind = optionsParser.getInjectingRuleKind();
this.requestId = requestId;
}
/**
* Derive a temporary directory path based on the path to the output jar, to avoid breaking
* fragile assumptions made by the implementation of javahotswap.
*/
// TODO(b/169793789): kill this with fire if javahotswap starts using jars instead of classes
@VisibleForTesting
static Path deriveDirectory(String label, String outputJar, String suffix, Path workDir) {
checkArgument(label != null, "--target_label is required");
checkArgument(outputJar != null, "--output is required");
checkArgument(
label.contains(":"), "--target_label must be a canonical label (containing a `:`)");
Path path = workDir.resolve(outputJar);
String name = MoreFiles.getNameWithoutExtension(path);
String base = label.substring(label.lastIndexOf(':') + 1);
return path.resolveSibling("_javac").resolve(base).resolve(name + suffix);
}
private Path deriveDirectory(String label, String outputJar, String suffix) {
return deriveDirectory(label, outputJar, suffix, workDir);
}
private ImmutableList<Path> asPaths(Collection<String> paths) {
return paths.stream().map(this::asPath).collect(toImmutableList());
}
@Nullable
private Path asPath(@Nullable String path) {
return path != null ? workDir.resolve(path) : null;
}
public ImmutableList<String> getJavacOpts() {
return javacOpts;
}
public Path getSourceGenDir() {
return sourceGenDir;
}
public ImmutableList<Path> getSourcePath() {
return sourcePath;
}
public Path getGeneratedSourcesOutputJar() {
return generatedSourcesOutputJar;
}
public ArrayList<Path> getSourceFiles() {
// TODO(cushon): This is being modified after parsing to add files from source jars.
return sourceFiles;
}
public ImmutableList<Path> getSourceJars() {
return sourceJars;
}
public ImmutableList<Path> getClassPath() {
return classPath;
}
public ImmutableList<Path> getBootClassPath() {
return bootClassPath;
}
public Path getSystem() {
return system;
}
public ImmutableList<Path> getProcessorPath() {
return processorPath;
}
public List<String> getProcessors() {
// TODO(bazel-team): This might be modified by a JavaLibraryBuilder to enable specific
// annotation processors.
return processorNames;
}
public Path getOutputJar() {
return outputJar;
}
public Path getNativeHeaderOutput() {
return nativeHeaderOutput;
}
public Path getClassDir() {
return classDir;
}
public Path getTempDir() {
return tempDir;
}
public Path getNativeHeaderDir() {
return getTempDir().resolve("native_headers");
}
public JacocoInstrumentationProcessor getJacocoInstrumentationProcessor() {
return jacocoInstrumentationProcessor;
}
public boolean compressJar() {
return compressJar;
}
public OptionsParser.ReduceClasspathMode reduceClasspathMode() {
return reduceClasspathMode;
}
public int fullClasspathLength() {
return fullClasspathLength;
}
public int reducedClasspathLength() {
return reducedClasspathLength;
}
public DependencyModule getDependencyModule() {
return dependencyModule;
}
public AnnotationProcessingModule getProcessingModule() {
return processingModule;
}
public ImmutableList<BlazeJavaCompilerPlugin> getPlugins() {
return plugins;
}
public OptionalInt getRequestId() {
return requestId;
}
@Nullable
public String getTargetLabel() {
return targetLabel;
}
@Nullable
public String getInjectingRuleKind() {
return injectingRuleKind;
}
public BlazeJavacArguments toBlazeJavacArguments(ImmutableList<Path> classPath) {
BlazeJavacArguments.Builder builder =
BlazeJavacArguments.builder()
.classPath(classPath)
.classOutput(getClassDir())
.bootClassPath(getBootClassPath())
.system(getSystem())
.sourceFiles(ImmutableList.copyOf(getSourceFiles()))
.sourcePath(getSourcePath())
.sourceOutput(getSourceGenDir())
.processorPath(getProcessorPath())
.plugins(getPlugins())
.requestId(getRequestId());
addJavacArguments(builder);
// Performance optimization: when reduced classpaths are enabled, stop the compilation after
// the first diagnostic that would result in fallback to the transitive classpath. The user
// only sees diagnostics from the fallback compilation, so collecting additional diagnostics
// from the reduced classpath compilation is a waste of time.
// Exception: this doesn't hold if annotation processing is enabled, since diagnostics may be
// resolved during subsequent processing rounds.
if (reduceClasspathMode.equals(OptionsParser.ReduceClasspathMode.BAZEL_REDUCED)
&& getProcessors().isEmpty()) {
builder.failFast(getProcessors().isEmpty());
}
if (getNativeHeaderOutput() != null) {
builder.nativeHeaderOutput(getNativeHeaderDir());
}
return builder.build();
}
/** Constructs a command line that can be used for a javac invocation. */
void addJavacArguments(BlazeJavacArguments.Builder builder) {
FilteredJavacopts filtered = JavacOptions.filterJavacopts(getJavacOpts());
builder.blazeJavacOptions(filtered.bazelJavacopts());
ImmutableList<String> javacOpts = filtered.standardJavacopts();
ImmutableList.Builder<String> javacArguments = ImmutableList.builder();
// default to -implicit:none, but allow the user to override with -implicit:class.
javacArguments.add("-implicit:none");
javacArguments.addAll(javacOpts);
if (!getProcessors().isEmpty() && !getSourceFiles().isEmpty()) {
// ImmutableSet.copyOf maintains order
ImmutableSet<String> deduplicatedProcessorNames = ImmutableSet.copyOf(getProcessors());
javacArguments.add("-processor");
javacArguments.add(Joiner.on(',').join(deduplicatedProcessorNames));
} else {
// This is necessary because some jars contain discoverable annotation processors that
// previously didn't run, and they break builds if the "-proc:none" option is not passed to
// javac.
javacArguments.add("-proc:none");
}
for (String option : javacOpts) {
if (option.startsWith("-J")) { // ignore the VM options.
continue;
}
if (option.equals("-processor") || option.equals("-processorpath")) {
throw new IllegalStateException(
"Using "
+ option
+ " in javacopts is no longer supported."
+ " Use a java_plugin() rule instead.");
}
}
builder.javacOptions(javacArguments.build());
}
}