| // 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.actions; | |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.MoreObjects; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.hash.HashFunction; | |
| import com.google.common.io.BaseEncoding; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; | |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; | |
| import com.google.devtools.build.lib.util.Fingerprint; | |
| import com.google.devtools.build.lib.vfs.DigestUtils; | |
| import com.google.devtools.build.lib.vfs.FileStatus; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.lib.vfs.Symlinks; | |
| import com.google.devtools.build.lib.vfs.SyscallCache; | |
| import com.google.devtools.build.lib.vfs.XattrProvider; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import java.io.ByteArrayInputStream; | |
| import java.io.IOException; | |
| import java.util.Arrays; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A value that represents a file for the purposes of up-to-dateness checks of actions. | |
| * | |
| * <p>It always stands for an actual file. In particular, tree artifacts and middlemen do not have a | |
| * corresponding {@link FileArtifactValue}. However, the file is not necessarily present in the file | |
| * system; this happens when intermediate build outputs are not downloaded (and maybe when an input | |
| * artifact of an action is missing?) | |
| * | |
| * <p>It makes its main appearance in {@code ActionExecutionValue.artifactData}. It has two main | |
| * uses: | |
| * | |
| * <ul> | |
| * <li>This is how dependent actions get hold of the output metadata of their generated inputs. | |
| * <li>This is how {@code FileSystemValueChecker} figures out which actions need to be invalidated | |
| * (just propagating the invalidation up from leaf nodes is not enough, because the output | |
| * tree may have been changed while Blaze was not looking) | |
| * </ul> | |
| */ | |
| @Immutable | |
| @ThreadSafe | |
| public abstract class FileArtifactValue implements SkyValue, HasDigest { | |
| /** | |
| * The type of the underlying file system object. If it is a regular file, then it is guaranteed | |
| * to have a digest. Otherwise it does not have a digest. | |
| */ | |
| public abstract FileStateType getType(); | |
| /** | |
| * Returns a digest of the content of the underlying file system object; must always return a | |
| * non-null value for instances of type {@link FileStateType#REGULAR_FILE} that are owned by an | |
| * {@code ActionExecutionValue}. | |
| * | |
| * <p>All instances of this interface must either have a digest or return a last-modified time. | |
| * Clients should prefer using the digest for content identification (e.g., for caching), and only | |
| * fall back to the last-modified time if no digest is available. | |
| * | |
| * <p>The return value is owned by this object and must not be modified. | |
| */ | |
| @Override | |
| public abstract byte[] getDigest(); | |
| /** Returns the file's size, or 0 if the underlying file system object is not a file. */ | |
| // TODO(ulfjack): Throw an exception if it's not a file. | |
| public abstract long getSize(); | |
| /** | |
| * Returns the last modified time; see the documentation of {@link #getDigest} for when this can | |
| * and should be called. | |
| */ | |
| public abstract long getModifiedTime(); | |
| // TODO(lberki): This is only used by FileArtifactValue itself. It seems possible to remove this. | |
| public abstract FileContentsProxy getContentsProxy(); | |
| @Nullable | |
| public byte[] getValueFingerprint() { | |
| // TODO(janakr): return fingerprint in other cases: symlink, directory. | |
| return getDigest(); | |
| } | |
| /** | |
| * Index used to resolve remote files. | |
| * | |
| * <p>0 indicates that no such information is available which can mean that it's either a local | |
| * file, empty, or an omitted output. | |
| */ | |
| public int getLocationIndex() { | |
| return 0; | |
| } | |
| /** | |
| * Remote action source identifier for the file. | |
| * | |
| * <p>"" indicates that a remote action output was not the source of this artifact. | |
| */ | |
| public String getActionId() { | |
| return ""; | |
| } | |
| /** Returns {@code true} if the file only exists remotely. */ | |
| public boolean isRemote() { | |
| return false; | |
| } | |
| /** | |
| * Provides a best-effort determination whether the file was changed since the digest was | |
| * computed. This method performs file system I/O, so may be expensive. It's primarily intended to | |
| * avoid storing bad cache entries in an action cache. It should return true if there is a chance | |
| * that the file was modified since the digest was computed. Better not upload if we are not sure | |
| * that the cache entry is reliable. | |
| */ | |
| // TODO(lberki): This is very similar to couldBeModifiedSince(). Check if we can unify these. | |
| public abstract boolean wasModifiedSinceDigest(Path path) throws IOException; | |
| /** | |
| * Returns whether the two {@link FileArtifactValue} instances could be considered the same for | |
| * purposes of action invalidation. | |
| */ | |
| // TODO(lberki): This is very similar to wasModifiedSinceDigest(). Check if we can unify these. | |
| public boolean couldBeModifiedSince(FileArtifactValue lastKnown) { | |
| if (this instanceof Singleton || lastKnown instanceof Singleton) { | |
| return true; | |
| } | |
| if (getType() != lastKnown.getType()) { | |
| return true; | |
| } | |
| if (getDigest() != null && lastKnown.getDigest() != null) { | |
| // If we know the digests, we can tell with certainty whether the file has changed. | |
| return !Arrays.equals(getDigest(), lastKnown.getDigest()) || getSize() != lastKnown.getSize(); | |
| } else { | |
| // If not, we assume by default that the file has changed, but individual implementations | |
| // might know better. For example, regular local files can be compared by ctime or mtime. | |
| return couldBeModifiedByMetadata(lastKnown); | |
| } | |
| } | |
| /** Adds this file metadata to the given {@link Fingerprint}. */ | |
| public final void addTo(Fingerprint fp) { | |
| byte[] digest = getDigest(); | |
| if (digest != null) { | |
| fp.addBytes(digest); | |
| } else { | |
| // Use the timestamp if the digest is not present, but not both. Modifying a timestamp while | |
| // keeping the contents of a file the same should not cause rebuilds. | |
| fp.addLong(getModifiedTime()); | |
| } | |
| } | |
| protected boolean couldBeModifiedByMetadata(FileArtifactValue lastKnown) { | |
| return true; | |
| } | |
| /** | |
| * Optional materialization path. | |
| * | |
| * <p>If present, this artifact is a copy of another artifact. It is still tracked as a | |
| * non-symlink by Bazel, but materialized in the local filesystem as a symlink to the original | |
| * artifact, whose contents live at this location. This is used by {@link | |
| * com.google.devtools.build.lib.remote.AbstractActionInputPrefetcher} to implement zero-cost | |
| * copies of remotely stored artifacts. | |
| */ | |
| public Optional<PathFragment> getMaterializationExecPath() { | |
| return Optional.empty(); | |
| } | |
| /** | |
| * Marker interface for singleton implementations of this class. | |
| * | |
| * <p>Needed for a correct implementation of {@code equals}. | |
| */ | |
| interface Singleton {} | |
| @SerializationConstant | |
| public static final FileArtifactValue DEFAULT_MIDDLEMAN = new SingletonMarkerValue(); | |
| /** Data that marks that a file is not present on the filesystem. */ | |
| @SerializationConstant | |
| public static final FileArtifactValue MISSING_FILE_MARKER = new SingletonMarkerValue(); | |
| /** | |
| * Represents an omitted file -- we are aware of it but it doesn't exist. All access methods are | |
| * unsupported. | |
| */ | |
| @SerializationConstant | |
| public static final FileArtifactValue OMITTED_FILE_MARKER = new OmittedFileValue(); | |
| public static FileArtifactValue createForSourceArtifact( | |
| Artifact artifact, FileValue fileValue, XattrProvider xattrProvider) throws IOException { | |
| // Artifacts with known generating actions should obtain the derived artifact's SkyValue | |
| // from the generating action, instead. | |
| Preconditions.checkState(!artifact.hasKnownGeneratingAction()); | |
| Preconditions.checkState(!artifact.isConstantMetadata()); | |
| boolean isFile = fileValue.isFile(); | |
| return create( | |
| artifact.getPath(), | |
| isFile, | |
| isFile ? fileValue.getSize() : 0, | |
| isFile ? fileValue.realFileStateValue().getContentsProxy() : null, | |
| isFile ? fileValue.getDigest() : null, | |
| xattrProvider); | |
| } | |
| public static FileArtifactValue createFromInjectedDigest( | |
| FileArtifactValue metadata, @Nullable byte[] digest) { | |
| return createForNormalFile(digest, metadata.getContentsProxy(), metadata.getSize()); | |
| } | |
| @VisibleForTesting | |
| public static FileArtifactValue createForTesting(Artifact artifact) throws IOException { | |
| return createForTesting(artifact.getPath()); | |
| } | |
| @VisibleForTesting | |
| public static FileArtifactValue createForTesting(Path path) throws IOException { | |
| // Caution: there's a race condition between stating the file and computing the digest. We need | |
| // to stat first, since we're using the stat to detect changes. We follow symlinks here to be | |
| // consistent with getDigest. | |
| return createFromStat(path, path.stat(Symlinks.FOLLOW), SyscallCache.NO_CACHE); | |
| } | |
| public static FileArtifactValue createFromStat( | |
| Path path, FileStatus stat, XattrProvider xattrProvider) throws IOException { | |
| return create( | |
| path, | |
| stat.isFile(), | |
| stat.getSize(), | |
| FileContentsProxy.create(stat), | |
| /* digest= */ null, | |
| xattrProvider); | |
| } | |
| private static FileArtifactValue create( | |
| Path path, | |
| boolean isFile, | |
| long size, | |
| FileContentsProxy proxy, | |
| @Nullable byte[] digest, | |
| XattrProvider xattrProvider) | |
| throws IOException { | |
| if (!isFile) { | |
| // In this case, we need to store the mtime because the action cache uses mtime for | |
| // directories to determine if this artifact has changed. We want this code path to go away | |
| // somehow. | |
| return new DirectoryArtifactValue(path.getLastModifiedTime()); | |
| } | |
| if (digest == null) { | |
| digest = DigestUtils.getDigestWithManualFallback(path, size, xattrProvider); | |
| } | |
| Preconditions.checkState(digest != null, path); | |
| return createForNormalFile(digest, proxy, size); | |
| } | |
| public static FileArtifactValue createForVirtualActionInput(byte[] digest, long size) { | |
| return new RegularFileArtifactValue(digest, /* proxy= */ null, size); | |
| } | |
| public static FileArtifactValue createForUnresolvedSymlink(Path symlink) throws IOException { | |
| byte[] digest = | |
| symlink | |
| .getFileSystem() | |
| .getDigestFunction() | |
| .getHashFunction() | |
| .hashString(symlink.readSymbolicLink().getPathString(), ISO_8859_1) | |
| .asBytes(); | |
| // We need to be able to tell the difference between a symlink and a file containing the same | |
| // text. So we transform the digest a bit. This works because if one wants to craft a file with | |
| // the same digest as a symlink, one would need to mount a preimage attack on the digest | |
| // function (this would be different if we tweaked the data before applying the hash function) | |
| digest[0] = (byte) (digest[0] ^ 0xff); | |
| return new UnresolvedSymlinkArtifactValue(digest); | |
| } | |
| @VisibleForTesting | |
| public static FileArtifactValue createForNormalFile( | |
| byte[] digest, @Nullable FileContentsProxy proxy, long size) { | |
| return new RegularFileArtifactValue(digest, proxy, size); | |
| } | |
| /** | |
| * Create a FileArtifactValue using the {@link Path} and size. FileArtifactValue#create will | |
| * handle getting the digest using the Path and size values. | |
| */ | |
| public static FileArtifactValue createForNormalFileUsingPath( | |
| Path path, long size, XattrProvider xattrProvider) throws IOException { | |
| return create( | |
| path, /* isFile= */ true, size, /* proxy= */ null, /* digest= */ null, xattrProvider); | |
| } | |
| public static FileArtifactValue createForDirectoryWithHash(byte[] digest) { | |
| return new HashedDirectoryArtifactValue(digest); | |
| } | |
| public static FileArtifactValue createForDirectoryWithMtime(long mtime) { | |
| return new DirectoryArtifactValue(mtime); | |
| } | |
| /** | |
| * Creates a FileArtifactValue used as a 'proxy' input for other ArtifactValues. These are used in | |
| * {@link ActionCacheChecker}. | |
| */ | |
| public static FileArtifactValue createProxy(byte[] digest) { | |
| Preconditions.checkNotNull(digest); | |
| return createForNormalFile(digest, /* proxy= */ null, /* size= */ 0); | |
| } | |
| private static String bytesToString(byte[] bytes) { | |
| return "0x" + BaseEncoding.base16().omitPadding().encode(bytes); | |
| } | |
| private static final class DirectoryArtifactValue extends FileArtifactValue { | |
| private final long mtime; | |
| private DirectoryArtifactValue(long mtime) { | |
| this.mtime = mtime; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (!(o instanceof DirectoryArtifactValue)) { | |
| return false; | |
| } | |
| DirectoryArtifactValue that = (DirectoryArtifactValue) o; | |
| return mtime == that.mtime; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Long.hashCode(mtime); | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.DIRECTORY; | |
| } | |
| @Nullable | |
| @Override | |
| public byte[] getDigest() { | |
| return null; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public byte[] getValueFingerprint() { | |
| return new Fingerprint() | |
| .addString(getClass().getCanonicalName()) | |
| .addLong(mtime) | |
| .digestAndReset(); | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| return mtime; | |
| } | |
| @Override | |
| public long getSize() { | |
| return 0; | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) throws IOException { | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this).add("mtime", mtime).toString(); | |
| } | |
| } | |
| private static final class HashedDirectoryArtifactValue extends FileArtifactValue { | |
| private final byte[] digest; | |
| private HashedDirectoryArtifactValue(byte[] digest) { | |
| this.digest = digest; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (!(o instanceof HashedDirectoryArtifactValue)) { | |
| return false; | |
| } | |
| HashedDirectoryArtifactValue that = (HashedDirectoryArtifactValue) o; | |
| return Arrays.equals(digest, that.digest); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Arrays.hashCode(digest); | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.DIRECTORY; | |
| } | |
| @Nullable | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| return 0; | |
| } | |
| @Override | |
| public long getSize() { | |
| return 0; | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| // TODO(ulfjack): Ideally, we'd attempt to detect intra-build modifications here. I'm | |
| // consciously deferring work here as this code will most likely change again, and we're | |
| // already doing better than before by detecting inter-build modifications. | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this).add("digest", digest).toString(); | |
| } | |
| } | |
| private static final class RegularFileArtifactValue extends FileArtifactValue { | |
| private final byte[] digest; | |
| @Nullable private final FileContentsProxy proxy; | |
| private final long size; | |
| private RegularFileArtifactValue( | |
| @Nullable byte[] digest, @Nullable FileContentsProxy proxy, long size) { | |
| this.digest = digest; | |
| this.proxy = proxy; | |
| this.size = size; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (!(o instanceof RegularFileArtifactValue)) { | |
| return false; | |
| } | |
| RegularFileArtifactValue that = (RegularFileArtifactValue) o; | |
| return Arrays.equals(digest, that.digest) | |
| && Objects.equals(proxy, that.proxy) | |
| && size == that.size; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(Arrays.hashCode(digest), proxy, size); | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.REGULAR_FILE; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| return proxy; | |
| } | |
| @Override | |
| public long getSize() { | |
| return size; | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) throws IOException { | |
| if (proxy == null) { | |
| return false; | |
| } | |
| FileStatus stat = path.statIfFound(Symlinks.FOLLOW); | |
| return stat == null || !stat.isFile() || !proxy.equals(FileContentsProxy.create(stat)); | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new UnsupportedOperationException( | |
| "regular file's mtime should never be called. (" + this + ")"); | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("digest", BaseEncoding.base16().lowerCase().encode(digest)) | |
| .add("size", size) | |
| .add("proxy", proxy) | |
| .toString(); | |
| } | |
| @Override | |
| protected boolean couldBeModifiedByMetadata(FileArtifactValue o) { | |
| if (!(o instanceof RegularFileArtifactValue)) { | |
| return true; | |
| } | |
| RegularFileArtifactValue lastKnown = (RegularFileArtifactValue) o; | |
| return size != lastKnown.size || !Objects.equals(proxy, lastKnown.proxy); | |
| } | |
| } | |
| /** Metadata for remotely stored files. */ | |
| public static class RemoteFileArtifactValue extends FileArtifactValue { | |
| protected final byte[] digest; | |
| protected final long size; | |
| protected final int locationIndex; | |
| protected final String actionId; | |
| private RemoteFileArtifactValue(byte[] digest, long size, int locationIndex, String actionId) { | |
| this.digest = Preconditions.checkNotNull(digest, actionId); | |
| this.size = size; | |
| this.locationIndex = locationIndex; | |
| this.actionId = actionId; | |
| } | |
| public static RemoteFileArtifactValue create( | |
| byte[] digest, long size, int locationIndex, String actionId) { | |
| return new RemoteFileArtifactValue(digest, size, locationIndex, actionId); | |
| } | |
| public static RemoteFileArtifactValue create(byte[] digest, long size, int locationIndex) { | |
| return new RemoteFileArtifactValue(digest, size, locationIndex, /* actionId= */ ""); | |
| } | |
| public static RemoteFileArtifactValue create( | |
| byte[] digest, | |
| long size, | |
| int locationIndex, | |
| String actionId, | |
| @Nullable PathFragment materializationExecPath) { | |
| if (materializationExecPath != null) { | |
| return new RemoteFileArtifactValueWithMaterializationPath( | |
| digest, size, locationIndex, actionId, materializationExecPath); | |
| } | |
| return new RemoteFileArtifactValue(digest, size, locationIndex, actionId); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (!(o instanceof RemoteFileArtifactValue)) { | |
| return false; | |
| } | |
| RemoteFileArtifactValue that = (RemoteFileArtifactValue) o; | |
| return Arrays.equals(digest, that.digest) | |
| && size == that.size | |
| && locationIndex == that.locationIndex | |
| && Objects.equals(actionId, that.actionId); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(Arrays.hashCode(digest), size, locationIndex, actionId); | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.REGULAR_FILE; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getSize() { | |
| return size; | |
| } | |
| @Override | |
| public String getActionId() { | |
| return actionId; | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new UnsupportedOperationException( | |
| "RemoteFileArtifactValue doesn't support getModifiedTime"); | |
| } | |
| @Override | |
| public int getLocationIndex() { | |
| return locationIndex; | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| return false; | |
| } | |
| @Override | |
| public boolean isRemote() { | |
| return true; | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("digest", bytesToString(digest)) | |
| .add("size", size) | |
| .add("locationIndex", locationIndex) | |
| .add("actionId", actionId) | |
| .toString(); | |
| } | |
| } | |
| /** | |
| * A remote artifact that should be materialized in the local filesystem as a symlink to another | |
| * location. | |
| * | |
| * <p>See the documentation for {@link FileArtifactValue#getMaterializationExecPath}. | |
| */ | |
| public static final class RemoteFileArtifactValueWithMaterializationPath | |
| extends RemoteFileArtifactValue { | |
| private final PathFragment materializationExecPath; | |
| private RemoteFileArtifactValueWithMaterializationPath( | |
| byte[] digest, | |
| long size, | |
| int locationIndex, | |
| String actionId, | |
| PathFragment materializationExecPath) { | |
| super(digest, size, locationIndex, actionId); | |
| this.materializationExecPath = Preconditions.checkNotNull(materializationExecPath); | |
| } | |
| @Override | |
| public Optional<PathFragment> getMaterializationExecPath() { | |
| return Optional.ofNullable(materializationExecPath); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (!(o instanceof RemoteFileArtifactValueWithMaterializationPath)) { | |
| return false; | |
| } | |
| RemoteFileArtifactValueWithMaterializationPath that = | |
| (RemoteFileArtifactValueWithMaterializationPath) o; | |
| return Arrays.equals(digest, that.digest) | |
| && size == that.size | |
| && locationIndex == that.locationIndex | |
| && Objects.equals(actionId, that.actionId) | |
| && Objects.equals(materializationExecPath, that.materializationExecPath); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash( | |
| Arrays.hashCode(digest), size, locationIndex, actionId, materializationExecPath); | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("digest", bytesToString(digest)) | |
| .add("size", size) | |
| .add("locationIndex", locationIndex) | |
| .add("actionId", actionId) | |
| .add("materializationExecPath", materializationExecPath) | |
| .toString(); | |
| } | |
| } | |
| /** A {@link FileArtifactValue} representing a symlink that is not to be resolved. */ | |
| public static final class UnresolvedSymlinkArtifactValue extends FileArtifactValue { | |
| private final byte[] digest; | |
| private UnresolvedSymlinkArtifactValue(byte[] digest) { | |
| this.digest = digest; | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.SYMLINK; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public long getSize() { | |
| return 0; | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new IllegalStateException(); | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new IllegalStateException(); | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| // We could store an mtime but I have no clue where to get one from createFromMetadata | |
| return true; | |
| } | |
| } | |
| /** File stored inline in metadata. */ | |
| public static final class InlineFileArtifactValue extends FileArtifactValue { | |
| public static InlineFileArtifactValue create(byte[] bytes, HashFunction hashFunction) { | |
| return new InlineFileArtifactValue(bytes, hashFunction.hashBytes(bytes).asBytes()); | |
| } | |
| private final byte[] data; | |
| private final byte[] digest; | |
| private InlineFileArtifactValue(byte[] data, byte[] digest) { | |
| this.data = Preconditions.checkNotNull(data); | |
| this.digest = Preconditions.checkNotNull(digest); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (!(o instanceof InlineFileArtifactValue)) { | |
| return false; | |
| } | |
| InlineFileArtifactValue that = (InlineFileArtifactValue) o; | |
| return Arrays.equals(digest, that.digest); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Arrays.hashCode(digest); | |
| } | |
| public ByteArrayInputStream getInputStream() { | |
| return new ByteArrayInputStream(data); | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.REGULAR_FILE; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getSize() { | |
| return data.length; | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| /** | |
| * Used to resolve source symlinks when diskless. | |
| * | |
| * <p>When the optional per-action file system creates symlinks, it relies on metadata ({@link | |
| * FileArtifactValue}) to resolve the actual underlying data. In the case of remote or inline | |
| * files, this information is self-contained. However, in the case of source files, the path is | |
| * required to resolve the content. | |
| */ | |
| public static final class SourceFileArtifactValue extends FileArtifactValue { | |
| private final PathFragment path; | |
| private final PathFragment execPath; | |
| private final byte[] digest; | |
| private final long size; | |
| public SourceFileArtifactValue( | |
| PathFragment path, PathFragment execPath, byte[] digest, long size) { | |
| Preconditions.checkArgument(path.isAbsolute(), "path %s isn't absolute", path); | |
| Preconditions.checkArgument( | |
| path.endsWith(execPath), "path %s doesn't end with execPath %s", path, execPath); | |
| this.path = path; | |
| this.execPath = Preconditions.checkNotNull(execPath); | |
| this.digest = Preconditions.checkNotNull(digest); | |
| this.size = size; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (!(o instanceof SourceFileArtifactValue)) { | |
| return false; | |
| } | |
| SourceFileArtifactValue that = (SourceFileArtifactValue) o; | |
| return Objects.equals(path, that.path) | |
| && Objects.equals(execPath, that.execPath) | |
| && Arrays.equals(digest, that.digest) | |
| && size == that.size; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(path, execPath, Arrays.hashCode(digest), size); | |
| } | |
| public PathFragment getPath() { | |
| return path; | |
| } | |
| public PathFragment getExecPath() { | |
| return execPath; | |
| } | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.REGULAR_FILE; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| return digest; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getSize() { | |
| return size; | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| private static final class SingletonMarkerValue extends FileArtifactValue implements Singleton { | |
| private static final byte[] FINGERPRINT = new byte[] {0x10}; | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.NONEXISTENT; | |
| } | |
| @Nullable | |
| @Override | |
| public byte[] getDigest() { | |
| return null; | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getSize() { | |
| return 0; | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| return 0; | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| return false; | |
| } | |
| @Override | |
| public byte[] getValueFingerprint() { | |
| return FINGERPRINT; | |
| } | |
| @Override | |
| public String toString() { | |
| return "singleton marker artifact value (" + hashCode() + ")"; | |
| } | |
| } | |
| private static final class OmittedFileValue extends FileArtifactValue implements Singleton { | |
| @Override | |
| public FileStateType getType() { | |
| return FileStateType.NONEXISTENT; | |
| } | |
| @Override | |
| public byte[] getDigest() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public FileContentsProxy getContentsProxy() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getSize() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public long getModifiedTime() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public boolean wasModifiedSinceDigest(Path path) { | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return "OMITTED_FILE_MARKER"; | |
| } | |
| } | |
| } |