/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.jvm.java;
import com.facebook.buck.event.CompilerErrorEvent;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.facebook.buck.util.CapturingPrintStream;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.Verbosity;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
/** Command used to compile java libraries with a variety of ways to handle dependencies. */
public class JavacStep implements Step {
private final Path outputDirectory;
private final ClassUsageFileWriter usedClassesFileWriter;
private final Optional<Path> workingDirectory;
private final ImmutableSortedSet<Path> javaSourceFilePaths;
private final Path pathToSrcsList;
private final JavacOptions javacOptions;
private final ImmutableSortedSet<Path> declaredClasspathEntries;
private final BuildTarget invokingRule;
private final SourcePathResolver resolver;
private final ProjectFilesystem filesystem;
private final Javac javac;
private final ClasspathChecker classpathChecker;
private final Optional<DirectToJarOutputSettings> directToJarOutputSettings;
public JavacStep(
Path outputDirectory,
ClassUsageFileWriter usedClassesFileWriter,
Optional<Path> workingDirectory,
ImmutableSortedSet<Path> javaSourceFilePaths,
Path pathToSrcsList,
ImmutableSortedSet<Path> declaredClasspathEntries,
Javac javac,
JavacOptions javacOptions,
BuildTarget invokingRule,
SourcePathResolver resolver,
ProjectFilesystem filesystem,
ClasspathChecker classpathChecker,
Optional<DirectToJarOutputSettings> directToJarOutputSettings) {
this.outputDirectory = outputDirectory;
this.usedClassesFileWriter = usedClassesFileWriter;
this.workingDirectory = workingDirectory;
this.javaSourceFilePaths = javaSourceFilePaths;
this.pathToSrcsList = pathToSrcsList;
this.javacOptions = javacOptions;
this.declaredClasspathEntries = declaredClasspathEntries;
this.javac = javac;
this.invokingRule = invokingRule;
this.resolver = resolver;
this.filesystem = filesystem;
this.classpathChecker = classpathChecker;
this.directToJarOutputSettings = directToJarOutputSettings;
}
@Override
public final StepExecutionResult execute(ExecutionContext context)
throws IOException, InterruptedException {
return tryBuildWithFirstOrderDeps(context, filesystem);
}
private StepExecutionResult tryBuildWithFirstOrderDeps(
ExecutionContext context, ProjectFilesystem filesystem)
throws InterruptedException, IOException {
try {
javacOptions.validateOptions(classpathChecker::validateClasspath);
} catch (IOException e) {
context.postEvent(ConsoleEvent.severe("Invalid Java compiler options: %s", e.getMessage()));
return StepExecutionResult.ERROR;
}
Verbosity verbosity =
context.getVerbosity().isSilent() ? Verbosity.STANDARD_INFORMATION : context.getVerbosity();
try (CapturingPrintStream stdout = new CapturingPrintStream();
CapturingPrintStream stderr = new CapturingPrintStream();
ExecutionContext firstOrderContext =
context.createSubContext(stdout, stderr, Optional.of(verbosity))) {
JavacExecutionContext javacExecutionContext =
JavacExecutionContext.of(
new JavacEventSinkToBuckEventBusBridge(firstOrderContext.getBuckEventBus()),
stderr,
firstOrderContext.getClassLoaderCache(),
verbosity,
firstOrderContext.getCellPathResolver(),
firstOrderContext.getJavaPackageFinder(),
filesystem,
usedClassesFileWriter,
firstOrderContext.getEnvironment(),
firstOrderContext.getProcessExecutor(),
getAbsolutePathsForJavacInputs(getJavac()),
directToJarOutputSettings);
return performBuild(context, stdout, stderr, getJavac(), javacExecutionContext);
}
}
private StepExecutionResult performBuild(
ExecutionContext context,
CapturingPrintStream stdout,
CapturingPrintStream stderr,
Javac javac,
JavacExecutionContext javacExecutionContext)
throws InterruptedException {
ImmutableList<JavacPluginJsr199Fields> pluginFields =
ImmutableList.copyOf(
javacOptions
.getAnnotationProcessingParams()
.getAnnotationProcessors(filesystem, resolver)
.stream()
.map(ResolvedJavacPluginProperties::getJavacPluginJsr199Fields)
.collect(Collectors.toList()));
int declaredDepsBuildResult =
javac.buildWithClasspath(
javacExecutionContext,
invokingRule,
getOptions(context, declaredClasspathEntries),
pluginFields,
javaSourceFilePaths,
pathToSrcsList,
workingDirectory,
javacOptions.getCompilationMode());
String firstOrderStdout = stdout.getContentsAsString(Charsets.UTF_8);
String firstOrderStderr = stderr.getContentsAsString(Charsets.UTF_8);
Optional<String> returnedStderr;
if (declaredDepsBuildResult != 0) {
returnedStderr = processBuildFailure(context, firstOrderStdout, firstOrderStderr);
} else {
returnedStderr = Optional.empty();
}
return StepExecutionResult.of(declaredDepsBuildResult, returnedStderr);
}
private Optional<String> processBuildFailure(
ExecutionContext context, String firstOrderStdout, String firstOrderStderr) {
ImmutableList.Builder<String> errorMessage = ImmutableList.builder();
errorMessage.add(firstOrderStderr);
ImmutableSet<String> suggestions = ImmutableSet.of();
CompilerErrorEvent evt =
CompilerErrorEvent.create(
invokingRule, firstOrderStderr, CompilerErrorEvent.CompilerType.Java, suggestions);
context.postEvent(evt);
if (!firstOrderStdout.isEmpty()) {
context.postEvent(ConsoleEvent.info("%s", firstOrderStdout));
}
return Optional.of(Joiner.on("\n").join(errorMessage.build()));
}
private ImmutableList<Path> getAbsolutePathsForJavacInputs(Javac javac) {
return javac
.getInputs()
.stream()
.map(resolver::getAbsolutePath)
.collect(MoreCollectors.toImmutableList());
}
@VisibleForTesting
Javac getJavac() {
return javac;
}
@Override
public String getDescription(ExecutionContext context) {
return getJavac()
.getDescription(
getOptions(context, getClasspathEntries()), javaSourceFilePaths, pathToSrcsList);
}
@Override
public String getShortName() {
return javacOptions.getCompilationMode() != Javac.CompilationMode.ABI
? getJavac().getShortName()
: "calculate_abi_from_source";
}
/**
* Returns a list of command-line options to pass to javac. These options reflect the
* configuration of this javac command.
*
* @param context the ExecutionContext with in which javac will run
* @return list of String command-line options.
*/
@VisibleForTesting
ImmutableList<String> getOptions(
ExecutionContext context, ImmutableSortedSet<Path> buildClasspathEntries) {
return getOptions(
javacOptions, filesystem, resolver, outputDirectory, context, buildClasspathEntries);
}
public static ImmutableList<String> getOptions(
JavacOptions javacOptions,
ProjectFilesystem filesystem,
SourcePathResolver pathResolver,
Path outputDirectory,
ExecutionContext context,
ImmutableSortedSet<Path> buildClasspathEntries) {
final ImmutableList.Builder<String> builder = ImmutableList.builder();
javacOptions.appendOptionsTo(
new OptionsConsumer() {
@Override
public void addOptionValue(String option, String value) {
builder.add("-" + option).add(value);
}
@Override
public void addFlag(String flagName) {
builder.add("-" + flagName);
}
@Override
public void addExtras(Collection<String> extras) {
builder.addAll(extras);
}
},
pathResolver,
filesystem);
// verbose flag, if appropriate.
if (context.getVerbosity().shouldUseVerbosityFlagIfAvailable()) {
builder.add("-verbose");
}
// Specify the output directory.
builder.add("-d").add(filesystem.resolve(outputDirectory).toString());
// Build up and set the classpath.
if (!buildClasspathEntries.isEmpty()) {
String classpath = Joiner.on(File.pathSeparator).join(buildClasspathEntries);
builder.add("-classpath", classpath);
} else {
builder.add("-classpath", "''");
}
return builder.build();
}
/** @return The classpath entries used to invoke javac. */
@VisibleForTesting
ImmutableSortedSet<Path> getClasspathEntries() {
return declaredClasspathEntries;
}
@VisibleForTesting
ImmutableSortedSet<Path> getSrcs() {
return javaSourceFilePaths;
}
}