|  | // 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.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.ActionAnalysisMetadata; | 
|  | import com.google.devtools.build.lib.actions.ActionKeyContext; | 
|  | import com.google.devtools.build.lib.actions.ActionLookupValue; | 
|  | import com.google.devtools.build.lib.actions.Artifact; | 
|  | import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; | 
|  | import com.google.devtools.build.lib.actions.ArtifactFactory; | 
|  | import com.google.devtools.build.lib.actions.ArtifactRoot; | 
|  | import com.google.devtools.build.lib.actions.MiddlemanFactory; | 
|  | 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.BuildConfiguration; | 
|  | import com.google.devtools.build.lib.events.ExtendedEventHandler; | 
|  | 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.PrecomputedValue; | 
|  | import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue; | 
|  | import com.google.devtools.build.lib.syntax.StarlarkSemantics; | 
|  | import com.google.devtools.build.lib.util.Pair; | 
|  | 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 ActionLookupValue.ActionLookupKey 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; | 
|  | private final boolean allowAnalysisFailures; | 
|  |  | 
|  | private final ActionKeyContext actionKeyContext; | 
|  |  | 
|  | private boolean enabled = true; | 
|  | private MiddlemanFactory middlemanFactory; | 
|  | private ExtendedEventHandler errorEventListener; | 
|  | private SkyFunction.Environment skyframeEnv; | 
|  | /** | 
|  | * Map of artifacts to either themselves or to {@code Pair<Artifact, String>} if | 
|  | * --experimental_extended_sanity_checks is enabled. In the latter case, the string will contain | 
|  | * the stack trace of where the artifact was created. In the former case, we'll construct a | 
|  | * generic message in case of error. | 
|  | * | 
|  | * <p>The artifact is stored so that we can deduplicate artifacts created multiple times. | 
|  | */ | 
|  | private Map<Artifact, Object> artifacts; | 
|  |  | 
|  | /** | 
|  | * 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<ActionAnalysisMetadata> actions = new ArrayList<>(); | 
|  |  | 
|  | public CachingAnalysisEnvironment( | 
|  | ArtifactFactory artifactFactory, | 
|  | ActionKeyContext actionKeyContext, | 
|  | ActionLookupValue.ActionLookupKey owner, | 
|  | boolean isSystemEnv, | 
|  | boolean extendedSanityChecks, | 
|  | boolean allowAnalysisFailures, | 
|  | ExtendedEventHandler errorEventListener, | 
|  | SkyFunction.Environment env) { | 
|  | this.artifactFactory = artifactFactory; | 
|  | this.actionKeyContext = actionKeyContext; | 
|  | this.owner = Preconditions.checkNotNull(owner); | 
|  | this.isSystemEnv = isSystemEnv; | 
|  | this.extendedSanityChecks = extendedSanityChecks; | 
|  | this.allowAnalysisFailures = allowAnalysisFailures; | 
|  | this.errorEventListener = errorEventListener; | 
|  | this.skyframeEnv = env; | 
|  | middlemanFactory = new MiddlemanFactory(artifactFactory, this); | 
|  | artifacts = new HashMap<>(); | 
|  | } | 
|  |  | 
|  | public void disable(Target target) { | 
|  | if (!hasErrors() && !allowAnalysisFailures) { | 
|  | verifyGeneratedArtifactHaveActions(target); | 
|  | } | 
|  | artifacts = null; | 
|  | middlemanFactory = null; | 
|  | enabled = false; | 
|  | errorEventListener = null; | 
|  | skyframeEnv = null; | 
|  | } | 
|  |  | 
|  | private static StringBuilder shortDescription(ActionAnalysisMetadata 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 (ActionAnalysisMetadata 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 do not have a generating action:\n%s\n" | 
|  | + "These actions were 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()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableSet<Artifact> getTreeArtifactsConflictingWithFiles() { | 
|  | boolean hasTreeArtifacts = false; | 
|  | for (Artifact artifact : artifacts.keySet()) { | 
|  | if (artifact.isTreeArtifact()) { | 
|  | hasTreeArtifacts = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!hasTreeArtifacts) { | 
|  | return ImmutableSet.of(); | 
|  | } | 
|  |  | 
|  | HashSet<PathFragment> collect = new HashSet<>(); | 
|  | for (Artifact artifact : artifacts.keySet()) { | 
|  | if (!artifact.isSourceArtifact() && !artifact.isTreeArtifact()) { | 
|  | collect.add(artifact.getExecPath()); | 
|  | } | 
|  | } | 
|  |  | 
|  | ImmutableSet.Builder<Artifact> sameExecPathTreeArtifacts = ImmutableSet.builder(); | 
|  | for (Artifact artifact : artifacts.keySet()) { | 
|  | if (artifact.isTreeArtifact() && collect.contains(artifact.getExecPath())) { | 
|  | sameExecPathTreeArtifacts.add(artifact); | 
|  | } | 
|  | } | 
|  |  | 
|  | return sameExecPathTreeArtifacts.build(); | 
|  | } | 
|  |  | 
|  | private Map<Artifact, String> getOrphanArtifactMap() { | 
|  | // Construct this set to avoid poor performance under large --runs_per_test. | 
|  | Set<Artifact> artifactsWithActions = new HashSet<>(); | 
|  | for (ActionAnalysisMetadata 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<>(Artifact.EXEC_PATH_COMPARATOR); | 
|  | for (Map.Entry<Artifact, Object> entry : artifacts.entrySet()) { | 
|  | Artifact a = entry.getKey(); | 
|  | if (!a.isSourceArtifact() && !artifactsWithActions.contains(a)) { | 
|  | Object value = entry.getValue(); | 
|  | if (value instanceof Artifact) { | 
|  | value = "No origin, run with --experimental_extended_sanity_checks"; | 
|  | } else { | 
|  | value = ((Pair<?, ?>) value).second; | 
|  | } | 
|  | orphanArtifacts.put( | 
|  | a, | 
|  | String.format( | 
|  | "%s\n%s", | 
|  | a.getExecPathString(), // uncovered artifact | 
|  | value)); // origin of creation | 
|  | } | 
|  | } | 
|  | return orphanArtifacts; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ExtendedEventHandler getEventHandler() { | 
|  | return errorEventListener; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionKeyContext getActionKeyContext() { | 
|  | return actionKeyContext; | 
|  | } | 
|  |  | 
|  | @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. | 
|  | */ | 
|  | @SuppressWarnings("unchecked") // Cast of artifacts map's value to Pair. | 
|  | private Artifact.DerivedArtifact dedupAndTrackArtifactAndOrigin( | 
|  | Artifact.DerivedArtifact a, @Nullable Throwable e) { | 
|  | if (artifacts.containsKey(a)) { | 
|  | Object value = artifacts.get(a); | 
|  | if (e == null) { | 
|  | return (Artifact.DerivedArtifact) value; | 
|  | } else { | 
|  | return ((Pair<Artifact.DerivedArtifact, String>) value).first; | 
|  | } | 
|  | } | 
|  | if ((e != null)) { | 
|  | StringWriter sw = new StringWriter(); | 
|  | e.printStackTrace(new PrintWriter(sw)); | 
|  | artifacts.put(a, Pair.of(a, sw.toString())); | 
|  | } else { | 
|  | artifacts.put(a, a); | 
|  | } | 
|  | return a; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact.DerivedArtifact getDerivedArtifact( | 
|  | PathFragment rootRelativePath, ArtifactRoot root) { | 
|  | return getDerivedArtifact(rootRelativePath, root, /*contentBasedPath=*/ false); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact.DerivedArtifact getDerivedArtifact( | 
|  | PathFragment rootRelativePath, ArtifactRoot root, boolean contentBasedPath) { | 
|  | Preconditions.checkState(enabled); | 
|  | return dedupAndTrackArtifactAndOrigin( | 
|  | artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner(), contentBasedPath), | 
|  | extendedSanityChecks ? new Throwable() : null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public SpecialArtifact getTreeArtifact(PathFragment rootRelativePath, ArtifactRoot root) { | 
|  | Preconditions.checkState(enabled); | 
|  | return (SpecialArtifact) | 
|  | dedupAndTrackArtifactAndOrigin( | 
|  | artifactFactory.getTreeArtifact(rootRelativePath, root, getOwner()), | 
|  | extendedSanityChecks ? new Throwable() : null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public SpecialArtifact getSymlinkArtifact(PathFragment rootRelativePath, ArtifactRoot root) { | 
|  | Preconditions.checkState(enabled); | 
|  | return (SpecialArtifact) | 
|  | dedupAndTrackArtifactAndOrigin( | 
|  | artifactFactory.getSymlinkArtifact(rootRelativePath, root, getOwner()), | 
|  | extendedSanityChecks ? new Throwable() : null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact.DerivedArtifact getFilesetArtifact( | 
|  | PathFragment rootRelativePath, ArtifactRoot root) { | 
|  | Preconditions.checkState(enabled); | 
|  | return dedupAndTrackArtifactAndOrigin( | 
|  | artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()), | 
|  | extendedSanityChecks ? new Throwable() : null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, ArtifactRoot root) { | 
|  | return artifactFactory.getConstantMetadataArtifact(rootRelativePath, root, getOwner()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void registerAction(ActionAnalysisMetadata... actions) { | 
|  | Preconditions.checkState(enabled); | 
|  | Collections.addAll(this.actions, actions); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionAnalysisMetadata getLocalGeneratingAction(Artifact artifact) { | 
|  | for (ActionAnalysisMetadata action : actions) { | 
|  | if (action.getOutputs().contains(artifact)) { | 
|  | return action; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableList<ActionAnalysisMetadata> getRegisteredActions() { | 
|  | return ImmutableList.copyOf(actions); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public SkyFunction.Environment getSkyframeEnv() { | 
|  | return skyframeEnv; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public StarlarkSemantics getSkylarkSemantics() throws InterruptedException { | 
|  | return PrecomputedValue.STARLARK_SEMANTICS.get(skyframeEnv); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact getStableWorkspaceStatusArtifact() throws InterruptedException { | 
|  | return getWorkspaceStatusValue().getStableArtifact(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Artifact getVolatileWorkspaceStatusArtifact() throws InterruptedException { | 
|  | return getWorkspaceStatusValue().getVolatileArtifact(); | 
|  | } | 
|  |  | 
|  | private WorkspaceStatusValue getWorkspaceStatusValue() throws InterruptedException { | 
|  | WorkspaceStatusValue workspaceStatusValue = | 
|  | ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.BUILD_INFO_KEY)); | 
|  | if (workspaceStatusValue == null) { | 
|  | throw new MissingDepException("Restart due to missing build info"); | 
|  | } | 
|  | return workspaceStatusValue; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ImmutableList<Artifact> getBuildInfo( | 
|  | boolean stamp, BuildInfoKey key, BuildConfiguration config) throws InterruptedException { | 
|  | BuildInfoCollectionValue collectionValue = | 
|  | (BuildInfoCollectionValue) skyframeEnv.getValue(BuildInfoCollectionValue.key(key, config)); | 
|  | if (collectionValue == null) { | 
|  | throw new MissingDepException( | 
|  | String.format("Restart due to missing BuildInfoCollectionValue (%s %s)", key, config)); | 
|  | } | 
|  | BuildInfoCollection collection = collectionValue.getCollection(); | 
|  | return stamp ? collection.getStampedBuildInfo() : collection.getRedactedBuildInfo(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionLookupValue.ActionLookupKey getOwner() { | 
|  | return owner; | 
|  | } | 
|  |  | 
|  | /** Thrown in case of a missing build info key. */ | 
|  | // TODO(ulfjack): It would be better for this to be a checked exception, which requires updating | 
|  | // all callers to pass the exception through. | 
|  | public static class MissingDepException extends RuntimeException { | 
|  | MissingDepException(String msg) { | 
|  | super(msg); | 
|  | } | 
|  | } | 
|  | } |