blob: aa48b30fe00923a07ea15ddbf19ea82a01d09162 [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.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
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.actions.PackageRoots;
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.ToolchainContext;
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.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.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.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 final Logger logger = 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 ConfiguredRuleClassProvider 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(), 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) {
logger.info("Discarding analysis cache: configurations have changed.");
skyframeExecutor.handleConfiguredTargetChange();
}
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());
}
Path singleSourceRoot = skyframeExecutor.getForcedSingleSourceRootIfNoExecrootSymlinkCreation();
PackageRoots packageRoots =
singleSourceRoot == null
? new MapAsPackageRoots(
LoadingPhaseRunner.collectPackageRoots(packages.build().toCollection()))
: new PackageRootsNoSymlinkCreation(singleSourceRoot);
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);
}
if (errorInfo.getException() != null) {
throw new ViewCreationFailedException(errorMsg, errorInfo.getException());
} else {
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 ((TransitiveTargetKey) culprit).getLabel();
} 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,
ImmutableMap<BuildInfoKey, BuildInfoFactory> buildInfoFactories)
throws InterruptedException {
env.getValue(WorkspaceStatusValue.SKY_KEY);
// 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, skyframeExecutor.getBuildInfoFactories())) {
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,
@Nullable ToolchainContext toolchainContext)
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,
toolchainContext);
}
/**
* 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>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) {
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()
: 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, ruleClassProvider.getDynamicTransitionMapper());
}
/**
* 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.
*/
public 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;
}
}
}
}
}