blob: 1dfec25289ade82849af72285d00e57babdf4e74 [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.lib.skyframe;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ActionInputMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArchivedTreeArtifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.CompletionContext;
import com.google.devtools.build.lib.actions.CompletionContext.PathResolverFactory;
import com.google.devtools.build.lib.actions.EventReportingArtifacts;
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
import com.google.devtools.build.lib.actions.InputFileErrorException;
import com.google.devtools.build.lib.analysis.ConfiguredObjectValue;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsInOutputGroup;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsToBuild;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.SuccessfulArtifactFilter;
import com.google.devtools.build.lib.bugreport.BugReporter;
import com.google.devtools.build.lib.causes.Cause;
import com.google.devtools.build.lib.causes.LabelCause;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.skyframe.ArtifactFunction.MissingArtifactValue;
import com.google.devtools.build.lib.skyframe.ArtifactFunction.SourceArtifactException;
import com.google.devtools.build.lib.skyframe.MetadataConsumerForMetrics.FilesMetricConsumer;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeIterableResult;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import net.starlark.java.syntax.Location;
/** CompletionFunction builds the artifactsToBuild collection of a {@link ConfiguredTarget}. */
public final class CompletionFunction<
ValueT extends ConfiguredObjectValue,
ResultT extends SkyValue,
KeyT extends TopLevelActionLookupKey,
FailureT>
implements SkyFunction {
/** A strategy for completing the build. */
interface Completor<
ValueT, ResultT extends SkyValue, KeyT extends TopLevelActionLookupKey, FailureT> {
/**
* Returns the options which determine the artifacts to build for the top-level targets.
*
* <p>For the Top level targets we made a conscious decision to include the
* TopLevelArtifactContext within the SkyKey as an argument to the CompletionFunction rather
* than a separate SkyKey. As a result we do have <num top level targets> extra SkyKeys for
* every unique TopLevelArtifactContexts used over the lifetime of Blaze. This is a minor
* tradeoff, since it significantly improves null build times when we're switching the
* TopLevelArtifactContexts frequently (common for IDEs), by reusing existing SkyKeys from
* earlier runs, instead of causing an eager invalidation were the TopLevelArtifactContext
* modeled as a separate SkyKey.
*/
/** Creates an event reporting an absent input artifact. */
Event getRootCauseError(ValueT value, KeyT key, LabelCause rootCause, Environment env)
throws InterruptedException;
@Nullable
Object getLocationIdentifier(ValueT value, KeyT key, Environment env)
throws InterruptedException;
/** Provides a successful completion value. */
ResultT getResult();
/**
* Creates supplementary data needed to call {@link #createFailed(Object, NestedSet,
* CompletionContext, ImmutableMap, Object)}; returns null if skyframe found missing values.
*/
@Nullable
FailureT getFailureData(KeyT key, ValueT value, Environment env) throws InterruptedException;
/** Creates a failed completion value. */
ExtendedEventHandler.Postable createFailed(
ValueT value,
NestedSet<Cause> rootCauses,
CompletionContext ctx,
ImmutableMap<String, ArtifactsInOutputGroup> outputs,
FailureT failureData)
throws InterruptedException;
/** Creates a succeeded completion value; returns null if skyframe found missing values. */
@Nullable
EventReportingArtifacts createSucceeded(
KeyT skyKey,
ValueT value,
CompletionContext completionContext,
ArtifactsToBuild artifactsToBuild,
Environment env)
throws InterruptedException;
}
private final PathResolverFactory pathResolverFactory;
private final Completor<ValueT, ResultT, KeyT, FailureT> completor;
private final SkyframeActionExecutor skyframeActionExecutor;
private final FilesMetricConsumer topLevelArtifactsMetric;
private final BugReporter bugReporter;
CompletionFunction(
PathResolverFactory pathResolverFactory,
Completor<ValueT, ResultT, KeyT, FailureT> completor,
SkyframeActionExecutor skyframeActionExecutor,
FilesMetricConsumer topLevelArtifactsMetric,
BugReporter bugReporter) {
this.pathResolverFactory = pathResolverFactory;
this.completor = completor;
this.skyframeActionExecutor = skyframeActionExecutor;
this.topLevelArtifactsMetric = topLevelArtifactsMetric;
this.bugReporter = bugReporter;
}
@SuppressWarnings("unchecked") // Cast to KeyT
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws CompletionFunctionException, InterruptedException {
WorkspaceNameValue workspaceNameValue =
(WorkspaceNameValue) env.getValue(WorkspaceNameValue.key());
if (workspaceNameValue == null) {
return null;
}
KeyT key = (KeyT) skyKey;
Pair<ValueT, ArtifactsToBuild> valueAndArtifactsToBuild = getValueAndArtifactsToBuild(key, env);
if (env.valuesMissing()) {
return null;
}
ValueT value = valueAndArtifactsToBuild.first;
ArtifactsToBuild artifactsToBuild = valueAndArtifactsToBuild.second;
ImmutableList<Artifact> allArtifacts = artifactsToBuild.getAllArtifacts().toList();
SkyframeIterableResult inputDeps =
env.getOrderedValuesAndExceptions(Artifact.keys(allArtifacts));
boolean allArtifactsAreImportant = artifactsToBuild.areAllOutputGroupsImportant();
ActionInputMap inputMap = new ActionInputMap(bugReporter, allArtifacts.size());
// Prepare an ActionInputMap for important artifacts separately, to be used by BEP events. The
// _validation output group can contain orders of magnitude more unimportant artifacts than
// there are important artifacts, and BEP events will retain the ActionInputMap until the
// event is delivered to transports. If the BEP events reference *all* artifacts it can increase
// heap high-watermark by multiple GB.
ActionInputMap importantInputMap;
Set<Artifact> importantArtifactSet;
if (allArtifactsAreImportant) {
importantArtifactSet = ImmutableSet.of();
importantInputMap = inputMap;
} else {
ImmutableList<Artifact> importantArtifacts =
artifactsToBuild.getImportantArtifacts().toList();
importantArtifactSet = new HashSet<>(importantArtifacts);
importantInputMap = new ActionInputMap(bugReporter, importantArtifacts.size());
}
Map<Artifact, ImmutableCollection<? extends Artifact>> expandedArtifacts = new HashMap<>();
Map<Artifact, ImmutableList<FilesetOutputSymlink>> expandedFilesets = new HashMap<>();
Map<SpecialArtifact, ArchivedTreeArtifact> archivedTreeArtifacts = new HashMap<>();
Map<Artifact, ImmutableList<FilesetOutputSymlink>> topLevelFilesets = new HashMap<>();
ActionExecutionException firstActionExecutionException = null;
NestedSetBuilder<Cause> rootCausesBuilder = NestedSetBuilder.stableOrder();
ImmutableSet.Builder<Artifact> builtArtifactsBuilder = ImmutableSet.builder();
// Don't double-count files due to Skyframe restarts.
FilesMetricConsumer currentConsumer = new FilesMetricConsumer();
for (Artifact input : allArtifacts) {
try {
SkyValue artifactValue =
inputDeps.nextOrThrow(ActionExecutionException.class, SourceArtifactException.class);
if (artifactValue != null) {
if (artifactValue instanceof MissingArtifactValue) {
handleSourceFileError(
input,
((MissingArtifactValue) artifactValue).getDetailedExitCode(),
rootCausesBuilder,
env,
value,
key);
} else {
builtArtifactsBuilder.add(input);
ActionInputMapHelper.addToMap(
inputMap,
expandedArtifacts,
archivedTreeArtifacts,
expandedFilesets,
topLevelFilesets,
input,
artifactValue,
env,
currentConsumer,
skyframeActionExecutor.supportsPartialTreeArtifactInputs());
if (!allArtifactsAreImportant && importantArtifactSet.contains(input)) {
// Calling #addToMap a second time with `input` and `artifactValue` will perform no-op
// updates to the secondary collections passed in (eg. expandedArtifacts,
// topLevelFilesets). MetadataConsumerForMetrics.NO_OP is used to avoid
// double-counting.
ActionInputMapHelper.addToMap(
importantInputMap,
expandedArtifacts,
archivedTreeArtifacts,
expandedFilesets,
topLevelFilesets,
input,
artifactValue,
env,
MetadataConsumerForMetrics.NO_OP,
skyframeActionExecutor.supportsPartialTreeArtifactInputs());
}
}
}
} catch (ActionExecutionException e) {
rootCausesBuilder.addTransitive(e.getRootCauses());
// Prefer a catastrophic exception as the one we propagate.
if (firstActionExecutionException == null
|| (!firstActionExecutionException.isCatastrophe() && e.isCatastrophe())) {
firstActionExecutionException = e;
}
} catch (SourceArtifactException e) {
if (!input.isSourceArtifact()) {
bugReporter.logUnexpected(
e, "Non-source artifact had SourceArtifactException: %s", input);
}
handleSourceFileError(input, e.getDetailedExitCode(), rootCausesBuilder, env, value, key);
}
}
expandedFilesets.putAll(topLevelFilesets);
NestedSet<Cause> rootCauses = rootCausesBuilder.build();
@Nullable FailureT failureData = null;
if (!rootCauses.isEmpty()) {
failureData = completor.getFailureData(key, value, env);
if (failureData == null) {
return null;
}
}
CompletionContext ctx =
CompletionContext.create(
expandedArtifacts,
expandedFilesets,
key.topLevelArtifactContext().expandFilesets(),
key.topLevelArtifactContext().fullyResolveFilesetSymlinks(),
inputMap,
importantInputMap,
pathResolverFactory,
skyframeActionExecutor.getExecRoot(),
workspaceNameValue.getName());
if (!rootCauses.isEmpty()) {
ImmutableMap<String, ArtifactsInOutputGroup> builtOutputs =
new SuccessfulArtifactFilter(builtArtifactsBuilder.build())
.filterArtifactsInOutputGroup(artifactsToBuild.getAllArtifactsByOutputGroup());
env.getListener()
.post(completor.createFailed(value, rootCauses, ctx, builtOutputs, failureData));
if (firstActionExecutionException != null) {
throw new CompletionFunctionException(firstActionExecutionException);
}
// locationPrefix theoretically *could* be null because of missing deps, but not in reality,
// and we're not allowed to wait for deps to be ready if we're failing anyway.
@Nullable Object locationPrefix = completor.getLocationIdentifier(value, key, env);
Pair<DetailedExitCode, String> codeAndMessage =
ActionExecutionFunction.createSourceErrorCodeAndMessage(rootCauses.toList(), key);
String message;
if (locationPrefix instanceof Location) {
message = codeAndMessage.getSecond();
env.getListener().handle(Event.error((Location) locationPrefix, message));
} else {
message = (locationPrefix == null ? "" : locationPrefix + " ") + codeAndMessage.getSecond();
env.getListener().handle(Event.error(message));
}
throw new CompletionFunctionException(
new InputFileErrorException(message, codeAndMessage.getFirst()));
}
// Only check for missing values *after* reporting errors: if there are missing files in a build
// with --nokeep_going, there may be missing dependencies during error bubbling, we still need
// to report the error.
if (env.valuesMissing()) {
return null;
}
ExtendedEventHandler.Postable postable =
completor.createSucceeded(key, value, ctx, artifactsToBuild, env);
if (postable == null) {
return null;
}
env.getListener().post(postable);
topLevelArtifactsMetric.mergeIn(currentConsumer);
return completor.getResult();
}
private void handleSourceFileError(
Artifact input,
DetailedExitCode detailedExitCode,
NestedSetBuilder<Cause> rootCausesBuilder,
Environment env,
ValueT value,
KeyT key)
throws InterruptedException {
LabelCause cause =
ActionExecutionFunction.createLabelCause(
input, detailedExitCode, key.actionLookupKey().getLabel(), bugReporter);
rootCausesBuilder.add(cause);
env.getListener().handle(completor.getRootCauseError(value, key, cause, env));
skyframeActionExecutor.recordExecutionError();
}
@Nullable
static <ValueT extends ConfiguredObjectValue>
Pair<ValueT, ArtifactsToBuild> getValueAndArtifactsToBuild(
TopLevelActionLookupKey key, Environment env) throws InterruptedException {
@SuppressWarnings("unchecked")
ValueT value = (ValueT) env.getValue(key.actionLookupKey());
if (env.valuesMissing()) {
return null;
}
TopLevelArtifactContext topLevelContext = key.topLevelArtifactContext();
ArtifactsToBuild artifactsToBuild =
TopLevelArtifactHelper.getAllArtifactsToBuild(value.getConfiguredObject(), topLevelContext);
return Pair.of(value, artifactsToBuild);
}
@Override
public String extractTag(SkyKey skyKey) {
return Label.print(((TopLevelActionLookupKey) skyKey).actionLookupKey().getLabel());
}
private static final class CompletionFunctionException extends SkyFunctionException {
private final ActionExecutionException actionException;
CompletionFunctionException(ActionExecutionException e) {
super(e, Transience.PERSISTENT);
this.actionException = e;
}
CompletionFunctionException(InputFileErrorException e) {
// Not transient from the point of view of this SkyFunction.
super(e, Transience.PERSISTENT);
this.actionException = null;
}
@Override
public boolean isCatastrophic() {
return actionException != null && actionException.isCatastrophe();
}
}
}