aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
new file mode 100644
index 0000000000..f9f80eefcf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
@@ -0,0 +1,502 @@
+// 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import com.google.devtools.build.lib.actions.cache.DigestUtils;
+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.AutoCodec;
+import com.google.devtools.build.lib.vfs.FileStatus;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.Symlinks;
+import com.google.devtools.build.skyframe.SkyValue;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * State of a file system object for the execution phase.
+ *
+ * <p>This is not used by Skyframe for invalidation, it is primarily used by the action cache and
+ * the various {@link com.google.devtools.build.lib.exec.SpawnRunner} implementations.
+ *
+ * <p>We have the following cases:
+ *
+ * <ul>
+ * <li>an ordinary file, in which case we would expect to see a digest and size;
+ * <li>a directory, in which case we would expect to see an mtime;
+ * <li>an intentionally omitted file which the build system is aware of but doesn't actually
+ * exist, where all access methods are unsupported;
+ * <li>a "middleman marker" object, which has a null digest, 0 size, and mtime of 0.
+ * <li>The "self data" of a TreeArtifact, where we would expect to see a digest representing the
+ * artifact's contents, and a size of 0.
+ * </ul>
+ */
+@Immutable
+@ThreadSafe
+public abstract class FileArtifactValue implements SkyValue {
+ @AutoCodec public static final FileArtifactValue DEFAULT_MIDDLEMAN = new SingletonMarkerValue();
+ /** Data that marks that a file is not present on the filesystem. */
+ @AutoCodec 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.
+ */
+ @AutoCodec public static final FileArtifactValue OMITTED_FILE_MARKER = new OmittedFileValue();
+
+ /**
+ * Marker interface for singleton implementations of this class.
+ *
+ * <p>Needed for a correct implementation of {@code equals}.
+ */
+ public interface Singleton {}
+
+ /**
+ * 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}. Otherwise may return
+ * null.
+ *
+ * <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.
+ */
+ @Nullable
+ 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();
+
+ /**
+ * 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 or empty.
+ */
+ public int getLocationIndex() {
+ return 0;
+ }
+
+ /**
+ * 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.
+ */
+ public abstract boolean wasModifiedSinceDigest(Path path) throws IOException;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof FileArtifactValue)) {
+ return false;
+ }
+ if ((this instanceof Singleton) || (o instanceof Singleton)) {
+ return false;
+ }
+ FileArtifactValue m = (FileArtifactValue) o;
+ if (getType() != m.getType()) {
+ return false;
+ }
+ if (getDigest() != null) {
+ return Arrays.equals(getDigest(), m.getDigest()) && getSize() == m.getSize();
+ } else {
+ return getModifiedTime() == m.getModifiedTime();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ if (this instanceof Singleton) {
+ return System.identityHashCode(this);
+ }
+ // Hash digest by content, not reference.
+ if (getDigest() != null) {
+ return 37 * Long.hashCode(getSize()) + Arrays.hashCode(getDigest());
+ } else {
+ return Long.hashCode(getModifiedTime());
+ }
+ }
+
+ public static FileArtifactValue create(Artifact artifact, FileValue fileValue)
+ throws IOException {
+ boolean isFile = fileValue.isFile();
+ FileContentsProxy proxy = getProxyFromFileStateValue(fileValue.realFileStateValue());
+ return create(
+ artifact.getPath(),
+ isFile,
+ isFile ? fileValue.getSize() : 0,
+ proxy,
+ isFile ? fileValue.getDigest() : null);
+ }
+
+ public static FileArtifactValue create(
+ Artifact artifact, FileValue fileValue, @Nullable byte[] injectedDigest) throws IOException {
+ boolean isFile = fileValue.isFile();
+ FileContentsProxy proxy = getProxyFromFileStateValue(fileValue.realFileStateValue());
+ return create(
+ artifact.getPath(), isFile, isFile ? fileValue.getSize() : 0, proxy, injectedDigest);
+ }
+
+ @VisibleForTesting
+ public static FileArtifactValue create(Artifact artifact) throws IOException {
+ return create(artifact.getPath());
+ }
+
+ @VisibleForTesting
+ public static FileArtifactValue create(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.
+ FileStatus stat = path.stat(Symlinks.FOLLOW);
+ return create(path, stat.isFile(), stat.getSize(), FileContentsProxy.create(stat), null);
+ }
+
+ private static FileArtifactValue create(
+ Path path, boolean isFile, long size, FileContentsProxy proxy, @Nullable byte[] digest)
+ 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.getDigestOrFail(path, size);
+ }
+ Preconditions.checkState(digest != null, path);
+ return new RegularFileArtifactValue(digest, proxy, size);
+ }
+
+ public static FileArtifactValue createForVirtualActionInput(byte[] digest, long size) {
+ return new RegularFileArtifactValue(digest, /*proxy=*/ null, size);
+ }
+
+ public static FileArtifactValue createNormalFile(
+ byte[] digest, @Nullable FileContentsProxy proxy, long size) {
+ return new RegularFileArtifactValue(digest, proxy, size);
+ }
+
+ public static FileArtifactValue createNormalFile(FileValue fileValue) {
+ FileContentsProxy proxy = getProxyFromFileStateValue(fileValue.realFileStateValue());
+ return new RegularFileArtifactValue(fileValue.getDigest(), proxy, fileValue.getSize());
+ }
+
+ @VisibleForTesting
+ public static FileArtifactValue createNormalFile(byte[] digest, long size) {
+ return createNormalFile(digest, /*proxy=*/ null, size);
+ }
+
+ public static FileArtifactValue createDirectory(long mtime) {
+ return new DirectoryArtifactValue(mtime);
+ }
+
+ /**
+ * Creates a FileArtifactValue used as a 'proxy' input for other ArtifactValues. These are used in
+ * {@link com.google.devtools.build.lib.actions.ActionCacheChecker}.
+ */
+ public static FileArtifactValue createProxy(byte[] digest) {
+ Preconditions.checkNotNull(digest);
+ return createNormalFile(digest, /*proxy=*/ null, /*size=*/ 0);
+ }
+
+ private static final class DirectoryArtifactValue extends FileArtifactValue {
+ private final long mtime;
+
+ private DirectoryArtifactValue(long mtime) {
+ this.mtime = mtime;
+ }
+
+ @Override
+ public FileStateType getType() {
+ return FileStateType.DIRECTORY;
+ }
+
+ @Nullable
+ @Override
+ public byte[] getDigest() {
+ return null;
+ }
+
+ @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 RegularFileArtifactValue extends FileArtifactValue {
+ private final byte[] digest;
+ @Nullable private final FileContentsProxy proxy;
+ private final long size;
+
+ private RegularFileArtifactValue(byte[] digest, @Nullable FileContentsProxy proxy, long size) {
+ this.digest = Preconditions.checkNotNull(digest);
+ this.proxy = proxy;
+ this.size = size;
+ }
+
+ @Override
+ public FileStateType getType() {
+ return FileStateType.REGULAR_FILE;
+ }
+
+ @Override
+ public byte[] getDigest() {
+ return digest;
+ }
+
+ @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
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RegularFileArtifactValue)) {
+ return false;
+ }
+ RegularFileArtifactValue r = (RegularFileArtifactValue) o;
+ return Arrays.equals(digest, r.digest) && Objects.equals(proxy, r.proxy) && size == r.size;
+ }
+
+ @Override
+ public int hashCode() {
+ return (proxy != null ? 127 * proxy.hashCode() : 0)
+ + 37 * Long.hashCode(getSize()) + Arrays.hashCode(getDigest());
+ }
+ }
+
+ /** Metadata for remotely stored files. */
+ public static final class RemoteFileArtifactValue extends FileArtifactValue {
+ private final byte[] digest;
+ private final long size;
+ private final int locationIndex;
+
+ public RemoteFileArtifactValue(byte[] digest, long size, int locationIndex) {
+ this.digest = digest;
+ this.size = size;
+ this.locationIndex = locationIndex;
+ }
+
+ @Override
+ public FileStateType getType() {
+ return FileStateType.REGULAR_FILE;
+ }
+
+ @Override
+ public byte[] getDigest() {
+ return digest;
+ }
+
+ @Override
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public long getModifiedTime() {
+ throw new UnsupportedOperationException(
+ "RemoteFileArifactValue doesn't support getModifiedTime");
+ }
+
+ @Override
+ public int getLocationIndex() {
+ return locationIndex;
+ }
+
+ @Override
+ public boolean wasModifiedSinceDigest(Path path) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** File stored inline in metadata. */
+ public static final class InlineFileArtifactValue extends FileArtifactValue {
+ private final byte[] data;
+ private final byte[] digest;
+
+ public InlineFileArtifactValue(byte[] data, byte[] digest) {
+ this.data = Preconditions.checkNotNull(data);
+ this.digest = Preconditions.checkNotNull(digest);
+ }
+
+ public ByteArrayInputStream getInputStream() {
+ return new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public FileStateType getType() {
+ return FileStateType.REGULAR_FILE;
+ }
+
+ @Override
+ public byte[] getDigest() {
+ return digest;
+ }
+
+ @Override
+ public long getSize() {
+ return data.length;
+ }
+
+ @Override
+ public long getModifiedTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean wasModifiedSinceDigest(Path path) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static FileContentsProxy getProxyFromFileStateValue(FileStateValue value) {
+ if (value instanceof FileStateValue.RegularFileStateValue) {
+ return ((FileStateValue.RegularFileStateValue) value).getContentsProxy();
+ } else if (value instanceof FileStateValue.SpecialFileStateValue) {
+ return ((FileStateValue.SpecialFileStateValue) value).getContentsProxy();
+ }
+ return null;
+ }
+
+ private static final class SingletonMarkerValue extends FileArtifactValue implements Singleton {
+ @Override
+ public FileStateType getType() {
+ return FileStateType.NONEXISTENT;
+ }
+
+ @Nullable
+ @Override
+ public byte[] getDigest() {
+ return null;
+ }
+
+ @Override
+ public long getSize() {
+ return 0;
+ }
+
+ @Override
+ public long getModifiedTime() {
+ return 0;
+ }
+
+ @Override
+ public boolean wasModifiedSinceDigest(Path path) throws IOException {
+ return false;
+ }
+
+ @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 long getSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getModifiedTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean wasModifiedSinceDigest(Path path) throws IOException {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "OMITTED_FILE_MARKER";
+ }
+ }
+}