| // 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.MoreObjects; |
| import com.google.common.base.Objects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Maps; |
| import com.google.devtools.build.lib.actions.ActionInputHelper; |
| import com.google.devtools.build.lib.actions.ActionLookupData; |
| import com.google.devtools.build.lib.actions.ActionLookupValue; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.OwnerlessArtifactWrapper; |
| import com.google.devtools.build.lib.actions.ArtifactFileMetadata; |
| import com.google.devtools.build.lib.actions.ArtifactOwner; |
| import com.google.devtools.build.lib.actions.FileArtifactValue; |
| import com.google.devtools.build.lib.actions.FileStateValue; |
| import com.google.devtools.build.lib.actions.FileValue; |
| import com.google.devtools.build.lib.actions.FilesetOutputSymlink; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| import com.google.devtools.build.lib.util.BigIntegerFingerprint; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import java.math.BigInteger; |
| import java.util.Comparator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A value representing an executed action. |
| * |
| * <p>Concerning the data in this class: |
| * |
| * <p>We want to track all output data from an ActionExecutionValue. See {@link OutputStore} for a |
| * discussion of how its fields {@link OutputStore#artifactData} and {@link |
| * OutputStore#additionalOutputData} relate. The relationship is the same between {@link |
| * #artifactData} and {@link #additionalOutputData} here. |
| */ |
| @Immutable |
| @ThreadSafe |
| public class ActionExecutionValue implements SkyValue { |
| |
| /** |
| * The metadata of all files for this ActionExecutionValue whose filesystem metadata differs from |
| * the metadata in {@link #additionalOutputData} or is not present there. This metadata can be |
| * checked quickly against the actual filesystem on incremental builds. |
| * |
| * <p>If additional metadata is not needed here over what is in {@link #additionalOutputData}, the |
| * value will be {@link ArtifactFileMetadata#PLACEHOLDER}. |
| */ |
| private final ImmutableMap<Artifact, ArtifactFileMetadata> artifactData; |
| |
| /** The TreeArtifactValue of all TreeArtifacts output by this Action. */ |
| private final ImmutableMap<Artifact, TreeArtifactValue> treeArtifactData; |
| |
| /** |
| * Contains all remaining data that weren't in the above maps. See {@link |
| * OutputStore#getAllAdditionalOutputData}. |
| */ |
| private final ImmutableMap<Artifact, FileArtifactValue> additionalOutputData; |
| |
| @Nullable private final ImmutableList<FilesetOutputSymlink> outputSymlinks; |
| |
| @Nullable private final NestedSet<Artifact> discoveredModules; |
| |
| /** |
| * Transient because it can be reconstituted on demand, and {@link BigInteger} isn't serializable. |
| */ |
| @Nullable private transient BigInteger valueFingerprint; |
| |
| /** |
| * @param artifactData Map from Artifacts to corresponding {@link ArtifactFileMetadata}. |
| * @param treeArtifactData All tree artifact data. |
| * @param additionalOutputData Map from Artifacts to values if the FileArtifactValue for this |
| * artifact cannot be derived from the corresponding FileValue (see {@link |
| * OutputStore#getAllAdditionalOutputData} for when this is necessary). These output data are |
| * not used by the {@link FilesystemValueChecker} to invalidate ActionExecutionValues. |
| * @param outputSymlinks This represents the SymlinkTree which is the output of a fileset action. |
| * @param discoveredModules cpp modules discovered |
| */ |
| private ActionExecutionValue( |
| Map<Artifact, ArtifactFileMetadata> artifactData, |
| Map<Artifact, TreeArtifactValue> treeArtifactData, |
| Map<Artifact, FileArtifactValue> additionalOutputData, |
| @Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks, |
| @Nullable NestedSet<Artifact> discoveredModules) { |
| this.artifactData = ImmutableMap.copyOf(artifactData); |
| this.additionalOutputData = ImmutableMap.copyOf(additionalOutputData); |
| this.treeArtifactData = ImmutableMap.copyOf(treeArtifactData); |
| this.outputSymlinks = outputSymlinks; |
| this.discoveredModules = discoveredModules; |
| } |
| |
| static ActionExecutionValue createFromOutputStore( |
| OutputStore outputStore, |
| @Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks, |
| @Nullable NestedSet<Artifact> discoveredModules, |
| boolean actionDependsOnBuildId) { |
| return create( |
| outputStore.getAllArtifactData(), |
| outputStore.getAllTreeArtifactData(), |
| outputStore.getAllAdditionalOutputData(), |
| outputSymlinks, |
| discoveredModules, |
| actionDependsOnBuildId); |
| } |
| |
| static ActionExecutionValue create( |
| Map<Artifact, ArtifactFileMetadata> artifactData, |
| Map<Artifact, TreeArtifactValue> treeArtifactData, |
| Map<Artifact, FileArtifactValue> additionalOutputData, |
| @Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks, |
| @Nullable NestedSet<Artifact> discoveredModules, |
| boolean actionDependsOnBuildId) { |
| return actionDependsOnBuildId |
| ? new CrossServerUnshareableActionExecutionValue( |
| artifactData, treeArtifactData, additionalOutputData, outputSymlinks, discoveredModules) |
| : new ActionExecutionValue( |
| artifactData, |
| treeArtifactData, |
| additionalOutputData, |
| outputSymlinks, |
| discoveredModules); |
| } |
| |
| /** |
| * Returns metadata for a given artifact, if that metadata cannot be inferred from the |
| * corresponding {@link #getData} call for that Artifact. See {@link |
| * OutputStore#getAllAdditionalOutputData} for when that can happen. |
| */ |
| @Nullable |
| public FileArtifactValue getArtifactValue(Artifact artifact) { |
| return additionalOutputData.get(artifact); |
| } |
| |
| /** |
| * @return The data for each non-middleman output of this action, in the form of the {@link |
| * FileValue} that would be created for the file if it were to be read from disk. |
| */ |
| ArtifactFileMetadata getData(Artifact artifact) { |
| Preconditions.checkState(!additionalOutputData.containsKey(artifact), |
| "Should not be requesting data for already-constructed FileArtifactValue: %s", artifact); |
| return artifactData.get(artifact); |
| } |
| |
| TreeArtifactValue getTreeArtifactValue(Artifact artifact) { |
| Preconditions.checkArgument(artifact.isTreeArtifact()); |
| return treeArtifactData.get(artifact); |
| } |
| |
| /** |
| * @return The map from {@link Artifact}s to the corresponding {@link FileValue}s that would be |
| * returned by {@link #getData}. Primarily needed by {@link FilesystemValueChecker}, also |
| * called by {@link ArtifactFunction} when aggregating a {@link TreeArtifactValue}. |
| */ |
| Map<Artifact, ArtifactFileMetadata> getAllFileValues() { |
| return Maps.transformEntries(artifactData, this::transformIfPlaceholder); |
| } |
| |
| /** |
| * @return The map from {@link Artifact}s to the corresponding {@link TreeArtifactValue}s that |
| * would be returned by {@link #getTreeArtifactValue}. Should only be needed by {@link |
| * FilesystemValueChecker}. |
| */ |
| ImmutableMap<Artifact, TreeArtifactValue> getAllTreeArtifactValues() { |
| return treeArtifactData; |
| } |
| |
| @Nullable |
| ImmutableList<FilesetOutputSymlink> getOutputSymlinks() { |
| return outputSymlinks; |
| } |
| |
| @Nullable |
| public NestedSet<Artifact> getDiscoveredModules() { |
| return discoveredModules; |
| } |
| |
| @Override |
| public BigInteger getValueFingerprint() { |
| if (valueFingerprint == null) { |
| BigIntegerFingerprint fp = new BigIntegerFingerprint(); |
| sortMapByArtifactExecPathAndStream(artifactData) |
| .forEach( |
| (entry) -> { |
| fp.addPath(entry.getKey().getExecPath()); |
| fp.addBigIntegerOrdered(entry.getValue().getFingerprint()); |
| }); |
| sortMapByArtifactExecPathAndStream(treeArtifactData) |
| .forEach( |
| (entry) -> { |
| fp.addPath(entry.getKey().getExecPath()); |
| fp.addBigIntegerOrdered(entry.getValue().getValueFingerprint()); |
| }); |
| sortMapByArtifactExecPathAndStream(additionalOutputData) |
| .forEach( |
| (entry) -> { |
| fp.addPath(entry.getKey().getExecPath()); |
| fp.addBigIntegerOrdered(entry.getValue().getValueFingerprint()); |
| }); |
| if (outputSymlinks != null) { |
| for (FilesetOutputSymlink symlink : outputSymlinks) { |
| fp.addBigIntegerOrdered(symlink.getFingerprint()); |
| } |
| } |
| valueFingerprint = fp.getFingerprint(); |
| } |
| return valueFingerprint; |
| } |
| |
| private static <T> Stream<Entry<Artifact, T>> sortMapByArtifactExecPathAndStream( |
| Map<Artifact, T> inputMap) { |
| return inputMap.entrySet().stream() |
| .sorted(Comparator.comparing(Entry::getKey, Artifact.EXEC_PATH_COMPARATOR)); |
| } |
| |
| /** |
| * @param lookupKey A {@link SkyKey} whose argument is an {@code ActionLookupKey}, whose |
| * corresponding {@code ActionLookupValue} contains the action to be executed. |
| * @param index the index of the action to be executed in the {@code ActionLookupValue}, to be |
| * passed to {@code ActionLookupValue#getAction}. |
| */ |
| @ThreadSafe |
| @VisibleForTesting |
| public static ActionLookupData key(ActionLookupValue.ActionLookupKey lookupKey, int index) { |
| return ActionLookupData.create(lookupKey, index); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("artifactData", artifactData) |
| .add("treeArtifactData", treeArtifactData) |
| .add("additionalOutputData", additionalOutputData) |
| .toString(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (!obj.getClass().equals(getClass())) { |
| return false; |
| } |
| ActionExecutionValue o = (ActionExecutionValue) obj; |
| return artifactData.equals(o.artifactData) |
| && treeArtifactData.equals(o.treeArtifactData) |
| && additionalOutputData.equals(o.additionalOutputData) |
| && (outputSymlinks == null || outputSymlinks.equals(o.outputSymlinks)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(artifactData, treeArtifactData, additionalOutputData); |
| } |
| |
| /** Transforms PLACEHOLDER values into normal instances. */ |
| private ArtifactFileMetadata transformIfPlaceholder( |
| Artifact artifact, ArtifactFileMetadata value) { |
| if (value == ArtifactFileMetadata.PLACEHOLDER) { |
| FileArtifactValue metadata = |
| Preconditions.checkNotNull( |
| additionalOutputData.get(artifact), |
| "Placeholder without corresponding FileArtifactValue for: %s", |
| artifact); |
| FileStateValue.RegularFileStateValue fileStateValue = |
| new FileStateValue.RegularFileStateValue( |
| metadata.getSize(), metadata.getDigest(), /*contentsProxy=*/ null); |
| PathFragment pathFragment = artifact.getPath().asFragment(); |
| return ArtifactFileMetadata.value(pathFragment, fileStateValue, pathFragment, fileStateValue); |
| } |
| return value; |
| } |
| |
| /** |
| * Subclass that reports this value cannot be shared across servers. Note that this is unrelated |
| * to the concept of shared actions. |
| */ |
| private static final class CrossServerUnshareableActionExecutionValue |
| extends ActionExecutionValue { |
| CrossServerUnshareableActionExecutionValue( |
| Map<Artifact, ArtifactFileMetadata> artifactData, |
| Map<Artifact, TreeArtifactValue> treeArtifactData, |
| Map<Artifact, FileArtifactValue> additionalOutputData, |
| @Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks, |
| @Nullable NestedSet<Artifact> discoveredModules) { |
| super( |
| artifactData, treeArtifactData, additionalOutputData, outputSymlinks, discoveredModules); |
| } |
| |
| @Override |
| public boolean dataIsShareable() { |
| return false; |
| } |
| } |
| |
| private static <V> ImmutableMap<Artifact, V> transformKeys( |
| ImmutableMap<Artifact, V> data, Map<OwnerlessArtifactWrapper, Artifact> newArtifactMap) { |
| if (data.isEmpty()) { |
| return data; |
| } |
| ImmutableMap.Builder<Artifact, V> result = ImmutableMap.builderWithExpectedSize(data.size()); |
| for (Map.Entry<Artifact, V> entry : data.entrySet()) { |
| Artifact artifact = entry.getKey(); |
| Artifact transformedArtifact = |
| newArtifactMap.get(new OwnerlessArtifactWrapper(entry.getKey())); |
| if (transformedArtifact == null) { |
| // If this action generated a tree artifact, then the declared outputs of the action will |
| // not include the contents of the directory corresponding to that artifact, but the |
| // contents are present in this ActionExecutionValue as TreeFileArtifacts. We must create |
| // corresponding artifacts in the shared action's ActionExecutionValue. We can do that since |
| // a TreeFileArtifact is uniquely described by its parent, its owner, and its parent- |
| // relative path. Since the child was not a declared output, the child and parent must be |
| // generated by the same action, hence they have the same owner, and the parent was a |
| // declared output, so it is present in the shared action. Then we can create the new |
| // TreeFileArtifact to have the shared action's version of the parent artifact (instead of |
| // the original parent artifact); the same parent-relative path; and the new parent's |
| // ArtifactOwner. |
| Preconditions.checkState( |
| artifact.hasParent(), |
| "Output artifact %s from one shared action not present in another's outputs (%s)", |
| artifact, |
| newArtifactMap); |
| ArtifactOwner childOwner = artifact.getArtifactOwner(); |
| Artifact parent = Preconditions.checkNotNull(artifact.getParent(), artifact); |
| ArtifactOwner parentOwner = parent.getArtifactOwner(); |
| Preconditions.checkState( |
| parentOwner.equals(childOwner), |
| "A parent tree artifact %s has a different ArtifactOwner (%s) than its child %s (owned " |
| + "by %s), but both artifacts were generated by the same action", |
| parent, |
| parentOwner, |
| artifact, |
| childOwner); |
| Artifact newParent = |
| Preconditions.checkNotNull( |
| newArtifactMap.get(new OwnerlessArtifactWrapper(parent)), |
| "parent %s of %s was not present in shared action's data (%s)", |
| parent, |
| artifact, |
| newArtifactMap); |
| transformedArtifact = |
| ActionInputHelper.treeFileArtifact( |
| (Artifact.SpecialArtifact) newParent, artifact.getParentRelativePath()); |
| } |
| result.put(transformedArtifact, entry.getValue()); |
| } |
| return result.build(); |
| } |
| |
| ActionExecutionValue transformForSharedAction(ImmutableSet<Artifact> outputs) { |
| Map<OwnerlessArtifactWrapper, Artifact> newArtifactMap = |
| outputs |
| .stream() |
| .collect(Collectors.toMap(OwnerlessArtifactWrapper::new, Function.identity())); |
| // This is only called for shared actions, so we'll almost certainly have to transform all keys |
| // in all sets. |
| // Discovered modules come from the action's inputs, and so don't need to be transformed. |
| return create( |
| transformKeys(artifactData, newArtifactMap), |
| transformKeys(treeArtifactData, newArtifactMap), |
| transformKeys(additionalOutputData, newArtifactMap), |
| outputSymlinks, |
| discoveredModules, |
| this instanceof CrossServerUnshareableActionExecutionValue); |
| } |
| } |