blob: 49f687014e85419f27a64fb43aa5bee7625c3830 [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;
Googlerc8acdbd2018-11-08 10:47:15 -080019import com.google.common.collect.ImmutableList;
felly5be4dd62018-02-05 11:11:53 -080020import com.google.devtools.build.lib.actions.Artifact;
Googlerc8acdbd2018-11-08 10:47:15 -080021import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
shahane35e8cf2018-06-18 08:14:01 -070022import com.google.devtools.build.lib.actions.ArtifactSkyKey;
shahan602cc852018-06-06 20:09:57 -070023import com.google.devtools.build.lib.actions.FileArtifactValue;
shahan602cc852018-06-06 20:09:57 -070024import com.google.devtools.build.lib.actions.FileValue;
Laszlo Csomor207140f2015-12-07 15:07:33 +000025import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
27import com.google.devtools.build.lib.events.Event;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
Laszlo Csomorb4d482b2015-12-04 13:23:54 +000029import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
31import com.google.devtools.build.lib.vfs.Dirent;
felly5be4dd62018-02-05 11:11:53 -080032import com.google.devtools.build.lib.vfs.FileStatus;
felly5be4dd62018-02-05 11:11:53 -080033import 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);
Googlerc8acdbd2018-11-08 10:47:15 -0800147 } else if (rootInfo.type.isDirectory() && rootInfo.metadata instanceof TreeArtifactValue) {
148 final TreeArtifactValue value = (TreeArtifactValue) rootInfo.metadata;
149 ImmutableList.Builder<RecursiveFilesystemTraversalValue> list = ImmutableList.builder();
150 for (Map.Entry<TreeFileArtifact, FileArtifactValue> entry
151 : value.getChildValues().entrySet()) {
152 RootedPath path =
153 RootedPath.toRootedPath(
154 traversal.root.asRootedPath().getRoot(), entry.getKey().getPath());
155 list.add(resultForFileRoot(
156 path,
157 // TreeArtifact can't have symbolic inside. So the assumption for FileType.FILE
158 // is always true.
159 new FileInfo(FileType.FILE, entry.getValue(), path, null)));
160 }
161 return resultForDirectory(traversal, rootInfo, list.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100162 }
163
164 // Otherwise the root is a directory or a symlink to one.
165 PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
166 traversal = pkgLookupResult.traversal;
167
168 if (pkgLookupResult.isConflicting()) {
169 // The traversal was requested for an output directory whose root-relative path conflicts
170 // with a source package. We can't handle that, bail out.
171 throw new RecursiveFilesystemTraversalFunctionException(
172 new GeneratedPathConflictException(traversal));
173 } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
174 // The traversal was requested for a directory that defines a package.
tomlu8cc5dcf2018-01-19 09:28:06 -0800175 String msg =
176 traversal.errorInfo
177 + " crosses package boundary into package rooted at "
felly5be4dd62018-02-05 11:11:53 -0800178 + traversal.root.asRootedPath().getRootRelativePath().getPathString();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000179 switch (traversal.crossPkgBoundaries) {
180 case CROSS:
181 // We are free to traverse the subpackage but we need to display a warning.
Ulf Adams760e7092016-04-21 08:09:51 +0000182 env.getListener().handle(Event.warn(null, msg));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000183 break;
184 case DONT_CROSS:
185 // We cannot traverse the subpackage and should skip it silently. Return empty results.
186 return RecursiveFilesystemTraversalValue.EMPTY;
187 case REPORT_ERROR:
188 // We cannot traverse the subpackage and should complain loudly (display an error).
189 throw new RecursiveFilesystemTraversalFunctionException(
190 new CannotCrossPackageBoundaryException(msg));
191 default:
192 throw new IllegalStateException(traversal.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100193 }
194 }
195
196 // We are free to traverse this directory.
197 Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
janakr5fb2a482018-03-02 17:48:57 -0800198 return resultForDirectory(
199 traversal,
200 rootInfo,
201 traverseChildren(env, dependentKeys, /*inline=*/ traversal.isRootGenerated));
ulfjacke4794532018-01-12 02:11:17 -0800202 } catch (IOException e) {
kush95bf7c82017-08-30 00:27:35 +0200203 throw new RecursiveFilesystemTraversalFunctionException(
204 new FileOperationException("Error while traversing fileset: " + e.getMessage()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100205 } catch (MissingDepException e) {
206 return null;
207 }
208 }
209
210 @Override
211 public String extractTag(SkyKey skyKey) {
212 return null;
213 }
214
215 private static final class FileInfo {
216 final FileType type;
kushb39c6932018-07-12 21:25:23 -0700217 final Object metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218 @Nullable final RootedPath realPath;
219 @Nullable final PathFragment unresolvedSymlinkTarget;
220
kush4b120e72018-07-11 16:21:27 -0700221 FileInfo(
222 FileType type,
kushb39c6932018-07-12 21:25:23 -0700223 Object metadata,
kush4b120e72018-07-11 16:21:27 -0700224 @Nullable RootedPath realPath,
kushb39c6932018-07-12 21:25:23 -0700225 @Nullable PathFragment unresolvedSymlinkTarget) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 this.type = Preconditions.checkNotNull(type);
kushb39c6932018-07-12 21:25:23 -0700227 this.metadata = metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100228 this.realPath = realPath;
229 this.unresolvedSymlinkTarget = unresolvedSymlinkTarget;
230 }
231
232 @Override
233 public String toString() {
234 if (type.isSymlink()) {
235 return String.format("(%s: link_value=%s, real_path=%s)", type,
236 unresolvedSymlinkTarget.getPathString(), realPath);
237 } else {
238 return String.format("(%s: real_path=%s)", type, realPath);
239 }
240 }
241 }
242
kushb39c6932018-07-12 21:25:23 -0700243 private static final FileInfo NON_EXISTENT_FILE_INFO =
244 new FileInfo(FileType.NONEXISTENT, new Integer(0), null, null);
245
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100246 private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
ulfjacke4794532018-01-12 02:11:17 -0800247 throws MissingDepException, IOException, InterruptedException {
janakr5fb2a482018-03-02 17:48:57 -0800248 if (traversal.isRootGenerated) {
Googlerc8acdbd2018-11-08 10:47:15 -0800249 SkyValue fsVal = null;
felly5be4dd62018-02-05 11:11:53 -0800250 if (traversal.root.getOutputArtifact() != null) {
251 Artifact artifact = traversal.root.getOutputArtifact();
252 SkyKey artifactKey = ArtifactSkyKey.key(artifact, true);
253 SkyValue value = env.getValue(artifactKey);
254 if (env.valuesMissing()) {
255 throw new MissingDepException();
256 }
kush95bf7c82017-08-30 00:27:35 +0200257
felly5be4dd62018-02-05 11:11:53 -0800258 if (value instanceof FileArtifactValue) {
Googlerc8acdbd2018-11-08 10:47:15 -0800259 fsVal = value;
260 } else if (value instanceof TreeArtifactValue) {
261 fsVal = value;
felly5be4dd62018-02-05 11:11:53 -0800262 } else {
kushb39c6932018-07-12 21:25:23 -0700263 return NON_EXISTENT_FILE_INFO;
felly5be4dd62018-02-05 11:11:53 -0800264 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100265 }
felly5be4dd62018-02-05 11:11:53 -0800266 RootedPath realPath = traversal.root.asRootedPath();
felly6c399d62018-07-18 15:55:59 -0700267 if (traversal.strictOutputFiles) {
268 Preconditions.checkNotNull(fsVal, "Strict Fileset output tree has null FileArtifactValue");
Googlerc8acdbd2018-11-08 10:47:15 -0800269 return new FileInfo(
270 (fsVal instanceof TreeArtifactValue ? FileType.DIRECTORY : FileType.FILE),
271 fsVal, realPath, null);
felly5be4dd62018-02-05 11:11:53 -0800272 } else {
felly6c399d62018-07-18 15:55:59 -0700273 // FileArtifactValue does not currently track symlinks. If it did, we could potentially
274 // remove some of the filesystem operations we're doing here.
275 Path path = traversal.root.asRootedPath().asPath();
276 FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
277 FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
278 FileType type;
279 PathFragment unresolvedLinkTarget = null;
280 if (followStat == null) {
281 type = FileType.DANGLING_SYMLINK;
282 if (!noFollowStat.isSymbolicLink()) {
283 throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
284 }
285 unresolvedLinkTarget = path.readSymbolicLink();
286 } else if (noFollowStat.isFile()) {
287 type = FileType.FILE;
288 } else if (noFollowStat.isDirectory()) {
289 type = FileType.DIRECTORY;
290 } else {
291 unresolvedLinkTarget = path.readSymbolicLink();
292 realPath =
293 RootedPath.toRootedPath(
294 Root.absoluteRoot(path.getFileSystem()), path.resolveSymbolicLinks());
295 type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
296 }
297 return new FileInfo(
298 type, fsVal != null ? fsVal : noFollowStat.hashCode(), realPath, unresolvedLinkTarget);
felly5be4dd62018-02-05 11:11:53 -0800299 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100300 } else {
felly5be4dd62018-02-05 11:11:53 -0800301 // Stat the file.
302 FileValue fileValue =
303 (FileValue) env.getValueOrThrow(
304 FileValue.key(traversal.root.asRootedPath()), IOException.class);
305
306 if (env.valuesMissing()) {
307 throw new MissingDepException();
308 }
309 if (fileValue.exists()) {
310 // If it exists, it may either be a symlink or a file/directory.
311 PathFragment unresolvedLinkTarget = null;
312 FileType type;
313 if (fileValue.isSymlink()) {
314 unresolvedLinkTarget = fileValue.getUnresolvedLinkTarget();
315 type = fileValue.isDirectory() ? FileType.SYMLINK_TO_DIRECTORY : FileType.SYMLINK_TO_FILE;
316 } else {
317 type = fileValue.isDirectory() ? FileType.DIRECTORY : FileType.FILE;
318 }
kush4b120e72018-07-11 16:21:27 -0700319 return new FileInfo(
kushb39c6932018-07-12 21:25:23 -0700320 type, fileValue.realFileStateValue(), fileValue.realRootedPath(), unresolvedLinkTarget);
felly5be4dd62018-02-05 11:11:53 -0800321 } else {
322 // If it doesn't exist, or it's a dangling symlink, we still want to handle that gracefully.
323 return new FileInfo(
324 fileValue.isSymlink() ? FileType.DANGLING_SYMLINK : FileType.NONEXISTENT,
kush4b120e72018-07-11 16:21:27 -0700325 fileValue.realFileStateValue(),
326 null,
kushb39c6932018-07-12 21:25:23 -0700327 fileValue.isSymlink() ? fileValue.getUnresolvedLinkTarget() : null);
felly5be4dd62018-02-05 11:11:53 -0800328 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100329 }
330 }
331
332 private static final class PkgLookupResult {
333 private enum Type {
334 CONFLICT, DIRECTORY, PKG
335 }
336
337 private final Type type;
338 final TraversalRequest traversal;
339 final FileInfo rootInfo;
340
341 /** Result for a generated directory that conflicts with a source package. */
342 static PkgLookupResult conflict(TraversalRequest traversal, FileInfo rootInfo) {
343 return new PkgLookupResult(Type.CONFLICT, traversal, rootInfo);
344 }
345
346 /** Result for a source or generated directory (not a package). */
347 static PkgLookupResult directory(TraversalRequest traversal, FileInfo rootInfo) {
348 return new PkgLookupResult(Type.DIRECTORY, traversal, rootInfo);
349 }
350
351 /** Result for a package, i.e. a directory with a BUILD file. */
352 static PkgLookupResult pkg(TraversalRequest traversal, FileInfo rootInfo) {
353 return new PkgLookupResult(Type.PKG, traversal, rootInfo);
354 }
355
356 private PkgLookupResult(Type type, TraversalRequest traversal, FileInfo rootInfo) {
357 this.type = Preconditions.checkNotNull(type);
358 this.traversal = Preconditions.checkNotNull(traversal);
359 this.rootInfo = Preconditions.checkNotNull(rootInfo);
360 }
361
362 boolean isPackage() {
363 return type == Type.PKG;
364 }
365
366 boolean isConflicting() {
367 return type == Type.CONFLICT;
368 }
369
370 @Override
371 public String toString() {
372 return String.format("(%s: info=%s, traversal=%s)", type, rootInfo, traversal);
373 }
374 }
375
376 /**
377 * Checks whether the {@code traversal}'s path refers to a package directory.
378 *
379 * @return the result of the lookup; it contains potentially new {@link TraversalRequest} and
380 * {@link FileInfo} so the caller should use these instead of the old ones (this happens when
381 * a package is found, but under a different root than expected)
382 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000383 private static PkgLookupResult checkIfPackage(
384 Environment env, TraversalRequest traversal, FileInfo rootInfo)
ulfjacke4794532018-01-12 02:11:17 -0800385 throws MissingDepException, IOException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100386 Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
387 "{%s} {%s}", traversal, rootInfo);
tomlu8cc5dcf2018-01-19 09:28:06 -0800388 PackageLookupValue pkgLookup =
389 (PackageLookupValue)
felly5be4dd62018-02-05 11:11:53 -0800390 getDependentSkyValue(env,
391 PackageLookupValue.key(traversal.root.asRootedPath().getRootRelativePath()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100392
393 if (pkgLookup.packageExists()) {
janakr5fb2a482018-03-02 17:48:57 -0800394 if (traversal.isRootGenerated) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100395 // The traversal's root was a generated directory, but its root-relative path conflicts with
396 // an existing package.
397 return PkgLookupResult.conflict(traversal, rootInfo);
398 } else {
399 // The traversal's root was a source directory and it defines a package.
tomluee6a6862018-01-17 14:36:26 -0800400 Root pkgRoot = pkgLookup.getRoot();
felly5be4dd62018-02-05 11:11:53 -0800401 if (!pkgRoot.equals(traversal.root.asRootedPath().getRoot())) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 // However the root of this package is different from what we expected. stat() the real
403 // BUILD file of that package.
404 traversal = traversal.forChangedRootPath(pkgRoot);
405 rootInfo = lookUpFileInfo(env, traversal);
406 Verify.verify(rootInfo.type.exists(), "{%s} {%s}", traversal, rootInfo);
407 }
408 return PkgLookupResult.pkg(traversal, rootInfo);
409 }
410 } else {
411 // The traversal's root was a directory (source or generated one), no package exists under the
412 // same root-relative path.
413 return PkgLookupResult.directory(traversal, rootInfo);
414 }
415 }
416
417 /**
418 * List the directory and create {@code SkyKey}s to request contents of its children recursively.
419 *
420 * <p>The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
421 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000422 private static Collection<SkyKey> createRecursiveTraversalKeys(
423 Environment env, TraversalRequest traversal)
felly5be4dd62018-02-05 11:11:53 -0800424 throws MissingDepException, InterruptedException, IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100425 // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
426 // in the result, must be relative to it.
felly5be4dd62018-02-05 11:11:53 -0800427 Iterable<Dirent> dirents;
janakr5fb2a482018-03-02 17:48:57 -0800428 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800429 // If we're dealing with an output file, read the directory directly instead of creating
430 // filesystem nodes under the output tree.
431 List<Dirent> direntsCollection =
432 new ArrayList<>(
433 traversal.root.asRootedPath().asPath().readdir(Symlinks.FOLLOW));
434 Collections.sort(direntsCollection);
435 dirents = direntsCollection;
436 } else {
437 dirents = ((DirectoryListingValue) getDependentSkyValue(env,
438 DirectoryListingValue.key(traversal.root.asRootedPath()))).getDirents();
439 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100440
441 List<SkyKey> result = new ArrayList<>();
felly5be4dd62018-02-05 11:11:53 -0800442 for (Dirent dirent : dirents) {
tomlu8cc5dcf2018-01-19 09:28:06 -0800443 RootedPath childPath =
444 RootedPath.toRootedPath(
felly5be4dd62018-02-05 11:11:53 -0800445 traversal.root.asRootedPath().getRoot(),
446 traversal.root.asRootedPath().getRootRelativePath().getRelative(dirent.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100447 TraversalRequest childTraversal = traversal.forChildEntry(childPath);
janakr5fb2a482018-03-02 17:48:57 -0800448 result.add(childTraversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100449 }
450 return result;
451 }
452
453 /**
454 * Creates result for a dangling symlink.
455 *
456 * @param linkName path to the symbolic link
457 * @param info the {@link FileInfo} associated with the link file
458 */
459 private static RecursiveFilesystemTraversalValue resultForDanglingSymlink(RootedPath linkName,
460 FileInfo info) {
461 Preconditions.checkState(info.type.isSymlink() && !info.type.exists(), "{%s} {%s}", linkName,
462 info.type);
463 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700464 ResolvedFileFactory.danglingSymlink(linkName, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100465 }
466
467 /**
468 * Creates results for a file or for a symlink that points to one.
469 *
470 * <p>A symlink may be direct (points to a file) or transitive (points at a direct or transitive
471 * symlink).
472 */
473 private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
474 FileInfo info) {
475 Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
476 info.type);
477 if (info.type.isSymlink()) {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000478 return RecursiveFilesystemTraversalValue.of(
479 ResolvedFileFactory.symlinkToFile(
kushb39c6932018-07-12 21:25:23 -0700480 info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100481 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000482 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700483 ResolvedFileFactory.regularFile(path, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100484 }
485 }
486
487 private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
488 FileInfo rootInfo, Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
489 // Collect transitive closure of files in subdirectories.
490 NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
491 for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
492 paths.addTransitive(child.getTransitiveFiles());
493 }
494 ResolvedFile root;
495 if (rootInfo.type.isSymlink()) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000496 NestedSet<ResolvedFile> children = paths.build();
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000497 root =
498 ResolvedFileFactory.symlinkToDirectory(
499 rootInfo.realPath,
felly5be4dd62018-02-05 11:11:53 -0800500 traversal.root.asRootedPath(),
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000501 rootInfo.unresolvedSymlinkTarget,
kushb39c6932018-07-12 21:25:23 -0700502 hashDirectorySymlink(children, rootInfo.metadata));
Laszlo Csomor207140f2015-12-07 15:07:33 +0000503 paths = NestedSetBuilder.<ResolvedFile>stableOrder().addTransitive(children).add(root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100504 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000505 root = ResolvedFileFactory.directory(rootInfo.realPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100506 }
507 return RecursiveFilesystemTraversalValue.of(root, paths.build());
508 }
509
kushb39c6932018-07-12 21:25:23 -0700510 private static int hashDirectorySymlink(Iterable<ResolvedFile> children, Object metadata) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000511 // If the root is a directory symlink, the associated FileStateValue does not change when the
512 // linked directory's contents change, so we can't use the FileStateValue as metadata like we
513 // do with other ResolvedFile kinds. Instead we compute a metadata hash from the child
514 // elements and return that as the ResolvedFile's metadata hash.
515
516 // Compute the hash using the method described in Effective Java, 2nd ed., Item 9.
517 int result = 0;
518 for (ResolvedFile c : children) {
kushb39c6932018-07-12 21:25:23 -0700519 result = 31 * result + c.getMetadata().hashCode();
Laszlo Csomor207140f2015-12-07 15:07:33 +0000520 }
kushb39c6932018-07-12 21:25:23 -0700521 return 31 * result + metadata.hashCode();
Laszlo Csomor207140f2015-12-07 15:07:33 +0000522 }
523
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100524 private static SkyValue getDependentSkyValue(Environment env, SkyKey key)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000525 throws MissingDepException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100526 SkyValue value = env.getValue(key);
527 if (env.valuesMissing()) {
528 throw new MissingDepException();
529 }
530 return value;
531 }
532
533 /**
534 * Requests Skyframe to compute the dependent values and returns them.
535 *
536 * <p>The keys must all be {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL} keys.
537 */
felly5be4dd62018-02-05 11:11:53 -0800538 private Collection<RecursiveFilesystemTraversalValue> traverseChildren(
539 Environment env, Iterable<SkyKey> keys, boolean inline)
540 throws MissingDepException, InterruptedException,
541 RecursiveFilesystemTraversalFunctionException {
542 Map<SkyKey, SkyValue> values;
543 if (inline) {
544 // Don't create Skyframe nodes for a recursive traversal over the output tree.
545 // Instead, inline the recursion in the top-level request.
546 values = new HashMap<>();
547 for (SkyKey depKey : keys) {
548 values.put(depKey, compute(depKey, env));
549 }
550 } else {
551 values = env.getValues(keys);
552 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100553 if (env.valuesMissing()) {
554 throw new MissingDepException();
555 }
felly5be4dd62018-02-05 11:11:53 -0800556
kushb39c6932018-07-12 21:25:23 -0700557 return Collections2.transform(values.values(), RecursiveFilesystemTraversalValue.class::cast);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100558 }
559
560 /** Type information about the filesystem entry residing at a path. */
561 enum FileType {
562 /** A regular file. */
563 FILE {
564 @Override boolean isFile() { return true; }
565 @Override boolean exists() { return true; }
566 @Override public String toString() { return "<f>"; }
567 },
568 /**
569 * A symlink to a regular file.
570 *
571 * <p>The symlink may be direct (points to a non-symlink (here a file)) or it may be transitive
572 * (points to a direct or transitive symlink).
573 */
574 SYMLINK_TO_FILE {
575 @Override boolean isFile() { return true; }
576 @Override boolean isSymlink() { return true; }
577 @Override boolean exists() { return true; }
578 @Override public String toString() { return "<lf>"; }
579 },
580 /** A directory. */
581 DIRECTORY {
582 @Override boolean isDirectory() { return true; }
583 @Override boolean exists() { return true; }
584 @Override public String toString() { return "<d>"; }
585 },
586 /**
587 * A symlink to a directory.
588 *
589 * <p>The symlink may be direct (points to a non-symlink (here a directory)) or it may be
590 * transitive (points to a direct or transitive symlink).
591 */
592 SYMLINK_TO_DIRECTORY {
593 @Override boolean isDirectory() { return true; }
594 @Override boolean isSymlink() { return true; }
595 @Override boolean exists() { return true; }
596 @Override public String toString() { return "<ld>"; }
597 },
598 /** A dangling symlink, i.e. one whose target is known not to exist. */
599 DANGLING_SYMLINK {
600 @Override boolean isFile() { throw new UnsupportedOperationException(); }
601 @Override boolean isDirectory() { throw new UnsupportedOperationException(); }
602 @Override boolean isSymlink() { return true; }
603 @Override public String toString() { return "<l?>"; }
604 },
605 /** A path that does not exist or should be ignored. */
606 NONEXISTENT {
607 @Override public String toString() { return "<?>"; }
608 };
609
610 boolean isFile() { return false; }
611 boolean isDirectory() { return false; }
612 boolean isSymlink() { return false; }
613 boolean exists() { return false; }
614 @Override public abstract String toString();
615 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100616}