blob: 73362d3e9fb5fc0202311ce52a753c4cfe2a2abd [file] [log] [blame]
// 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.Preconditions;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
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.PathFragment;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.AbstractSkyKey;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* A value that corresponds to 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). Anything in Skyframe that cares about the fully resolved path of a file (e.g. anything
* that cares about the contents of a file) should have a dependency on the corresponding {@link
* FileValue}.
*
* <p>Note that the existence of a file value does not imply that the file exists on the filesystem.
* File values for missing files will be created on purpose in order to facilitate incremental
* builds in the case those files have reappeared.
*
* <p>This class contains the relevant metadata for a file, although not the contents. Note that
* since a FileValue doesn't store its corresponding SkyKey, it's possible for the FileValues for
* two different paths to be the same.
*
* <p>This should not be used for build outputs; use {@link ArtifactSkyKey} to create keys for
* those.
*/
@Immutable
@ThreadSafe
public abstract class FileValue implements SkyValue {
// Depends non-hermetically on package path, but that is under the control of a flag, so use
// semi-hermetic.
public static final SkyFunctionName FILE = SkyFunctionName.createSemiHermetic("FILE");
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 special file or symlink to a 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;
}
/**
* Returns the real rooted path of the file, taking ancestor symlinks into account. For example,
* the rooted path ['root']/['a/b'] is really ['root']/['c/b'] if 'a' is a symlink to 'c'. Note
* that ancestor symlinks outside the root boundary are not taken into consideration.
*/
public abstract RootedPath realRootedPath();
public abstract FileStateValue realFileStateValue();
/**
* Returns the unresolved link target if {@link #isSymlink()}.
*
* <p>This is useful if the caller wants to, for example, duplicate a relative symlink. An actual
* example could be a build rule that copies a set of input files to the output directory, but
* upon encountering symbolic links it can decide between copying or following them.
*/
public PathFragment getUnresolvedLinkTarget() {
throw new IllegalStateException(this.toString());
}
public long getSize() {
Preconditions.checkState(isFile(), this);
return realFileStateValue().getSize();
}
@Nullable
public byte[] getDigest() {
Preconditions.checkState(isFile(), this);
return realFileStateValue().getDigest();
}
/** Returns a key for building a file value for the given root-relative path. */
@ThreadSafe
public static Key key(RootedPath rootedPath) {
return Key.create(rootedPath);
}
@AutoCodec.VisibleForSerialization
@AutoCodec
static class Key extends AbstractSkyKey<RootedPath> {
private static final Interner<Key> interner = BlazeInterners.newWeakInterner();
private Key(RootedPath arg) {
super(arg);
}
@AutoCodec.VisibleForSerialization
@AutoCodec.Instantiator
static Key create(RootedPath arg) {
return interner.intern(new Key(arg));
}
@Override
public SkyFunctionName functionName() {
return FILE;
}
}
/**
* Only intended to be used by {@link com.google.devtools.build.lib.skyframe.FileFunction}. Should
* not be used for symlink cycles.
*/
public static FileValue value(
RootedPath rootedPath,
FileStateValue fileStateValue,
RootedPath realRootedPath,
FileStateValue realFileStateValue) {
if (rootedPath.equals(realRootedPath)) {
Preconditions.checkState(fileStateValue.getType() != FileStateType.SYMLINK,
"rootedPath: %s, fileStateValue: %s, realRootedPath: %s, realFileStateValue: %s",
rootedPath, fileStateValue, realRootedPath, realFileStateValue);
return new RegularFileValue(rootedPath, fileStateValue);
} else {
if (fileStateValue.getType() == FileStateType.SYMLINK) {
return new SymlinkFileValue(realRootedPath, realFileStateValue,
fileStateValue.getSymlinkTarget());
} else {
return new DifferentRealPathFileValue(
realRootedPath, realFileStateValue);
}
}
}
/**
* Implementation of {@link FileValue} 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.
*/
@VisibleForTesting
@AutoCodec
public static final class RegularFileValue extends FileValue {
private final RootedPath rootedPath;
private final FileStateValue fileStateValue;
public RegularFileValue(RootedPath rootedPath, FileStateValue fileStateValue) {
this.rootedPath = Preconditions.checkNotNull(rootedPath);
this.fileStateValue = Preconditions.checkNotNull(fileStateValue);
}
@Override
public RootedPath realRootedPath() {
return rootedPath;
}
@Override
public FileStateValue realFileStateValue() {
return fileStateValue;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != RegularFileValue.class) {
return false;
}
RegularFileValue other = (RegularFileValue) obj;
return rootedPath.equals(other.rootedPath) && fileStateValue.equals(other.fileStateValue);
}
@Override
public int hashCode() {
return Objects.hash(rootedPath, fileStateValue);
}
@Override
public String toString() {
return rootedPath + ", " + fileStateValue;
}
}
/**
* Base class for {@link FileValue}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.
*/
@AutoCodec.VisibleForSerialization
@AutoCodec
public static class DifferentRealPathFileValue extends FileValue {
protected final RootedPath realRootedPath;
protected final FileStateValue realFileStateValue;
public DifferentRealPathFileValue(
RootedPath realRootedPath, FileStateValue realFileStateValue) {
this.realRootedPath = Preconditions.checkNotNull(realRootedPath);
this.realFileStateValue = Preconditions.checkNotNull(realFileStateValue);
}
@Override
public RootedPath realRootedPath() {
return realRootedPath;
}
@Override
public FileStateValue realFileStateValue() {
return realFileStateValue;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != DifferentRealPathFileValue.class) {
return false;
}
DifferentRealPathFileValue other = (DifferentRealPathFileValue) obj;
return realRootedPath.equals(other.realRootedPath)
&& realFileStateValue.equals(other.realFileStateValue);
}
@Override
public int hashCode() {
return Objects.hash(realRootedPath, realFileStateValue);
}
@Override
public String toString() {
return realRootedPath + ", " + realFileStateValue + " (symlink ancestor)";
}
}
/** Implementation of {@link FileValue} for files that are symlinks. */
@VisibleForTesting
@AutoCodec
public static final class SymlinkFileValue extends DifferentRealPathFileValue {
private final PathFragment linkTarget;
@VisibleForTesting
public SymlinkFileValue(
RootedPath realRootedPath, FileStateValue realFileStateValue, PathFragment linkTarget) {
super(realRootedPath, realFileStateValue);
this.linkTarget = linkTarget;
}
@Override
public boolean isSymlink() {
return true;
}
@Override
public PathFragment getUnresolvedLinkTarget() {
return linkTarget;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != SymlinkFileValue.class) {
return false;
}
SymlinkFileValue other = (SymlinkFileValue) obj;
return realRootedPath.equals(other.realRootedPath)
&& realFileStateValue.equals(other.realFileStateValue)
&& linkTarget.equals(other.linkTarget);
}
@Override
public int hashCode() {
return Objects.hash(realRootedPath, realFileStateValue, linkTarget);
}
@Override
public String toString() {
return String.format(
"symlink (real_path=%s, real_state=%s, link_value=%s)",
realRootedPath, realFileStateValue, linkTarget);
}
}
}