blob: 070b83141b7e0ca58e7b7a552065845aafd11fa9 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
tomlua155b532017-11-08 20:12:47 +010016import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.base.Verify;
18import com.google.common.collect.Collections2;
felly5be4dd62018-02-05 11:11:53 -080019import com.google.devtools.build.lib.actions.Artifact;
shahane35e8cf2018-06-18 08:14:01 -070020import com.google.devtools.build.lib.actions.ArtifactSkyKey;
shahan602cc852018-06-06 20:09:57 -070021import com.google.devtools.build.lib.actions.FileArtifactValue;
shahan602cc852018-06-06 20:09:57 -070022import com.google.devtools.build.lib.actions.FileValue;
Laszlo Csomor207140f2015-12-07 15:07:33 +000023import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
25import com.google.devtools.build.lib.events.Event;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
Laszlo Csomorb4d482b2015-12-04 13:23:54 +000027import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
29import com.google.devtools.build.lib.vfs.Dirent;
felly5be4dd62018-02-05 11:11:53 -080030import com.google.devtools.build.lib.vfs.FileStatus;
felly5be4dd62018-02-05 11:11:53 -080031import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032import com.google.devtools.build.lib.vfs.PathFragment;
tomluee6a6862018-01-17 14:36:26 -080033import com.google.devtools.build.lib.vfs.Root;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import com.google.devtools.build.lib.vfs.RootedPath;
felly5be4dd62018-02-05 11:11:53 -080035import com.google.devtools.build.lib.vfs.Symlinks;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import com.google.devtools.build.skyframe.SkyFunction;
37import com.google.devtools.build.skyframe.SkyFunctionException;
38import com.google.devtools.build.skyframe.SkyKey;
39import com.google.devtools.build.skyframe.SkyValue;
kush95bf7c82017-08-30 00:27:35 +020040import java.io.IOException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041import java.util.ArrayList;
42import java.util.Collection;
felly5be4dd62018-02-05 11:11:53 -080043import java.util.Collections;
44import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045import java.util.List;
46import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import javax.annotation.Nullable;
48
49/** A {@link SkyFunction} to build {@link RecursiveFilesystemTraversalValue}s. */
50public final class RecursiveFilesystemTraversalFunction implements SkyFunction {
51
52 private static final class MissingDepException extends Exception {}
53
54 /** Base class for exceptions that {@link RecursiveFilesystemTraversalFunctionException} wraps. */
55 public abstract static class RecursiveFilesystemTraversalException extends Exception {
56 protected RecursiveFilesystemTraversalException(String message) {
57 super(message);
58 }
59 }
60
61 /** Thrown when a generated directory's root-relative path conflicts with a package's path. */
62 public static final class GeneratedPathConflictException extends
63 RecursiveFilesystemTraversalException {
64 GeneratedPathConflictException(TraversalRequest traversal) {
tomlu8cc5dcf2018-01-19 09:28:06 -080065 super(
66 String.format(
67 "Generated directory %s conflicts with package under the same path. "
68 + "Additional info: %s",
felly5be4dd62018-02-05 11:11:53 -080069 traversal.root.asRootedPath().getRootRelativePath().getPathString(),
tomlu8cc5dcf2018-01-19 09:28:06 -080070 traversal.errorInfo != null ? traversal.errorInfo : traversal.toString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010071 }
72 }
73
74 /**
75 * Thrown when the traversal encounters a subdirectory with a BUILD file but is not allowed to
Laszlo Csomorf04efcc2015-02-12 17:08:06 +000076 * recurse into it. See {@code PackageBoundaryMode#REPORT_ERROR}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010077 */
78 public static final class CannotCrossPackageBoundaryException extends
79 RecursiveFilesystemTraversalException {
80 CannotCrossPackageBoundaryException(String message) {
81 super(message);
82 }
83 }
84
85 /**
86 * Thrown when a dangling symlink is attempted to be dereferenced.
87 *
88 * <p>Note: this class is not identical to the one in com.google.devtools.build.lib.view.fileset
89 * and it's not easy to merge the two because of the dependency structure. The other one will
90 * probably be removed along with the rest of the legacy Fileset code.
91 */
92 public static final class DanglingSymlinkException extends RecursiveFilesystemTraversalException {
93 public final String path;
94 public final String unresolvedLink;
95
96 public DanglingSymlinkException(String path, String unresolvedLink) {
Laszlo Csomor0ad729f2015-12-02 15:20:35 +000097 super(
98 String.format(
99 "Found dangling symlink: %s, unresolved path: \"%s\"", path, unresolvedLink));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100100 Preconditions.checkArgument(path != null && !path.isEmpty());
101 Preconditions.checkArgument(unresolvedLink != null && !unresolvedLink.isEmpty());
102 this.path = path;
103 this.unresolvedLink = unresolvedLink;
104 }
105
106 public String getPath() {
107 return path;
108 }
109 }
110
kush95bf7c82017-08-30 00:27:35 +0200111 /** Thrown when we encounter errors from underlying File operations */
112 public static final class FileOperationException extends RecursiveFilesystemTraversalException {
113 public FileOperationException(String message) {
114 super(message);
115 }
116 }
117
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100118 /** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
119 private static final class RecursiveFilesystemTraversalFunctionException extends
120 SkyFunctionException {
121 RecursiveFilesystemTraversalFunctionException(RecursiveFilesystemTraversalException e) {
122 super(e, Transience.PERSISTENT);
123 }
124 }
125
126 @Override
127 public SkyValue compute(SkyKey skyKey, Environment env)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000128 throws RecursiveFilesystemTraversalFunctionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100129 TraversalRequest traversal = (TraversalRequest) skyKey.argument();
130 try {
131 // Stat the traversal root.
132 FileInfo rootInfo = lookUpFileInfo(env, traversal);
133
134 if (!rootInfo.type.exists()) {
135 // May be a dangling symlink or a non-existent file. Handle gracefully.
136 if (rootInfo.type.isSymlink()) {
felly5be4dd62018-02-05 11:11:53 -0800137 return resultForDanglingSymlink(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100138 } else {
139 return RecursiveFilesystemTraversalValue.EMPTY;
140 }
141 }
142
143 if (rootInfo.type.isFile()) {
felly5be4dd62018-02-05 11:11:53 -0800144 return resultForFileRoot(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100145 }
146
147 // Otherwise the root is a directory or a symlink to one.
148 PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
149 traversal = pkgLookupResult.traversal;
150
151 if (pkgLookupResult.isConflicting()) {
152 // The traversal was requested for an output directory whose root-relative path conflicts
153 // with a source package. We can't handle that, bail out.
154 throw new RecursiveFilesystemTraversalFunctionException(
155 new GeneratedPathConflictException(traversal));
156 } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
157 // The traversal was requested for a directory that defines a package.
tomlu8cc5dcf2018-01-19 09:28:06 -0800158 String msg =
159 traversal.errorInfo
160 + " crosses package boundary into package rooted at "
felly5be4dd62018-02-05 11:11:53 -0800161 + traversal.root.asRootedPath().getRootRelativePath().getPathString();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000162 switch (traversal.crossPkgBoundaries) {
163 case CROSS:
164 // We are free to traverse the subpackage but we need to display a warning.
Ulf Adams760e7092016-04-21 08:09:51 +0000165 env.getListener().handle(Event.warn(null, msg));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000166 break;
167 case DONT_CROSS:
168 // We cannot traverse the subpackage and should skip it silently. Return empty results.
169 return RecursiveFilesystemTraversalValue.EMPTY;
170 case REPORT_ERROR:
171 // We cannot traverse the subpackage and should complain loudly (display an error).
172 throw new RecursiveFilesystemTraversalFunctionException(
173 new CannotCrossPackageBoundaryException(msg));
174 default:
175 throw new IllegalStateException(traversal.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100176 }
177 }
178
179 // We are free to traverse this directory.
180 Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
janakr5fb2a482018-03-02 17:48:57 -0800181 return resultForDirectory(
182 traversal,
183 rootInfo,
184 traverseChildren(env, dependentKeys, /*inline=*/ traversal.isRootGenerated));
ulfjacke4794532018-01-12 02:11:17 -0800185 } catch (IOException e) {
kush95bf7c82017-08-30 00:27:35 +0200186 throw new RecursiveFilesystemTraversalFunctionException(
187 new FileOperationException("Error while traversing fileset: " + e.getMessage()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188 } catch (MissingDepException e) {
189 return null;
190 }
191 }
192
193 @Override
194 public String extractTag(SkyKey skyKey) {
195 return null;
196 }
197
198 private static final class FileInfo {
199 final FileType type;
kushb39c6932018-07-12 21:25:23 -0700200 final Object metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100201 @Nullable final RootedPath realPath;
202 @Nullable final PathFragment unresolvedSymlinkTarget;
203
kush4b120e72018-07-11 16:21:27 -0700204 FileInfo(
205 FileType type,
kushb39c6932018-07-12 21:25:23 -0700206 Object metadata,
kush4b120e72018-07-11 16:21:27 -0700207 @Nullable RootedPath realPath,
kushb39c6932018-07-12 21:25:23 -0700208 @Nullable PathFragment unresolvedSymlinkTarget) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 this.type = Preconditions.checkNotNull(type);
kushb39c6932018-07-12 21:25:23 -0700210 this.metadata = metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100211 this.realPath = realPath;
212 this.unresolvedSymlinkTarget = unresolvedSymlinkTarget;
213 }
214
215 @Override
216 public String toString() {
217 if (type.isSymlink()) {
218 return String.format("(%s: link_value=%s, real_path=%s)", type,
219 unresolvedSymlinkTarget.getPathString(), realPath);
220 } else {
221 return String.format("(%s: real_path=%s)", type, realPath);
222 }
223 }
224 }
225
kushb39c6932018-07-12 21:25:23 -0700226 private static final FileInfo NON_EXISTENT_FILE_INFO =
227 new FileInfo(FileType.NONEXISTENT, new Integer(0), null, null);
228
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100229 private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
ulfjacke4794532018-01-12 02:11:17 -0800230 throws MissingDepException, IOException, InterruptedException {
janakr5fb2a482018-03-02 17:48:57 -0800231 if (traversal.isRootGenerated) {
kush4b120e72018-07-11 16:21:27 -0700232 FileArtifactValue fsVal = null;
felly5be4dd62018-02-05 11:11:53 -0800233 if (traversal.root.getOutputArtifact() != null) {
234 Artifact artifact = traversal.root.getOutputArtifact();
235 SkyKey artifactKey = ArtifactSkyKey.key(artifact, true);
236 SkyValue value = env.getValue(artifactKey);
237 if (env.valuesMissing()) {
238 throw new MissingDepException();
239 }
kush95bf7c82017-08-30 00:27:35 +0200240
felly5be4dd62018-02-05 11:11:53 -0800241 if (value instanceof FileArtifactValue) {
kush4b120e72018-07-11 16:21:27 -0700242 fsVal = (FileArtifactValue) value;
felly5be4dd62018-02-05 11:11:53 -0800243 } else {
kushb39c6932018-07-12 21:25:23 -0700244 return NON_EXISTENT_FILE_INFO;
felly5be4dd62018-02-05 11:11:53 -0800245 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100246 }
felly5be4dd62018-02-05 11:11:53 -0800247
248 // FileArtifactValue does not currently track symlinks. If it did, we could potentially remove
249 // some of the filesystem operations we're doing here.
250 Path path = traversal.root.asRootedPath().asPath();
251 FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
252 FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
253 FileType type;
254 PathFragment unresolvedLinkTarget = null;
255 RootedPath realPath = traversal.root.asRootedPath();
256 if (followStat == null) {
257 type = FileType.DANGLING_SYMLINK;
258 if (!noFollowStat.isSymbolicLink()) {
259 throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
260 }
261 unresolvedLinkTarget = path.readSymbolicLink();
262 } else if (noFollowStat.isFile()) {
263 type = FileType.FILE;
264 } else if (noFollowStat.isDirectory()) {
265 type = FileType.DIRECTORY;
266 } else {
267 unresolvedLinkTarget = path.readSymbolicLink();
268 realPath = RootedPath.toRootedPath(
269 Root.absoluteRoot(path.getFileSystem()),
270 path.resolveSymbolicLinks());
271 type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
272 }
kush4b120e72018-07-11 16:21:27 -0700273 return new FileInfo(
kushb39c6932018-07-12 21:25:23 -0700274 type, fsVal != null ? fsVal : noFollowStat.hashCode(), realPath, unresolvedLinkTarget);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 } else {
felly5be4dd62018-02-05 11:11:53 -0800276 // Stat the file.
277 FileValue fileValue =
278 (FileValue) env.getValueOrThrow(
279 FileValue.key(traversal.root.asRootedPath()), IOException.class);
280
281 if (env.valuesMissing()) {
282 throw new MissingDepException();
283 }
284 if (fileValue.exists()) {
285 // If it exists, it may either be a symlink or a file/directory.
286 PathFragment unresolvedLinkTarget = null;
287 FileType type;
288 if (fileValue.isSymlink()) {
289 unresolvedLinkTarget = fileValue.getUnresolvedLinkTarget();
290 type = fileValue.isDirectory() ? FileType.SYMLINK_TO_DIRECTORY : FileType.SYMLINK_TO_FILE;
291 } else {
292 type = fileValue.isDirectory() ? FileType.DIRECTORY : FileType.FILE;
293 }
kush4b120e72018-07-11 16:21:27 -0700294 return new FileInfo(
kushb39c6932018-07-12 21:25:23 -0700295 type, fileValue.realFileStateValue(), fileValue.realRootedPath(), unresolvedLinkTarget);
felly5be4dd62018-02-05 11:11:53 -0800296 } else {
297 // If it doesn't exist, or it's a dangling symlink, we still want to handle that gracefully.
298 return new FileInfo(
299 fileValue.isSymlink() ? FileType.DANGLING_SYMLINK : FileType.NONEXISTENT,
kush4b120e72018-07-11 16:21:27 -0700300 fileValue.realFileStateValue(),
301 null,
kushb39c6932018-07-12 21:25:23 -0700302 fileValue.isSymlink() ? fileValue.getUnresolvedLinkTarget() : null);
felly5be4dd62018-02-05 11:11:53 -0800303 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 }
305 }
306
307 private static final class PkgLookupResult {
308 private enum Type {
309 CONFLICT, DIRECTORY, PKG
310 }
311
312 private final Type type;
313 final TraversalRequest traversal;
314 final FileInfo rootInfo;
315
316 /** Result for a generated directory that conflicts with a source package. */
317 static PkgLookupResult conflict(TraversalRequest traversal, FileInfo rootInfo) {
318 return new PkgLookupResult(Type.CONFLICT, traversal, rootInfo);
319 }
320
321 /** Result for a source or generated directory (not a package). */
322 static PkgLookupResult directory(TraversalRequest traversal, FileInfo rootInfo) {
323 return new PkgLookupResult(Type.DIRECTORY, traversal, rootInfo);
324 }
325
326 /** Result for a package, i.e. a directory with a BUILD file. */
327 static PkgLookupResult pkg(TraversalRequest traversal, FileInfo rootInfo) {
328 return new PkgLookupResult(Type.PKG, traversal, rootInfo);
329 }
330
331 private PkgLookupResult(Type type, TraversalRequest traversal, FileInfo rootInfo) {
332 this.type = Preconditions.checkNotNull(type);
333 this.traversal = Preconditions.checkNotNull(traversal);
334 this.rootInfo = Preconditions.checkNotNull(rootInfo);
335 }
336
337 boolean isPackage() {
338 return type == Type.PKG;
339 }
340
341 boolean isConflicting() {
342 return type == Type.CONFLICT;
343 }
344
345 @Override
346 public String toString() {
347 return String.format("(%s: info=%s, traversal=%s)", type, rootInfo, traversal);
348 }
349 }
350
351 /**
352 * Checks whether the {@code traversal}'s path refers to a package directory.
353 *
354 * @return the result of the lookup; it contains potentially new {@link TraversalRequest} and
355 * {@link FileInfo} so the caller should use these instead of the old ones (this happens when
356 * a package is found, but under a different root than expected)
357 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000358 private static PkgLookupResult checkIfPackage(
359 Environment env, TraversalRequest traversal, FileInfo rootInfo)
ulfjacke4794532018-01-12 02:11:17 -0800360 throws MissingDepException, IOException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100361 Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
362 "{%s} {%s}", traversal, rootInfo);
tomlu8cc5dcf2018-01-19 09:28:06 -0800363 PackageLookupValue pkgLookup =
364 (PackageLookupValue)
felly5be4dd62018-02-05 11:11:53 -0800365 getDependentSkyValue(env,
366 PackageLookupValue.key(traversal.root.asRootedPath().getRootRelativePath()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367
368 if (pkgLookup.packageExists()) {
janakr5fb2a482018-03-02 17:48:57 -0800369 if (traversal.isRootGenerated) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100370 // The traversal's root was a generated directory, but its root-relative path conflicts with
371 // an existing package.
372 return PkgLookupResult.conflict(traversal, rootInfo);
373 } else {
374 // The traversal's root was a source directory and it defines a package.
tomluee6a6862018-01-17 14:36:26 -0800375 Root pkgRoot = pkgLookup.getRoot();
felly5be4dd62018-02-05 11:11:53 -0800376 if (!pkgRoot.equals(traversal.root.asRootedPath().getRoot())) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100377 // However the root of this package is different from what we expected. stat() the real
378 // BUILD file of that package.
379 traversal = traversal.forChangedRootPath(pkgRoot);
380 rootInfo = lookUpFileInfo(env, traversal);
381 Verify.verify(rootInfo.type.exists(), "{%s} {%s}", traversal, rootInfo);
382 }
383 return PkgLookupResult.pkg(traversal, rootInfo);
384 }
385 } else {
386 // The traversal's root was a directory (source or generated one), no package exists under the
387 // same root-relative path.
388 return PkgLookupResult.directory(traversal, rootInfo);
389 }
390 }
391
392 /**
393 * List the directory and create {@code SkyKey}s to request contents of its children recursively.
394 *
395 * <p>The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
396 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000397 private static Collection<SkyKey> createRecursiveTraversalKeys(
398 Environment env, TraversalRequest traversal)
felly5be4dd62018-02-05 11:11:53 -0800399 throws MissingDepException, InterruptedException, IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100400 // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
401 // in the result, must be relative to it.
felly5be4dd62018-02-05 11:11:53 -0800402 Iterable<Dirent> dirents;
janakr5fb2a482018-03-02 17:48:57 -0800403 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800404 // If we're dealing with an output file, read the directory directly instead of creating
405 // filesystem nodes under the output tree.
406 List<Dirent> direntsCollection =
407 new ArrayList<>(
408 traversal.root.asRootedPath().asPath().readdir(Symlinks.FOLLOW));
409 Collections.sort(direntsCollection);
410 dirents = direntsCollection;
411 } else {
412 dirents = ((DirectoryListingValue) getDependentSkyValue(env,
413 DirectoryListingValue.key(traversal.root.asRootedPath()))).getDirents();
414 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100415
416 List<SkyKey> result = new ArrayList<>();
felly5be4dd62018-02-05 11:11:53 -0800417 for (Dirent dirent : dirents) {
tomlu8cc5dcf2018-01-19 09:28:06 -0800418 RootedPath childPath =
419 RootedPath.toRootedPath(
felly5be4dd62018-02-05 11:11:53 -0800420 traversal.root.asRootedPath().getRoot(),
421 traversal.root.asRootedPath().getRootRelativePath().getRelative(dirent.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100422 TraversalRequest childTraversal = traversal.forChildEntry(childPath);
janakr5fb2a482018-03-02 17:48:57 -0800423 result.add(childTraversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100424 }
425 return result;
426 }
427
428 /**
429 * Creates result for a dangling symlink.
430 *
431 * @param linkName path to the symbolic link
432 * @param info the {@link FileInfo} associated with the link file
433 */
434 private static RecursiveFilesystemTraversalValue resultForDanglingSymlink(RootedPath linkName,
435 FileInfo info) {
436 Preconditions.checkState(info.type.isSymlink() && !info.type.exists(), "{%s} {%s}", linkName,
437 info.type);
438 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700439 ResolvedFileFactory.danglingSymlink(linkName, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100440 }
441
442 /**
443 * Creates results for a file or for a symlink that points to one.
444 *
445 * <p>A symlink may be direct (points to a file) or transitive (points at a direct or transitive
446 * symlink).
447 */
448 private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
449 FileInfo info) {
450 Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
451 info.type);
452 if (info.type.isSymlink()) {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000453 return RecursiveFilesystemTraversalValue.of(
454 ResolvedFileFactory.symlinkToFile(
kushb39c6932018-07-12 21:25:23 -0700455 info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100456 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000457 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700458 ResolvedFileFactory.regularFile(path, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100459 }
460 }
461
462 private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
463 FileInfo rootInfo, Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
464 // Collect transitive closure of files in subdirectories.
465 NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
466 for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
467 paths.addTransitive(child.getTransitiveFiles());
468 }
469 ResolvedFile root;
470 if (rootInfo.type.isSymlink()) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000471 NestedSet<ResolvedFile> children = paths.build();
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000472 root =
473 ResolvedFileFactory.symlinkToDirectory(
474 rootInfo.realPath,
felly5be4dd62018-02-05 11:11:53 -0800475 traversal.root.asRootedPath(),
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000476 rootInfo.unresolvedSymlinkTarget,
kushb39c6932018-07-12 21:25:23 -0700477 hashDirectorySymlink(children, rootInfo.metadata));
Laszlo Csomor207140f2015-12-07 15:07:33 +0000478 paths = NestedSetBuilder.<ResolvedFile>stableOrder().addTransitive(children).add(root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100479 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000480 root = ResolvedFileFactory.directory(rootInfo.realPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100481 }
482 return RecursiveFilesystemTraversalValue.of(root, paths.build());
483 }
484
kushb39c6932018-07-12 21:25:23 -0700485 private static int hashDirectorySymlink(Iterable<ResolvedFile> children, Object metadata) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000486 // If the root is a directory symlink, the associated FileStateValue does not change when the
487 // linked directory's contents change, so we can't use the FileStateValue as metadata like we
488 // do with other ResolvedFile kinds. Instead we compute a metadata hash from the child
489 // elements and return that as the ResolvedFile's metadata hash.
490
491 // Compute the hash using the method described in Effective Java, 2nd ed., Item 9.
492 int result = 0;
493 for (ResolvedFile c : children) {
kushb39c6932018-07-12 21:25:23 -0700494 result = 31 * result + c.getMetadata().hashCode();
Laszlo Csomor207140f2015-12-07 15:07:33 +0000495 }
kushb39c6932018-07-12 21:25:23 -0700496 return 31 * result + metadata.hashCode();
Laszlo Csomor207140f2015-12-07 15:07:33 +0000497 }
498
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100499 private static SkyValue getDependentSkyValue(Environment env, SkyKey key)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000500 throws MissingDepException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100501 SkyValue value = env.getValue(key);
502 if (env.valuesMissing()) {
503 throw new MissingDepException();
504 }
505 return value;
506 }
507
508 /**
509 * Requests Skyframe to compute the dependent values and returns them.
510 *
511 * <p>The keys must all be {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL} keys.
512 */
felly5be4dd62018-02-05 11:11:53 -0800513 private Collection<RecursiveFilesystemTraversalValue> traverseChildren(
514 Environment env, Iterable<SkyKey> keys, boolean inline)
515 throws MissingDepException, InterruptedException,
516 RecursiveFilesystemTraversalFunctionException {
517 Map<SkyKey, SkyValue> values;
518 if (inline) {
519 // Don't create Skyframe nodes for a recursive traversal over the output tree.
520 // Instead, inline the recursion in the top-level request.
521 values = new HashMap<>();
522 for (SkyKey depKey : keys) {
523 values.put(depKey, compute(depKey, env));
524 }
525 } else {
526 values = env.getValues(keys);
527 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 if (env.valuesMissing()) {
529 throw new MissingDepException();
530 }
felly5be4dd62018-02-05 11:11:53 -0800531
kushb39c6932018-07-12 21:25:23 -0700532 return Collections2.transform(values.values(), RecursiveFilesystemTraversalValue.class::cast);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100533 }
534
535 /** Type information about the filesystem entry residing at a path. */
536 enum FileType {
537 /** A regular file. */
538 FILE {
539 @Override boolean isFile() { return true; }
540 @Override boolean exists() { return true; }
541 @Override public String toString() { return "<f>"; }
542 },
543 /**
544 * A symlink to a regular file.
545 *
546 * <p>The symlink may be direct (points to a non-symlink (here a file)) or it may be transitive
547 * (points to a direct or transitive symlink).
548 */
549 SYMLINK_TO_FILE {
550 @Override boolean isFile() { return true; }
551 @Override boolean isSymlink() { return true; }
552 @Override boolean exists() { return true; }
553 @Override public String toString() { return "<lf>"; }
554 },
555 /** A directory. */
556 DIRECTORY {
557 @Override boolean isDirectory() { return true; }
558 @Override boolean exists() { return true; }
559 @Override public String toString() { return "<d>"; }
560 },
561 /**
562 * A symlink to a directory.
563 *
564 * <p>The symlink may be direct (points to a non-symlink (here a directory)) or it may be
565 * transitive (points to a direct or transitive symlink).
566 */
567 SYMLINK_TO_DIRECTORY {
568 @Override boolean isDirectory() { return true; }
569 @Override boolean isSymlink() { return true; }
570 @Override boolean exists() { return true; }
571 @Override public String toString() { return "<ld>"; }
572 },
573 /** A dangling symlink, i.e. one whose target is known not to exist. */
574 DANGLING_SYMLINK {
575 @Override boolean isFile() { throw new UnsupportedOperationException(); }
576 @Override boolean isDirectory() { throw new UnsupportedOperationException(); }
577 @Override boolean isSymlink() { return true; }
578 @Override public String toString() { return "<l?>"; }
579 },
580 /** A path that does not exist or should be ignored. */
581 NONEXISTENT {
582 @Override public String toString() { return "<?>"; }
583 };
584
585 boolean isFile() { return false; }
586 boolean isDirectory() { return false; }
587 boolean isSymlink() { return false; }
588 boolean exists() { return false; }
589 @Override public abstract String toString();
590 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100591}