Java tutorial
/* * 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.jvm.core.SuggestBuildRules; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; 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.base.Splitter; 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.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * 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 Optional<SuggestBuildRules> suggestBuildRules; private final SourcePathRuleFinder ruleFinder; private final SourcePathResolver resolver; private final ProjectFilesystem filesystem; private final Javac javac; private final ClasspathChecker classpathChecker; private final Optional<DirectToJarOutputSettings> directToJarOutputSettings; private static final Pattern IS_WARNING = Pattern.compile(":\\s*warning:", Pattern.CASE_INSENSITIVE); private static final Pattern IMPORT_FAILURE = Pattern.compile("import ([\\w\\.\\*]*);"); private static final Pattern PACKAGE_FAILURE = Pattern.compile(".*?package ([\\w\\.\\*]*) does not exist"); private static final Pattern ACCESS_FAILURE = Pattern.compile(".*?error: cannot access ([\\w\\.\\*]*)"); private static final Pattern CLASS_NOT_FOUND = Pattern.compile(".*?class file for ([\\w\\.\\*]*) not found"); private static final Pattern CLASS_SYMBOL_NOT_FOUND = Pattern.compile(".*?symbol:\\s*class\\s*([\\w\\.\\*]*)"); private static final ImmutableList<Pattern> MISSING_IMPORT_PATTERNS = ImmutableList.of(IMPORT_FAILURE, PACKAGE_FAILURE, ACCESS_FAILURE, CLASS_NOT_FOUND, CLASS_SYMBOL_NOT_FOUND); @Nullable private static final String LINE_SEPARATOR = System.getProperty("line.separator"); public JavacStep(Path outputDirectory, ClassUsageFileWriter usedClassesFileWriter, Optional<Path> workingDirectory, ImmutableSortedSet<Path> javaSourceFilePaths, Path pathToSrcsList, ImmutableSortedSet<Path> declaredClasspathEntries, Javac javac, JavacOptions javacOptions, BuildTarget invokingRule, Optional<SuggestBuildRules> suggestBuildRules, SourcePathResolver resolver, SourcePathRuleFinder ruleFinder, 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.suggestBuildRules = suggestBuildRules; this.resolver = resolver; this.ruleFinder = ruleFinder; 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))) { Javac javac = getJavac(); JavacExecutionContext javacExecutionContext = JavacExecutionContext.of( new JavacEventSinkToBuckEventBusBridge(firstOrderContext.getBuckEventBus()), stderr, firstOrderContext.getClassLoaderCache(), firstOrderContext.getObjectMapper(), verbosity, firstOrderContext.getCellPathResolver(), firstOrderContext.getJavaPackageFinder(), filesystem, usedClassesFileWriter, firstOrderContext.getEnvironment(), firstOrderContext.getProcessExecutor(), getAbsolutePathsForJavacInputs(javac), directToJarOutputSettings); return performBuild(context, stdout, stderr, javac, javacExecutionContext); } } private StepExecutionResult performBuild(ExecutionContext context, CapturingPrintStream stdout, CapturingPrintStream stderr, Javac javac, JavacExecutionContext javacExecutionContext) throws InterruptedException { int declaredDepsBuildResult = javac.buildWithClasspath(javacExecutionContext, invokingRule, getOptions(context, declaredClasspathEntries), javacOptions.getSafeAnnotationProcessors(), javaSourceFilePaths, pathToSrcsList, workingDirectory, javacOptions.getAbiGenerationMode()); 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) { Optional<String> returnedStderr; returnedStderr = Optional.of(firstOrderStderr); ImmutableList.Builder<String> errorMessage = ImmutableList.builder(); errorMessage.add(firstOrderStderr); if (suggestBuildRules.isPresent()) { processBuildFailureWithFailedImports(context, firstOrderStderr, errorMessage); } else { processGeneralBuildFailure(context, firstOrderStderr); } if (!firstOrderStdout.isEmpty()) { context.postEvent(ConsoleEvent.info("%s", firstOrderStdout)); } if (!firstOrderStderr.isEmpty()) { context.postEvent(ConsoleEvent.severe("%s", Joiner.on("\n").join(errorMessage.build()))); } return returnedStderr; } private void processGeneralBuildFailure(ExecutionContext context, String firstOrderStderr) { ImmutableSet<String> suggestions = ImmutableSet.of(); CompilerErrorEvent evt = CompilerErrorEvent.create(invokingRule, firstOrderStderr, CompilerErrorEvent.CompilerType.Java, suggestions); context.postEvent(evt); } private void processBuildFailureWithFailedImports(ExecutionContext context, String firstOrderStderr, ImmutableList.Builder<String> errorMessage) { ImmutableSet<String> failedImports = findFailedImports(firstOrderStderr); ImmutableSortedSet<String> suggestions = ImmutableSortedSet .copyOf(suggestBuildRules.get().suggest(failedImports)); if (!suggestions.isEmpty()) { appendSuggestionMessage(errorMessage, failedImports, suggestions); } CompilerErrorEvent evt = CompilerErrorEvent.create(invokingRule, firstOrderStderr, CompilerErrorEvent.CompilerType.Java, suggestions); context.postEvent(evt); } private void appendSuggestionMessage(ImmutableList.Builder<String> errorMessage, ImmutableSet<String> failedImports, ImmutableSortedSet<String> suggestions) { String invoker = invokingRule.toString(); errorMessage.add(String.format("Rule %s has failed to build.", invoker)); errorMessage.add(Joiner.on(LINE_SEPARATOR).join(failedImports)); errorMessage.add("Try adding the following deps:"); errorMessage.add(Joiner.on(LINE_SEPARATOR).join(suggestions)); errorMessage.add(""); errorMessage.add(""); } private ImmutableList<Path> getAbsolutePathsForJavacInputs(Javac javac) { return javac.getInputs().stream().flatMap(input -> { com.google.common.base.Optional<BuildRule> rule = com.google.common.base.Optional .fromNullable(ruleFinder.getRule(input).orElse(null)); if (rule instanceof JavaLibrary) { return ((JavaLibrary) rule).getTransitiveClasspaths().stream(); } else { return ImmutableSet.of(resolver.getAbsolutePath(input)).stream(); } }).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 getJavac().getShortName(); } @VisibleForTesting static ImmutableSet<String> findFailedImports(String output) { Iterable<String> lines = Splitter.on(LINE_SEPARATOR).split(output); ImmutableSortedSet.Builder<String> failedImports = ImmutableSortedSet.naturalOrder(); for (String line : lines) { if (IS_WARNING.matcher(line).find()) { continue; } for (Pattern missingImportPattern : MISSING_IMPORT_PATTERNS) { Matcher lineMatch = missingImportPattern.matcher(line); if (lineMatch.matches()) { failedImports.add(lineMatch.group(1)); break; } } } return failedImports.build(); } /** * 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, outputDirectory, context, buildClasspathEntries); } public static ImmutableList<String> getOptions(JavacOptions javacOptions, ProjectFilesystem filesystem, 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); } }, filesystem::resolve); // 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; } }