// 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.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.cache.DigestUtils; import com.google.devtools.build.lib.actions.cache.Metadata; 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.Preconditions; import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.skyframe.SkyValue; import java.io.IOException; import java.util.Arrays; import javax.annotation.Nullable; /** * Stores the actual metadata data of a file. We have the following cases: * * */ // TODO(janakr): make this an interface once JDK8 allows us to have static methods on interfaces. @Immutable @ThreadSafe public abstract class FileArtifactValue implements SkyValue, Metadata { private static final class SingletonMarkerValue extends FileArtifactValue implements Singleton { @Nullable @Override public byte[] getDigest() { return null; } @Override public boolean isFile() { return false; } @Override public long getSize() { return 0; } @Override public long getModifiedTime() { return 0; } @Override public String toString() { return "singleton marker artifact value (" + hashCode() + ")"; } } private static final class OmittedFileValue extends FileArtifactValue implements Singleton { @Override public byte[] getDigest() { throw new UnsupportedOperationException(); } @Override public boolean isFile() { throw new UnsupportedOperationException(); } @Override public long getSize() { throw new UnsupportedOperationException(); } @Override public long getModifiedTime() { throw new UnsupportedOperationException(); } @Override public String toString() { return "OMITTED_FILE_MARKER"; } } static final FileArtifactValue DEFAULT_MIDDLEMAN = new SingletonMarkerValue(); /** Data that marks that a file is not present on the filesystem. */ @VisibleForTesting 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. */ static final FileArtifactValue OMITTED_FILE_MARKER = new OmittedFileValue(); private static final class DirectoryArtifactValue extends FileArtifactValue { private final long mtime; private DirectoryArtifactValue(long mtime) { this.mtime = mtime; } @Nullable @Override public byte[] getDigest() { return null; } @Override public long getModifiedTime() { return mtime; } @Override public long getSize() { return 0; } @Override public boolean isFile() { 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; private final long size; private RegularFileArtifactValue(byte[] digest, long size) { this.digest = Preconditions.checkNotNull(digest); this.size = size; } @Override public byte[] getDigest() { return digest; } @Override public boolean isFile() { return true; } @Override public long getSize() { return size; } @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", digest).add("size", size).toString(); } } @VisibleForTesting public static FileArtifactValue create(Artifact artifact) throws IOException { Path path = artifact.getPath(); FileStatus stat = path.stat(); boolean isFile = stat.isFile(); return create(artifact, isFile, isFile ? stat.getSize() : 0, null); } static FileArtifactValue create(Artifact artifact, FileValue fileValue) throws IOException { boolean isFile = fileValue.isFile(); return create(artifact, isFile, isFile ? fileValue.getSize() : 0, isFile ? fileValue.getDigest() : null); } static FileArtifactValue create(Artifact artifact, boolean isFile, long size, @Nullable byte[] digest) throws IOException { if (isFile && digest == null) { digest = DigestUtils.getDigestOrFail(artifact.getPath(), size); } 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 (maybe by implementing FileSet in Skyframe). return new DirectoryArtifactValue(artifact.getPath().getLastModifiedTime()); } Preconditions.checkState(digest != null, artifact); return createNormalFile(digest, size); } public static FileArtifactValue createNormalFile(byte[] digest, long size) { return new RegularFileArtifactValue(digest, size); } static FileArtifactValue createNormalFile(FileValue fileValue) { return new RegularFileArtifactValue(fileValue.getDigest(), fileValue.getSize()); } 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}. */ static FileArtifactValue createProxy(byte[] digest) { Preconditions.checkNotNull(digest); return createNormalFile(digest, /*size=*/ 0); } @Override public abstract boolean isFile(); @Nullable @Override public abstract byte[] getDigest(); @Override public abstract long getSize(); @Override public abstract long getModifiedTime(); @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Metadata)) { return false; } if ((this instanceof Singleton) || (o instanceof Singleton)) { return false; } Metadata m = (Metadata) o; if (isFile()) { return m.isFile() && Arrays.equals(getDigest(), m.getDigest()) && getSize() == m.getSize(); } else { return !m.isFile() && getModifiedTime() == m.getModifiedTime(); } } @Override public int hashCode() { if (this instanceof Singleton) { return System.identityHashCode(this); } // Hash digest by content, not reference. if (isFile()) { return 37 * Long.hashCode(getSize()) + Arrays.hashCode(getDigest()); } else { return Long.hashCode(getModifiedTime()); } } }