blob: a03f87977f33bf07e31aefbfe18d197b6b12bb90 [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;
shahan602cc852018-06-06 20:09:57 -070021import com.google.devtools.build.lib.actions.FileArtifactValue;
22import com.google.devtools.build.lib.actions.FileStateValue;
23import com.google.devtools.build.lib.actions.FileValue;
Laszlo Csomor207140f2015-12-07 15:07:33 +000024import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
26import com.google.devtools.build.lib.events.Event;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
Laszlo Csomorb4d482b2015-12-04 13:23:54 +000028import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
30import com.google.devtools.build.lib.vfs.Dirent;
felly5be4dd62018-02-05 11:11:53 -080031import com.google.devtools.build.lib.vfs.FileStatus;
32import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
33import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import com.google.devtools.build.lib.vfs.PathFragment;
tomluee6a6862018-01-17 14:36:26 -080035import com.google.devtools.build.lib.vfs.Root;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import com.google.devtools.build.lib.vfs.RootedPath;
felly5be4dd62018-02-05 11:11:53 -080037import com.google.devtools.build.lib.vfs.Symlinks;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.skyframe.SkyFunction;
39import com.google.devtools.build.skyframe.SkyFunctionException;
40import com.google.devtools.build.skyframe.SkyKey;
41import com.google.devtools.build.skyframe.SkyValue;
kush95bf7c82017-08-30 00:27:35 +020042import java.io.IOException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.util.ArrayList;
44import java.util.Collection;
felly5be4dd62018-02-05 11:11:53 -080045import java.util.Collections;
46import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import java.util.List;
48import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049import javax.annotation.Nullable;
50
51/** A {@link SkyFunction} to build {@link RecursiveFilesystemTraversalValue}s. */
52public final class RecursiveFilesystemTraversalFunction implements SkyFunction {
53
54 private static final class MissingDepException extends Exception {}
55
56 /** Base class for exceptions that {@link RecursiveFilesystemTraversalFunctionException} wraps. */
57 public abstract static class RecursiveFilesystemTraversalException extends Exception {
58 protected RecursiveFilesystemTraversalException(String message) {
59 super(message);
60 }
61 }
62
63 /** Thrown when a generated directory's root-relative path conflicts with a package's path. */
64 public static final class GeneratedPathConflictException extends
65 RecursiveFilesystemTraversalException {
66 GeneratedPathConflictException(TraversalRequest traversal) {
tomlu8cc5dcf2018-01-19 09:28:06 -080067 super(
68 String.format(
69 "Generated directory %s conflicts with package under the same path. "
70 + "Additional info: %s",
felly5be4dd62018-02-05 11:11:53 -080071 traversal.root.asRootedPath().getRootRelativePath().getPathString(),
tomlu8cc5dcf2018-01-19 09:28:06 -080072 traversal.errorInfo != null ? traversal.errorInfo : traversal.toString()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010073 }
74 }
75
76 /**
77 * Thrown when the traversal encounters a subdirectory with a BUILD file but is not allowed to
Laszlo Csomorf04efcc2015-02-12 17:08:06 +000078 * recurse into it. See {@code PackageBoundaryMode#REPORT_ERROR}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 */
80 public static final class CannotCrossPackageBoundaryException extends
81 RecursiveFilesystemTraversalException {
82 CannotCrossPackageBoundaryException(String message) {
83 super(message);
84 }
85 }
86
87 /**
88 * Thrown when a dangling symlink is attempted to be dereferenced.
89 *
90 * <p>Note: this class is not identical to the one in com.google.devtools.build.lib.view.fileset
91 * and it's not easy to merge the two because of the dependency structure. The other one will
92 * probably be removed along with the rest of the legacy Fileset code.
93 */
94 public static final class DanglingSymlinkException extends RecursiveFilesystemTraversalException {
95 public final String path;
96 public final String unresolvedLink;
97
98 public DanglingSymlinkException(String path, String unresolvedLink) {
Laszlo Csomor0ad729f2015-12-02 15:20:35 +000099 super(
100 String.format(
101 "Found dangling symlink: %s, unresolved path: \"%s\"", path, unresolvedLink));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100102 Preconditions.checkArgument(path != null && !path.isEmpty());
103 Preconditions.checkArgument(unresolvedLink != null && !unresolvedLink.isEmpty());
104 this.path = path;
105 this.unresolvedLink = unresolvedLink;
106 }
107
108 public String getPath() {
109 return path;
110 }
111 }
112
kush95bf7c82017-08-30 00:27:35 +0200113 /** Thrown when we encounter errors from underlying File operations */
114 public static final class FileOperationException extends RecursiveFilesystemTraversalException {
115 public FileOperationException(String message) {
116 super(message);
117 }
118 }
119
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120 /** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
121 private static final class RecursiveFilesystemTraversalFunctionException extends
122 SkyFunctionException {
123 RecursiveFilesystemTraversalFunctionException(RecursiveFilesystemTraversalException e) {
124 super(e, Transience.PERSISTENT);
125 }
126 }
127
128 @Override
129 public SkyValue compute(SkyKey skyKey, Environment env)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000130 throws RecursiveFilesystemTraversalFunctionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131 TraversalRequest traversal = (TraversalRequest) skyKey.argument();
132 try {
133 // Stat the traversal root.
134 FileInfo rootInfo = lookUpFileInfo(env, traversal);
135
136 if (!rootInfo.type.exists()) {
137 // May be a dangling symlink or a non-existent file. Handle gracefully.
138 if (rootInfo.type.isSymlink()) {
felly5be4dd62018-02-05 11:11:53 -0800139 return resultForDanglingSymlink(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140 } else {
141 return RecursiveFilesystemTraversalValue.EMPTY;
142 }
143 }
144
145 if (rootInfo.type.isFile()) {
felly5be4dd62018-02-05 11:11:53 -0800146 return resultForFileRoot(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100147 }
148
149 // Otherwise the root is a directory or a symlink to one.
150 PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
151 traversal = pkgLookupResult.traversal;
152
153 if (pkgLookupResult.isConflicting()) {
154 // The traversal was requested for an output directory whose root-relative path conflicts
155 // with a source package. We can't handle that, bail out.
156 throw new RecursiveFilesystemTraversalFunctionException(
157 new GeneratedPathConflictException(traversal));
158 } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
159 // The traversal was requested for a directory that defines a package.
tomlu8cc5dcf2018-01-19 09:28:06 -0800160 String msg =
161 traversal.errorInfo
162 + " crosses package boundary into package rooted at "
felly5be4dd62018-02-05 11:11:53 -0800163 + traversal.root.asRootedPath().getRootRelativePath().getPathString();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000164 switch (traversal.crossPkgBoundaries) {
165 case CROSS:
166 // We are free to traverse the subpackage but we need to display a warning.
Ulf Adams760e7092016-04-21 08:09:51 +0000167 env.getListener().handle(Event.warn(null, msg));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000168 break;
169 case DONT_CROSS:
170 // We cannot traverse the subpackage and should skip it silently. Return empty results.
171 return RecursiveFilesystemTraversalValue.EMPTY;
172 case REPORT_ERROR:
173 // We cannot traverse the subpackage and should complain loudly (display an error).
174 throw new RecursiveFilesystemTraversalFunctionException(
175 new CannotCrossPackageBoundaryException(msg));
176 default:
177 throw new IllegalStateException(traversal.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100178 }
179 }
180
181 // We are free to traverse this directory.
182 Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
janakr5fb2a482018-03-02 17:48:57 -0800183 return resultForDirectory(
184 traversal,
185 rootInfo,
186 traverseChildren(env, dependentKeys, /*inline=*/ traversal.isRootGenerated));
ulfjacke4794532018-01-12 02:11:17 -0800187 } catch (IOException e) {
kush95bf7c82017-08-30 00:27:35 +0200188 throw new RecursiveFilesystemTraversalFunctionException(
189 new FileOperationException("Error while traversing fileset: " + e.getMessage()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190 } catch (MissingDepException e) {
191 return null;
192 }
193 }
194
195 @Override
196 public String extractTag(SkyKey skyKey) {
197 return null;
198 }
199
200 private static final class FileInfo {
201 final FileType type;
202 final FileStateValue metadata;
203 @Nullable final RootedPath realPath;
204 @Nullable final PathFragment unresolvedSymlinkTarget;
205
206 FileInfo(FileType type, FileStateValue metadata, @Nullable RootedPath realPath,
207 @Nullable PathFragment unresolvedSymlinkTarget) {
208 this.type = Preconditions.checkNotNull(type);
209 this.metadata = Preconditions.checkNotNull(metadata);
210 this.realPath = realPath;
211 this.unresolvedSymlinkTarget = unresolvedSymlinkTarget;
212 }
213
214 @Override
215 public String toString() {
216 if (type.isSymlink()) {
217 return String.format("(%s: link_value=%s, real_path=%s)", type,
218 unresolvedSymlinkTarget.getPathString(), realPath);
219 } else {
220 return String.format("(%s: real_path=%s)", type, realPath);
221 }
222 }
223 }
224
225 private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
ulfjacke4794532018-01-12 02:11:17 -0800226 throws MissingDepException, IOException, InterruptedException {
janakr5fb2a482018-03-02 17:48:57 -0800227 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800228 byte[] digest = null;
229 if (traversal.root.getOutputArtifact() != null) {
230 Artifact artifact = traversal.root.getOutputArtifact();
231 SkyKey artifactKey = ArtifactSkyKey.key(artifact, true);
232 SkyValue value = env.getValue(artifactKey);
233 if (env.valuesMissing()) {
234 throw new MissingDepException();
235 }
kush95bf7c82017-08-30 00:27:35 +0200236
felly5be4dd62018-02-05 11:11:53 -0800237 if (value instanceof FileArtifactValue) {
238 FileArtifactValue fsVal = (FileArtifactValue) value;
239 digest = fsVal.getDigest();
240 } else {
241 return new FileInfo(
242 FileType.NONEXISTENT, null, null, null);
243 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100244 }
felly5be4dd62018-02-05 11:11:53 -0800245
246 // FileArtifactValue does not currently track symlinks. If it did, we could potentially remove
247 // some of the filesystem operations we're doing here.
248 Path path = traversal.root.asRootedPath().asPath();
249 FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
250 FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
251 FileType type;
252 PathFragment unresolvedLinkTarget = null;
253 RootedPath realPath = traversal.root.asRootedPath();
254 if (followStat == null) {
255 type = FileType.DANGLING_SYMLINK;
256 if (!noFollowStat.isSymbolicLink()) {
257 throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
258 }
259 unresolvedLinkTarget = path.readSymbolicLink();
260 } else if (noFollowStat.isFile()) {
261 type = FileType.FILE;
262 } else if (noFollowStat.isDirectory()) {
263 type = FileType.DIRECTORY;
264 } else {
265 unresolvedLinkTarget = path.readSymbolicLink();
266 realPath = RootedPath.toRootedPath(
267 Root.absoluteRoot(path.getFileSystem()),
268 path.resolveSymbolicLinks());
269 type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
270 }
271 return new FileInfo(type,
272 FileStateValue.createWithStatNoFollow(traversal.root.asRootedPath(),
273 new StatWithDigest(noFollowStat, digest), null),
274 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 }
294 return new FileInfo(type, fileValue.realFileStateValue(),
295 fileValue.realRootedPath(), unresolvedLinkTarget);
296 } 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,
300 fileValue.realFileStateValue(), null,
301 fileValue.isSymlink() ? fileValue.getUnresolvedLinkTarget() : null);
302 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100303 }
304 }
305
306 private static final class PkgLookupResult {
307 private enum Type {
308 CONFLICT, DIRECTORY, PKG
309 }
310
311 private final Type type;
312 final TraversalRequest traversal;
313 final FileInfo rootInfo;
314
315 /** Result for a generated directory that conflicts with a source package. */
316 static PkgLookupResult conflict(TraversalRequest traversal, FileInfo rootInfo) {
317 return new PkgLookupResult(Type.CONFLICT, traversal, rootInfo);
318 }
319
320 /** Result for a source or generated directory (not a package). */
321 static PkgLookupResult directory(TraversalRequest traversal, FileInfo rootInfo) {
322 return new PkgLookupResult(Type.DIRECTORY, traversal, rootInfo);
323 }
324
325 /** Result for a package, i.e. a directory with a BUILD file. */
326 static PkgLookupResult pkg(TraversalRequest traversal, FileInfo rootInfo) {
327 return new PkgLookupResult(Type.PKG, traversal, rootInfo);
328 }
329
330 private PkgLookupResult(Type type, TraversalRequest traversal, FileInfo rootInfo) {
331 this.type = Preconditions.checkNotNull(type);
332 this.traversal = Preconditions.checkNotNull(traversal);
333 this.rootInfo = Preconditions.checkNotNull(rootInfo);
334 }
335
336 boolean isPackage() {
337 return type == Type.PKG;
338 }
339
340 boolean isConflicting() {
341 return type == Type.CONFLICT;
342 }
343
344 @Override
345 public String toString() {
346 return String.format("(%s: info=%s, traversal=%s)", type, rootInfo, traversal);
347 }
348 }
349
350 /**
351 * Checks whether the {@code traversal}'s path refers to a package directory.
352 *
353 * @return the result of the lookup; it contains potentially new {@link TraversalRequest} and
354 * {@link FileInfo} so the caller should use these instead of the old ones (this happens when
355 * a package is found, but under a different root than expected)
356 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000357 private static PkgLookupResult checkIfPackage(
358 Environment env, TraversalRequest traversal, FileInfo rootInfo)
ulfjacke4794532018-01-12 02:11:17 -0800359 throws MissingDepException, IOException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100360 Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
361 "{%s} {%s}", traversal, rootInfo);
tomlu8cc5dcf2018-01-19 09:28:06 -0800362 PackageLookupValue pkgLookup =
363 (PackageLookupValue)
felly5be4dd62018-02-05 11:11:53 -0800364 getDependentSkyValue(env,
365 PackageLookupValue.key(traversal.root.asRootedPath().getRootRelativePath()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100366
367 if (pkgLookup.packageExists()) {
janakr5fb2a482018-03-02 17:48:57 -0800368 if (traversal.isRootGenerated) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100369 // The traversal's root was a generated directory, but its root-relative path conflicts with
370 // an existing package.
371 return PkgLookupResult.conflict(traversal, rootInfo);
372 } else {
373 // The traversal's root was a source directory and it defines a package.
tomluee6a6862018-01-17 14:36:26 -0800374 Root pkgRoot = pkgLookup.getRoot();
felly5be4dd62018-02-05 11:11:53 -0800375 if (!pkgRoot.equals(traversal.root.asRootedPath().getRoot())) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100376 // However the root of this package is different from what we expected. stat() the real
377 // BUILD file of that package.
378 traversal = traversal.forChangedRootPath(pkgRoot);
379 rootInfo = lookUpFileInfo(env, traversal);
380 Verify.verify(rootInfo.type.exists(), "{%s} {%s}", traversal, rootInfo);
381 }
382 return PkgLookupResult.pkg(traversal, rootInfo);
383 }
384 } else {
385 // The traversal's root was a directory (source or generated one), no package exists under the
386 // same root-relative path.
387 return PkgLookupResult.directory(traversal, rootInfo);
388 }
389 }
390
391 /**
392 * List the directory and create {@code SkyKey}s to request contents of its children recursively.
393 *
394 * <p>The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
395 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000396 private static Collection<SkyKey> createRecursiveTraversalKeys(
397 Environment env, TraversalRequest traversal)
felly5be4dd62018-02-05 11:11:53 -0800398 throws MissingDepException, InterruptedException, IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
400 // in the result, must be relative to it.
felly5be4dd62018-02-05 11:11:53 -0800401 Iterable<Dirent> dirents;
janakr5fb2a482018-03-02 17:48:57 -0800402 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800403 // If we're dealing with an output file, read the directory directly instead of creating
404 // filesystem nodes under the output tree.
405 List<Dirent> direntsCollection =
406 new ArrayList<>(
407 traversal.root.asRootedPath().asPath().readdir(Symlinks.FOLLOW));
408 Collections.sort(direntsCollection);
409 dirents = direntsCollection;
410 } else {
411 dirents = ((DirectoryListingValue) getDependentSkyValue(env,
412 DirectoryListingValue.key(traversal.root.asRootedPath()))).getDirents();
413 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100414
415 List<SkyKey> result = new ArrayList<>();
felly5be4dd62018-02-05 11:11:53 -0800416 for (Dirent dirent : dirents) {
tomlu8cc5dcf2018-01-19 09:28:06 -0800417 RootedPath childPath =
418 RootedPath.toRootedPath(
felly5be4dd62018-02-05 11:11:53 -0800419 traversal.root.asRootedPath().getRoot(),
420 traversal.root.asRootedPath().getRootRelativePath().getRelative(dirent.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100421 TraversalRequest childTraversal = traversal.forChildEntry(childPath);
janakr5fb2a482018-03-02 17:48:57 -0800422 result.add(childTraversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100423 }
424 return result;
425 }
426
427 /**
428 * Creates result for a dangling symlink.
429 *
430 * @param linkName path to the symbolic link
431 * @param info the {@link FileInfo} associated with the link file
432 */
433 private static RecursiveFilesystemTraversalValue resultForDanglingSymlink(RootedPath linkName,
434 FileInfo info) {
435 Preconditions.checkState(info.type.isSymlink() && !info.type.exists(), "{%s} {%s}", linkName,
436 info.type);
437 return RecursiveFilesystemTraversalValue.of(
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000438 ResolvedFileFactory.danglingSymlink(linkName, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100439 }
440
441 /**
442 * Creates results for a file or for a symlink that points to one.
443 *
444 * <p>A symlink may be direct (points to a file) or transitive (points at a direct or transitive
445 * symlink).
446 */
447 private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
448 FileInfo info) {
449 Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
450 info.type);
451 if (info.type.isSymlink()) {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000452 return RecursiveFilesystemTraversalValue.of(
453 ResolvedFileFactory.symlinkToFile(
454 info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000456 return RecursiveFilesystemTraversalValue.of(
457 ResolvedFileFactory.regularFile(path, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100458 }
459 }
460
461 private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
462 FileInfo rootInfo, Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
463 // Collect transitive closure of files in subdirectories.
464 NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
465 for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
466 paths.addTransitive(child.getTransitiveFiles());
467 }
468 ResolvedFile root;
469 if (rootInfo.type.isSymlink()) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000470 NestedSet<ResolvedFile> children = paths.build();
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000471 root =
472 ResolvedFileFactory.symlinkToDirectory(
473 rootInfo.realPath,
felly5be4dd62018-02-05 11:11:53 -0800474 traversal.root.asRootedPath(),
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000475 rootInfo.unresolvedSymlinkTarget,
Laszlo Csomor207140f2015-12-07 15:07:33 +0000476 hashDirectorySymlink(children, rootInfo.metadata.hashCode()));
477 paths = NestedSetBuilder.<ResolvedFile>stableOrder().addTransitive(children).add(root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100478 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000479 root = ResolvedFileFactory.directory(rootInfo.realPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100480 }
481 return RecursiveFilesystemTraversalValue.of(root, paths.build());
482 }
483
Laszlo Csomor371cb712015-12-07 15:37:59 +0000484 private static int hashDirectorySymlink(
485 Iterable<ResolvedFile> children, int symlinkHash) {
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) {
494 result = 31 * result + c.getMetadataHash();
495 }
Laszlo Csomor207140f2015-12-07 15:07:33 +0000496 return 31 * result + symlinkHash;
497 }
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
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100532 return Collections2.transform(values.values(),
533 new Function<SkyValue, RecursiveFilesystemTraversalValue>() {
534 @Override
535 public RecursiveFilesystemTraversalValue apply(SkyValue input) {
536 return (RecursiveFilesystemTraversalValue) input;
537 }
538 });
539 }
540
541 /** Type information about the filesystem entry residing at a path. */
542 enum FileType {
543 /** A regular file. */
544 FILE {
545 @Override boolean isFile() { return true; }
546 @Override boolean exists() { return true; }
547 @Override public String toString() { return "<f>"; }
548 },
549 /**
550 * A symlink to a regular file.
551 *
552 * <p>The symlink may be direct (points to a non-symlink (here a file)) or it may be transitive
553 * (points to a direct or transitive symlink).
554 */
555 SYMLINK_TO_FILE {
556 @Override boolean isFile() { return true; }
557 @Override boolean isSymlink() { return true; }
558 @Override boolean exists() { return true; }
559 @Override public String toString() { return "<lf>"; }
560 },
561 /** A directory. */
562 DIRECTORY {
563 @Override boolean isDirectory() { return true; }
564 @Override boolean exists() { return true; }
565 @Override public String toString() { return "<d>"; }
566 },
567 /**
568 * A symlink to a directory.
569 *
570 * <p>The symlink may be direct (points to a non-symlink (here a directory)) or it may be
571 * transitive (points to a direct or transitive symlink).
572 */
573 SYMLINK_TO_DIRECTORY {
574 @Override boolean isDirectory() { return true; }
575 @Override boolean isSymlink() { return true; }
576 @Override boolean exists() { return true; }
577 @Override public String toString() { return "<ld>"; }
578 },
579 /** A dangling symlink, i.e. one whose target is known not to exist. */
580 DANGLING_SYMLINK {
581 @Override boolean isFile() { throw new UnsupportedOperationException(); }
582 @Override boolean isDirectory() { throw new UnsupportedOperationException(); }
583 @Override boolean isSymlink() { return true; }
584 @Override public String toString() { return "<l?>"; }
585 },
586 /** A path that does not exist or should be ignored. */
587 NONEXISTENT {
588 @Override public String toString() { return "<?>"; }
589 };
590
591 boolean isFile() { return false; }
592 boolean isDirectory() { return false; }
593 boolean isSymlink() { return false; }
594 boolean exists() { return false; }
595 @Override public abstract String toString();
596 }
felly5be4dd62018-02-05 11:11:53 -0800597
598 private static class StatWithDigest implements FileStatusWithDigest {
599 private final FileStatus noFollowStat;
600 private final byte[] digest;
601
602 public StatWithDigest(FileStatus noFollowStat, byte[] digest) {
603 this.noFollowStat = noFollowStat;
604 this.digest = digest;
605 }
606
607 @Nullable
608 @Override
609 public byte[] getDigest() throws IOException {
610 return digest;
611 }
612
613 @Override
614 public boolean isFile() {
615 return noFollowStat.isFile();
616 }
617
618 @Override
619 public boolean isDirectory() {
620 return noFollowStat.isDirectory();
621 }
622
623 @Override
624 public boolean isSymbolicLink() {
625 return noFollowStat.isSymbolicLink();
626 }
627
628 @Override
629 public boolean isSpecialFile() {
630 return noFollowStat.isSpecialFile();
631 }
632
633 @Override
634 public long getSize() throws IOException {
635 return noFollowStat.getSize();
636 }
637
638 @Override
639 public long getLastModifiedTime() throws IOException {
640 return noFollowStat.getLastModifiedTime();
641 }
642
643 @Override
644 public long getLastChangeTime() throws IOException {
645 return noFollowStat.getLastChangeTime();
646 }
647
648 @Override
649 public long getNodeId() throws IOException {
650 return noFollowStat.getNodeId();
651 }
652 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100653}