aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java510
1 files changed, 510 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
new file mode 100644
index 0000000000..857c23187f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -0,0 +1,510 @@
+// Copyright 2014 Google Inc. 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.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+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.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.AnalysisFailureEvent;
+import com.google.devtools.build.lib.analysis.Aspect;
+import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
+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.RuleConfiguredTarget;
+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.BinTools;
+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.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.ActionLookupValue.ActionLookupKey;
+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.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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 final ConfiguredTargetFactory factory;
+ private final ArtifactFactory artifactFactory;
+ @Nullable private EventHandler warningListener;
+ private final SkyframeExecutor skyframeExecutor;
+ private final Runnable legacyDataCleaner;
+ private final BinTools binTools;
+ 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 invalidationReceiver =
+ new ConfiguredTargetValueInvalidationReceiver();
+ 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 targets so that we can know if something
+ // has been invalidated after graph pruning has been executed.
+ private Set<ConfiguredTargetValue> dirtyConfiguredTargets = Sets.newConcurrentHashSet();
+ private volatile boolean anyConfiguredTargetDeleted = false;
+ private SkyKey configurationKey = null;
+
+ public SkyframeBuildView(ConfiguredTargetFactory factory,
+ ArtifactFactory artifactFactory,
+ SkyframeExecutor skyframeExecutor, Runnable legacyDataCleaner, BinTools binTools) {
+ this.factory = factory;
+ this.artifactFactory = artifactFactory;
+ this.skyframeExecutor = skyframeExecutor;
+ this.legacyDataCleaner = legacyDataCleaner;
+ this.binTools = binTools;
+ skyframeExecutor.setArtifactFactoryAndBinTools(artifactFactory, binTools);
+ }
+
+ public void setWarningListener(@Nullable EventHandler warningListener) {
+ this.warningListener = warningListener;
+ }
+
+ public void setConfigurationSkyKey(SkyKey skyKey) {
+ this.configurationKey = skyKey;
+ }
+
+ public void resetEvaluatedConfiguredTargetKeysSet() {
+ evaluatedConfiguredTargets.clear();
+ }
+
+ public Set<SkyKey> getEvaluatedTargetKeys() {
+ return ImmutableSet.copyOf(evaluatedConfiguredTargets);
+ }
+
+ private void setDeserializedArtifactOwners() throws ViewCreationFailedException {
+ Map<PathFragment, Artifact> deserializedArtifactMap =
+ artifactFactory.getDeserializedArtifacts();
+ Set<Artifact> deserializedArtifacts = new HashSet<>();
+ for (Artifact artifact : deserializedArtifactMap.values()) {
+ if (!artifact.getExecPath().getBaseName().endsWith(".gcda")) {
+ // gcda files are classified as generated artifacts, but are not actually generated. All
+ // others need owners.
+ deserializedArtifacts.add(artifact);
+ }
+ }
+ if (deserializedArtifacts.isEmpty()) {
+ // If there are no deserialized artifacts to process, don't pay the price of iterating over
+ // the graph.
+ return;
+ }
+ for (Map.Entry<SkyKey, ActionLookupValue> entry :
+ skyframeExecutor.getActionLookupValueMap().entrySet()) {
+ for (Action action : entry.getValue().getActionsForFindingArtifactOwners()) {
+ for (Artifact output : action.getOutputs()) {
+ Artifact deserializedArtifact = deserializedArtifactMap.get(output.getExecPath());
+ if (deserializedArtifact != null) {
+ deserializedArtifact.setArtifactOwner((ActionLookupKey) entry.getKey().argument());
+ deserializedArtifacts.remove(deserializedArtifact);
+ }
+ }
+ }
+ }
+ if (!deserializedArtifacts.isEmpty()) {
+ throw new ViewCreationFailedException("These artifacts were read in from the FDO profile but"
+ + " have no generating action that could be found. If you are confident that your profile was"
+ + " collected from the same source state at which you're building, please report this:\n"
+ + Artifact.asExecPaths(deserializedArtifacts));
+ }
+ artifactFactory.clearDeserializedArtifacts();
+ }
+
+ /**
+ * Analyzes the specified targets using Skyframe as the driving framework.
+ *
+ * @return the configured targets that should be built
+ */
+ public Collection<ConfiguredTarget> configureTargets(List<ConfiguredTargetKey> values,
+ EventBus eventBus, boolean keepGoing)
+ throws InterruptedException, ViewCreationFailedException {
+ enableAnalysis(true);
+ EvaluationResult<ConfiguredTargetValue> result;
+ try {
+ result = skyframeExecutor.configureTargets(values, keepGoing);
+ } finally {
+ enableAnalysis(false);
+ }
+ // For Skyframe m1, note that we already reported action conflicts during action registration
+ // in the legacy action graph.
+ ImmutableMap<Action, ConflictException> badActions = skyframeExecutor.findArtifactConflicts();
+
+ // 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 = result.get(ConfiguredTargetValue.key(value));
+ if (ctValue == null) {
+ continue;
+ }
+ goodCts.add(ctValue.getConfiguredTarget());
+ }
+
+ if (!result.hasError() && badActions.isEmpty()) {
+ setDeserializedArtifactOwners();
+ return goodCts;
+ }
+
+ // --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<Action, ConflictException> bad : badActions.entrySet()) {
+ ConflictException ex = bad.getValue();
+ try {
+ ex.rethrowTyped();
+ } catch (MutableActionGraph.ActionConflictException ace) {
+ ace.reportTo(skyframeExecutor.getReporter());
+ String errorMsg = "Analysis of target '" + bad.getKey().getOwner().getLabel()
+ + "' failed; build aborted";
+ throw new ViewCreationFailedException(errorMsg);
+ } catch (ArtifactPrefixConflictException apce) {
+ skyframeExecutor.getReporter().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,
+ skyframeExecutor.getReporter());
+ Throwable cause = errorInfo.getException();
+ Preconditions.checkState(cause != null || !Iterables.isEmpty(errorInfo.getCycleInfo()),
+ errorInfo);
+ String errorMsg = "Analysis of target '" + ConfiguredTargetValue.extractLabel(topLevel)
+ + "' failed; build aborted";
+ throw new ViewCreationFailedException(errorMsg);
+ }
+
+ // --keep_going : We notify the error and return a ConfiguredTargetValue
+ for (Map.Entry<SkyKey, ErrorInfo> errorEntry : result.errorMap().entrySet()) {
+ if (values.contains(errorEntry.getKey().argument())) {
+ SkyKey errorKey = errorEntry.getKey();
+ ConfiguredTargetKey label = (ConfiguredTargetKey) errorKey.argument();
+ ErrorInfo errorInfo = errorEntry.getValue();
+ assertSaneAnalysisError(errorInfo, errorKey);
+
+ skyframeExecutor.getCyclesReporter().reportCycles(errorInfo.getCycleInfo(), errorKey,
+ skyframeExecutor.getReporter());
+ // We try to get the root cause key first from ErrorInfo rootCauses. If we don't have one
+ // we try to use the cycle culprit if the error is a cycle. Otherwise we use the top-level
+ // error key.
+ Label root;
+ if (!Iterables.isEmpty(errorEntry.getValue().getRootCauses())) {
+ SkyKey culprit = Preconditions.checkNotNull(Iterables.getFirst(
+ errorEntry.getValue().getRootCauses(), null));
+ root = ((ConfiguredTargetKey) culprit.argument()).getLabel();
+ } else {
+ root = maybeGetConfiguredTargetCycleCulprit(errorInfo.getCycleInfo());
+ }
+ if (warningListener != null) {
+ warningListener.handle(Event.warn("errors encountered while analyzing target '"
+ + label + "': it will not be built"));
+ }
+ eventBus.post(new AnalysisFailureEvent(
+ LabelAndConfiguration.of(label.getLabel(), label.getConfiguration()), root));
+ }
+ }
+
+ Collection<Exception> reportedExceptions = Sets.newHashSet();
+ for (Map.Entry<Action, ConflictException> bad : badActions.entrySet()) {
+ ConflictException ex = bad.getValue();
+ try {
+ ex.rethrowTyped();
+ } catch (MutableActionGraph.ActionConflictException ace) {
+ ace.reportTo(skyframeExecutor.getReporter());
+ if (warningListener != null) {
+ warningListener.handle(Event.warn("errors encountered while analyzing target '"
+ + bad.getKey().getOwner().getLabel() + "': it will not be built"));
+ }
+ } catch (ArtifactPrefixConflictException apce) {
+ if (reportedExceptions.add(apce)) {
+ skyframeExecutor.getReporter().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(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());
+ }
+ }
+ }
+ setDeserializedArtifactOwners();
+ return goodCts;
+ }
+
+ @Nullable
+ Label maybeGetConfiguredTargetCycleCulprit(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 ((LabelAndConfiguration) culprit.argument()).getLabel();
+ }
+ }
+ 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,
+ "%s -> %s", key, errorInfo);
+ }
+ }
+
+ ArtifactFactory getArtifactFactory() {
+ return artifactFactory;
+ }
+
+ @Nullable
+ EventHandler getWarningListener() {
+ return warningListener;
+ }
+
+ /**
+ * 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 boolean getWorkspaceStatusValues(Environment env) {
+ env.getValue(WorkspaceStatusValue.SKY_KEY);
+ Map<BuildInfoKey, BuildInfoFactory> buildInfoFactories =
+ PrecomputedValue.BUILD_INFO_FACTORIES.get(env);
+ if (buildInfoFactories == null) {
+ return false;
+ }
+ BuildConfigurationCollection configurations = getBuildConfigurationCollection(env);
+ if (configurations == 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()) {
+ for (BuildConfiguration config : configurations.getAllConfigurations()) {
+ 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, boolean extendedSanityChecks, EventHandler eventHandler,
+ Environment env, boolean allowRegisteringActions) {
+ if (!getWorkspaceStatusValues(env)) {
+ return null;
+ }
+ return new CachingAnalysisEnvironment(
+ artifactFactory, owner, isSystemEnv, extendedSanityChecks, eventHandler, env,
+ allowRegisteringActions, binTools);
+ }
+
+ /**
+ * 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,
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
+ Set<ConfigMatchingProvider> configConditions)
+ throws InterruptedException {
+ Preconditions.checkState(enableAnalysis,
+ "Already in execution phase %s %s", target, configuration);
+ return factory.createConfiguredTarget(analysisEnvironment, artifactFactory, target,
+ configuration, prerequisiteMap, configConditions);
+ }
+
+ @Nullable
+ public Aspect createAspect(
+ AnalysisEnvironment env, RuleConfiguredTarget associatedTarget,
+ ConfiguredAspectFactory aspectFactory,
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
+ Set<ConfigMatchingProvider> configConditions) {
+ return factory.createAspect(
+ env, associatedTarget, aspectFactory, prerequisiteMap, configConditions);
+ }
+
+ @Nullable
+ private BuildConfigurationCollection getBuildConfigurationCollection(Environment env) {
+ ConfigurationCollectionValue configurationsValue =
+ (ConfigurationCollectionValue) env.getValue(configurationKey);
+ return configurationsValue == null ? null : configurationsValue.getConfigurationCollection();
+ }
+
+ @Nullable
+ SkyframeDependencyResolver createDependencyResolver(Environment env) {
+ BuildConfigurationCollection configurations = getBuildConfigurationCollection(env);
+ return configurations == null ? null : new SkyframeDependencyResolver(env);
+ }
+
+ /**
+ * Workaround to clear all legacy data, like the action graph and the artifact factory. We need
+ * to clear them to avoid conflicts.
+ * TODO(bazel-team): Remove this workaround. [skyframe-execution]
+ */
+ void clearLegacyData() {
+ legacyDataCleaner.run();
+ }
+
+ /**
+ * Hack to invalidate actions in legacy action graph when their values are invalidated in
+ * skyframe.
+ */
+ EvaluationProgressReceiver getInvalidationReceiver() {
+ return invalidationReceiver;
+ }
+
+ /** Clear the invalidated configured targets detected during loading and analysis phases. */
+ public void clearInvalidatedConfiguredTargets() {
+ dirtyConfiguredTargets = Sets.newConcurrentHashSet();
+ anyConfiguredTargetDeleted = false;
+ }
+
+ public boolean isSomeConfiguredTargetInvalidated() {
+ return anyConfiguredTargetDeleted || !dirtyConfiguredTargets.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 ConfiguredTargetValueInvalidationReceiver implements EvaluationProgressReceiver {
+ @Override
+ public void invalidated(SkyValue value, InvalidationState state) {
+ if (value instanceof ConfiguredTargetValue) {
+ ConfiguredTargetValue ctValue = (ConfiguredTargetValue) value;
+ // If the value was just dirtied and not deleted, then it may not be truly invalid, since
+ // it may later get re-validated.
+ if (state == InvalidationState.DELETED) {
+ anyConfiguredTargetDeleted = true;
+ } else {
+ dirtyConfiguredTargets.add(ctValue);
+ }
+ }
+ }
+
+ @Override
+ public void enqueueing(SkyKey skyKey) {}
+
+ @Override
+ public void evaluated(SkyKey skyKey, SkyValue value, EvaluationState state) {
+ if (skyKey.functionName() == SkyFunctions.CONFIGURED_TARGET) {
+ if (state == EvaluationState.BUILT) {
+ evaluatedConfiguredTargets.add(skyKey);
+ // During multithreaded operation, this is only set to true, so no concurrency issues.
+ someConfiguredTargetEvaluated = true;
+ }
+ Preconditions.checkNotNull(value, "%s %s", skyKey, state);
+ ConfiguredTargetValue ctValue = (ConfiguredTargetValue) value;
+ dirtyConfiguredTargets.remove(ctValue);
+ }
+ }
+ }
+}