blob: 8b273878c9669b926aa1b52ea1d44de3e2cb35cb [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
fellyf1e30f32019-07-09 12:26:10 -070016import static com.google.devtools.build.lib.vfs.UnixGlob.DEFAULT_SYSCALLS;
17import static java.nio.charset.StandardCharsets.UTF_8;
18
Googler5284e6c2019-10-30 07:43:54 -070019import com.google.common.annotations.VisibleForTesting;
tomlua155b532017-11-08 20:12:47 +010020import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.base.Verify;
22import com.google.common.collect.Collections2;
Googlerc8acdbd2018-11-08 10:47:15 -080023import com.google.common.collect.ImmutableList;
felly5be4dd62018-02-05 11:11:53 -080024import com.google.devtools.build.lib.actions.Artifact;
Googlerc8acdbd2018-11-08 10:47:15 -080025import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
shahan602cc852018-06-06 20:09:57 -070026import com.google.devtools.build.lib.actions.FileArtifactValue;
fellyf1e30f32019-07-09 12:26:10 -070027import com.google.devtools.build.lib.actions.FileStateType;
28import com.google.devtools.build.lib.actions.FileStateValue;
Googler5284e6c2019-10-30 07:43:54 -070029import com.google.devtools.build.lib.actions.FileStateValue.RegularFileStateValue;
shahan602cc852018-06-06 20:09:57 -070030import com.google.devtools.build.lib.actions.FileValue;
fellyf1e30f32019-07-09 12:26:10 -070031import com.google.devtools.build.lib.actions.HasDigest;
Laszlo Csomor207140f2015-12-07 15:07:33 +000032import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
34import com.google.devtools.build.lib.events.Event;
jcaterb11c6e22020-04-03 11:40:16 -070035import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.FileType;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
Laszlo Csomorb4d482b2015-12-04 13:23:54 +000037import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFileFactory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
fellyf1e30f32019-07-09 12:26:10 -070039import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
40import com.google.devtools.build.lib.util.Fingerprint;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041import com.google.devtools.build.lib.vfs.Dirent;
felly5be4dd62018-02-05 11:11:53 -080042import com.google.devtools.build.lib.vfs.FileStatus;
felly5be4dd62018-02-05 11:11:53 -080043import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import com.google.devtools.build.lib.vfs.PathFragment;
tomluee6a6862018-01-17 14:36:26 -080045import com.google.devtools.build.lib.vfs.Root;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046import com.google.devtools.build.lib.vfs.RootedPath;
felly5be4dd62018-02-05 11:11:53 -080047import com.google.devtools.build.lib.vfs.Symlinks;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048import com.google.devtools.build.skyframe.SkyFunction;
49import com.google.devtools.build.skyframe.SkyFunctionException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050import com.google.devtools.build.skyframe.SkyKey;
51import com.google.devtools.build.skyframe.SkyValue;
kush95bf7c82017-08-30 00:27:35 +020052import java.io.IOException;
fellyf1e30f32019-07-09 12:26:10 -070053import java.math.BigInteger;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054import java.util.ArrayList;
55import java.util.Collection;
felly5be4dd62018-02-05 11:11:53 -080056import java.util.Collections;
57import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058import java.util.List;
59import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060import javax.annotation.Nullable;
61
62/** A {@link SkyFunction} to build {@link RecursiveFilesystemTraversalValue}s. */
63public final class RecursiveFilesystemTraversalFunction implements SkyFunction {
64
65 private static final class MissingDepException extends Exception {}
66
fellyf1e30f32019-07-09 12:26:10 -070067 private static final byte[] MISSING_FINGERPRINT =
68 new BigInteger(1, "NonexistentFileStateValue".getBytes(UTF_8)).toByteArray();
69
70 @AutoCodec @AutoCodec.VisibleForSerialization
71 static final HasDigest NON_EXISTENT_HAS_DIGEST = () -> MISSING_FINGERPRINT;
72
73 private static final FileInfo NON_EXISTENT_FILE_INFO =
74 new FileInfo(FileType.NONEXISTENT, NON_EXISTENT_HAS_DIGEST, null, null);
75
mschallerc12c0442020-06-08 12:06:35 -070076 /** The exception that {@link RecursiveFilesystemTraversalFunctionException} wraps. */
77 public static class RecursiveFilesystemTraversalException extends Exception {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078
mschallerc12c0442020-06-08 12:06:35 -070079 /**
80 * Categories of errors that prevent normal {@link RecursiveFilesystemTraversalFunction}
81 * evaluation.
82 */
83 public enum Type {
84 /**
85 * The traversal encountered a subdirectory with a BUILD file but is not allowed to recurse
86 * into it. See {@code PackageBoundaryMode#REPORT_ERROR}.
87 */
88 CANNOT_CROSS_PACKAGE_BOUNDARY,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089
mschallerc12c0442020-06-08 12:06:35 -070090 /** A dangling symlink was dereferenced. */
91 DANGLING_SYMLINK,
92
93 /** A file operation failed. */
94 FILE_OPERATION_FAILURE,
95
96 /** A generated directory's root-relative path conflicts with a package's path. */
97 GENERATED_PATH_CONFLICT,
98 }
99
100 private final Type type;
101
102 RecursiveFilesystemTraversalException(String message, Type type) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 super(message);
mschallerc12c0442020-06-08 12:06:35 -0700104 this.type = type;
105 }
106
107 public Type getType() {
108 return type;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100109 }
110 }
111
112 /**
113 * Thrown when a dangling symlink is attempted to be dereferenced.
114 *
115 * <p>Note: this class is not identical to the one in com.google.devtools.build.lib.view.fileset
116 * and it's not easy to merge the two because of the dependency structure. The other one will
117 * probably be removed along with the rest of the legacy Fileset code.
118 */
mschallerc12c0442020-06-08 12:06:35 -0700119 static final class DanglingSymlinkException extends RecursiveFilesystemTraversalException {
120 DanglingSymlinkException(String path, String unresolvedLink) {
Laszlo Csomor0ad729f2015-12-02 15:20:35 +0000121 super(
122 String.format(
mschallerc12c0442020-06-08 12:06:35 -0700123 "Found dangling symlink: %s, unresolved path: \"%s\"", path, unresolvedLink),
124 Type.DANGLING_SYMLINK);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125 Preconditions.checkArgument(path != null && !path.isEmpty());
126 Preconditions.checkArgument(unresolvedLink != null && !unresolvedLink.isEmpty());
kush95bf7c82017-08-30 00:27:35 +0200127 }
128 }
129
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130 /** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
131 private static final class RecursiveFilesystemTraversalFunctionException extends
132 SkyFunctionException {
133 RecursiveFilesystemTraversalFunctionException(RecursiveFilesystemTraversalException e) {
134 super(e, Transience.PERSISTENT);
135 }
136 }
137
138 @Override
139 public SkyValue compute(SkyKey skyKey, Environment env)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000140 throws RecursiveFilesystemTraversalFunctionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100141 TraversalRequest traversal = (TraversalRequest) skyKey.argument();
142 try {
143 // Stat the traversal root.
144 FileInfo rootInfo = lookUpFileInfo(env, traversal);
145
146 if (!rootInfo.type.exists()) {
147 // May be a dangling symlink or a non-existent file. Handle gracefully.
148 if (rootInfo.type.isSymlink()) {
felly5be4dd62018-02-05 11:11:53 -0800149 return resultForDanglingSymlink(traversal.root.asRootedPath(), rootInfo);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100150 } else {
151 return RecursiveFilesystemTraversalValue.EMPTY;
152 }
153 }
154
155 if (rootInfo.type.isFile()) {
felly5be4dd62018-02-05 11:11:53 -0800156 return resultForFileRoot(traversal.root.asRootedPath(), rootInfo);
Googlerc8acdbd2018-11-08 10:47:15 -0800157 } else if (rootInfo.type.isDirectory() && rootInfo.metadata instanceof TreeArtifactValue) {
158 final TreeArtifactValue value = (TreeArtifactValue) rootInfo.metadata;
159 ImmutableList.Builder<RecursiveFilesystemTraversalValue> list = ImmutableList.builder();
160 for (Map.Entry<TreeFileArtifact, FileArtifactValue> entry
161 : value.getChildValues().entrySet()) {
162 RootedPath path =
163 RootedPath.toRootedPath(
164 traversal.root.asRootedPath().getRoot(), entry.getKey().getPath());
jcaterb11c6e22020-04-03 11:40:16 -0700165 list.add(
166 resultForFileRoot(
167 path,
168 // TreeArtifact can't have symbolic inside. So the assumption for FileType.FILE
169 // is always true.
170 new FileInfo(FileType.FILE, entry.getValue(), path, null)));
Googlerc8acdbd2018-11-08 10:47:15 -0800171 }
172 return resultForDirectory(traversal, rootInfo, list.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173 }
174
175 // Otherwise the root is a directory or a symlink to one.
176 PkgLookupResult pkgLookupResult = checkIfPackage(env, traversal, rootInfo);
177 traversal = pkgLookupResult.traversal;
178
179 if (pkgLookupResult.isConflicting()) {
180 // The traversal was requested for an output directory whose root-relative path conflicts
181 // with a source package. We can't handle that, bail out.
mschallerc12c0442020-06-08 12:06:35 -0700182 throw createGeneratedPathConflictException(traversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100183 } else if (pkgLookupResult.isPackage() && !traversal.skipTestingForSubpackage) {
184 // The traversal was requested for a directory that defines a package.
tomlu8cc5dcf2018-01-19 09:28:06 -0800185 String msg =
186 traversal.errorInfo
187 + " crosses package boundary into package rooted at "
felly5be4dd62018-02-05 11:11:53 -0800188 + traversal.root.asRootedPath().getRootRelativePath().getPathString();
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000189 switch (traversal.crossPkgBoundaries) {
190 case CROSS:
191 // We are free to traverse the subpackage but we need to display a warning.
Ulf Adams760e7092016-04-21 08:09:51 +0000192 env.getListener().handle(Event.warn(null, msg));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000193 break;
194 case DONT_CROSS:
195 // We cannot traverse the subpackage and should skip it silently. Return empty results.
196 return RecursiveFilesystemTraversalValue.EMPTY;
197 case REPORT_ERROR:
198 // We cannot traverse the subpackage and should complain loudly (display an error).
199 throw new RecursiveFilesystemTraversalFunctionException(
mschallerc12c0442020-06-08 12:06:35 -0700200 new RecursiveFilesystemTraversalException(
201 msg, RecursiveFilesystemTraversalException.Type.CANNOT_CROSS_PACKAGE_BOUNDARY));
Laszlo Csomorf04efcc2015-02-12 17:08:06 +0000202 default:
203 throw new IllegalStateException(traversal.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100204 }
205 }
206
207 // We are free to traverse this directory.
208 Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
janakr5fb2a482018-03-02 17:48:57 -0800209 return resultForDirectory(
210 traversal,
211 rootInfo,
212 traverseChildren(env, dependentKeys, /*inline=*/ traversal.isRootGenerated));
ulfjacke4794532018-01-12 02:11:17 -0800213 } catch (IOException e) {
mschallerc12c0442020-06-08 12:06:35 -0700214 String message = "Error while traversing fileset: " + e.getMessage();
kush95bf7c82017-08-30 00:27:35 +0200215 throw new RecursiveFilesystemTraversalFunctionException(
mschallerc12c0442020-06-08 12:06:35 -0700216 new RecursiveFilesystemTraversalException(
217 message, RecursiveFilesystemTraversalException.Type.FILE_OPERATION_FAILURE));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218 } catch (MissingDepException e) {
219 return null;
220 }
221 }
222
mschallerc12c0442020-06-08 12:06:35 -0700223 private static RecursiveFilesystemTraversalFunctionException createGeneratedPathConflictException(
224 TraversalRequest traversal) {
225 String message =
226 String.format(
227 "Generated directory %s conflicts with package under the same path. "
228 + "Additional info: %s",
229 traversal.root.asRootedPath().getRootRelativePath().getPathString(),
230 traversal.errorInfo != null ? traversal.errorInfo : traversal.toString());
231 return new RecursiveFilesystemTraversalFunctionException(
232 new RecursiveFilesystemTraversalException(
233 message, RecursiveFilesystemTraversalException.Type.GENERATED_PATH_CONFLICT));
234 }
235
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100236 @Override
237 public String extractTag(SkyKey skyKey) {
238 return null;
239 }
240
241 private static final class FileInfo {
242 final FileType type;
fellyf1e30f32019-07-09 12:26:10 -0700243 final HasDigest metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100244 @Nullable final RootedPath realPath;
245 @Nullable final PathFragment unresolvedSymlinkTarget;
246
kush4b120e72018-07-11 16:21:27 -0700247 FileInfo(
248 FileType type,
fellyf1e30f32019-07-09 12:26:10 -0700249 HasDigest metadata,
kush4b120e72018-07-11 16:21:27 -0700250 @Nullable RootedPath realPath,
kushb39c6932018-07-12 21:25:23 -0700251 @Nullable PathFragment unresolvedSymlinkTarget) {
fellyf1e30f32019-07-09 12:26:10 -0700252 Preconditions.checkNotNull(metadata.getDigest(), metadata);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 this.type = Preconditions.checkNotNull(type);
kushb39c6932018-07-12 21:25:23 -0700254 this.metadata = metadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 this.realPath = realPath;
256 this.unresolvedSymlinkTarget = unresolvedSymlinkTarget;
257 }
258
259 @Override
260 public String toString() {
261 if (type.isSymlink()) {
262 return String.format("(%s: link_value=%s, real_path=%s)", type,
263 unresolvedSymlinkTarget.getPathString(), realPath);
264 } else {
265 return String.format("(%s: real_path=%s)", type, realPath);
266 }
267 }
268 }
269
270 private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
ulfjacke4794532018-01-12 02:11:17 -0800271 throws MissingDepException, IOException, InterruptedException {
janakr5fb2a482018-03-02 17:48:57 -0800272 if (traversal.isRootGenerated) {
fellyf1e30f32019-07-09 12:26:10 -0700273 HasDigest fsVal = null;
felly5be4dd62018-02-05 11:11:53 -0800274 if (traversal.root.getOutputArtifact() != null) {
275 Artifact artifact = traversal.root.getOutputArtifact();
lberki36df7ed2019-06-27 06:32:03 -0700276 SkyKey artifactKey = Artifact.key(artifact);
felly5be4dd62018-02-05 11:11:53 -0800277 SkyValue value = env.getValue(artifactKey);
278 if (env.valuesMissing()) {
279 throw new MissingDepException();
280 }
kush95bf7c82017-08-30 00:27:35 +0200281
fellyf1e30f32019-07-09 12:26:10 -0700282 if (value instanceof FileArtifactValue || value instanceof TreeArtifactValue) {
283 fsVal = (HasDigest) value;
janakr8541f6d2019-06-11 14:40:21 -0700284 } else if (value instanceof ActionExecutionValue) {
Googleraed41602020-06-02 12:22:53 -0700285 fsVal = ((ActionExecutionValue) value).getExistingFileArtifactValue(artifact);
felly5be4dd62018-02-05 11:11:53 -0800286 } else {
kushb39c6932018-07-12 21:25:23 -0700287 return NON_EXISTENT_FILE_INFO;
felly5be4dd62018-02-05 11:11:53 -0800288 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100289 }
felly5be4dd62018-02-05 11:11:53 -0800290 RootedPath realPath = traversal.root.asRootedPath();
felly6c399d62018-07-18 15:55:59 -0700291 if (traversal.strictOutputFiles) {
292 Preconditions.checkNotNull(fsVal, "Strict Fileset output tree has null FileArtifactValue");
Googlerc8acdbd2018-11-08 10:47:15 -0800293 return new FileInfo(
294 (fsVal instanceof TreeArtifactValue ? FileType.DIRECTORY : FileType.FILE),
jcaterb11c6e22020-04-03 11:40:16 -0700295 fsVal,
296 realPath,
297 null);
felly5be4dd62018-02-05 11:11:53 -0800298 } else {
felly6c399d62018-07-18 15:55:59 -0700299 // FileArtifactValue does not currently track symlinks. If it did, we could potentially
300 // remove some of the filesystem operations we're doing here.
301 Path path = traversal.root.asRootedPath().asPath();
fellyf1e30f32019-07-09 12:26:10 -0700302 FileStateValue fileState =
303 FileStateValue.create(traversal.root.asRootedPath(), DEFAULT_SYSCALLS, null);
304 if (fileState.getType() == FileStateType.NONEXISTENT) {
305 throw new IOException("Missing file: " + path);
306 }
felly6c399d62018-07-18 15:55:59 -0700307 FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
308 FileType type;
309 PathFragment unresolvedLinkTarget = null;
310 if (followStat == null) {
311 type = FileType.DANGLING_SYMLINK;
fellyf1e30f32019-07-09 12:26:10 -0700312 if (fileState.getType() != FileStateType.SYMLINK) {
313 throw new IOException("Expected symlink for " + path + ", but got: " + fileState);
felly6c399d62018-07-18 15:55:59 -0700314 }
315 unresolvedLinkTarget = path.readSymbolicLink();
fellyf1e30f32019-07-09 12:26:10 -0700316 } else if (fileState.getType() == FileStateType.REGULAR_FILE) {
felly6c399d62018-07-18 15:55:59 -0700317 type = FileType.FILE;
fellyf1e30f32019-07-09 12:26:10 -0700318 } else if (fileState.getType() == FileStateType.DIRECTORY) {
felly6c399d62018-07-18 15:55:59 -0700319 type = FileType.DIRECTORY;
320 } else {
321 unresolvedLinkTarget = path.readSymbolicLink();
322 realPath =
323 RootedPath.toRootedPath(
324 Root.absoluteRoot(path.getFileSystem()), path.resolveSymbolicLinks());
325 type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
326 }
fellyf1e30f32019-07-09 12:26:10 -0700327 if (fsVal == null) {
328 fsVal = fileState;
329 }
Googler5284e6c2019-10-30 07:43:54 -0700330 return new FileInfo(type, withDigest(fsVal, path), realPath, unresolvedLinkTarget);
felly5be4dd62018-02-05 11:11:53 -0800331 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100332 } else {
felly5be4dd62018-02-05 11:11:53 -0800333 // Stat the file.
334 FileValue fileValue =
335 (FileValue) env.getValueOrThrow(
336 FileValue.key(traversal.root.asRootedPath()), IOException.class);
337
338 if (env.valuesMissing()) {
339 throw new MissingDepException();
340 }
341 if (fileValue.exists()) {
342 // If it exists, it may either be a symlink or a file/directory.
343 PathFragment unresolvedLinkTarget = null;
344 FileType type;
345 if (fileValue.isSymlink()) {
346 unresolvedLinkTarget = fileValue.getUnresolvedLinkTarget();
347 type = fileValue.isDirectory() ? FileType.SYMLINK_TO_DIRECTORY : FileType.SYMLINK_TO_FILE;
348 } else {
349 type = fileValue.isDirectory() ? FileType.DIRECTORY : FileType.FILE;
350 }
Googler5284e6c2019-10-30 07:43:54 -0700351 Path path = traversal.root.asRootedPath().asPath();
kush4b120e72018-07-11 16:21:27 -0700352 return new FileInfo(
fellyf1e30f32019-07-09 12:26:10 -0700353 type,
Googler5284e6c2019-10-30 07:43:54 -0700354 withDigest(fileValue.realFileStateValue(), path),
fellyf1e30f32019-07-09 12:26:10 -0700355 fileValue.realRootedPath(),
356 unresolvedLinkTarget);
felly5be4dd62018-02-05 11:11:53 -0800357 } else {
358 // If it doesn't exist, or it's a dangling symlink, we still want to handle that gracefully.
359 return new FileInfo(
360 fileValue.isSymlink() ? FileType.DANGLING_SYMLINK : FileType.NONEXISTENT,
Googler5284e6c2019-10-30 07:43:54 -0700361 withDigest(fileValue.realFileStateValue(), null),
kush4b120e72018-07-11 16:21:27 -0700362 null,
kushb39c6932018-07-12 21:25:23 -0700363 fileValue.isSymlink() ? fileValue.getUnresolvedLinkTarget() : null);
felly5be4dd62018-02-05 11:11:53 -0800364 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100365 }
366 }
367
Googler5284e6c2019-10-30 07:43:54 -0700368 /**
369 * Transform the HasDigest to the appropriate type based on the current state of the digest. If
370 * fsVal is type RegularFileStateValue or FileArtifactValue and has a valid digest value, then we
371 * want to convert it to a new FileArtifactValue type. Otherwise if they are of the two
372 * forementioned types but do not have a digest, then we will create a FileArtifactValue using its
373 * {@link Path}. Otherwise we will fingerprint the digest and return it as a new {@link
374 * HasDigest.ByteStringDigest} object.
375 *
376 * @param fsVal - the HasDigest value that was in the graph.
377 * @param path - the Path of the digest.
378 * @return transformed HasDigest value based on the digest field and object type.
379 */
380 @VisibleForTesting
381 static HasDigest withDigest(HasDigest fsVal, Path path) throws IOException {
fellyf1e30f32019-07-09 12:26:10 -0700382 if (fsVal instanceof FileStateValue) {
Googler5284e6c2019-10-30 07:43:54 -0700383 FileStateValue fsv = (FileStateValue) fsVal;
384 if (fsv instanceof RegularFileStateValue) {
385 RegularFileStateValue rfsv = (RegularFileStateValue) fsv;
386 return rfsv.getDigest() != null
387 // If we have the digest, then simply convert it with the digest value.
388 ? FileArtifactValue.createForVirtualActionInput(rfsv.getDigest(), rfsv.getSize())
389 // Otherwise, create a file FileArtifactValue (RegularFileArtifactValue) based on the
390 // path and size.
391 : FileArtifactValue.createForNormalFileUsingPath(path, rfsv.getSize());
392 }
393 return new HasDigest.ByteStringDigest(fsv.getValueFingerprint().toByteArray());
fellyf1e30f32019-07-09 12:26:10 -0700394 } else if (fsVal instanceof FileArtifactValue) {
Googler5284e6c2019-10-30 07:43:54 -0700395 FileArtifactValue fav = ((FileArtifactValue) fsVal);
396 if (fav.getDigest() != null) {
397 return fav;
398 }
399
400 // In the case there is a directory, the HasDigest value should not be converted. Otherwise,
401 // if the HasDigest value is a file, convert it using the Path and size values.
402 return fav.getType().isFile()
403 ? FileArtifactValue.createForNormalFileUsingPath(path, fav.getSize())
404 : new HasDigest.ByteStringDigest(fav.getValueFingerprint().toByteArray());
fellyf1e30f32019-07-09 12:26:10 -0700405 }
406 return fsVal;
407 }
408
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100409 private static final class PkgLookupResult {
410 private enum Type {
411 CONFLICT, DIRECTORY, PKG
412 }
413
414 private final Type type;
415 final TraversalRequest traversal;
416 final FileInfo rootInfo;
417
418 /** Result for a generated directory that conflicts with a source package. */
419 static PkgLookupResult conflict(TraversalRequest traversal, FileInfo rootInfo) {
420 return new PkgLookupResult(Type.CONFLICT, traversal, rootInfo);
421 }
422
423 /** Result for a source or generated directory (not a package). */
424 static PkgLookupResult directory(TraversalRequest traversal, FileInfo rootInfo) {
425 return new PkgLookupResult(Type.DIRECTORY, traversal, rootInfo);
426 }
427
428 /** Result for a package, i.e. a directory with a BUILD file. */
429 static PkgLookupResult pkg(TraversalRequest traversal, FileInfo rootInfo) {
430 return new PkgLookupResult(Type.PKG, traversal, rootInfo);
431 }
432
433 private PkgLookupResult(Type type, TraversalRequest traversal, FileInfo rootInfo) {
434 this.type = Preconditions.checkNotNull(type);
435 this.traversal = Preconditions.checkNotNull(traversal);
436 this.rootInfo = Preconditions.checkNotNull(rootInfo);
437 }
438
439 boolean isPackage() {
440 return type == Type.PKG;
441 }
442
443 boolean isConflicting() {
444 return type == Type.CONFLICT;
445 }
446
447 @Override
448 public String toString() {
449 return String.format("(%s: info=%s, traversal=%s)", type, rootInfo, traversal);
450 }
451 }
452
453 /**
454 * Checks whether the {@code traversal}'s path refers to a package directory.
455 *
456 * @return the result of the lookup; it contains potentially new {@link TraversalRequest} and
457 * {@link FileInfo} so the caller should use these instead of the old ones (this happens when
458 * a package is found, but under a different root than expected)
459 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000460 private static PkgLookupResult checkIfPackage(
461 Environment env, TraversalRequest traversal, FileInfo rootInfo)
ulfjacke4794532018-01-12 02:11:17 -0800462 throws MissingDepException, IOException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100463 Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
464 "{%s} {%s}", traversal, rootInfo);
tomlu8cc5dcf2018-01-19 09:28:06 -0800465 PackageLookupValue pkgLookup =
466 (PackageLookupValue)
felly5be4dd62018-02-05 11:11:53 -0800467 getDependentSkyValue(env,
468 PackageLookupValue.key(traversal.root.asRootedPath().getRootRelativePath()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100469
470 if (pkgLookup.packageExists()) {
janakr5fb2a482018-03-02 17:48:57 -0800471 if (traversal.isRootGenerated) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100472 // The traversal's root was a generated directory, but its root-relative path conflicts with
473 // an existing package.
474 return PkgLookupResult.conflict(traversal, rootInfo);
475 } else {
476 // The traversal's root was a source directory and it defines a package.
tomluee6a6862018-01-17 14:36:26 -0800477 Root pkgRoot = pkgLookup.getRoot();
felly5be4dd62018-02-05 11:11:53 -0800478 if (!pkgRoot.equals(traversal.root.asRootedPath().getRoot())) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100479 // However the root of this package is different from what we expected. stat() the real
480 // BUILD file of that package.
481 traversal = traversal.forChangedRootPath(pkgRoot);
482 rootInfo = lookUpFileInfo(env, traversal);
483 Verify.verify(rootInfo.type.exists(), "{%s} {%s}", traversal, rootInfo);
484 }
485 return PkgLookupResult.pkg(traversal, rootInfo);
486 }
487 } else {
488 // The traversal's root was a directory (source or generated one), no package exists under the
489 // same root-relative path.
490 return PkgLookupResult.directory(traversal, rootInfo);
491 }
492 }
493
494 /**
495 * List the directory and create {@code SkyKey}s to request contents of its children recursively.
496 *
497 * <p>The returned keys are of type {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL}.
498 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000499 private static Collection<SkyKey> createRecursiveTraversalKeys(
500 Environment env, TraversalRequest traversal)
felly5be4dd62018-02-05 11:11:53 -0800501 throws MissingDepException, InterruptedException, IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100502 // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
503 // in the result, must be relative to it.
felly5be4dd62018-02-05 11:11:53 -0800504 Iterable<Dirent> dirents;
janakr5fb2a482018-03-02 17:48:57 -0800505 if (traversal.isRootGenerated) {
felly5be4dd62018-02-05 11:11:53 -0800506 // If we're dealing with an output file, read the directory directly instead of creating
507 // filesystem nodes under the output tree.
508 List<Dirent> direntsCollection =
509 new ArrayList<>(
510 traversal.root.asRootedPath().asPath().readdir(Symlinks.FOLLOW));
511 Collections.sort(direntsCollection);
512 dirents = direntsCollection;
513 } else {
514 dirents = ((DirectoryListingValue) getDependentSkyValue(env,
515 DirectoryListingValue.key(traversal.root.asRootedPath()))).getDirents();
516 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100517
518 List<SkyKey> result = new ArrayList<>();
felly5be4dd62018-02-05 11:11:53 -0800519 for (Dirent dirent : dirents) {
tomlu8cc5dcf2018-01-19 09:28:06 -0800520 RootedPath childPath =
521 RootedPath.toRootedPath(
felly5be4dd62018-02-05 11:11:53 -0800522 traversal.root.asRootedPath().getRoot(),
523 traversal.root.asRootedPath().getRootRelativePath().getRelative(dirent.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100524 TraversalRequest childTraversal = traversal.forChildEntry(childPath);
janakr5fb2a482018-03-02 17:48:57 -0800525 result.add(childTraversal);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100526 }
527 return result;
528 }
529
530 /**
531 * Creates result for a dangling symlink.
532 *
533 * @param linkName path to the symbolic link
534 * @param info the {@link FileInfo} associated with the link file
535 */
536 private static RecursiveFilesystemTraversalValue resultForDanglingSymlink(RootedPath linkName,
537 FileInfo info) {
538 Preconditions.checkState(info.type.isSymlink() && !info.type.exists(), "{%s} {%s}", linkName,
539 info.type);
540 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700541 ResolvedFileFactory.danglingSymlink(linkName, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100542 }
543
544 /**
545 * Creates results for a file or for a symlink that points to one.
546 *
547 * <p>A symlink may be direct (points to a file) or transitive (points at a direct or transitive
548 * symlink).
549 */
550 private static RecursiveFilesystemTraversalValue resultForFileRoot(RootedPath path,
551 FileInfo info) {
552 Preconditions.checkState(info.type.isFile() && info.type.exists(), "{%s} {%s}", path,
553 info.type);
554 if (info.type.isSymlink()) {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000555 return RecursiveFilesystemTraversalValue.of(
556 ResolvedFileFactory.symlinkToFile(
kushb39c6932018-07-12 21:25:23 -0700557 info.realPath, path, info.unresolvedSymlinkTarget, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100558 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000559 return RecursiveFilesystemTraversalValue.of(
kushb39c6932018-07-12 21:25:23 -0700560 ResolvedFileFactory.regularFile(path, info.metadata));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100561 }
562 }
563
564 private static RecursiveFilesystemTraversalValue resultForDirectory(TraversalRequest traversal,
565 FileInfo rootInfo, Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
566 // Collect transitive closure of files in subdirectories.
567 NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
568 for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
569 paths.addTransitive(child.getTransitiveFiles());
570 }
571 ResolvedFile root;
572 if (rootInfo.type.isSymlink()) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000573 NestedSet<ResolvedFile> children = paths.build();
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000574 root =
575 ResolvedFileFactory.symlinkToDirectory(
576 rootInfo.realPath,
felly5be4dd62018-02-05 11:11:53 -0800577 traversal.root.asRootedPath(),
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000578 rootInfo.unresolvedSymlinkTarget,
kushb39c6932018-07-12 21:25:23 -0700579 hashDirectorySymlink(children, rootInfo.metadata));
Laszlo Csomor207140f2015-12-07 15:07:33 +0000580 paths = NestedSetBuilder.<ResolvedFile>stableOrder().addTransitive(children).add(root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100581 } else {
Laszlo Csomorb4d482b2015-12-04 13:23:54 +0000582 root = ResolvedFileFactory.directory(rootInfo.realPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100583 }
584 return RecursiveFilesystemTraversalValue.of(root, paths.build());
585 }
586
fellyf1e30f32019-07-09 12:26:10 -0700587 private static HasDigest hashDirectorySymlink(
ulfjack61216fd2019-12-18 12:31:03 -0800588 NestedSet<ResolvedFile> children, HasDigest metadata) {
Laszlo Csomor207140f2015-12-07 15:07:33 +0000589 // If the root is a directory symlink, the associated FileStateValue does not change when the
590 // linked directory's contents change, so we can't use the FileStateValue as metadata like we
591 // do with other ResolvedFile kinds. Instead we compute a metadata hash from the child
592 // elements and return that as the ResolvedFile's metadata hash.
fellyf1e30f32019-07-09 12:26:10 -0700593 Fingerprint fp = new Fingerprint();
594 fp.addBytes(metadata.getDigest());
ulfjack61216fd2019-12-18 12:31:03 -0800595 for (ResolvedFile file : children.toList()) {
fellyf1e30f32019-07-09 12:26:10 -0700596 fp.addPath(file.getNameInSymlinkTree());
597 fp.addBytes(file.getMetadata().getDigest());
Laszlo Csomor207140f2015-12-07 15:07:33 +0000598 }
fellyf1e30f32019-07-09 12:26:10 -0700599 byte[] result = fp.digestAndReset();
600 return new HasDigest.ByteStringDigest(result);
Laszlo Csomor207140f2015-12-07 15:07:33 +0000601 }
602
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100603 private static SkyValue getDependentSkyValue(Environment env, SkyKey key)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000604 throws MissingDepException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100605 SkyValue value = env.getValue(key);
606 if (env.valuesMissing()) {
607 throw new MissingDepException();
608 }
609 return value;
610 }
611
612 /**
613 * Requests Skyframe to compute the dependent values and returns them.
614 *
615 * <p>The keys must all be {@link SkyFunctions#RECURSIVE_FILESYSTEM_TRAVERSAL} keys.
616 */
felly5be4dd62018-02-05 11:11:53 -0800617 private Collection<RecursiveFilesystemTraversalValue> traverseChildren(
618 Environment env, Iterable<SkyKey> keys, boolean inline)
619 throws MissingDepException, InterruptedException,
620 RecursiveFilesystemTraversalFunctionException {
621 Map<SkyKey, SkyValue> values;
622 if (inline) {
623 // Don't create Skyframe nodes for a recursive traversal over the output tree.
624 // Instead, inline the recursion in the top-level request.
625 values = new HashMap<>();
626 for (SkyKey depKey : keys) {
627 values.put(depKey, compute(depKey, env));
628 }
629 } else {
630 values = env.getValues(keys);
631 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100632 if (env.valuesMissing()) {
633 throw new MissingDepException();
634 }
felly5be4dd62018-02-05 11:11:53 -0800635
kushb39c6932018-07-12 21:25:23 -0700636 return Collections2.transform(values.values(), RecursiveFilesystemTraversalValue.class::cast);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100637 }
638
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100639}