Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
new file mode 100644
index 0000000..bc45ba3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -0,0 +1,303 @@
+// 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.analysis;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+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.MiddlemanFactory;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection;
+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.events.EventHandler;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue;
+import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.annotation.Nullable;
+
+/**
+ * The implementation of AnalysisEnvironment used for analysis. It tracks metadata for each
+ * configured target, such as the errors and warnings emitted by that target. It is intended that
+ * a separate instance is used for each configured target, so that these don't mix up.
+ */
+public class CachingAnalysisEnvironment implements AnalysisEnvironment {
+ private final ArtifactFactory artifactFactory;
+
+ private final ArtifactOwner owner;
+ /**
+ * If this is the system analysis environment, then errors and warnings are directly reported
+ * to the global reporter, rather than stored, i.e., we don't track here whether there are any
+ * errors.
+ */
+ private final boolean isSystemEnv;
+ private final boolean extendedSanityChecks;
+
+ /**
+ * If false, no actions will be registered, they'll all be just dropped.
+ *
+ * <p>Usually, an analysis environment should register all actions. However, in some scenarios we
+ * analyze some targets twice, but the first one only serves the purpose of collecting information
+ * for the second analysis. In this case we don't register actions created by the first pass in
+ * order to avoid action conflicts.
+ */
+ private final boolean allowRegisteringActions;
+
+ private boolean enabled = true;
+ private MiddlemanFactory middlemanFactory;
+ private EventHandler errorEventListener;
+ private SkyFunction.Environment skyframeEnv;
+ private Map<Artifact, String> artifacts;
+ private final BinTools binTools;
+
+ /**
+ * The list of actions registered by the configured target this analysis environment is
+ * responsible for. May get cleared out at the end of the analysis of said target.
+ */
+ final List<Action> actions = new ArrayList<>();
+
+ public CachingAnalysisEnvironment(ArtifactFactory artifactFactory,
+ ArtifactOwner owner, boolean isSystemEnv, boolean extendedSanityChecks,
+ EventHandler errorEventListener, SkyFunction.Environment env, boolean allowRegisteringActions,
+ BinTools binTools) {
+ this.artifactFactory = artifactFactory;
+ this.owner = Preconditions.checkNotNull(owner);
+ this.isSystemEnv = isSystemEnv;
+ this.extendedSanityChecks = extendedSanityChecks;
+ this.errorEventListener = errorEventListener;
+ this.skyframeEnv = env;
+ this.allowRegisteringActions = allowRegisteringActions;
+ this.binTools = binTools;
+ middlemanFactory = new MiddlemanFactory(artifactFactory, this);
+ artifacts = new HashMap<>();
+ }
+
+ public void disable(Target target) {
+ if (!hasErrors() && allowRegisteringActions) {
+ verifyGeneratedArtifactHaveActions(target);
+ }
+ artifacts = null;
+ middlemanFactory = null;
+ enabled = false;
+ errorEventListener = null;
+ skyframeEnv = null;
+ }
+
+ private static StringBuilder shortDescription(Action action) {
+ if (action == null) {
+ return new StringBuilder("null Action");
+ }
+ return new StringBuilder()
+ .append(action.getClass().getName())
+ .append(' ')
+ .append(action.getMnemonic());
+ }
+
+ /**
+ * Sanity checks that all generated artifacts have a generating action.
+ * @param target for error reporting
+ */
+ public void verifyGeneratedArtifactHaveActions(Target target) {
+ Collection<String> orphanArtifacts = getOrphanArtifactMap().values();
+ List<String> checkedActions = null;
+ if (!orphanArtifacts.isEmpty()) {
+ checkedActions = Lists.newArrayListWithCapacity(actions.size());
+ for (Action action : actions) {
+ StringBuilder sb = shortDescription(action);
+ for (Artifact o : action.getOutputs()) {
+ sb.append("\n ");
+ sb.append(o.getExecPathString());
+ }
+ checkedActions.add(sb.toString());
+ }
+ throw new IllegalStateException(
+ String.format(
+ "%s %s : These artifacts miss a generating action:\n%s\n"
+ + "These actions we checked:\n%s\n",
+ target.getTargetKind(), target.getLabel(),
+ Joiner.on('\n').join(orphanArtifacts), Joiner.on('\n').join(checkedActions)));
+ }
+ }
+
+ @Override
+ public ImmutableSet<Artifact> getOrphanArtifacts() {
+ return ImmutableSet.copyOf(getOrphanArtifactMap().keySet());
+ }
+
+ private Map<Artifact, String> getOrphanArtifactMap() {
+ // Construct this set to avoid poor performance under large --runs_per_test.
+ Set<Artifact> artifactsWithActions = new HashSet<>();
+ for (Action action : actions) {
+ // Don't bother checking that every Artifact only appears once; that test is performed
+ // elsewhere (see #testNonUniqueOutputs in ActionListenerIntegrationTest).
+ artifactsWithActions.addAll(action.getOutputs());
+ }
+ // The order of the artifacts.entrySet iteration is unspecified - we use a TreeMap here to
+ // guarantee that the return value of this method is deterministic.
+ Map<Artifact, String> orphanArtifacts = new TreeMap<>();
+ for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) {
+ Artifact a = entry.getKey();
+ if (!a.isSourceArtifact() && !artifactsWithActions.contains(a)) {
+ orphanArtifacts.put(a, String.format("%s\n%s",
+ a.getExecPathString(), // uncovered artifact
+ entry.getValue())); // origin of creation
+ }
+ }
+ return orphanArtifacts;
+ }
+
+ @Override
+ public EventHandler getEventHandler() {
+ return errorEventListener;
+ }
+
+ @Override
+ public boolean hasErrors() {
+ // The system analysis environment never has errors.
+ if (isSystemEnv) {
+ return false;
+ }
+ Preconditions.checkState(enabled);
+ return ((StoredEventHandler) errorEventListener).hasErrors();
+ }
+
+ @Override
+ public MiddlemanFactory getMiddlemanFactory() {
+ Preconditions.checkState(enabled);
+ return middlemanFactory;
+ }
+
+ /**
+ * Keeps track of artifacts. We check that all of them have an owner when the environment is
+ * sealed (disable()). For performance reasons we only track the originating stacktrace when
+ * running with --experimental_extended_sanity_checks.
+ */
+ private Artifact trackArtifactAndOrigin(Artifact a, @Nullable Throwable e) {
+ if ((e != null) && !artifacts.containsKey(a)) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ artifacts.put(a, sw.toString());
+ } else {
+ artifacts.put(a, "No origin, run with --experimental_extended_sanity_checks");
+ }
+ return a;
+ }
+
+ @Override
+ public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) {
+ Preconditions.checkState(enabled);
+ return trackArtifactAndOrigin(
+ artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()),
+ extendedSanityChecks ? new Throwable() : null);
+ }
+
+ @Override
+ public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) {
+ Preconditions.checkState(enabled);
+ return trackArtifactAndOrigin(
+ artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()),
+ extendedSanityChecks ? new Throwable() : null);
+ }
+
+ @Override
+ public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) {
+ return artifactFactory.getConstantMetadataArtifact(rootRelativePath, root, getOwner());
+ }
+
+ @Override
+ public Artifact getEmbeddedToolArtifact(String embeddedPath) {
+ Preconditions.checkState(enabled);
+ return binTools.getEmbeddedArtifact(embeddedPath, artifactFactory);
+ }
+
+ @Override
+ public void registerAction(Action... actions) {
+ Preconditions.checkState(enabled);
+ if (allowRegisteringActions) {
+ for (Action action : actions) {
+ this.actions.add(action);
+ }
+ }
+ }
+
+ @Override
+ public Action getLocalGeneratingAction(Artifact artifact) {
+ Preconditions.checkState(allowRegisteringActions);
+ for (Action action : actions) {
+ if (action.getOutputs().contains(artifact)) {
+ return action;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<Action> getRegisteredActions() {
+ return Collections.unmodifiableCollection(actions);
+ }
+
+ @Override
+ public SkyFunction.Environment getSkyframeEnv() {
+ return skyframeEnv;
+ }
+
+ @Override
+ public Artifact getStableWorkspaceStatusArtifact() {
+ return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY))
+ .getStableArtifact();
+ }
+
+ @Override
+ public Artifact getVolatileWorkspaceStatusArtifact() {
+ return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY))
+ .getVolatileArtifact();
+ }
+
+ @Override
+ public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) {
+ boolean stamp = AnalysisUtils.isStampingEnabled(ruleContext);
+ BuildInfoCollection collection =
+ ((BuildInfoCollectionValue) skyframeEnv.getValue(BuildInfoCollectionValue.key(
+ new BuildInfoCollectionValue.BuildInfoKeyAndConfig(key, ruleContext.getConfiguration()))))
+ .getCollection();
+ return stamp ? collection.getStampedBuildInfo() : collection.getRedactedBuildInfo();
+ }
+
+ @Override
+ public ArtifactOwner getOwner() {
+ return owner;
+ }
+}