// Copyright 2014 Google Inc. 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.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.actions.FilesetTraversalParams.PackageBoundaryMode; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.DanglingSymlinkException; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.FileType; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import javax.annotation.Nullable; /** * Collection of files found while recursively traversing a path. * *
The path may refer to files, symlinks or directories that may or may not exist. * *
Traversing a file or a symlink results in a single {@link ResolvedFile} corresponding to the * file or symlink. * *
Traversing a directory results in a collection of {@link ResolvedFile}s for all files and * symlinks under it, and in all of its subdirectories. The {@link TraversalRequest} can specify * whether to traverse source subdirectories that are packages (have BUILD files in them). * *
Traversing a symlink that points to a directory is the same as traversing a normal directory. * The paths in the result will not be resolved; the files will be listed under the symlink, as if * it was the actual directory they reside in. * *
Editing a file that is part of this traversal, or adding or removing a file in a directory
* that is part of this traversal, will invalidate this {@link SkyValue}. This also applies to
* directories that are symlinked to.
*/
public final class RecursiveFilesystemTraversalValue implements SkyValue {
static final RecursiveFilesystemTraversalValue EMPTY = new RecursiveFilesystemTraversalValue(
Optional. The returned set may be empty if no files were found, or the ones found were to be
* considered non-existent. Unless it's empty, the returned set always includes the
* {@link #getResolvedRoot() resolved root}.
*
* The returned set also includes symlinks. If a symlink points to a directory, its contents
* are also included in this set, and their path will start with the symlink's path, just like on
* a usual Unix file system.
*/
public NestedSet Such paths and all their subdirectories are assumed not to define packages, so package
* lookup for them is skipped.
*/
final boolean isGenerated;
/** Whether traversal should descend into directories that are roots of subpackages. */
final PackageBoundaryMode crossPkgBoundaries;
/**
* Whether to skip checking if the root (if it's a directory) contains a BUILD file.
*
* Such directories are not considered to be packages when this flag is true. This needs to
* be true in order to traverse directories of packages, but should be false for their
* subdirectories.
*/
final boolean skipTestingForSubpackage;
/** Information to be attached to any error messages that may be reported. */
@Nullable final String errorInfo;
public TraversalRequest(RootedPath path, boolean isRootGenerated,
PackageBoundaryMode crossPkgBoundaries, boolean skipTestingForSubpackage,
@Nullable String errorInfo) {
this.path = path;
this.isGenerated = isRootGenerated;
this.crossPkgBoundaries = crossPkgBoundaries;
this.skipTestingForSubpackage = skipTestingForSubpackage;
this.errorInfo = errorInfo;
}
private TraversalRequest duplicate(RootedPath newRoot, boolean newSkipTestingForSubpackage) {
return new TraversalRequest(newRoot, isGenerated, crossPkgBoundaries,
newSkipTestingForSubpackage, errorInfo);
}
/** Creates a new request to traverse a child element in the current directory (the root). */
TraversalRequest forChildEntry(RootedPath newPath) {
return duplicate(newPath, false);
}
/**
* Creates a new request for a changed root.
*
* This method can be used when a package is found out to be under a different root path than
* originally assumed.
*/
TraversalRequest forChangedRootPath(Path newRoot) {
return duplicate(RootedPath.toRootedPath(newRoot, path.getRelativePath()),
skipTestingForSubpackage);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TraversalRequest)) {
return false;
}
TraversalRequest o = (TraversalRequest) obj;
return path.equals(o.path) && isGenerated == o.isGenerated
&& crossPkgBoundaries == o.crossPkgBoundaries
&& skipTestingForSubpackage == o.skipTestingForSubpackage;
}
@Override
public int hashCode() {
return Objects.hashCode(path, isGenerated, crossPkgBoundaries, skipTestingForSubpackage);
}
@Override
public String toString() {
return String.format(
"TraversalParams(root=%s, is_generated=%d, skip_testing_for_subpkg=%d,"
+ " pkg_boundaries=%s)", path, isGenerated ? 1 : 0, skipTestingForSubpackage ? 1 : 0,
crossPkgBoundaries);
}
}
/**
* Path and type information about a single file or symlink.
*
* The object stores things such as the absolute path of the file or symlink, its exact type
* and, if it's a symlink, the resolved and unresolved link target paths.
*/
public abstract static class ResolvedFile {
private static final class Symlink {
private final RootedPath linkName;
private final PathFragment unresolvedLinkTarget;
// The resolved link target is stored in ResolvedFile.path
private Symlink(RootedPath linkName, PathFragment unresolvedLinkTarget) {
this.linkName = Preconditions.checkNotNull(linkName);
this.unresolvedLinkTarget = Preconditions.checkNotNull(unresolvedLinkTarget);
}
PathFragment getNameInSymlinkTree() {
return linkName.getRelativePath();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Symlink)) {
return false;
}
Symlink o = (Symlink) obj;
return linkName.equals(o.linkName) && unresolvedLinkTarget.equals(o.unresolvedLinkTarget);
}
@Override
public int hashCode() {
return Objects.hashCode(linkName, unresolvedLinkTarget);
}
@Override
public String toString() {
return String.format("Symlink(link_name=%s, unresolved_target=%s)",
linkName, unresolvedLinkTarget);
}
}
private static final class RegularFile extends ResolvedFile {
private RegularFile(RootedPath path) {
super(FileType.FILE, Optional.of(path), Optional. May only be absent for dangling symlinks.
*/
protected final Optional This field must be stored so that this {@link ResolvedFile} is (also) the function of the
* stat() of the file, but otherwise it is likely not something the consumer of the
* {@link ResolvedFile} is directly interested in.
*
* May only be absent if stripped for tests.
*/
final Optional The path should contain the FilesetEntry-specific destination directory (if any) and
* should have necessary prefixes stripped (if any).
*/
public abstract PathFragment getNameInSymlinkTree();
/**
* Returns the path of the symlink target.
*
* @throws DanglingSymlinkException if the target cannot be resolved because the symlink is
* dangling
*/
public abstract PathFragment getTargetInSymlinkTree(boolean followSymlinks)
throws DanglingSymlinkException;
/**
* Returns a copy of this object with the metadata stripped away.
*
* This method should only be used by tests that wish to assert that this
* {@link ResolvedFile} refers to the expected absolute path and has the expected type, without
* asserting its actual contents (which the metadata is a function of).
*/
@VisibleForTesting
abstract ResolvedFile stripMetadataForTesting();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof RecursiveFilesystemTraversalValue)) {
return false;
}
RecursiveFilesystemTraversalValue o = (RecursiveFilesystemTraversalValue) obj;
return resolvedRoot.equals(o.resolvedRoot) && resolvedPaths.equals(o.resolvedPaths);
}
@Override
public int hashCode() {
return Objects.hashCode(resolvedRoot, resolvedPaths);
}
}