blob: baa12932047c17daac4190129d41eeb16ddc4fad [file] [log] [blame]
/*
* 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.event.BuckEventBus;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.JavaPackageFinder;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.ActionGraph;
import com.facebook.buck.rules.ArtifactCache;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildDependencies;
import com.facebook.buck.rules.BuildEngine;
import com.facebook.buck.rules.BuildEvent;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleSuccess;
import com.facebook.buck.rules.ImmutableBuildContext;
import com.facebook.buck.step.DefaultStepRunner;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.StepFailedException;
import com.facebook.buck.step.TargetDevice;
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.environment.Platform;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
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.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
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.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
public class Build implements Closeable {
private static final Predicate<Optional<BuildRuleSuccess>> RULES_FAILED_PREDICATE =
new Predicate<Optional<BuildRuleSuccess>>() {
@Override
public boolean apply(Optional<BuildRuleSuccess> input) {
return !input.isPresent();
}
};
private final ActionGraph actionGraph;
private final ExecutionContext executionContext;
private final ArtifactCache artifactCache;
private final BuildEngine buildEngine;
private final DefaultStepRunner stepRunner;
private final JavaPackageFinder javaPackageFinder;
private final BuildDependencies buildDependencies;
private final Clock clock;
/** Not set until {@link #executeBuild(Iterable, boolean)} is invoked. */
@Nullable
private BuildContext buildContext;
/**
* @param buildDependencies How to include dependencies when building rules.
*/
public Build(
ActionGraph actionGraph,
Optional<TargetDevice> targetDevice,
ProjectFilesystem projectFilesystem,
Supplier<AndroidPlatformTarget> androidPlatformTargetSupplier,
BuildEngine buildEngine,
ArtifactCache artifactCache,
ListeningExecutorService service,
JavaPackageFinder javaPackageFinder,
Console console,
long defaultTestTimeoutMillis,
boolean isCodeCoverageEnabled,
boolean isDebugEnabled,
BuildDependencies buildDependencies,
BuckEventBus eventBus,
Platform platform,
ImmutableMap<String, String> environment,
ObjectMapper objectMapper,
Clock clock) {
this.actionGraph = actionGraph;
this.executionContext = ExecutionContext.builder()
.setProjectFilesystem(projectFilesystem)
.setConsole(console)
.setAndroidPlatformTargetSupplier(androidPlatformTargetSupplier)
.setTargetDevice(targetDevice)
.setDefaultTestTimeoutMillis(defaultTestTimeoutMillis)
.setCodeCoverageEnabled(isCodeCoverageEnabled)
.setDebugEnabled(isDebugEnabled)
.setEventBus(eventBus)
.setPlatform(platform)
.setEnvironment(environment)
.setJavaPackageFinder(javaPackageFinder)
.setObjectMapper(objectMapper)
.build();
this.artifactCache = artifactCache;
this.buildEngine = buildEngine;
this.stepRunner = new DefaultStepRunner(executionContext, service);
this.javaPackageFinder = javaPackageFinder;
this.buildDependencies = buildDependencies;
this.clock = clock;
}
public ActionGraph getActionGraph() {
return actionGraph;
}
public ExecutionContext getExecutionContext() {
return executionContext;
}
/** Returns null until {@link #executeBuild(Iterable, boolean)} is invoked. */
@Nullable
public BuildContext getBuildContext() {
return buildContext;
}
/**
* 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 LinkedHashMap<BuildRule, Optional<BuildRuleSuccess>> executeBuild(
Iterable<? extends HasBuildTarget> targetish,
boolean isKeepGoing)
throws IOException, StepFailedException, ExecutionException, InterruptedException {
buildContext = ImmutableBuildContext.builder()
.setActionGraph(actionGraph)
.setStepRunner(stepRunner)
.setProjectFilesystem(executionContext.getProjectFilesystem())
.setClock(clock)
.setArtifactCache(artifactCache)
.setJavaPackageFinder(javaPackageFinder)
.setEventBus(executionContext.getBuckEventBus())
.setAndroidBootclasspathSupplier(BuildContext.createBootclasspathSupplier(
executionContext.getAndroidPlatformTargetSupplier()))
.setBuildDependencies(buildDependencies)
.setBuildId(executionContext.getBuildId())
.putAllEnvironment(executionContext.getEnvironment())
.build();
ImmutableSet<BuildTarget> targetsToBuild = FluentIterable.from(targetish)
.transform(HasBuildTarget.TO_TARGET)
.toSet();
// 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(
FluentIterable
.from(targetsToBuild)
.transform(new Function<HasBuildTarget, BuildRule>() {
@Override
public BuildRule apply(HasBuildTarget hasBuildTarget) {
return Preconditions.checkNotNull(
actionGraph.findBuildRuleByTarget(hasBuildTarget.getBuildTarget()));
}
})
.toSet());
// Calculate and post the number of rules that need to built.
int numRules = getNumRulesToBuild(targetsToBuild, actionGraph);
getExecutionContext().getBuckEventBus().post(
BuildEvent.ruleCountCalculated(
targetsToBuild,
numRules));
List<ListenableFuture<BuildRuleSuccess>> futures = FluentIterable.from(rulesToBuild)
.transform(
new Function<BuildRule, ListenableFuture<BuildRuleSuccess>>() {
@Override
public ListenableFuture<BuildRuleSuccess> apply(BuildRule rule) {
return buildEngine.build(buildContext, rule);
}
}).toList();
// Get the Future representing the build and then block until everything is built.
ListenableFuture<List<BuildRuleSuccess>> buildFuture;
if (isKeepGoing) {
buildFuture = Futures.successfulAsList(futures);
} else {
buildFuture = Futures.allAsList(futures);
}
List<BuildRuleSuccess> results;
try {
results = buildFuture.get();
} catch (InterruptedException e) {
try {
buildFuture.cancel(true);
} catch (CancellationException ignored) {
// Rethrow original InterruptedException instead.
}
Thread.currentThread().interrupt();
throw e;
}
// Insertion order matters
LinkedHashMap<BuildRule, Optional<BuildRuleSuccess>> resultBuilder = new LinkedHashMap<>();
Preconditions.checkState(rulesToBuild.size() == results.size());
for (int i = 0, len = rulesToBuild.size(); i < len; i++) {
BuildRule rule = rulesToBuild.get(i);
BuildRuleSuccess success = results.get(i);
resultBuilder.put(rule, Optional.fromNullable(success));
}
return resultBuilder;
}
public int executeAndPrintFailuresToConsole(
Iterable<? extends HasBuildTarget> targetsish,
boolean isKeepGoing,
Console console,
Optional<Path> pathToBuildReport) throws InterruptedException {
int exitCode;
try {
LinkedHashMap<BuildRule, Optional<BuildRuleSuccess>> ruleToResult = executeBuild(
targetsish,
isKeepGoing);
BuildReport buildReport = new BuildReport(ruleToResult);
if (isKeepGoing) {
String buildReportText = buildReport.generateForConsole(console.getAnsi());
// Remove trailing newline from build report.
buildReportText = buildReportText.substring(0, buildReportText.length() - 1);
executionContext.getBuckEventBus().post(ConsoleEvent.info(buildReportText));
exitCode = Iterables.any(ruleToResult.values(), RULES_FAILED_PREDICATE) ? 1 : 0;
if (exitCode != 0) {
console.printBuildFailure("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) {
e.printStackTrace(console.getStdErr());
exitCode = 1;
}
}
} catch (IOException e) {
console.printBuildFailureWithoutStacktrace(e);
exitCode = 1;
} catch (StepFailedException e) {
console.printBuildFailureWithoutStacktrace(e);
exitCode = e.getExitCode();
} catch (ExecutionException e) {
// This is likely a checked exception that was caught while building a build rule.
Throwable cause = e.getCause();
if (cause instanceof HumanReadableException) {
throw ((HumanReadableException) cause);
} else if (cause instanceof ExceptionWithHumanReadableMessage) {
throw new HumanReadableException((ExceptionWithHumanReadableMessage) cause);
} else {
if (cause instanceof RuntimeException) {
console.printBuildFailureWithStacktrace(e);
} else {
console.printBuildFailureWithoutStacktrace(e);
}
exitCode = 1;
}
}
return exitCode;
}
@Override
public void close() throws IOException {
executionContext.close();
}
private int getNumRulesToBuild(
Iterable<BuildTarget> buildTargets,
final ActionGraph actionGraph) {
Set<BuildRule> baseBuildRules = FluentIterable
.from(buildTargets)
.transform(new Function<HasBuildTarget, BuildRule>() {
@Override
public BuildRule apply(HasBuildTarget hasBuildTarget) {
return Preconditions.checkNotNull(
actionGraph.findBuildRuleByTarget(hasBuildTarget.getBuildTarget()));
}
})
.toSet();
Set<BuildRule> allBuildRules = Sets.newHashSet();
for (BuildRule rule : baseBuildRules) {
addTransitiveDepsForRule(rule, allBuildRules);
}
allBuildRules.addAll(baseBuildRules);
return allBuildRules.size();
}
private static void addTransitiveDepsForRule(
BuildRule buildRule,
Set<BuildRule> transitiveDeps) {
ImmutableSortedSet<BuildRule> deps = buildRule.getDeps();
if (deps.isEmpty()) {
return;
}
for (BuildRule dep : deps) {
if (!transitiveDeps.contains(dep)) {
transitiveDeps.add(dep);
addTransitiveDepsForRule(dep, transitiveDeps);
}
}
}
}