blob: 2948e4e16d6cee77665884ddb884e4532007808e [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
16import com.google.common.base.Function;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.base.Verify;
19import com.google.common.collect.Collections2;
felly5be4dd62018-02-05 11:11:53 -080020import com.google.devtools.build.lib.actions.Artifact;
Laszlo Csomor207140f2015-12-07 15:07:33 +000021import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
23import com.google.devtools.build.lib.events.Event;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
Laszlo Csomorb4d482b2015-12-04 13:23:54 +000025import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
27import com.google.devtools.build.lib.vfs.Dirent;
felly5be4dd62018-02-05 11:11:53 -080028import com.google.devtools.build.lib.vfs.FileStatus;
29import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
30import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import com.google.devtools.build.lib.vfs.PathFragment;
tomluee6a6862018-01-17 14:36:26 -080032import com.google.devtools.build.lib.vfs.Root;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.vfs.RootedPath;
felly5be4dd62018-02-05 11:11:53 -080034import com.google.devtools.build.lib.vfs.Symlinks;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.skyframe.SkyFunction;
36import com.google.devtools.build.skyframe.SkyFunctionException;
37import com.google.devtools.build.skyframe.SkyKey;
38import com.google.devtools.build.skyframe.SkyValue;
kush95bf7c82017-08-30 00:27:35 +020039import java.io.IOException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040import java.util.ArrayList;
41import java.util.Collection;
felly5be4dd62018-02-05 11:11:53 -080042import java.util.Collections;
43import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import java.util.List;
45import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046import javax.annotation.Nullable;
47
48/** A {@link SkyFunction} to build {@link RecursiveFilesystemTraversalValue}s. */
49public final class RecursiveFilesystemTraversalFunction implements SkyFunction {
50
51 private static final class MissingDepException extends Exception {}
52
53 /** Base class for exceptions that {@link RecursiveFilesystemTraversalFunctionException} wraps. */
54 public abstract static class RecursiveFilesystemTraversalException extends Exception {
55 protected RecursiveFilesystemTraversalException(String message) {
56 super(message);
57 }
58 }
59
60 /** Thrown when a generated directory's root-relative path conflicts with a package's path. */
61 public static final class GeneratedPathConflictException extends
62 RecursiveFilesystemTraversalException {
63 GeneratedPathConflictException(TraversalRequest traversal) {
tomlu8cc5dcf2018-01-19 09:28:06 -080064 super(
65 String.format(
66 "Generated directory %s conflicts with package under the same path. "
67 + "Additional info: %s",
felly5be4dd62018-02-05 11:11:53 -080068 traversal.root.asRootedPath().getRootRelativePath().getPathString(),
tomlu8cc5dcf2018-01-19 09:28:06 -080069 traversal.errorInfo != null ? traversal.errorInfo : traversal.toString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010070 }
71 }
72
73 /**
74 * Thrown when the traversal encounters a subdirectory with a BUILD file but is not allowed to
Laszlo Csomorf04efcc2015-02-12 17:08:06 +000075 * recurse into it. See {@code PackageBoundaryMode#REPORT_ERROR}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 */
77 public static final class CannotCrossPackageBoundaryException extends
78 RecursiveFilesystemTraversalException {
79 CannotCrossPackageBoundaryException(String message) {
80 super(message);
81 }
82 }
83
84 /**
85 * Thrown when a dangling symlink is attempted to be dereferenced.
86 *
87 * <p>Note: this class is not identical to the one in com.google.devtools.build.lib.view.fileset
88 * and it's not easy to merge the two because of the dependency structure. The other one will
89 * probably be removed along with the rest of the legacy Fileset code.
90 */
91 public static final class DanglingSymlinkException extends RecursiveFilesystemTraversalException {
92 public final String path;
93 public final String unresolvedLink;
94
95 public DanglingSymlinkException(String path, String unresolvedLink) {
Laszlo Csomor0ad729f2015-12-02 15:20:35 +000096 super(
97 String.format(
98 "Found dangling symlink: %s, unresolved path: \"%s\"", path, unresolvedLink));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099 Preconditions.checkArgument(path != null && !path.isEmpty());
100 Preconditions.checkArgument(unresolvedLink != null && !unresolvedLink.isEmpty());
101 this.path = path;
102 this.unresolvedLink = unresolvedLink;
103 }
104
105 public String getPath() {
106 return path;
107 }
108 }
109
kush95bf7c82017-08-30 00:27:35 +0200110 /** Thrown when we encounter errors from underlying File operations */
111 public static final class FileOperationException extends RecursiveFilesystemTraversalException {
112 public FileOperationException(String message) {
113 super(message);
114 }
115 }
116
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100117 /** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
118 private static final class RecursiveFilesystemTraversalFunctionException extends
119 SkyFunctionException {
120 RecursiveFilesystemTraversalFunctionException(RecursiveFilesystemTraversalException e) {
121 super(e, Transience.PERSISTENT);
122 }
123 }
124
125 @Override
126 public SkyValue compute(SkyKey skyKey, Environment env)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000127 throws RecursiveFilesystemTraversalFunctionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100128 TraversalRequest traversal = (TraversalRequest) skyKey.argument();
129 try {
130 // Stat the traversal root.
131 FileInfo rootInfo = lookUpFileInfo(env, traversal);
132
133 if (!rootInfo.type.exists()) {
134 // May be a dangling symlink or a non-existent file. Handle gracefully.
135 if (rootInfo.type.isSymlink()) {
felly5be4dd62018-02-05 11:11:53 -0800136 return resultForDanglingSymlink(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100137 } else {
138 return RecursiveFilesystemTraversalValue.EMPTY;
139 }
140 }
141
142 if (rootInfo.type.isFile()) {
felly5be4dd62018-02-05 11:11:53 -0800143 return resultForFileRoot(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100144 }
145
146 // Otherwise the root is a directory or a symlink to one.
147 PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
148 traversal = pkgLookupResult.traversal;
149
150 if (pkgLookupResult.isConflicting()) {
151 // The traversal was requested for an output directory whose root-relative path conflicts
152 // with a source package. We can't handle that, bail out.
153 throw new RecursiveFilesystemTraversalFunctionException(
154 new GeneratedPathConflictException(traversal));
155 } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
156 // The traversal was requested for a directory that defines a package.
tomlu8cc5dcf2018-01-19 09:28:06 -0800157 String msg =
158 traversal.errorInfo
159 + " crosses package boundary into package rooted at "
felly5be4dd62018-02-05 11:11:53 -0800160 + traversal.root.asRootedPath().getRootRelativePath().getPathString();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000161 switch (traversal.crossPkgBoundaries) {
162 case CROSS:
163 // We are free to traverse the subpackage but we need to display a warning.
Ulf Adams760e7092016-04-21 08:09:51 +0000164 env.getListener().handle(Event.warn(null, msg));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000165 break;
166 case DONT_CROSS:
167 // We cannot traverse the subpackage and should skip it silently. Return empty results.
168 return RecursiveFilesystemTraversalValue.EMPTY;
169 case REPORT_ERROR:
170 // We cannot traverse the subpackage and should complain loudly (display an error).
171 throw new RecursiveFilesystemTraversalFunctionException(
172 new CannotCrossPackageBoundaryException(msg));
173 default:
174 throw new IllegalStateException(traversal.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100175 }
176 }
177
178 // We are free to traverse this directory.
179 Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
janakr5fb2a482018-03-02 17:48:57 -0800180 return resultForDirectory(
181 traversal,
182 rootInfo,
183 traverseChildren(env, dependentKeys, /*inline=*/ traversal.isRootGenerated));
ulfjacke4794532018-01-12 02:11:17 -0800184 } catch (IOException e) {
kush95bf7c82017-08-30 00:27:35 +0200185 throw new RecursiveFilesystemTraversalFunctionException(
186 new FileOperationException("Error while traversing fileset: " + e.getMessage()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187 } catch (MissingDepException e) {
188 return null;
189 }
190 }
191
192 @Override
193 public String extractTag(SkyKey skyKey) {
194 return null;
195 }
196
197 private static final class FileInfo {
198 final FileType type;
199 final FileStateValue metadata;
200 @Nullable final RootedPath realPath;
201 @Nullable final PathFragment unresolvedSymlinkTarget;
202
203 FileInfo(FileType type, FileStateValue metadata, @Nullable RootedPath realPath,
204 @Nullable PathFragment unresolvedSymlinkTarget) {
205 this.type = Preconditions.checkNotNull(type);
206 this.metadata = Preconditions.checkNotNull(metadata);
207 this.realPath = realPath;
208 this.unresolvedSymlinkTarget = unresolvedSymlinkTarget;
209 }
210
211 @Override
212 public String toString() {
213 if (type.isSymlink()) {
214 return String.format("(%s: link_value=%s, real_path=%s)", type,
215 unresolvedSymlinkTarget.getPathString(), realPath);
216 } else {
217 return String.format("(%s: real_path=%s)", type, realPath);
218 }
219 }
220 }
221
222 private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
ulfjacke4794532018-01-12 02:11:17 -0800223 throws MissingDepException, IOException, InterruptedException {
janakr5fb2a482018-03-02 17:48:57 -0800224 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800225 byte[] digest = null;
226 if (traversal.root.getOutputArtifact() != null) {
227 Artifact artifact = traversal.root.getOutputArtifact();
228 SkyKey artifactKey = ArtifactSkyKey.key(artifact, true);
229 SkyValue value = env.getValue(artifactKey);
230 if (env.valuesMissing()) {
231 throw new MissingDepException();
232 }
kush95bf7c82017-08-30 00:27:35 +0200233
felly5be4dd62018-02-05 11:11:53 -0800234 if (value instanceof FileArtifactValue) {
235 FileArtifactValue fsVal = (FileArtifactValue) value;
236 digest = fsVal.getDigest();
237 } else {
238 return new FileInfo(
239 FileType.NONEXISTENT, null, null, null);
240 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241 }
felly5be4dd62018-02-05 11:11:53 -0800242
243 // FileArtifactValue does not currently track symlinks. If it did, we could potentially remove
244 // some of the filesystem operations we're doing here.
245 Path path = traversal.root.asRootedPath().asPath();
246 FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
247 FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
248 FileType type;
249 PathFragment unresolvedLinkTarget = null;
250 RootedPath realPath = traversal.root.asRootedPath();
251 if (followStat == null) {
252 type = FileType.DANGLING_SYMLINK;
253 if (!noFollowStat.isSymbolicLink()) {
254 throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
255 }
256 unresolvedLinkTarget = path.readSymbolicLink();
257 } else if (noFollowStat.isFile()) {
258 type = FileType.FILE;
259 } else if (noFollowStat.isDirectory()) {
260 type = FileType.DIRECTORY;
261 } else {
262 unresolvedLinkTarget = path.readSymbolicLink();
263 realPath = RootedPath.toRootedPath(
264 Root.absoluteRoot(path.getFileSystem()),
265 path.resolveSymbolicLinks());
266 type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
267 }
268 return new FileInfo(type,
269 FileStateValue.createWithStatNoFollow(traversal.root.asRootedPath(),
270 new StatWithDigest(noFollowStat, digest), null),
271 realPath, unresolvedLinkTarget);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100272 } else {
felly5be4dd62018-02-05 11:11:53 -0800273 // Stat the file.
274 FileValue fileValue =
275 (FileValue) env.getValueOrThrow(
276 FileValue.key(traversal.root.asRootedPath()), IOException.class);
277
278 if (env.valuesMissing()) {
279 throw new MissingDepException();
280 }
281 if (fileValue.exists()) {
282 // If it exists, it may either be a symlink or a file/directory.
283 PathFragment unresolvedLinkTarget = null;
284 FileType type;
285 if (fileValue.isSymlink()) {
286 unresolvedLinkTarget = fileValue.getUnresolvedLinkTarget();
287 type = fileValue.isDirectory() ? FileType.SYMLINK_TO_DIRECTORY : FileType.SYMLINK_TO_FILE;
288 } else {
289 type = fileValue.isDirectory() ? FileType.DIRECTORY : FileType.FILE;
290 }
291 return new FileInfo(type, fileValue.realFileStateValue(),
292 fileValue.realRootedPath(), unresolvedLinkTarget);
293 } else {
294 // If it doesn't exist, or it's a dangling symlink, we still want to handle that gracefully.
295 return new FileInfo(
296 fileValue.isSymlink() ? FileType.DANGLING_SYMLINK : FileType.NONEXISTENT,
297 fileValue.realFileStateValue(), null,
298 fileValue.isSymlink() ? fileValue.getUnresolvedLinkTarget() : null);
299 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100300 }
301 }
302
303 private static final class PkgLookupResult {
304 private enum Type {
305 CONFLICT, DIRECTORY, PKG
306 }
307
308 private final Type type;
309 final TraversalRequest traversal;
310 final FileInfo rootInfo;
311
312 /** Result for a generated directory that conflicts with a source package. */
313 static PkgLookupResult conflict(TraversalRequest traversal, FileInfo rootInfo) {
314 return new PkgLookupResult(Type.CONFLICT, traversal, rootInfo);
315 }
316
317 /** Result for a source or generated directory (not a package). */
318 static PkgLookupResult directory(TraversalRequest traversal, FileInfo rootInfo) {
319 return new PkgLookupResult(Type.DIRECTORY, traversal, rootInfo);
320 }
321
322 /** Result for a package, i.e. a directory with a BUILD file. */
323 static PkgLookupResult pkg(TraversalRequest traversal, FileInfo rootInfo) {
324 return new PkgLookupResult(Type.PKG, traversal, rootInfo);
325 }
326
327 private PkgLookupResult(Type type, TraversalRequest traversal, FileInfo rootInfo) {
328 this.type = Preconditions.checkNotNull(type);
329 this.traversal = Preconditions.checkNotNull(traversal);
330 this.rootInfo = Preconditions.checkNotNull(rootInfo);
331 }
332
333 boolean isPackage() {
334 return type == Type.PKG;
335 }
336
337 boolean isConflicting() {
338 return type == Type.CONFLICT;
339 }
340
341 @Override
342 public String toString() {
343 return String.format("(%s: info=%s, traversal=%s)", type, rootInfo, traversal);
344 }
345 }
346
347 /**
348 * Checks whether the {@code traversal}'s path refers to a package directory.
349 *
350 * @return the result of the lookup; it contains potentially new {@link TraversalRequest} and
351 * {@link FileInfo} so the caller should use these instead of the old ones (this happens when
352 * a package is found, but under a different root than expected)
353 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000354 private static PkgLookupResult checkIfPackage(
355 Environment env, TraversalRequest traversal, FileInfo rootInfo)
ulfjacke4794532018-01-12 02:11:17 -0800356 throws MissingDepException, IOException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357 Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
358 "{%s} {%s}", traversal, rootInfo);
tomlu8cc5dcf2018-01-19 09:28:06 -0800359 PackageLookupValue pkgLookup =
360 (PackageLookupValue)
felly5be4dd62018-02-05 11:11:53 -0800361 getDependentSkyValue(env,
362 PackageLookupValue.key(traversal.root.asRootedPath().getRootRelativePath()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100363
364 if (pkgLookup.packageExists()) {
janakr5fb2a482018-03-02 17:48:57 -0800365 if (traversal.isRootGenerated) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100366 // The traversal's root was a generated directory, but its root-relative path conflicts with
367 // an existing package.
368 return PkgLookupResult.conflict(traversal, rootInfo);
369 } else {
370 // The traversal's root was a source directory and it defines a package.
tomluee6a6862018-01-17 14:36:26 -0800371 Root pkgRoot = pkgLookup.getRoot();
felly5be4dd62018-02-05 11:11:53 -0800372 if (!pkgRoot.equals(traversal.root.asRootedPath().getRoot())) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100373 // However the root of this package is different from what we expected. stat() the real
374 // BUILD file of that package.
375 traversal = traversal.forChangedRootPath(pkgRoot);
376 rootInfo = lookUpFileInfo(env, traversal);
377 Verify.verify(rootInfo.type.exists(), "{%s} {%s}", traversal, rootInfo);
378 }
379 return PkgLookupResult.pkg(traversal, rootInfo);
380 }
381 } else {
382 // The traversal's root was a directory (source or generated one), no package exists under the
383 // same root-relative path.
384 return PkgLookupResult.directory(traversal, rootInfo);
385 }
386 }
387
388 /**
389 * List the directory and create {@code SkyKey}s to request contents of its children recursively.
390 *
391 * <p>The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
392 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000393 private static Collection<SkyKey> createRecursiveTraversalKeys(
394 Environment env, TraversalRequest traversal)
felly5be4dd62018-02-05 11:11:53 -0800395 throws MissingDepException, InterruptedException, IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100396 // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
397 // in the result, must be relative to it.
felly5be4dd62018-02-05 11:11:53 -0800398 Iterable<Dirent> dirents;
janakr5fb2a482018-03-02 17:48:57 -0800399 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800400 // If we're dealing with an output file, read the directory directly instead of creating
401 // filesystem nodes under the output tree.
402 List<Dirent> direntsCollection =
403 new ArrayList<>(
404 traversal.root.asRootedPath().asPath().readdir(Symlinks.FOLLOW));
405 Collections.sort(direntsCollection);
406 dirents = direntsCollection;
407 } else {
408 dirents = ((DirectoryListingValue) getDependentSkyValue(env,
409 DirectoryListingValue.key(traversal.root.asRootedPath()))).getDirents();
410 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100411
412 List<SkyKey> result = new ArrayList<>();
felly5be4dd62018-02-05 11:11:53 -0800413 for (Dirent dirent : dirents) {
tomlu8cc5dcf2018-01-19 09:28:06 -0800414 RootedPath childPath =
415 RootedPath.toRootedPath(
felly5be4dd62018-02-05 11:11:53 -0800416 traversal.root.asRootedPath().getRoot(),
417 traversal.root.asRootedPath().getRootRelativePath().getRelative(dirent.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 TraversalRequest childTraversal = traversal.forChildEntry(childPath);
janakr5fb2a482018-03-02 17:48:57 -0800419 result.add(childTraversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100420 }
421 return result;
422 }
423
424 /**
425 * Creates result for a dangling symlink.
426 *
427 * @param linkName path to the symbolic link
428 * @param info the {@link FileInfo} associated with the link file
429 */
430 private static RecursiveFilesystemTraversalValue resultForDanglingSymlink(RootedPath linkName,
431 FileInfo info) {
432 Preconditions.checkState(info.type.isSymlink() && !info.type.exists(), "{%s} {%s}", linkName,
433 info.type);
434 return RecursiveFilesystemTraversalValue.of(
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000435 ResolvedFileFactory.danglingSymlink(linkName, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100436 }
437
438 /**
439 * Creates results for a file or for a symlink that points to one.
440 *
441 * <p>A symlink may be direct (points to a file) or transitive (points at a direct or transitive
442 * symlink).
443 */
444 private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
445 FileInfo info) {
446 Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
447 info.type);
448 if (info.type.isSymlink()) {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000449 return RecursiveFilesystemTraversalValue.of(
450 ResolvedFileFactory.symlinkToFile(
451 info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100452 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000453 return RecursiveFilesystemTraversalValue.of(
454 ResolvedFileFactory.regularFile(path, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 }
456 }
457
458 private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
459 FileInfo rootInfo, Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
460 // Collect transitive closure of files in subdirectories.
461 NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
462 for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
463 paths.addTransitive(child.getTransitiveFiles());
464 }
465 ResolvedFile root;
466 if (rootInfo.type.isSymlink()) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000467 NestedSet<ResolvedFile> children = paths.build();
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000468 root =
469 ResolvedFileFactory.symlinkToDirectory(
470 rootInfo.realPath,
felly5be4dd62018-02-05 11:11:53 -0800471 traversal.root.asRootedPath(),
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000472 rootInfo.unresolvedSymlinkTarget,
Laszlo Csomor207140f2015-12-07 15:07:33 +0000473 hashDirectorySymlink(children, rootInfo.metadata.hashCode()));
474 paths = NestedSetBuilder.<ResolvedFile>stableOrder().addTransitive(children).add(root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100475 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000476 root = ResolvedFileFactory.directory(rootInfo.realPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100477 }
478 return RecursiveFilesystemTraversalValue.of(root, paths.build());
479 }
480
Laszlo Csomor371cb712015-12-07 15:37:59 +0000481 private static int hashDirectorySymlink(
482 Iterable<ResolvedFile> children, int symlinkHash) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000483 // If the root is a directory symlink, the associated FileStateValue does not change when the
484 // linked directory's contents change, so we can't use the FileStateValue as metadata like we
485 // do with other ResolvedFile kinds. Instead we compute a metadata hash from the child
486 // elements and return that as the ResolvedFile's metadata hash.
487
488 // Compute the hash using the method described in Effective Java, 2nd ed., Item 9.
489 int result = 0;
490 for (ResolvedFile c : children) {
491 result = 31 * result + c.getMetadataHash();
492 }
Laszlo Csomor207140f2015-12-07 15:07:33 +0000493 return 31 * result + symlinkHash;
494 }
495
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100496 private static SkyValue getDependentSkyValue(Environment env, SkyKey key)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000497 throws MissingDepException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100498 SkyValue value = env.getValue(key);
499 if (env.valuesMissing()) {
500 throw new MissingDepException();
501 }
502 return value;
503 }
504
505 /**
506 * Requests Skyframe to compute the dependent values and returns them.
507 *
508 * <p>The keys must all be {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL} keys.
509 */
felly5be4dd62018-02-05 11:11:53 -0800510 private Collection<RecursiveFilesystemTraversalValue> traverseChildren(
511 Environment env, Iterable<SkyKey> keys, boolean inline)
512 throws MissingDepException, InterruptedException,
513 RecursiveFilesystemTraversalFunctionException {
514 Map<SkyKey, SkyValue> values;
515 if (inline) {
516 // Don't create Skyframe nodes for a recursive traversal over the output tree.
517 // Instead, inline the recursion in the top-level request.
518 values = new HashMap<>();
519 for (SkyKey depKey : keys) {
520 values.put(depKey, compute(depKey, env));
521 }
522 } else {
523 values = env.getValues(keys);
524 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100525 if (env.valuesMissing()) {
526 throw new MissingDepException();
527 }
felly5be4dd62018-02-05 11:11:53 -0800528
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100529 return Collections2.transform(values.values(),
530 new Function<SkyValue, RecursiveFilesystemTraversalValue>() {
531 @Override
532 public RecursiveFilesystemTraversalValue apply(SkyValue input) {
533 return (RecursiveFilesystemTraversalValue) input;
534 }
535 });
536 }
537
538 /** Type information about the filesystem entry residing at a path. */
539 enum FileType {
540 /** A regular file. */
541 FILE {
542 @Override boolean isFile() { return true; }
543 @Override boolean exists() { return true; }
544 @Override public String toString() { return "<f>"; }
545 },
546 /**
547 * A symlink to a regular file.
548 *
549 * <p>The symlink may be direct (points to a non-symlink (here a file)) or it may be transitive
550 * (points to a direct or transitive symlink).
551 */
552 SYMLINK_TO_FILE {
553 @Override boolean isFile() { return true; }
554 @Override boolean isSymlink() { return true; }
555 @Override boolean exists() { return true; }
556 @Override public String toString() { return "<lf>"; }
557 },
558 /** A directory. */
559 DIRECTORY {
560 @Override boolean isDirectory() { return true; }
561 @Override boolean exists() { return true; }
562 @Override public String toString() { return "<d>"; }
563 },
564 /**
565 * A symlink to a directory.
566 *
567 * <p>The symlink may be direct (points to a non-symlink (here a directory)) or it may be
568 * transitive (points to a direct or transitive symlink).
569 */
570 SYMLINK_TO_DIRECTORY {
571 @Override boolean isDirectory() { return true; }
572 @Override boolean isSymlink() { return true; }
573 @Override boolean exists() { return true; }
574 @Override public String toString() { return "<ld>"; }
575 },
576 /** A dangling symlink, i.e. one whose target is known not to exist. */
577 DANGLING_SYMLINK {
578 @Override boolean isFile() { throw new UnsupportedOperationException(); }
579 @Override boolean isDirectory() { throw new UnsupportedOperationException(); }
580 @Override boolean isSymlink() { return true; }
581 @Override public String toString() { return "<l?>"; }
582 },
583 /** A path that does not exist or should be ignored. */
584 NONEXISTENT {
585 @Override public String toString() { return "<?>"; }
586 };
587
588 boolean isFile() { return false; }
589 boolean isDirectory() { return false; }
590 boolean isSymlink() { return false; }
591 boolean exists() { return false; }
592 @Override public abstract String toString();
593 }
felly5be4dd62018-02-05 11:11:53 -0800594
595 private static class StatWithDigest implements FileStatusWithDigest {
596 private final FileStatus noFollowStat;
597 private final byte[] digest;
598
599 public StatWithDigest(FileStatus noFollowStat, byte[] digest) {
600 this.noFollowStat = noFollowStat;
601 this.digest = digest;
602 }
603
604 @Nullable
605 @Override
606 public byte[] getDigest() throws IOException {
607 return digest;
608 }
609
610 @Override
611 public boolean isFile() {
612 return noFollowStat.isFile();
613 }
614
615 @Override
616 public boolean isDirectory() {
617 return noFollowStat.isDirectory();
618 }
619
620 @Override
621 public boolean isSymbolicLink() {
622 return noFollowStat.isSymbolicLink();
623 }
624
625 @Override
626 public boolean isSpecialFile() {
627 return noFollowStat.isSpecialFile();
628 }
629
630 @Override
631 public long getSize() throws IOException {
632 return noFollowStat.getSize();
633 }
634
635 @Override
636 public long getLastModifiedTime() throws IOException {
637 return noFollowStat.getLastModifiedTime();
638 }
639
640 @Override
641 public long getLastChangeTime() throws IOException {
642 return noFollowStat.getLastChangeTime();
643 }
644
645 @Override
646 public long getNodeId() throws IOException {
647 return noFollowStat.getNodeId();
648 }
649 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100650}