// 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.base.Function; import com.google.common.base.Verify; import com.google.common.collect.Collections2; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory; import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.Dirent; 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.SkyFunction; import com.google.devtools.build.skyframe.SkyFunctionException; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** A {@link SkyFunction} to build {@link RecursiveFilesystemTraversalValue}s. */ public final class RecursiveFilesystemTraversalFunction implements SkyFunction { private static final class MissingDepException extends Exception {} /** Base class for exceptions that {@link RecursiveFilesystemTraversalFunctionException} wraps. */ public abstract static class RecursiveFilesystemTraversalException extends Exception { protected RecursiveFilesystemTraversalException(String message) { super(message); } } /** Thrown when a generated directory's root-relative path conflicts with a package's path. */ public static final class GeneratedPathConflictException extends RecursiveFilesystemTraversalException { GeneratedPathConflictException(TraversalRequest traversal) { super(String.format( "Generated directory %s conflicts with package under the same path. Additional info: %s", traversal.path.getRelativePath().getPathString(), traversal.errorInfo != null ? traversal.errorInfo : traversal.toString())); } } /** * Thrown when the traversal encounters a subdirectory with a BUILD file but is not allowed to * recurse into it. See {@code PackageBoundaryMode#REPORT_ERROR}. */ public static final class CannotCrossPackageBoundaryException extends RecursiveFilesystemTraversalException { CannotCrossPackageBoundaryException(String message) { super(message); } } /** * Thrown when a dangling symlink is attempted to be dereferenced. * *
Note: this class is not identical to the one in com.google.devtools.build.lib.view.fileset
* and it's not easy to merge the two because of the dependency structure. The other one will
* probably be removed along with the rest of the legacy Fileset code.
*/
public static final class DanglingSymlinkException extends RecursiveFilesystemTraversalException {
public final String path;
public final String unresolvedLink;
public DanglingSymlinkException(String path, String unresolvedLink) {
super(
String.format(
"Found dangling symlink: %s, unresolved path: \"%s\"", path, unresolvedLink));
Preconditions.checkArgument(path != null && !path.isEmpty());
Preconditions.checkArgument(unresolvedLink != null && !unresolvedLink.isEmpty());
this.path = path;
this.unresolvedLink = unresolvedLink;
}
public String getPath() {
return path;
}
}
/** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
private static final class RecursiveFilesystemTraversalFunctionException extends
SkyFunctionException {
RecursiveFilesystemTraversalFunctionException(RecursiveFilesystemTraversalException e) {
super(e, Transience.PERSISTENT);
}
}
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws RecursiveFilesystemTraversalFunctionException, InterruptedException {
TraversalRequest traversal = (TraversalRequest) skyKey.argument();
try {
// Stat the traversal root.
FileInfo rootInfo = lookUpFileInfo(env, traversal);
if (!rootInfo.type.exists()) {
// May be a dangling symlink or a non-existent file. Handle gracefully.
if (rootInfo.type.isSymlink()) {
return resultForDanglingSymlink(traversal.path, rootInfo);
} else {
return RecursiveFilesystemTraversalValue.EMPTY;
}
}
if (rootInfo.type.isFile()) {
if (traversal.pattern == null
|| traversal.pattern.matcher(
rootInfo.realPath.getRelativePath().getPathString()).matches()) {
// The root is a file or a symlink to one.
return resultForFileRoot(traversal.path, rootInfo);
} else {
return RecursiveFilesystemTraversalValue.EMPTY;
}
}
// Otherwise the root is a directory or a symlink to one.
PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
traversal = pkgLookupResult.traversal;
if (pkgLookupResult.isConflicting()) {
// The traversal was requested for an output directory whose root-relative path conflicts
// with a source package. We can't handle that, bail out.
throw new RecursiveFilesystemTraversalFunctionException(
new GeneratedPathConflictException(traversal));
} else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
// The traversal was requested for a directory that defines a package.
String msg = traversal.errorInfo + " crosses package boundary into package rooted at "
+ traversal.path.getRelativePath().getPathString();
switch (traversal.crossPkgBoundaries) {
case CROSS:
// We are free to traverse the subpackage but we need to display a warning.
env.getListener().handle(Event.warn(null, msg));
break;
case DONT_CROSS:
// We cannot traverse the subpackage and should skip it silently. Return empty results.
return RecursiveFilesystemTraversalValue.EMPTY;
case REPORT_ERROR:
// We cannot traverse the subpackage and should complain loudly (display an error).
throw new RecursiveFilesystemTraversalFunctionException(
new CannotCrossPackageBoundaryException(msg));
default:
throw new IllegalStateException(traversal.toString());
}
}
// We are free to traverse this directory.
Collection The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
*/
private static Collection A symlink may be direct (points to a file) or transitive (points at a direct or transitive
* symlink).
*/
private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
FileInfo info) {
Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
info.type);
if (info.type.isSymlink()) {
return RecursiveFilesystemTraversalValue.of(
ResolvedFileFactory.symlinkToFile(
info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
} else {
return RecursiveFilesystemTraversalValue.of(
ResolvedFileFactory.regularFile(path, info.metadata));
}
}
private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
FileInfo rootInfo, Collection The keys must all be {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL} keys.
*/
private static Collection The symlink may be direct (points to a non-symlink (here a file)) or it may be transitive
* (points to a direct or transitive symlink).
*/
SYMLINK_TO_FILE {
@Override boolean isFile() { return true; }
@Override boolean isSymlink() { return true; }
@Override boolean exists() { return true; }
@Override public String toString() { return " The symlink may be direct (points to a non-symlink (here a directory)) or it may be
* transitive (points to a direct or transitive symlink).
*/
SYMLINK_TO_DIRECTORY {
@Override boolean isDirectory() { return true; }
@Override boolean isSymlink() { return true; }
@Override boolean exists() { return true; }
@Override public String toString() { return "