// 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.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; import com.google.devtools.build.lib.actions.ActionLookupValue; import com.google.devtools.build.lib.actions.ArtifactFactory; import com.google.devtools.build.lib.actions.ArtifactOwner; import com.google.devtools.build.lib.actions.ArtifactPrefixConflictException; import com.google.devtools.build.lib.actions.MutableActionGraph; import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; import com.google.devtools.build.lib.analysis.AnalysisFailureEvent; import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.BuildView; import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.ConfiguredTargetFactory; import com.google.devtools.build.lib.analysis.LabelAndConfiguration; import com.google.devtools.build.lib.analysis.ViewCreationFailedException; import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory; import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection; import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; 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.packages.Attribute; import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.packages.RuleClassProvider; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.pkgcache.LoadingFailureEvent; import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner; import com.google.devtools.build.lib.skyframe.AspectFunction.AspectCreationException; import com.google.devtools.build.lib.skyframe.AspectValue.AspectValueKey; import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue.BuildInfoKeyAndConfig; import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException; import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ConflictException; import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException; import com.google.devtools.build.lib.util.OrderedSetMultimap; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.skyframe.CycleInfo; import com.google.devtools.build.skyframe.ErrorInfo; import com.google.devtools.build.skyframe.EvaluationProgressReceiver; import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.SkyFunction.Environment; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; /** * Skyframe-based driver of analysis. * * <p>Covers enough functionality to work as a substitute for {@code BuildView#configureTargets}. */ public final class SkyframeBuildView { private static Logger LOG = Logger.getLogger(BuildView.class.getName()); private final ConfiguredTargetFactory factory; private final ArtifactFactory artifactFactory; private final SkyframeExecutor skyframeExecutor; private boolean enableAnalysis = false; // This hack allows us to see when a configured target has been invalidated, and thus when the set // of artifact conflicts needs to be recomputed (whenever a configured target has been invalidated // or newly evaluated). private final EvaluationProgressReceiver progressReceiver = new ConfiguredTargetValueProgressReceiver(); private final Set<SkyKey> evaluatedConfiguredTargets = Sets.newConcurrentHashSet(); // Used to see if checks of graph consistency need to be done after analysis. private volatile boolean someConfiguredTargetEvaluated = false; // We keep the set of invalidated configuration target keys so that we can know if something // has been invalidated after graph pruning has been executed. private Set<SkyKey> dirtiedConfiguredTargetKeys = Sets.newConcurrentHashSet(); private volatile boolean anyConfiguredTargetDeleted = false; private final RuleClassProvider ruleClassProvider; // The host configuration containing all fragments used by this build's transitive closure. private BuildConfiguration topLevelHostConfiguration; // Fragment-limited versions of the host configuration. It's faster to create/cache these here // than to store them in Skyframe. private Map<Set<Class<? extends BuildConfiguration.Fragment>>, BuildConfiguration> hostConfigurationCache = Maps.newConcurrentMap(); private BuildConfigurationCollection configurations; /** * If the last build was executed with {@code Options#discard_analysis_cache} and we are not * running Skyframe full, we should clear the legacy data since it is out-of-sync. */ private boolean skyframeAnalysisWasDiscarded; public SkyframeBuildView(BlazeDirectories directories, SkyframeExecutor skyframeExecutor, ConfiguredRuleClassProvider ruleClassProvider) { this.factory = new ConfiguredTargetFactory(ruleClassProvider); this.artifactFactory = new ArtifactFactory( directories.getExecRoot().getParentDirectory(), directories.getRelativeOutputPath()); this.skyframeExecutor = skyframeExecutor; this.ruleClassProvider = ruleClassProvider; } public void resetEvaluatedConfiguredTargetKeysSet() { evaluatedConfiguredTargets.clear(); } public Set<SkyKey> getEvaluatedTargetKeys() { return ImmutableSet.copyOf(evaluatedConfiguredTargets); } ConfiguredTargetFactory getConfiguredTargetFactory() { return factory; } /** * Sets the configurations. Not thread-safe. DO NOT CALL except from tests! */ @VisibleForTesting public void setConfigurations(BuildConfigurationCollection configurations) { // Clear all cached ConfiguredTargets on configuration change of if --discard_analysis_cache // was set on the previous build. In the former case, it's not required for correctness, but // prevents unbounded memory usage. if ((this.configurations != null && !configurations.equals(this.configurations)) || skyframeAnalysisWasDiscarded) { LOG.info("Discarding analysis cache: configurations have changed."); skyframeExecutor.dropConfiguredTargets(); } skyframeAnalysisWasDiscarded = false; this.configurations = configurations; setTopLevelHostConfiguration(configurations.getHostConfiguration()); } /** * Sets the host configuration consisting of all fragments that will be used by the top level * targets' transitive closures. * * <p>This is used to power {@link #getHostConfiguration} during analysis, which computes * fragment-trimmed host configurations from the top-level one. */ private void setTopLevelHostConfiguration(BuildConfiguration topLevelHostConfiguration) { if (topLevelHostConfiguration.equals(this.topLevelHostConfiguration)) { return; } hostConfigurationCache.clear(); this.topLevelHostConfiguration = topLevelHostConfiguration; } /** * Drops the analysis cache. If building with Skyframe, targets in {@code topLevelTargets} may * remain in the cache for use during the execution phase. * * @see com.google.devtools.build.lib.analysis.BuildView.Options#discardAnalysisCache */ public void clearAnalysisCache( Collection<ConfiguredTarget> topLevelTargets, Collection<AspectValue> topLevelAspects) { // TODO(bazel-team): Consider clearing packages too to save more memory. skyframeAnalysisWasDiscarded = true; skyframeExecutor.clearAnalysisCache(topLevelTargets, topLevelAspects); } /** * Analyzes the specified targets using Skyframe as the driving framework. * * @return the configured targets that should be built along with a WalkableGraph of the analysis. */ public SkyframeAnalysisResult configureTargets( ExtendedEventHandler eventHandler, List<ConfiguredTargetKey> values, List<AspectValueKey> aspectKeys, EventBus eventBus, boolean keepGoing, int numThreads) throws InterruptedException, ViewCreationFailedException { enableAnalysis(true); EvaluationResult<ActionLookupValue> result; try { result = skyframeExecutor.configureTargets( eventHandler, values, aspectKeys, keepGoing, numThreads); } finally { enableAnalysis(false); } ImmutableMap<ActionAnalysisMetadata, ConflictException> badActions = skyframeExecutor.findArtifactConflicts(); Collection<AspectValue> goodAspects = Lists.newArrayListWithCapacity(values.size()); NestedSetBuilder<Package> packages = NestedSetBuilder.stableOrder(); for (AspectValueKey aspectKey : aspectKeys) { AspectValue value = (AspectValue) result.get(aspectKey.getSkyKey()); if (value == null) { // Skip aspects that couldn't be applied to targets. continue; } goodAspects.add(value); packages.addTransitive(value.getTransitivePackages()); } // Filter out all CTs that have a bad action and convert to a list of configured targets. This // code ensures that the resulting list of configured targets has the same order as the incoming // list of values, i.e., that the order is deterministic. Collection<ConfiguredTarget> goodCts = Lists.newArrayListWithCapacity(values.size()); for (ConfiguredTargetKey value : values) { ConfiguredTargetValue ctValue = (ConfiguredTargetValue) result.get(ConfiguredTargetValue.key(value)); if (ctValue == null) { continue; } goodCts.add(ctValue.getConfiguredTarget()); packages.addTransitive(ctValue.getTransitivePackages()); } ImmutableMap<PackageIdentifier, Path> packageRoots = LoadingPhaseRunner.collectPackageRoots(packages.build().toCollection()); if (!result.hasError() && badActions.isEmpty()) { return new SkyframeAnalysisResult( /*hasLoadingError=*/false, /*hasAnalysisError=*/false, ImmutableList.copyOf(goodCts), result.getWalkableGraph(), ImmutableList.copyOf(goodAspects), packageRoots); } // --nokeep_going so we fail with an exception for the first error. // TODO(bazel-team): We might want to report the other errors through the event bus but // for keeping this code in parity with legacy we just report the first error for now. if (!keepGoing) { for (Map.Entry<ActionAnalysisMetadata, ConflictException> bad : badActions.entrySet()) { ConflictException ex = bad.getValue(); try { ex.rethrowTyped(); } catch (MutableActionGraph.ActionConflictException ace) { ace.reportTo(eventHandler); String errorMsg = "Analysis of target '" + bad.getKey().getOwner().getLabel() + "' failed; build aborted"; throw new ViewCreationFailedException(errorMsg); } catch (ArtifactPrefixConflictException apce) { eventHandler.handle(Event.error(apce.getMessage())); } throw new ViewCreationFailedException(ex.getMessage()); } Map.Entry<SkyKey, ErrorInfo> error = result.errorMap().entrySet().iterator().next(); SkyKey topLevel = error.getKey(); ErrorInfo errorInfo = error.getValue(); assertSaneAnalysisError(errorInfo, topLevel); skyframeExecutor.getCyclesReporter().reportCycles(errorInfo.getCycleInfo(), topLevel, eventHandler); Throwable cause = errorInfo.getException(); Preconditions.checkState(cause != null || !Iterables.isEmpty(errorInfo.getCycleInfo()), errorInfo); String errorMsg = null; if (topLevel.argument() instanceof ConfiguredTargetKey) { errorMsg = "Analysis of target '" + ConfiguredTargetValue.extractLabel(topLevel) + "' failed; build aborted"; } else if (topLevel.argument() instanceof AspectValueKey) { AspectValueKey aspectKey = (AspectValueKey) topLevel.argument(); errorMsg = "Analysis of aspect '" + aspectKey.getDescription() + "' failed; build aborted"; } else { assert false; } if (cause instanceof ActionConflictException) { ((ActionConflictException) cause).reportTo(eventHandler); } throw new ViewCreationFailedException(errorMsg); } boolean hasLoadingError = false; // --keep_going : We notify the error and return a ConfiguredTargetValue for (Map.Entry<SkyKey, ErrorInfo> errorEntry : result.errorMap().entrySet()) { // Only handle errors of configured targets, not errors of top-level aspects. // TODO(ulfjack): this is quadratic - if there are a lot of CTs, this could be rather slow. if (!values.contains(errorEntry.getKey().argument())) { continue; } SkyKey errorKey = errorEntry.getKey(); ConfiguredTargetKey label = (ConfiguredTargetKey) errorKey.argument(); Label topLevelLabel = label.getLabel(); ErrorInfo errorInfo = errorEntry.getValue(); assertSaneAnalysisError(errorInfo, errorKey); skyframeExecutor.getCyclesReporter().reportCycles(errorInfo.getCycleInfo(), errorKey, eventHandler); Exception cause = errorInfo.getException(); Label analysisRootCause = null; if (cause instanceof ConfiguredValueCreationException) { ConfiguredValueCreationException ctCause = (ConfiguredValueCreationException) cause; for (Label rootCause : ctCause.getRootCauses()) { hasLoadingError = true; eventBus.post(new LoadingFailureEvent(topLevelLabel, rootCause)); } analysisRootCause = ctCause.getAnalysisRootCause(); } else if (!Iterables.isEmpty(errorInfo.getCycleInfo())) { analysisRootCause = maybeGetConfiguredTargetCycleCulprit( topLevelLabel, errorInfo.getCycleInfo()); } else if (cause instanceof ActionConflictException) { ((ActionConflictException) cause).reportTo(eventHandler); } eventHandler.handle( Event.warn("errors encountered while analyzing target '" + topLevelLabel + "': it will not be built")); if (analysisRootCause != null) { eventBus.post(new AnalysisFailureEvent( LabelAndConfiguration.of(topLevelLabel, label.getConfiguration()), analysisRootCause)); } } Collection<Exception> reportedExceptions = Sets.newHashSet(); for (Map.Entry<ActionAnalysisMetadata, ConflictException> bad : badActions.entrySet()) { ConflictException ex = bad.getValue(); try { ex.rethrowTyped(); } catch (MutableActionGraph.ActionConflictException ace) { ace.reportTo(eventHandler); eventHandler .handle(Event.warn("errors encountered while analyzing target '" + bad.getKey().getOwner().getLabel() + "': it will not be built")); } catch (ArtifactPrefixConflictException apce) { if (reportedExceptions.add(apce)) { eventHandler.handle(Event.error(apce.getMessage())); } } } if (!badActions.isEmpty()) { // In order to determine the set of configured targets transitively error free from action // conflict issues, we run a post-processing update() that uses the bad action map. EvaluationResult<PostConfiguredTargetValue> actionConflictResult = skyframeExecutor.postConfigureTargets(eventHandler, values, keepGoing, badActions); goodCts = Lists.newArrayListWithCapacity(values.size()); for (ConfiguredTargetKey value : values) { PostConfiguredTargetValue postCt = actionConflictResult.get(PostConfiguredTargetValue.key(value)); if (postCt != null) { goodCts.add(postCt.getCt()); } } } return new SkyframeAnalysisResult( hasLoadingError, result.hasError() || !badActions.isEmpty(), ImmutableList.copyOf(goodCts), result.getWalkableGraph(), ImmutableList.copyOf(goodAspects), packageRoots); } @Nullable private static Label maybeGetConfiguredTargetCycleCulprit( Label labelToLoad, Iterable<CycleInfo> cycleInfos) { for (CycleInfo cycleInfo : cycleInfos) { SkyKey culprit = Iterables.getFirst(cycleInfo.getCycle(), null); if (culprit == null) { continue; } if (culprit.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) { return ((ConfiguredTargetKey) culprit.argument()).getLabel(); } else if (culprit.functionName().equals(SkyFunctions.TRANSITIVE_TARGET)) { return (Label) culprit.argument(); } else { return labelToLoad; } } return null; } private static void assertSaneAnalysisError(ErrorInfo errorInfo, SkyKey key) { Throwable cause = errorInfo.getException(); if (cause != null) { // We should only be trying to configure targets when the loading phase succeeds, meaning // that the only errors should be analysis errors. Preconditions.checkState( cause instanceof ConfiguredValueCreationException || cause instanceof ActionConflictException // For top-level aspects || cause instanceof AspectCreationException || cause instanceof SkylarkImportFailedException // Only if we run the reduced loading phase and then analyze with --nokeep_going. || cause instanceof NoSuchTargetException || cause instanceof NoSuchPackageException, "%s -> %s", key, errorInfo); } } public ArtifactFactory getArtifactFactory() { return artifactFactory; } /** * Because we don't know what build-info artifacts this configured target may request, we * conservatively register a dep on all of them. */ // TODO(bazel-team): Allow analysis to return null so the value builder can exit and wait for a // restart deps are not present. private static boolean getWorkspaceStatusValues(Environment env, BuildConfiguration config) throws InterruptedException { env.getValue(WorkspaceStatusValue.SKY_KEY); Map<BuildInfoKey, BuildInfoFactory> buildInfoFactories = PrecomputedValue.BUILD_INFO_FACTORIES.get(env); if (buildInfoFactories == null) { return false; } // These factories may each create their own build info artifacts, all depending on the basic // build-info.txt and build-changelist.txt. List<SkyKey> depKeys = Lists.newArrayList(); for (BuildInfoKey key : buildInfoFactories.keySet()) { if (buildInfoFactories.get(key).isEnabled(config)) { depKeys.add(BuildInfoCollectionValue.key(new BuildInfoKeyAndConfig(key, config))); } } env.getValues(depKeys); return !env.valuesMissing(); } /** Returns null if any build-info values are not ready. */ @Nullable CachingAnalysisEnvironment createAnalysisEnvironment( ArtifactOwner owner, boolean isSystemEnv, ExtendedEventHandler eventHandler, Environment env, BuildConfiguration config) throws InterruptedException { if (config != null && !getWorkspaceStatusValues(env, config)) { return null; } boolean extendedSanityChecks = config != null && config.extendedSanityChecks(); boolean allowRegisteringActions = config == null || config.isActionsEnabled(); return new CachingAnalysisEnvironment( artifactFactory, owner, isSystemEnv, extendedSanityChecks, eventHandler, env, allowRegisteringActions); } /** * Invokes the appropriate constructor to create a {@link ConfiguredTarget} instance. * * <p>For use in {@code ConfiguredTargetFunction}. * * <p>Returns null if Skyframe deps are missing or upon certain errors. */ @Nullable ConfiguredTarget createConfiguredTarget(Target target, BuildConfiguration configuration, CachingAnalysisEnvironment analysisEnvironment, OrderedSetMultimap<Attribute, ConfiguredTarget> prerequisiteMap, ImmutableMap<Label, ConfigMatchingProvider> configConditions) throws InterruptedException { Preconditions.checkState(enableAnalysis, "Already in execution phase %s %s", target, configuration); Preconditions.checkNotNull(analysisEnvironment); Preconditions.checkNotNull(target); Preconditions.checkNotNull(prerequisiteMap); return factory.createConfiguredTarget(analysisEnvironment, artifactFactory, target, configuration, getHostConfiguration(configuration), prerequisiteMap, configConditions); } /** * Returns the host configuration trimmed to the same fragments as the input configuration. If * the input is null, returns the top-level host configuration. * * <p>For static configurations, this unconditionally returns the (sole) top-level configuration. * * <p>This may only be called after {@link #setTopLevelHostConfiguration} has set the * correct host configuration at the top-level. */ public BuildConfiguration getHostConfiguration(BuildConfiguration config) { if (config == null || !config.useDynamicConfigurations()) { return topLevelHostConfiguration; } // TODO(bazel-team): have the fragment classes be those required by the consuming target's // transitive closure. This isn't the same as the input configuration's fragment classes - // the latter may be a proper subset of the former. // // ConfigurationFactory.getConfiguration provides the reason why: if a declared required // fragment is evaluated and returns null, it never gets added to the configuration. So if we // use the configuration's fragments as the source of truth, that excludes required fragments // that never made it in. // // If we're just trimming an existing configuration, this is no big deal (if the original // configuration doesn't need the fragment, the trimmed one doesn't either). But this method // trims a host configuration to the same scope as a target configuration. Since their options // are different, the host instance may actually be able to produce the fragment. So it's // wrong and potentially dangerous to unilaterally exclude it. Set<Class<? extends BuildConfiguration.Fragment>> fragmentClasses = config.trimConfigurations() ? config.fragmentClasses() : ((ConfiguredRuleClassProvider) ruleClassProvider).getAllFragments(); BuildConfiguration hostConfig = hostConfigurationCache.get(fragmentClasses); if (hostConfig != null) { return hostConfig; } // TODO(bazel-team): investigate getting the trimmed config from Skyframe instead of cloning. // This is the only place we instantiate BuildConfigurations outside of Skyframe, This can // produce surprising effects, such as requesting a configuration that's in the Skyframe cache // but still produces a unique instance because we don't check that cache. It'd be nice to // guarantee that *all* instantiations happen through Skyframe. That could, for example, // guarantee that config1.equals(config2) implies config1 == config2, which is nice for // verifying we don't accidentally create extra configurations. But unfortunately, // hostConfigurationCache was specifically created because Skyframe is too slow for this use // case. So further optimization is necessary to make that viable (proto_library in particular // contributes to much of the difference). BuildConfiguration trimmedConfig = topLevelHostConfiguration.clone(fragmentClasses, ruleClassProvider); hostConfigurationCache.put(fragmentClasses, trimmedConfig); return trimmedConfig; } SkyframeDependencyResolver createDependencyResolver(Environment env) { return new SkyframeDependencyResolver(env); } /** * Workaround to clear all legacy data, like the artifact factory. We need * to clear them to avoid conflicts. * TODO(bazel-team): Remove this workaround. [skyframe-execution] */ void clearLegacyData() { artifactFactory.clear(); } /** * Hack to invalidate actions in legacy action graph when their values are invalidated in * skyframe. */ EvaluationProgressReceiver getProgressReceiver() { return progressReceiver; } /** Clear the invalidated configured targets detected during loading and analysis phases. */ public void clearInvalidatedConfiguredTargets() { dirtiedConfiguredTargetKeys = Sets.newConcurrentHashSet(); anyConfiguredTargetDeleted = false; } public boolean isSomeConfiguredTargetInvalidated() { return anyConfiguredTargetDeleted || !dirtiedConfiguredTargetKeys.isEmpty(); } /** * Called from SkyframeExecutor to see whether the graph needs to be checked for artifact * conflicts. Returns true if some configured target has been evaluated since the last time the * graph was checked for artifact conflicts (with that last time marked by a call to * {@link #resetEvaluatedConfiguredTargetFlag()}). */ boolean isSomeConfiguredTargetEvaluated() { Preconditions.checkState(!enableAnalysis); return someConfiguredTargetEvaluated; } /** * Called from SkyframeExecutor after the graph is checked for artifact conflicts so that * the next time {@link #isSomeConfiguredTargetEvaluated} is called, it will return true only if * some configured target has been evaluated since the last check for artifact conflicts. */ void resetEvaluatedConfiguredTargetFlag() { someConfiguredTargetEvaluated = false; } /** * {@link #createConfiguredTarget} will only create configured targets if this is set to true. It * should be set to true before any Skyframe update call that might call into {@link * #createConfiguredTarget}, and false immediately after the call. Use it to fail-fast in the case * that a target is requested for analysis not during the analysis phase. */ void enableAnalysis(boolean enable) { this.enableAnalysis = enable; } private class ConfiguredTargetValueProgressReceiver extends EvaluationProgressReceiver.NullEvaluationProgressReceiver { @Override public void invalidated(SkyKey skyKey, InvalidationState state) { if (skyKey.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) { if (state == InvalidationState.DELETED) { anyConfiguredTargetDeleted = true; } else { // If the value was just dirtied and not deleted, then it may not be truly invalid, since // it may later get re-validated. Therefore adding the key to dirtiedConfiguredTargetKeys // is provisional--if the key is later evaluated and the value found to be clean, then we // remove it from the set. dirtiedConfiguredTargetKeys.add(skyKey); } } } @Override public void evaluated(SkyKey skyKey, Supplier<SkyValue> skyValueSupplier, EvaluationState state) { if (skyKey.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) { switch (state) { case BUILT: if (skyValueSupplier.get() != null) { evaluatedConfiguredTargets.add(skyKey); // During multithreaded operation, this is only set to true, so no concurrency issues. someConfiguredTargetEvaluated = true; } break; case CLEAN: // If the configured target value did not need to be rebuilt, then it wasn't truly // invalid. dirtiedConfiguredTargetKeys.remove(skyKey); break; } } } } }