// 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.eventbus.EventBus; import com.google.devtools.build.lib.actions.ActionExecutionException; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.MissingInputFileException; import com.google.devtools.build.lib.analysis.AspectCompleteEvent; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.LabelAndConfiguration; import com.google.devtools.build.lib.analysis.TargetCompleteEvent; import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper; import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsToBuild; 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.skyframe.AspectCompletionValue.AspectCompletionKey; import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey; import com.google.devtools.build.lib.skyframe.TargetCompletionValue.TargetCompletionKey; 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.ValueOrException2; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** * CompletionFunction builds the artifactsToBuild collection of a {@link ConfiguredTarget}. */ public final class CompletionFunction<TValue extends SkyValue, TResult extends SkyValue> implements SkyFunction { /** A strategy for completing the build. */ interface Completor<TValue, TResult extends SkyValue> { /** Obtains an analysis result value from environment. */ TValue getValueFromSkyKey(SkyKey skyKey, Environment env) throws InterruptedException; /** * 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. */ TopLevelArtifactContext getTopLevelArtifactContext(SkyKey skyKey); /** * Returns all artifacts that need to be built to complete the {@code value} */ ArtifactsToBuild getAllArtifactsToBuild(TValue value, TopLevelArtifactContext context); /** Creates an event reporting an absent input artifact. */ Event getRootCauseError(TValue value, Cause rootCause); /** * Creates an error message reporting {@code missingCount} missing input files. */ MissingInputFileException getMissingFilesException(TValue value, int missingCount); /** * Creates a successful completion value. */ TResult createResult(TValue value); /** Creates a failed completion value. */ SkyValue createFailed(TValue value, NestedSet<Cause> rootCauses); /** * Extracts a tag given the {@link SkyKey}. */ String extractTag(SkyKey skyKey); } private static class TargetCompletor implements Completor<ConfiguredTargetValue, TargetCompletionValue> { @Override public ConfiguredTargetValue getValueFromSkyKey(SkyKey skyKey, Environment env) throws InterruptedException { TargetCompletionKey tcKey = (TargetCompletionKey) skyKey.argument(); LabelAndConfiguration lac = tcKey.labelAndConfiguration(); return (ConfiguredTargetValue) env.getValue(ConfiguredTargetValue.key(lac.getLabel(), lac.getConfiguration())); } @Override public TopLevelArtifactContext getTopLevelArtifactContext(SkyKey skyKey) { TargetCompletionKey tcKey = (TargetCompletionKey) skyKey.argument(); return tcKey.topLevelArtifactContext(); } @Override public ArtifactsToBuild getAllArtifactsToBuild( ConfiguredTargetValue value, TopLevelArtifactContext topLevelContext) { return TopLevelArtifactHelper.getAllArtifactsToBuild( value.getConfiguredTarget(), topLevelContext); } @Override public Event getRootCauseError(ConfiguredTargetValue ctValue, Cause rootCause) { return Event.error( ctValue.getConfiguredTarget().getTarget().getLocation(), String.format( "%s: missing input file '%s'", ctValue.getConfiguredTarget().getLabel(), rootCause)); } @Override public MissingInputFileException getMissingFilesException( ConfiguredTargetValue value, int missingCount) { return new MissingInputFileException( value.getConfiguredTarget().getTarget().getLocation() + " " + missingCount + " input file(s) do not exist", value.getConfiguredTarget().getTarget().getLocation()); } @Override public TargetCompletionValue createResult(ConfiguredTargetValue value) { return new TargetCompletionValue(value.getConfiguredTarget()); } @Override public SkyValue createFailed(ConfiguredTargetValue value, NestedSet<Cause> rootCauses) { return TargetCompleteEvent.createFailed(value.getConfiguredTarget(), rootCauses); } @Override public String extractTag(SkyKey skyKey) { return Label.print( ((TargetCompletionKey) skyKey.argument()).labelAndConfiguration().getLabel()); } } private static class AspectCompletor implements Completor<AspectValue, AspectCompletionValue> { @Override public AspectValue getValueFromSkyKey(SkyKey skyKey, Environment env) throws InterruptedException { AspectCompletionKey acKey = (AspectCompletionKey) skyKey.argument(); AspectKey aspectKey = acKey.aspectKey(); return (AspectValue) env.getValue(aspectKey.getSkyKey()); } @Override public TopLevelArtifactContext getTopLevelArtifactContext(SkyKey skyKey) { AspectCompletionKey acKey = (AspectCompletionKey) skyKey.argument(); return acKey.topLevelArtifactContext(); } @Override public ArtifactsToBuild getAllArtifactsToBuild( AspectValue value, TopLevelArtifactContext topLevelArtifactContext) { return TopLevelArtifactHelper.getAllArtifactsToBuild(value, topLevelArtifactContext); } @Override public Event getRootCauseError(AspectValue value, Cause rootCause) { return Event.error( value.getLocation(), String.format( "%s, aspect %s: missing input file '%s'", value.getLabel(), value.getConfiguredAspect().getName(), rootCause)); } @Override public MissingInputFileException getMissingFilesException(AspectValue value, int missingCount) { return new MissingInputFileException( value.getLabel() + ", aspect " + value.getConfiguredAspect().getName() + missingCount + " input file(s) do not exist", value.getLocation()); } @Override public AspectCompletionValue createResult(AspectValue value) { return new AspectCompletionValue(value); } @Override public SkyValue createFailed(AspectValue value, NestedSet<Cause> rootCauses) { return AspectCompleteEvent.createFailed(value, rootCauses); } @Override public String extractTag(SkyKey skyKey) { return Label.print(((AspectCompletionKey) skyKey.argument()).aspectKey().getLabel()); } } public static SkyFunction targetCompletionFunction(AtomicReference<EventBus> eventBusRef) { return new CompletionFunction<>(eventBusRef, new TargetCompletor()); } public static SkyFunction aspectCompletionFunction(AtomicReference<EventBus> eventBusRef) { return new CompletionFunction<>(eventBusRef, new AspectCompletor()); } private final AtomicReference<EventBus> eventBusRef; private final Completor<TValue, TResult> completor; private CompletionFunction( AtomicReference<EventBus> eventBusRef, Completor<TValue, TResult> completor) { this.eventBusRef = eventBusRef; this.completor = completor; } @Nullable @Override public SkyValue compute(SkyKey skyKey, Environment env) throws CompletionFunctionException, InterruptedException { TValue value = completor.getValueFromSkyKey(skyKey, env); TopLevelArtifactContext topLevelContext = completor.getTopLevelArtifactContext(skyKey); if (env.valuesMissing()) { return null; } Map<SkyKey, ValueOrException2<MissingInputFileException, ActionExecutionException>> inputDeps = env.getValuesOrThrow( ArtifactSkyKey.mandatoryKeys( completor.getAllArtifactsToBuild(value, topLevelContext).getAllArtifacts()), MissingInputFileException.class, ActionExecutionException.class); int missingCount = 0; ActionExecutionException firstActionExecutionException = null; MissingInputFileException missingInputException = null; NestedSetBuilder<Cause> rootCausesBuilder = NestedSetBuilder.stableOrder(); for (Map.Entry<SkyKey, ValueOrException2<MissingInputFileException, ActionExecutionException>> depsEntry : inputDeps.entrySet()) { Artifact input = ArtifactSkyKey.artifact(depsEntry.getKey()); try { depsEntry.getValue().get(); } catch (MissingInputFileException e) { missingCount++; final Label inputOwner = input.getOwner(); if (inputOwner != null) { Cause cause = new LabelCause(inputOwner); rootCausesBuilder.add(cause); env.getListener().handle(completor.getRootCauseError(value, cause)); } } catch (ActionExecutionException e) { rootCausesBuilder.addTransitive(e.getRootCauses()); if (firstActionExecutionException == null) { firstActionExecutionException = e; } } } if (missingCount > 0) { missingInputException = completor.getMissingFilesException(value, missingCount); } NestedSet<Cause> rootCauses = rootCausesBuilder.build(); if (!rootCauses.isEmpty()) { eventBusRef.get().post(completor.createFailed(value, rootCauses)); if (firstActionExecutionException != null) { throw new CompletionFunctionException(firstActionExecutionException); } else { throw new CompletionFunctionException(missingInputException); } } return env.valuesMissing() ? null : completor.createResult(value); } @Override public String extractTag(SkyKey skyKey) { return completor.extractTag(skyKey); } private static final class CompletionFunctionException extends SkyFunctionException { private final ActionExecutionException actionException; public CompletionFunctionException(ActionExecutionException e) { super(e, Transience.PERSISTENT); this.actionException = e; } public CompletionFunctionException(MissingInputFileException e) { super(e, Transience.TRANSIENT); this.actionException = null; } @Override public boolean isCatastrophic() { return actionException != null && actionException.isCatastrophe(); } } }