| // 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.base.Preconditions; | |
| 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.util.BigIntegerFingerprint; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import java.math.BigInteger; | |
| import java.util.Objects; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A value that corresponds to the metadata for an artifact, which may be a file or directory or | |
| * symlink or non-existent file, fully accounting for symlinks (e.g. proper dependencies on ancestor | |
| * symlinks so as to be incrementally correct). | |
| * | |
| * <p>Note that the existence of this object does not imply that the file exists on the filesystem. | |
| * Values for missing files may be created on purpose in order to facilitate incremental builds in | |
| * the case those files have reappeared. | |
| * | |
| * <p>Very similar to {@link FileValue}, but contains strictly less information: does not have a | |
| * {@link com.google.devtools.build.lib.vfs.RootedPath}, since execution never needs to access the | |
| * filesystem via this object. | |
| */ | |
| @Immutable | |
| @ThreadSafe | |
| public abstract class ArtifactFileMetadata { | |
| /** | |
| * Exists to accommodate the control flow of {@link | |
| * com.google.devtools.build.lib.skyframe.ActionMetadataHandler#getMetadata}. | |
| * | |
| * <p>{@link com.google.devtools.build.lib.skyframe.ActionMetadataHandler#getMetadata} always | |
| * checks {@link com.google.devtools.build.lib.skyframe.OutputStore#getArtifactData} before | |
| * checking {@link com.google.devtools.build.lib.skyframe.OutputStore#getAdditionalOutputData} so | |
| * some placeholder value is needed to allow an injected {@link FileArtifactValue} to be returned. | |
| * | |
| * <p>Similarly, {@link | |
| * com.google.devtools.build.lib.skyframe.ActionExecutionValue#getAllFileValues} replaces this | |
| * placeholder with metadata from {@link | |
| * com.google.devtools.build.lib.skyframe.ActionExecutionValue#additionalOutputData}. | |
| */ | |
| @AutoCodec public static final ArtifactFileMetadata PLACEHOLDER = new PlaceholderFileValue(); | |
| // No implementations outside this class. | |
| private ArtifactFileMetadata() {} | |
| public boolean exists() { | |
| return realFileStateValue().getType() != FileStateType.NONEXISTENT; | |
| } | |
| /** Returns true if the original path is a symlink; the target path can never be a symlink. */ | |
| public boolean isSymlink() { | |
| return false; | |
| } | |
| /** | |
| * Returns true if this value corresponds to a file or symlink to an existing regular or special | |
| * file. If so, its parent directory is guaranteed to exist. | |
| */ | |
| public boolean isFile() { | |
| return realFileStateValue().getType() == FileStateType.REGULAR_FILE | |
| || realFileStateValue().getType() == FileStateType.SPECIAL_FILE; | |
| } | |
| /** | |
| * Returns true if this value corresponds to a file or symlink to an existing special file. If so, | |
| * its parent directory is guaranteed to exist. | |
| */ | |
| public boolean isSpecialFile() { | |
| return realFileStateValue().getType() == FileStateType.SPECIAL_FILE; | |
| } | |
| /** | |
| * Returns true if the file is a directory or a symlink to an existing directory. If so, its | |
| * parent directory is guaranteed to exist. | |
| */ | |
| public boolean isDirectory() { | |
| return realFileStateValue().getType() == FileStateType.DIRECTORY; | |
| } | |
| public abstract FileStateValue realFileStateValue(); | |
| public long getSize() { | |
| Preconditions.checkState(isFile(), this); | |
| return realFileStateValue().getSize(); | |
| } | |
| @Nullable | |
| public byte[] getDigest() { | |
| Preconditions.checkState(isFile(), this); | |
| return realFileStateValue().getDigest(); | |
| } | |
| /** Returns a quick fingerprint via a BigInteger */ | |
| public BigInteger getFingerprint() { | |
| BigIntegerFingerprint fp = new BigIntegerFingerprint(); | |
| fp.addBoolean(exists()); | |
| fp.addBoolean(isSpecialFile()); | |
| fp.addBoolean(isDirectory()); | |
| fp.addBoolean(isFile()); | |
| if (isFile()) { | |
| fp.addLong(getSize()); | |
| fp.addDigestedBytes(getDigest()); | |
| } | |
| return fp.getFingerprint(); | |
| } | |
| public static ArtifactFileMetadata value( | |
| PathFragment pathFragment, | |
| FileStateValue fileStateValue, | |
| PathFragment realPathFragment, | |
| FileStateValue realFileStateValue) { | |
| Preconditions.checkState(pathFragment.isAbsolute(), pathFragment); | |
| Preconditions.checkState(realPathFragment.isAbsolute(), realPathFragment); | |
| if (pathFragment.equals(realPathFragment)) { | |
| Preconditions.checkState( | |
| fileStateValue.getType() != FileStateType.SYMLINK, | |
| "path: %s, fileStateValue: %s, realPath: %s, realFileStateValue: %s", | |
| pathFragment, | |
| fileStateValue, | |
| realPathFragment, | |
| realFileStateValue); | |
| return new Regular(pathFragment, fileStateValue); | |
| } else { | |
| if (fileStateValue.getType() == FileStateType.SYMLINK) { | |
| return new Symlink(realPathFragment, realFileStateValue, fileStateValue.getSymlinkTarget()); | |
| } else { | |
| return new DifferentRealPath(realPathFragment, realFileStateValue); | |
| } | |
| } | |
| } | |
| /** | |
| * Implementation of {@link ArtifactFileMetadata} for files whose fully resolved path is the same | |
| * as the requested path. For example, this is the case for the path "foo/bar/baz" if neither | |
| * 'foo' nor 'foo/bar' nor 'foo/bar/baz' are symlinks. | |
| */ | |
| private static final class Regular extends ArtifactFileMetadata { | |
| private final PathFragment realPath; | |
| private final FileStateValue fileStateValue; | |
| Regular(PathFragment realPath, FileStateValue fileStateValue) { | |
| this.realPath = Preconditions.checkNotNull(realPath); | |
| this.fileStateValue = Preconditions.checkNotNull(fileStateValue); | |
| } | |
| @Override | |
| public FileStateValue realFileStateValue() { | |
| return fileStateValue; | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj == null) { | |
| return false; | |
| } | |
| if (obj.getClass() != Regular.class) { | |
| return false; | |
| } | |
| Regular other = (Regular) obj; | |
| return realPath.equals(other.realPath) && fileStateValue.equals(other.fileStateValue); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(realPath, fileStateValue); | |
| } | |
| @Override | |
| public String toString() { | |
| return realPath + ", " + fileStateValue; | |
| } | |
| @Override | |
| public BigInteger getFingerprint() { | |
| BigInteger original = super.getFingerprint(); | |
| BigIntegerFingerprint fp = new BigIntegerFingerprint(); | |
| fp.addBigIntegerOrdered(original); | |
| fp.addString(getClass().getCanonicalName()); | |
| fp.addPath(realPath); | |
| fp.addBigIntegerOrdered(fileStateValue.getValueFingerprint()); | |
| return fp.getFingerprint(); | |
| } | |
| } | |
| /** | |
| * Base class for {@link ArtifactFileMetadata}s for files whose fully resolved path is different | |
| * than the requested path. For example, this is the case for the path "foo/bar/baz" if at least | |
| * one of 'foo', 'foo/bar', or 'foo/bar/baz' is a symlink. | |
| */ | |
| private static class DifferentRealPath extends ArtifactFileMetadata { | |
| protected final PathFragment realPath; | |
| protected final FileStateValue realFileStateValue; | |
| DifferentRealPath(PathFragment realPath, FileStateValue realFileStateValue) { | |
| this.realPath = Preconditions.checkNotNull(realPath); | |
| this.realFileStateValue = Preconditions.checkNotNull(realFileStateValue); | |
| } | |
| @Override | |
| public BigInteger getFingerprint() { | |
| BigInteger original = super.getFingerprint(); | |
| BigIntegerFingerprint fp = new BigIntegerFingerprint(); | |
| fp.addBigIntegerOrdered(original); | |
| fp.addString(getClass().getCanonicalName()); | |
| fp.addPath(realPath); | |
| fp.addBigIntegerOrdered(realFileStateValue.getValueFingerprint()); | |
| return fp.getFingerprint(); | |
| } | |
| @Override | |
| public FileStateValue realFileStateValue() { | |
| return realFileStateValue; | |
| } | |
| @SuppressWarnings("EqualsGetClass") // Only subclass should never be equal to this class. | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj == null) { | |
| return false; | |
| } | |
| if (obj.getClass() != DifferentRealPath.class) { | |
| return false; | |
| } | |
| DifferentRealPath other = (DifferentRealPath) obj; | |
| return realPath.equals(other.realPath) && realFileStateValue.equals(other.realFileStateValue); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(realPath, realFileStateValue); | |
| } | |
| @Override | |
| public String toString() { | |
| return realPath + ", " + realFileStateValue + " (symlink ancestor)"; | |
| } | |
| } | |
| /** Implementation of {@link ArtifactFileMetadata} for files that are symlinks. */ | |
| private static final class Symlink extends DifferentRealPath { | |
| private final PathFragment linkTarget; | |
| private Symlink( | |
| PathFragment realPath, FileStateValue realFileStateValue, PathFragment linkTarget) { | |
| super(realPath, realFileStateValue); | |
| this.linkTarget = linkTarget; | |
| } | |
| @Override | |
| public BigInteger getFingerprint() { | |
| BigInteger original = super.getFingerprint(); | |
| BigIntegerFingerprint fp = new BigIntegerFingerprint(); | |
| fp.addBigIntegerOrdered(original); | |
| fp.addString(getClass().getCanonicalName()); | |
| fp.addPath(linkTarget); | |
| return fp.getFingerprint(); | |
| } | |
| @Override | |
| public boolean isSymlink() { | |
| return true; | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj == null) { | |
| return false; | |
| } | |
| if (obj.getClass() != Symlink.class) { | |
| return false; | |
| } | |
| Symlink other = (Symlink) obj; | |
| return realPath.equals(other.realPath) | |
| && realFileStateValue.equals(other.realFileStateValue) | |
| && linkTarget.equals(other.linkTarget); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(realPath, realFileStateValue, linkTarget, Boolean.TRUE); | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format( | |
| "symlink (real_path=%s, real_state=%s, link_value=%s)", | |
| realPath, realFileStateValue, linkTarget); | |
| } | |
| } | |
| private static final class PlaceholderFileValue extends ArtifactFileMetadata { | |
| private static final BigInteger FINGERPRINT = | |
| new BigIntegerFingerprint().addString("PlaceholderFileValue").getFingerprint(); | |
| private PlaceholderFileValue() {} | |
| @Override | |
| public FileStateValue realFileStateValue() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| @Override | |
| public BigInteger getFingerprint() { | |
| return FINGERPRINT; | |
| } | |
| @Override | |
| public String toString() { | |
| return "PlaceholderFileValue:Singleton"; | |
| } | |
| } | |
| } |