| // Copyright 2021 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 static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.EXCLUSIVE; | |
| import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.EXCLUSIVE_IF_LOCAL; | |
| import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.NOT_TEST; | |
| import static com.google.devtools.build.lib.skyframe.BuildDriverKey.TestType.PARALLEL; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.base.Supplier; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Iterables; | |
| import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; | |
| import com.google.devtools.build.lib.actions.ActionLookupKey; | |
| import com.google.devtools.build.lib.actions.ActionLookupValue; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.analysis.AspectValue; | |
| import com.google.devtools.build.lib.analysis.ConfiguredAspect; | |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; | |
| import com.google.devtools.build.lib.analysis.ConfiguredTargetValue; | |
| import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider; | |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; | |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; | |
| import com.google.devtools.build.lib.analysis.constraints.RuleContextConstraintSemantics; | |
| import com.google.devtools.build.lib.analysis.constraints.TopLevelConstraintSemantics; | |
| import com.google.devtools.build.lib.analysis.constraints.TopLevelConstraintSemantics.EnvironmentCompatibility; | |
| import com.google.devtools.build.lib.analysis.constraints.TopLevelConstraintSemantics.PlatformCompatibility; | |
| import com.google.devtools.build.lib.analysis.constraints.TopLevelConstraintSemantics.TargetCompatibilityCheckException; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.concurrent.Sharder; | |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; | |
| import com.google.devtools.build.lib.packages.Package; | |
| import com.google.devtools.build.lib.packages.Target; | |
| import com.google.devtools.build.lib.profiler.Profiler; | |
| import com.google.devtools.build.lib.profiler.SilentCloseable; | |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis; | |
| import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code; | |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; | |
| import com.google.devtools.build.lib.skyframe.ArtifactConflictFinder.ConflictException; | |
| import com.google.devtools.build.lib.skyframe.AspectCompletionValue.AspectCompletionKey; | |
| import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.AspectAnalyzedEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.SomeExecutionStartedEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TestAnalyzedEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelEntityAnalysisConcludedEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetAnalyzedEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetPendingExecutionEvent; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetReadyForSymlinkPlanting; | |
| import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetSkippedEvent; | |
| import com.google.devtools.build.lib.util.RegexFilter; | |
| import com.google.devtools.build.skyframe.SkyFunction; | |
| import com.google.devtools.build.skyframe.SkyFunction.Environment.SkyKeyComputeState; | |
| 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.SkyframeLookupResult; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Drives the analysis & execution of an ActionLookupKey, which is wrapped inside a BuildDriverKey. | |
| */ | |
| public class BuildDriverFunction implements SkyFunction { | |
| private final TransitiveActionLookupValuesHelper transitiveActionLookupValuesHelper; | |
| private final Supplier<IncrementalArtifactConflictFinder> incrementalArtifactConflictFinder; | |
| private final Supplier<RuleContextConstraintSemantics> ruleContextConstraintSemantics; | |
| private final Supplier<RegexFilter> extraActionFilterSupplier; | |
| BuildDriverFunction( | |
| TransitiveActionLookupValuesHelper transitiveActionLookupValuesHelper, | |
| Supplier<IncrementalArtifactConflictFinder> incrementalArtifactConflictFinder, | |
| Supplier<RuleContextConstraintSemantics> ruleContextConstraintSemantics, | |
| Supplier<RegexFilter> extraActionFilterSupplier) { | |
| this.transitiveActionLookupValuesHelper = transitiveActionLookupValuesHelper; | |
| this.incrementalArtifactConflictFinder = incrementalArtifactConflictFinder; | |
| this.ruleContextConstraintSemantics = ruleContextConstraintSemantics; | |
| this.extraActionFilterSupplier = extraActionFilterSupplier; | |
| } | |
| private static class State implements SkyKeyComputeState { | |
| private ImmutableMap<ActionAnalysisMetadata, ConflictException> actionConflicts; | |
| // It's only necessary to do this check once. | |
| private boolean checkedForCompatibility = false; | |
| private boolean checkedForPlatformCompatibility = false; | |
| } | |
| /** | |
| * From the ConfiguredTarget/Aspect keys, get the top-level artifacts. Then evaluate them together | |
| * with the appropriate CompletionFunctions. This is the bridge between the conceptual analysis & | |
| * execution phases. | |
| * | |
| * <p>TODO(b/240944910): implement coverage. | |
| */ | |
| @Nullable | |
| @Override | |
| public SkyValue compute(SkyKey skyKey, Environment env) | |
| throws SkyFunctionException, InterruptedException { | |
| BuildDriverKey buildDriverKey = (BuildDriverKey) skyKey; | |
| ActionLookupKey actionLookupKey = buildDriverKey.getActionLookupKey(); | |
| TopLevelArtifactContext topLevelArtifactContext = buildDriverKey.getTopLevelArtifactContext(); | |
| State state = env.getState(State::new); | |
| // Register a dependency on the BUILD_ID. We do this to make sure BuildDriverFunction is | |
| // reevaluated every build. | |
| PrecomputedValue.BUILD_ID.get(env); | |
| // Why SkyValue and not ActionLookupValue? The evaluation of some ActionLookupKey can result in | |
| // classes that don't implement ActionLookupValue | |
| // (e.g. ConfiguredTargetKey -> NonRuleConfiguredTargetValue). | |
| SkyValue topLevelSkyValue = env.getValue(actionLookupKey); | |
| if (env.valuesMissing()) { | |
| return null; | |
| } | |
| // This code path should not be run during error bubbling for several reasons: | |
| // 1. Correctness: to check for action conflicts, we need access to the transitive | |
| // ConfiguredTargets, which will be null after AnalysisPhaseCompleteEvent in | |
| // --discard_analysis_cache mode. | |
| // 2. Performance: this method is CPU intensive, and it does not offer anything while error | |
| // bubbling. | |
| if (!env.inErrorBubblingForSkyFunctionsThatCanFullyRecoverFromErrors()) { | |
| // Unconditionally check for action conflicts. | |
| // TODO(b/214371092): Only check when necessary. | |
| try (SilentCloseable c = | |
| Profiler.instance().profile("BuildDriverFunction.checkActionConflicts")) { | |
| if (state.actionConflicts == null) { | |
| state.actionConflicts = | |
| checkActionConflicts(actionLookupKey, buildDriverKey.strictActionConflictCheck()); | |
| } | |
| if (!state.actionConflicts.isEmpty()) { | |
| throw new BuildDriverFunctionException( | |
| new TopLevelConflictException( | |
| "Action conflict(s) detected while analyzing top-level target " | |
| + actionLookupKey.getLabel(), | |
| state.actionConflicts)); | |
| } | |
| } | |
| } | |
| Preconditions.checkState( | |
| topLevelSkyValue instanceof ConfiguredTargetValue | |
| || topLevelSkyValue instanceof TopLevelAspectsValue); | |
| if (topLevelSkyValue instanceof ConfiguredTargetValue) { | |
| ConfiguredTargetValue configuredTargetValue = (ConfiguredTargetValue) topLevelSkyValue; | |
| ConfiguredTarget configuredTarget = configuredTargetValue.getConfiguredTarget(); | |
| // At this point, the target is considered "analyzed". It's important that this event is sent | |
| // before the TopLevelEntityAnalysisConcludedEvent: when the last of the analysis work is | |
| // concluded, we need to have the *complete* list of analyzed targets ready in | |
| // BuildResultListener. | |
| postTopLevelTargetAnalyzedEvent(env, configuredTargetValue, configuredTarget); | |
| BuildConfigurationValue buildConfigurationValue = | |
| configuredTarget.getConfigurationKey() == null | |
| ? null | |
| : (BuildConfigurationValue) env.getValue(configuredTarget.getConfigurationKey()); | |
| if (env.valuesMissing()) { | |
| return null; | |
| } | |
| if (!state.checkedForCompatibility) { | |
| try { | |
| Boolean isConfiguredTargetCompatible = | |
| isConfiguredTargetCompatible( | |
| env, | |
| state, | |
| configuredTarget, | |
| buildConfigurationValue, | |
| buildDriverKey.isExplicitlyRequested()); | |
| if (isConfiguredTargetCompatible == null) { | |
| return null; | |
| } | |
| state.checkedForCompatibility = true; | |
| if (!isConfiguredTargetCompatible) { | |
| env.getListener().post(TopLevelTargetSkippedEvent.create(configuredTarget)); | |
| // We still record analyzed but skipped tests, as this information is needed for the | |
| // result summary. | |
| if (!NOT_TEST.equals(buildDriverKey.getTestType())) { | |
| env.getListener() | |
| .post( | |
| TestAnalyzedEvent.create( | |
| configuredTarget, | |
| Preconditions.checkNotNull(buildConfigurationValue), | |
| /*isSkipped=*/ true)); | |
| } | |
| // Only send the event now to include the compatibility check in the measurement for | |
| // time spent on analysis work. | |
| env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey)); | |
| // We consider the evaluation of this BuildDriverKey successful at this point, even when | |
| // the target is skipped. | |
| return new BuildDriverValue(topLevelSkyValue, /*skipped=*/ true); | |
| } | |
| } catch (TargetCompatibilityCheckException e) { | |
| throw new BuildDriverFunctionException(e); | |
| } | |
| } | |
| env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey)); | |
| env.getListener() | |
| .post( | |
| TopLevelTargetPendingExecutionEvent.create( | |
| configuredTarget, buildDriverKey.isTest())); | |
| requestConfiguredTargetExecution( | |
| configuredTarget, | |
| buildDriverKey, | |
| actionLookupKey, | |
| buildConfigurationValue, | |
| env, | |
| topLevelArtifactContext); | |
| } else { | |
| announceAspectAnalysisDoneAndRequestExecution( | |
| buildDriverKey, (TopLevelAspectsValue) topLevelSkyValue, env, topLevelArtifactContext); | |
| } | |
| if (env.valuesMissing()) { | |
| return null; | |
| } | |
| // If we get to this point, the execution of this target/aspect succeeded. | |
| if (EXCLUSIVE.equals(buildDriverKey.getTestType()) | |
| || EXCLUSIVE_IF_LOCAL.equals(buildDriverKey.getTestType())) { | |
| Preconditions.checkState(topLevelSkyValue instanceof ConfiguredTargetValue); | |
| return new ExclusiveTestBuildDriverValue( | |
| topLevelSkyValue, ((ConfiguredTargetValue) topLevelSkyValue).getConfiguredTarget()); | |
| } | |
| return new BuildDriverValue(topLevelSkyValue, /*skipped=*/ false); | |
| } | |
| private static void postTopLevelTargetAnalyzedEvent( | |
| Environment env, | |
| ConfiguredTargetValue configuredTargetValue, | |
| ConfiguredTarget configuredTarget) { | |
| env.getListener().post(TopLevelTargetAnalyzedEvent.create(configuredTarget)); | |
| // It's possible that this code path is triggered AFTER the analysis cache clean up and the | |
| // transitive packages for package root resolution is already cleared. In such a case, the | |
| // symlinks should have already been planted. | |
| if (configuredTargetValue.getTransitivePackages() != null) { | |
| env.getListener() | |
| .post( | |
| TopLevelTargetReadyForSymlinkPlanting.create( | |
| configuredTargetValue.getTransitivePackages())); | |
| } | |
| } | |
| /** | |
| * Checks if a ConfiguredTarget is compatible with the platform/environment. See {@link | |
| * TopLevelConstraintSemantics}. | |
| * | |
| * @return null if a value is missing in the environment. | |
| */ | |
| @Nullable | |
| private Boolean isConfiguredTargetCompatible( | |
| Environment env, | |
| State state, | |
| ConfiguredTarget configuredTarget, | |
| BuildConfigurationValue buildConfigurationValue, | |
| boolean isExplicitlyRequested) | |
| throws InterruptedException, TargetCompatibilityCheckException { | |
| if (!state.checkedForPlatformCompatibility) { | |
| PlatformCompatibility platformCompatibility = | |
| TopLevelConstraintSemantics.compatibilityWithPlatformRestrictions( | |
| configuredTarget, | |
| env.getListener(), | |
| /*eagerlyThrowError=*/ true, | |
| isExplicitlyRequested); | |
| state.checkedForPlatformCompatibility = true; | |
| switch (platformCompatibility) { | |
| case INCOMPATIBLE_EXPLICIT: | |
| case INCOMPATIBLE_IMPLICIT: | |
| return false; | |
| case COMPATIBLE: | |
| break; | |
| } | |
| } | |
| EnvironmentCompatibility environmentCompatibility = | |
| TopLevelConstraintSemantics.compatibilityWithTargetEnvironment( | |
| configuredTarget, | |
| buildConfigurationValue, | |
| label -> getTarget(env, label), | |
| env.getListener()); | |
| if (env.valuesMissing() || environmentCompatibility == null) { | |
| return null; | |
| } | |
| if (environmentCompatibility.isCompatible()) { | |
| return true; | |
| } | |
| if (environmentCompatibility.severeMissingEnvironments() == null) { | |
| return false; | |
| } | |
| String badTargetsUserMessage = | |
| TopLevelConstraintSemantics.getErrorMessageForTarget( | |
| ruleContextConstraintSemantics.get(), | |
| configuredTarget, | |
| environmentCompatibility.severeMissingEnvironments()); | |
| throw new TargetCompatibilityCheckException( | |
| badTargetsUserMessage, | |
| FailureDetail.newBuilder() | |
| .setMessage(badTargetsUserMessage) | |
| .setAnalysis(Analysis.newBuilder().setCode(Code.TARGETS_MISSING_ENVIRONMENTS)) | |
| .build()); | |
| } | |
| @Nullable | |
| private static Target getTarget(Environment env, Label label) | |
| throws InterruptedException, NoSuchTargetException { | |
| PackageValue packageValue = | |
| (PackageValue) env.getValue(PackageValue.key(label.getPackageIdentifier())); | |
| if (env.valuesMissing() || packageValue == null) { | |
| return null; | |
| } | |
| Package pkg = packageValue.getPackage(); | |
| return pkg.getTarget(label.getName()); | |
| } | |
| private void requestConfiguredTargetExecution( | |
| ConfiguredTarget configuredTarget, | |
| BuildDriverKey buildDriverKey, | |
| ActionLookupKey actionLookupKey, | |
| BuildConfigurationValue buildConfigurationValue, | |
| Environment env, | |
| TopLevelArtifactContext topLevelArtifactContext) | |
| throws InterruptedException { | |
| ImmutableSet.Builder<Artifact> artifactsToBuild = ImmutableSet.builder(); | |
| addExtraActionsIfRequested( | |
| configuredTarget.getProvider(ExtraActionArtifactsProvider.class), artifactsToBuild); | |
| env.getListener().post(SomeExecutionStartedEvent.create()); | |
| if (NOT_TEST.equals(buildDriverKey.getTestType())) { | |
| declareDependenciesAndCheckValues( | |
| env, | |
| Iterables.concat( | |
| Artifact.keys(artifactsToBuild.build()), | |
| Collections.singletonList( | |
| TargetCompletionValue.key( | |
| (ConfiguredTargetKey) actionLookupKey, topLevelArtifactContext, false)))); | |
| return; | |
| } | |
| env.getListener() | |
| .post( | |
| TestAnalyzedEvent.create( | |
| configuredTarget, | |
| Preconditions.checkNotNull(buildConfigurationValue), | |
| /*isSkipped=*/ false)); | |
| if (PARALLEL.equals(buildDriverKey.getTestType())) { | |
| // Only run non-exclusive tests here. Exclusive tests need to be run sequentially later. | |
| declareDependenciesAndCheckValues( | |
| env, | |
| Iterables.concat( | |
| artifactsToBuild.build(), | |
| Collections.singletonList( | |
| TestCompletionValue.key( | |
| (ConfiguredTargetKey) actionLookupKey, | |
| topLevelArtifactContext, | |
| /*exclusiveTesting=*/ false)))); | |
| return; | |
| } | |
| // Exclusive tests will be run with sequential Skyframe evaluations afterwards. | |
| declareDependenciesAndCheckValues(env, artifactsToBuild.build()); | |
| } | |
| private void announceAspectAnalysisDoneAndRequestExecution( | |
| BuildDriverKey buildDriverKey, | |
| TopLevelAspectsValue topLevelAspectsValue, | |
| Environment env, | |
| TopLevelArtifactContext topLevelArtifactContext) | |
| throws InterruptedException { | |
| env.getListener().post(SomeExecutionStartedEvent.create()); | |
| ImmutableSet.Builder<Artifact> artifactsToBuild = ImmutableSet.builder(); | |
| List<SkyKey> aspectCompletionKeys = new ArrayList<>(); | |
| for (AspectValue aspectValue : topLevelAspectsValue.getTopLevelAspectsValues()) { | |
| AspectKey aspectKey = aspectValue.getKey(); | |
| ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect(); | |
| addExtraActionsIfRequested( | |
| configuredAspect.getProvider(ExtraActionArtifactsProvider.class), artifactsToBuild); | |
| postAspectAnalyzedEvent(env, aspectValue, aspectKey, configuredAspect); | |
| aspectCompletionKeys.add(AspectCompletionKey.create(aspectKey, topLevelArtifactContext)); | |
| } | |
| // Send the AspectAnalyzedEvents first to make sure the BuildResultListener is up-to-date before | |
| // signaling that the analysis of this top level aspect has concluded. | |
| env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey)); | |
| declareDependenciesAndCheckValues( | |
| env, Iterables.concat(Artifact.keys(artifactsToBuild.build()), aspectCompletionKeys)); | |
| } | |
| private static void postAspectAnalyzedEvent( | |
| Environment env, | |
| AspectValue aspectValue, | |
| AspectKey aspectKey, | |
| ConfiguredAspect configuredAspect) { | |
| // It's possible that this code path is triggered AFTER the analysis cache clean up and the | |
| // transitive packages for package root resolution is already cleared. In such a case, the | |
| // symlinks should have already been planted. | |
| AspectAnalyzedEvent aspectAnalyzedEvent = | |
| aspectValue.getTransitivePackages() == null | |
| ? AspectAnalyzedEvent.createWithoutFurtherSymlinkPlanting(aspectKey, configuredAspect) | |
| : AspectAnalyzedEvent.create( | |
| aspectKey, configuredAspect, aspectValue.getTransitivePackages()); | |
| env.getListener().post(aspectAnalyzedEvent); | |
| } | |
| /** | |
| * Declares dependencies and checks values for requested nodes in the graph. | |
| * | |
| * <p>Calls {@link SkyFunction.Environment#getValuesAndExceptions} and iterates over the result. | |
| * If any node is not done, or during iteration any value has exception, {@link | |
| * SkyFunction.Environment#valuesMissing} will return true. | |
| */ | |
| private static void declareDependenciesAndCheckValues( | |
| Environment env, Iterable<? extends SkyKey> skyKeys) throws InterruptedException { | |
| SkyframeLookupResult result = env.getValuesAndExceptions(skyKeys); | |
| for (SkyKey key : skyKeys) { | |
| if (result.get(key) == null) { | |
| return; | |
| } | |
| } | |
| } | |
| @VisibleForTesting | |
| ImmutableMap<ActionAnalysisMetadata, ConflictException> checkActionConflicts( | |
| ActionLookupKey actionLookupKey, boolean strictConflictCheck) throws InterruptedException { | |
| ActionLookupValuesCollectionResult transitiveValueCollectionResult = | |
| transitiveActionLookupValuesHelper.collect(actionLookupKey); | |
| ImmutableMap<ActionAnalysisMetadata, ConflictException> conflicts = | |
| incrementalArtifactConflictFinder | |
| .get() | |
| .findArtifactConflicts( | |
| transitiveValueCollectionResult.collectedValues(), strictConflictCheck) | |
| .getConflicts(); | |
| if (conflicts.isEmpty()) { | |
| transitiveActionLookupValuesHelper.registerConflictFreeKeys( | |
| transitiveValueCollectionResult.visitedKeys()); | |
| } | |
| return conflicts; | |
| } | |
| private void addExtraActionsIfRequested( | |
| ExtraActionArtifactsProvider provider, ImmutableSet.Builder<Artifact> artifactsToBuild) { | |
| if (provider != null) { | |
| addArtifactsToBuilder( | |
| provider.getTransitiveExtraActionArtifacts().toList(), | |
| artifactsToBuild, | |
| extraActionFilterSupplier.get()); | |
| } | |
| } | |
| private static void addArtifactsToBuilder( | |
| List<? extends Artifact> artifacts, | |
| ImmutableSet.Builder<Artifact> builder, | |
| RegexFilter filter) { | |
| for (Artifact artifact : artifacts) { | |
| if (filter.isIncluded(artifact.getOwnerLabel().toString())) { | |
| builder.add(artifact); | |
| } | |
| } | |
| } | |
| /** A SkyFunctionException wrapper for the actual TopLevelConflictException. */ | |
| private static final class BuildDriverFunctionException extends SkyFunctionException { | |
| // The exception is transient here since it could be caused by external factors (conflict with | |
| // another target). | |
| BuildDriverFunctionException(TopLevelConflictException cause) { | |
| super(cause, Transience.TRANSIENT); | |
| } | |
| BuildDriverFunctionException(TargetCompatibilityCheckException cause) { | |
| super(cause, Transience.TRANSIENT); | |
| } | |
| } | |
| interface TransitiveActionLookupValuesHelper { | |
| /** | |
| * Perform the traversal of the transitive closure of the {@code key} and collect the | |
| * corresponding ActionLookupValues. | |
| */ | |
| ActionLookupValuesCollectionResult collect(ActionLookupKey key) throws InterruptedException; | |
| /** Register with the helper that the {@code keys} are conflict-free. */ | |
| void registerConflictFreeKeys(ImmutableSet<SkyKey> keys); | |
| } | |
| @AutoValue | |
| abstract static class ActionLookupValuesCollectionResult { | |
| abstract Sharder<ActionLookupValue> collectedValues(); | |
| abstract ImmutableSet<SkyKey> visitedKeys(); | |
| static ActionLookupValuesCollectionResult create( | |
| Sharder<ActionLookupValue> collectedValues, ImmutableSet<SkyKey> visitedKeys) { | |
| return new AutoValue_BuildDriverFunction_ActionLookupValuesCollectionResult( | |
| collectedValues, visitedKeys); | |
| } | |
| } | |
| } |