/* * 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.command; import com.facebook.buck.android.AndroidPlatformTarget; import com.facebook.buck.artifact_cache.ArtifactCache; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.event.ThrowableConsoleEvent; import com.facebook.buck.io.BuckPaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.core.JavaPackageFinder; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildId; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.ActionGraph; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildEngine; import com.facebook.buck.rules.BuildEngineBuildContext; import com.facebook.buck.rules.BuildEngineResult; import com.facebook.buck.rules.BuildEvent; import com.facebook.buck.rules.BuildResult; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.Cell; import com.facebook.buck.rules.RuleKeyDiagnosticsMode; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.shell.WorkerProcessPool; import com.facebook.buck.step.AdbOptions; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.ExecutorPool; import com.facebook.buck.step.StepFailedException; import com.facebook.buck.step.TargetDevice; import com.facebook.buck.step.TargetDeviceOptions; import com.facebook.buck.timing.Clock; import com.facebook.buck.util.Console; import com.facebook.buck.util.ExceptionWithHumanReadableMessage; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.concurrent.ConcurrencyLimit; import com.facebook.buck.util.environment.Platform; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.io.Files; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.Closeable; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.nio.file.Path; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.immutables.value.Value; public class Build implements Closeable { private static final Logger LOG = Logger.get(Build.class); private final ActionGraph actionGraph; private final BuildRuleResolver ruleResolver; private final Cell rootCell; private final ExecutionContext executionContext; private final ArtifactCache artifactCache; private final BuildEngine buildEngine; private final JavaPackageFinder javaPackageFinder; private final Clock clock; public Build( ActionGraph actionGraph, BuildRuleResolver ruleResolver, Cell rootCell, Optional<TargetDevice> targetDevice, Supplier<AndroidPlatformTarget> androidPlatformTargetSupplier, BuildEngine buildEngine, ArtifactCache artifactCache, JavaPackageFinder javaPackageFinder, Console console, long defaultTestTimeoutMillis, boolean isCodeCoverageEnabled, boolean isInclNoLocationClassesEnabled, boolean isDebugEnabled, boolean shouldReportAbsolutePaths, RuleKeyDiagnosticsMode ruleKeyDiagnosticsMode, BuckEventBus eventBus, Platform platform, ImmutableMap<String, String> environment, Clock clock, ConcurrencyLimit concurrencyLimit, Optional<AdbOptions> adbOptions, Optional<TargetDeviceOptions> targetDeviceOptions, Optional<ConcurrentMap<String, WorkerProcessPool>> persistentWorkerPools, ProcessExecutor processExecutor, Map<ExecutorPool, ListeningExecutorService> executors) { this.actionGraph = actionGraph; this.ruleResolver = ruleResolver; this.rootCell = rootCell; this.executionContext = ExecutionContext.builder() .setConsole(console) .setAndroidPlatformTargetSupplier(androidPlatformTargetSupplier) .setTargetDevice(targetDevice) .setDefaultTestTimeoutMillis(defaultTestTimeoutMillis) .setCodeCoverageEnabled(isCodeCoverageEnabled) .setInclNoLocationClassesEnabled(isInclNoLocationClassesEnabled) .setDebugEnabled(isDebugEnabled) .setRuleKeyDiagnosticsMode(ruleKeyDiagnosticsMode) .setShouldReportAbsolutePaths(shouldReportAbsolutePaths) .setBuckEventBus(eventBus) .setPlatform(platform) .setEnvironment(environment) .setJavaPackageFinder(javaPackageFinder) .setConcurrencyLimit(concurrencyLimit) .setAdbOptions(adbOptions) .setPersistentWorkerPools(persistentWorkerPools) .setTargetDeviceOptions(targetDeviceOptions) .setExecutors(executors) .setCellPathResolver(rootCell.getCellPathResolver()) .setProcessExecutor(processExecutor) .build(); this.artifactCache = artifactCache; this.buildEngine = buildEngine; this.javaPackageFinder = javaPackageFinder; this.clock = clock; } public BuildRuleResolver getRuleResolver() { return ruleResolver; } public ExecutionContext getExecutionContext() { return executionContext; } /** * When the user overrides the configured buck-out directory via the `.buckconfig` and also sets * the `project.buck_out_compat_link` setting to `true`, we symlink the original output path * (`buck-out/`) to this newly configured location for backwards compatibility. */ private void createConfiguredBuckOutSymlinks() throws IOException { for (Cell cell : rootCell.getAllCells()) { BuckConfig buckConfig = cell.getBuckConfig(); ProjectFilesystem filesystem = cell.getFilesystem(); BuckPaths configuredPaths = filesystem.getBuckPaths(); if (!configuredPaths.getConfiguredBuckOut().equals(configuredPaths.getBuckOut()) && buckConfig.getBuckOutCompatLink() && Platform.detect() != Platform.WINDOWS) { BuckPaths unconfiguredPaths = configuredPaths.withConfiguredBuckOut(configuredPaths.getBuckOut()); ImmutableMap<Path, Path> paths = ImmutableMap.of( unconfiguredPaths.getGenDir(), configuredPaths.getGenDir(), unconfiguredPaths.getScratchDir(), configuredPaths.getScratchDir()); for (Map.Entry<Path, Path> entry : paths.entrySet()) { filesystem.deleteRecursivelyIfExists(entry.getKey()); filesystem.createSymLink( entry.getKey(), entry.getKey().getParent().relativize(entry.getValue()), /* force */ false); } } } } /** * If {@code isKeepGoing} is false, then this returns a future that succeeds only if all of {@code * rulesToBuild} build successfully. Otherwise, this returns a future that should always succeed, * even if individual rules fail to build. In that case, a failed build rule is indicated by a * {@code null} value in the corresponding position in the iteration order of {@code * rulesToBuild}. * * @param targetish The targets to build. All targets in this iterable must be unique. */ @SuppressWarnings("PMD.EmptyCatchBlock") public BuildExecutionResult executeBuild( Iterable<? extends BuildTarget> targetish, boolean isKeepGoing) throws IOException, ExecutionException, InterruptedException { BuildId buildId = executionContext.getBuildId(); BuildEngineBuildContext buildContext = BuildEngineBuildContext.builder() .setBuildContext( BuildContext.builder() .setActionGraph(actionGraph) .setSourcePathResolver( new SourcePathResolver(new SourcePathRuleFinder(ruleResolver))) .setJavaPackageFinder(javaPackageFinder) .setEventBus(executionContext.getBuckEventBus()) .setAndroidPlatformTargetSupplier( executionContext.getAndroidPlatformTargetSupplier()) .build()) .setClock(clock) .setArtifactCache(artifactCache) .setBuildId(buildId) .putAllEnvironment(executionContext.getEnvironment()) .setKeepGoing(isKeepGoing) .build(); // It is important to use this logic to determine the set of rules to build rather than // build.getActionGraph().getNodesWithNoIncomingEdges() because, due to graph enhancement, // there could be disconnected subgraphs in the DependencyGraph that we do not want to build. ImmutableSet<BuildTarget> targetsToBuild = StreamSupport.stream(targetish.spliterator(), false) .collect(MoreCollectors.toImmutableSet()); // It is important to use this logic to determine the set of rules to build rather than // build.getActionGraph().getNodesWithNoIncomingEdges() because, due to graph enhancement, // there could be disconnected subgraphs in the DependencyGraph that we do not want to build. ImmutableList<BuildRule> rulesToBuild = ImmutableList.copyOf( targetsToBuild .stream() .map( buildTarget -> { try { return getRuleResolver().requireRule(buildTarget); } catch (NoSuchBuildTargetException e) { throw new HumanReadableException( "No build rule found for target %s", buildTarget); } }) .collect(MoreCollectors.toImmutableSet())); // Calculate and post the number of rules that need to built. int numRules = buildEngine.getNumRulesToBuild(rulesToBuild); getExecutionContext() .getBuckEventBus() .post(BuildEvent.ruleCountCalculated(targetsToBuild, numRules)); // Setup symlinks required when configuring the output path. createConfiguredBuckOutSymlinks(); List<BuildEngineResult> futures = rulesToBuild .stream() .map(rule -> buildEngine.build(buildContext, executionContext, rule)) .collect(MoreCollectors.toImmutableList()); // Get the Future representing the build and then block until everything is built. ListenableFuture<List<BuildResult>> buildFuture = Futures.allAsList( futures.stream().map(BuildEngineResult::getResult).collect(Collectors.toList())); List<BuildResult> results; try { results = buildFuture.get(); if (!isKeepGoing) { for (BuildResult result : results) { Throwable thrown = result.getFailure(); if (thrown != null) { throw new ExecutionException(thrown); } } } } catch (ExecutionException | InterruptedException | RuntimeException e) { Throwable t = Throwables.getRootCause(e); if (e instanceof InterruptedException || t instanceof InterruptedException || t instanceof ClosedByInterruptException) { try { buildFuture.cancel(true); } catch (CancellationException ignored) { // Rethrow original InterruptedException instead. } Thread.currentThread().interrupt(); } throw e; } // Insertion order matters LinkedHashMap<BuildRule, Optional<BuildResult>> resultBuilder = new LinkedHashMap<>(); Preconditions.checkState(rulesToBuild.size() == results.size()); for (int i = 0, len = rulesToBuild.size(); i < len; i++) { BuildRule rule = rulesToBuild.get(i); resultBuilder.put(rule, Optional.ofNullable(results.get(i))); } return BuildExecutionResult.builder() .setFailures(FluentIterable.from(results).filter(input -> input.getSuccess() == null)) .setResults(resultBuilder) .build(); } private String getFailureMessage(Throwable thrown) { return "BUILD FAILED: " + thrown.getMessage(); } private String getFailureMessageWithClassName(Throwable thrown) { return "BUILD FAILED: " + thrown.getClass().getName() + " " + thrown.getMessage(); } public int executeAndPrintFailuresToEventBus( Iterable<BuildTarget> targetsish, boolean isKeepGoing, BuckEventBus eventBus, Console console, Optional<Path> pathToBuildReport) throws InterruptedException { int exitCode; try { try { BuildExecutionResult buildExecutionResult = executeBuild(targetsish, isKeepGoing); SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); BuildReport buildReport = new BuildReport(buildExecutionResult, pathResolver); if (isKeepGoing) { String buildReportText = buildReport.generateForConsole(console); buildReportText = buildReportText.isEmpty() ? "Failure report is empty." : // Remove trailing newline from build report. buildReportText.substring(0, buildReportText.length() - 1); eventBus.post(ConsoleEvent.info(buildReportText)); exitCode = buildExecutionResult.getFailures().isEmpty() ? 0 : 1; if (exitCode != 0) { eventBus.post(ConsoleEvent.severe("Not all rules succeeded.")); } } else { exitCode = 0; } if (pathToBuildReport.isPresent()) { // Note that pathToBuildReport is an absolute path that may exist outside of the project // root, so it is not appropriate to use ProjectFilesystem to write the output. String jsonBuildReport = buildReport.generateJsonBuildReport(); try { Files.write(jsonBuildReport, pathToBuildReport.get().toFile(), Charsets.UTF_8); } catch (IOException e) { eventBus.post(ThrowableConsoleEvent.create(e, "Failed writing report")); exitCode = 1; } } } catch (ExecutionException | RuntimeException e) { // This is likely a checked exception that was caught while building a build rule. Throwable cause = e.getCause(); if (cause == null) { Throwables.throwIfInstanceOf(e, RuntimeException.class); throw new RuntimeException(e); } Throwables.throwIfInstanceOf(cause, IOException.class); Throwables.throwIfInstanceOf(cause, StepFailedException.class); Throwables.throwIfInstanceOf(cause, InterruptedException.class); Throwables.throwIfInstanceOf(cause, ClosedByInterruptException.class); Throwables.throwIfInstanceOf(cause, HumanReadableException.class); if (cause instanceof ExceptionWithHumanReadableMessage) { throw new HumanReadableException((ExceptionWithHumanReadableMessage) cause); } LOG.debug(e, "Got an exception during the build."); throw new RuntimeException(e); } } catch (IOException e) { LOG.debug(e, "Got an exception during the build."); eventBus.post(ConsoleEvent.severe(getFailureMessageWithClassName(e))); exitCode = 1; } catch (StepFailedException e) { LOG.debug(e, "Got an exception during the build."); eventBus.post(ConsoleEvent.severe(getFailureMessage(e))); exitCode = 1; } return exitCode; } @Override public void close() throws IOException { executionContext.close(); } @Value.Immutable @BuckStyleImmutable abstract static class AbstractBuildExecutionResult { /** * @return Keys are build rules built during this invocation of Buck. Values reflect the success * of each build rule, if it succeeded. ({@link Optional#empty()} represents a failed build * rule.) */ public abstract Map<BuildRule, Optional<BuildResult>> getResults(); public abstract ImmutableSet<BuildResult> getFailures(); } }