blob: 8f125a19148a5a824dbc35e12fcf9f5172393528 [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;
nharmata8494cc72019-02-28 13:09:20 -080017import com.google.common.collect.ImmutableList;
Janak Ramakrishnance372c32016-03-14 16:19:18 +000018import com.google.common.collect.Maps;
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +000019import com.google.common.collect.Sets;
shahan602cc852018-06-06 20:09:57 -070020import com.google.devtools.build.lib.actions.FileValue;
21import com.google.devtools.build.lib.actions.InconsistentFilesystemException;
Kristina Chodorow73fa2032015-08-28 17:57:46 +000022import com.google.devtools.build.lib.cmdline.PackageIdentifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.collect.nestedset.NestedSet;
24import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
25import com.google.devtools.build.lib.vfs.Dirent;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.vfs.PathFragment;
27import com.google.devtools.build.lib.vfs.RootedPath;
28import com.google.devtools.build.lib.vfs.UnixGlob;
29import com.google.devtools.build.skyframe.SkyFunction;
30import com.google.devtools.build.skyframe.SkyFunctionException;
31import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
32import com.google.devtools.build.skyframe.SkyKey;
33import com.google.devtools.build.skyframe.SkyValue;
Janak Ramakrishnance372c32016-03-14 16:19:18 +000034import java.util.Map;
Googlerd3501d82018-08-15 13:39:01 -070035import java.util.concurrent.ConcurrentHashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import java.util.regex.Pattern;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import javax.annotation.Nullable;
38
39/**
40 * A {@link SkyFunction} for {@link GlobValue}s.
41 *
42 * <p>This code drives the glob matching process.
43 */
Nathan Harmata029de3d2015-07-27 18:08:09 +000044public final class GlobFunction implements SkyFunction {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045
Googlerd3501d82018-08-15 13:39:01 -070046 private final ConcurrentHashMap<String, Pattern> regexPatternCache = new ConcurrentHashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047
Eric Fellheimer434e4732015-08-04 18:08:45 +000048 private final boolean alwaysUseDirListing;
49
50 public GlobFunction(boolean alwaysUseDirListing) {
51 this.alwaysUseDirListing = alwaysUseDirListing;
52 }
53
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000055 public SkyValue compute(SkyKey skyKey, Environment env)
56 throws GlobFunctionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010057 GlobDescriptor glob = (GlobDescriptor) skyKey.argument();
58
lberki7abdcb42019-10-22 02:17:13 -070059 BlacklistedPackagePrefixesValue blacklistedPackagePrefixes =
60 (BlacklistedPackagePrefixesValue) env.getValue(BlacklistedPackagePrefixesValue.key());
61 if (env.valuesMissing()) {
62 return null;
63 }
64
65 PathFragment globSubdir = glob.getSubdir();
66 PathFragment dirPathFragment = glob.getPackageId().getPackageFragment().getRelative(globSubdir);
67
68 for (PathFragment blacklistedPrefix : blacklistedPackagePrefixes.getPatterns()) {
69 if (dirPathFragment.startsWith(blacklistedPrefix)) {
70 return GlobValue.EMPTY;
71 }
72 }
73
Nathan Harmatab795e6b2016-02-04 01:10:19 +000074 // Note that the glob's package is assumed to exist which implies that the package's BUILD file
75 // exists which implies that the package's directory exists.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 if (!globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) {
Janak Ramakrishnance372c32016-03-14 16:19:18 +000077 PackageLookupValue globSubdirPkgLookupValue =
78 (PackageLookupValue)
79 env.getValue(
80 PackageLookupValue.key(
81 PackageIdentifier.create(
82 glob.getPackageId().getRepository(),
83 glob.getPackageId().getPackageFragment().getRelative(globSubdir))));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084 if (globSubdirPkgLookupValue == null) {
85 return null;
86 }
87 if (globSubdirPkgLookupValue.packageExists()) {
88 // We crossed the package boundary, that is, pkg/subdir contains a BUILD file and thus
89 // defines another package, so glob expansion should not descend into that subdir.
90 return GlobValue.EMPTY;
John Cater4117c862017-11-20 08:09:02 -080091 } else if (globSubdirPkgLookupValue
92 instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) {
93 // We crossed a repository boundary, so glob expansion should not descend into that subdir.
94 return GlobValue.EMPTY;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010095 }
96 }
97
98 String pattern = glob.getPattern();
99 // Split off the first path component of the pattern.
Ulf Adams07dba942015-03-05 14:47:37 +0000100 int slashPos = pattern.indexOf('/');
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100101 String patternHead;
102 String patternTail;
103 if (slashPos == -1) {
104 patternHead = pattern;
105 patternTail = null;
106 } else {
107 // Substrings will share the backing array of the original glob string. That should be fine.
108 patternHead = pattern.substring(0, slashPos);
109 patternTail = pattern.substring(slashPos + 1);
110 }
111
112 NestedSetBuilder<PathFragment> matches = NestedSetBuilder.stableOrder();
113
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000114 boolean globMatchesBareFile = patternTail == null;
115
lberki7abdcb42019-10-22 02:17:13 -0700116
Nathan Harmatab795e6b2016-02-04 01:10:19 +0000117 RootedPath dirRootedPath = RootedPath.toRootedPath(glob.getPackageRoot(), dirPathFragment);
Eric Fellheimer434e4732015-08-04 18:08:45 +0000118 if (alwaysUseDirListing || containsGlobs(patternHead)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100119 // Pattern contains globs, so a directory listing is required.
120 //
121 // Note that we have good reason to believe the directory exists: if this is the
122 // top-level directory of the package, the package's existence implies the directory's
123 // existence; if this is a lower-level directory in the package, then we got here from
124 // previous directory listings. Filesystem operations concurrent with build could mean the
125 // directory no longer exists, but DirectoryListingFunction handles that gracefully.
nharmata8494cc72019-02-28 13:09:20 -0800126 SkyKey directoryListingKey = DirectoryListingValue.key(dirRootedPath);
127 DirectoryListingValue listingValue = null;
128
129 boolean patternHeadIsStarStar = "**".equals(patternHead);
130 if (patternHeadIsStarStar) {
131 // "**" also matches an empty segment, so try the case where it is not present.
132 if (globMatchesBareFile) {
133 // Recursive globs aren't supposed to match the package's directory.
134 if (!glob.excludeDirs() && !globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) {
135 matches.add(globSubdir);
136 }
137 } else {
138 // Optimize away a Skyframe restart by requesting the DirectoryListingValue dep and
139 // recursive GlobValue dep in a single batch.
140
141 SkyKey keyForRecursiveGlobInCurrentDirectory =
142 GlobValue.internalKey(
143 glob.getPackageId(),
144 glob.getPackageRoot(),
145 globSubdir,
146 patternTail,
147 glob.excludeDirs());
148 Map<SkyKey, SkyValue> listingAndRecursiveGlobMap =
149 env.getValues(
150 ImmutableList.of(keyForRecursiveGlobInCurrentDirectory, directoryListingKey));
151 if (env.valuesMissing()) {
152 return null;
153 }
154 GlobValue globValue =
155 (GlobValue) listingAndRecursiveGlobMap.get(keyForRecursiveGlobInCurrentDirectory);
156 matches.addTransitive(globValue.getMatches());
157 listingValue =
158 (DirectoryListingValue) listingAndRecursiveGlobMap.get(directoryListingKey);
159 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100160 }
161
nharmata8494cc72019-02-28 13:09:20 -0800162 if (listingValue == null) {
163 listingValue = (DirectoryListingValue) env.getValue(directoryListingKey);
164 if (listingValue == null) {
165 return null;
166 }
167 }
168
169 // Now that we have the directory listing, we do three passes over it so as to maximize
170 // skyframe batching:
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000171 // (1) Process every dirent, keeping track of values we need to request if the dirent cannot
172 // be processed with current information (symlink targets and subdirectory globs/package
173 // lookups for some subdirectories).
174 // (2) Get those values and process the symlinks, keeping track of subdirectory globs/package
175 // lookups we may need to request in case the symlink's target is a directory.
176 // (3) Process the necessary subdirectories.
177 int direntsSize = listingValue.getDirents().size();
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000178 Map<SkyKey, Dirent> symlinkFileMap = Maps.newHashMapWithExpectedSize(direntsSize);
179 Map<SkyKey, Dirent> subdirMap = Maps.newHashMapWithExpectedSize(direntsSize);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000180 Map<Dirent, Object> sortedResultMap = Maps.newTreeMap();
nharmata8494cc72019-02-28 13:09:20 -0800181 String subdirPattern = patternHeadIsStarStar ? glob.getPattern() : patternTail;
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000182 // First pass: do normal files and collect SkyKeys to request for subdirectories and symlinks.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100183 for (Dirent dirent : listingValue.getDirents()) {
jcatercecb3a82018-05-01 14:37:48 -0700184 Dirent.Type direntType = dirent.getType();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 String fileName = dirent.getName();
laszlocsomor9efbb492019-07-04 08:36:29 -0700186 if (!UnixGlob.matches(patternHead, fileName, regexPatternCache)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187 continue;
188 }
189
190 if (direntType == Dirent.Type.SYMLINK) {
191 // TODO(bazel-team): Consider extracting the symlink resolution logic.
192 // For symlinks, look up the corresponding FileValue. This ensures that if the symlink
193 // changes and "switches types" (say, from a file to a directory), this value will be
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000194 // invalidated. We also need the target's type to properly process the symlink.
195 symlinkFileMap.put(
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000196 FileValue.key(
197 RootedPath.toRootedPath(
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000198 glob.getPackageRoot(), dirPathFragment.getRelative(fileName))),
199 dirent);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000200 continue;
Janak Ramakrishnan4bf00182016-02-29 19:59:44 +0000201 }
Janak Ramakrishnan4bf00182016-02-29 19:59:44 +0000202
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000203 if (direntType == Dirent.Type.DIRECTORY) {
204 SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, subdirPattern);
205 if (keyToRequest != null) {
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000206 subdirMap.put(keyToRequest, dirent);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000207 }
208 } else if (globMatchesBareFile) {
209 sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName));
210 }
211 }
212
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000213 Map<SkyKey, SkyValue> subdirAndSymlinksResult =
214 env.getValues(Sets.union(subdirMap.keySet(), symlinkFileMap.keySet()));
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000215 if (env.valuesMissing()) {
216 return null;
217 }
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000218 Map<SkyKey, Dirent> symlinkSubdirMap = Maps.newHashMapWithExpectedSize(symlinkFileMap.size());
219 // Second pass: process the symlinks and subdirectories from the first pass, and maybe
220 // collect further SkyKeys if fully resolved symlink targets are themselves directories.
221 // Also process any known directories.
222 for (Map.Entry<SkyKey, SkyValue> lookedUpKeyAndValue : subdirAndSymlinksResult.entrySet()) {
223 if (symlinkFileMap.containsKey(lookedUpKeyAndValue.getKey())) {
224 FileValue symlinkFileValue = (FileValue) lookedUpKeyAndValue.getValue();
225 if (!symlinkFileValue.isSymlink()) {
226 throw new GlobFunctionException(
227 new InconsistentFilesystemException(
228 "readdir and stat disagree about whether "
229 + ((RootedPath) lookedUpKeyAndValue.getKey().argument()).asPath()
230 + " is a symlink."),
231 Transience.TRANSIENT);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000232 }
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000233 if (!symlinkFileValue.exists()) {
234 continue;
235 }
236 Dirent dirent = symlinkFileMap.get(lookedUpKeyAndValue.getKey());
237 String fileName = dirent.getName();
238 if (symlinkFileValue.isDirectory()) {
239 SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, subdirPattern);
240 if (keyToRequest != null) {
241 symlinkSubdirMap.put(keyToRequest, dirent);
242 }
243 } else if (globMatchesBareFile) {
244 sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName));
245 }
246 } else {
247 processSubdir(lookedUpKeyAndValue, subdirMap, glob, sortedResultMap);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000248 }
249 }
250
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000251 Map<SkyKey, SkyValue> symlinkSubdirResult = env.getValues(symlinkSubdirMap.keySet());
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000252 if (env.valuesMissing()) {
253 return null;
254 }
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000255 // Third pass: do needed subdirectories of symlinked directories discovered during the second
256 // pass.
257 for (Map.Entry<SkyKey, SkyValue> lookedUpKeyAndValue : symlinkSubdirResult.entrySet()) {
258 processSubdir(lookedUpKeyAndValue, symlinkSubdirMap, glob, sortedResultMap);
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000259 }
260 for (Map.Entry<Dirent, Object> fileMatches : sortedResultMap.entrySet()) {
261 addToMatches(fileMatches.getValue(), matches);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100262 }
263 } else {
264 // Pattern does not contain globs, so a direct stat is enough.
265 String fileName = patternHead;
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000266 RootedPath fileRootedPath =
267 RootedPath.toRootedPath(glob.getPackageRoot(), dirPathFragment.getRelative(fileName));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100268 FileValue fileValue = (FileValue) env.getValue(FileValue.key(fileRootedPath));
269 if (fileValue == null) {
270 return null;
271 }
272 if (fileValue.exists()) {
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000273 if (fileValue.isDirectory()) {
274 SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, patternTail);
275 if (keyToRequest != null) {
276 SkyValue valueRequested = env.getValue(keyToRequest);
277 if (env.valuesMissing()) {
278 return null;
279 }
280 Object fileMatches = getSubdirMatchesFromSkyValue(fileName, glob, valueRequested);
281 if (fileMatches != null) {
282 addToMatches(fileMatches, matches);
283 }
284 }
285 } else if (globMatchesBareFile) {
286 matches.add(glob.getSubdir().getRelative(fileName));
287 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100288 }
289 }
290
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000291 Preconditions.checkState(!env.valuesMissing(), skyKey);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292
293 NestedSet<PathFragment> matchesBuilt = matches.build();
294 // Use the same value to represent that we did not match anything.
295 if (matchesBuilt.isEmpty()) {
296 return GlobValue.EMPTY;
297 }
298 return new GlobValue(matchesBuilt);
299 }
300
Janak Ramakrishnanbce6fc52016-08-18 16:46:09 +0000301 private static void processSubdir(
302 Map.Entry<SkyKey, SkyValue> keyAndValue,
303 Map<SkyKey, Dirent> subdirMap,
304 GlobDescriptor glob,
305 Map<Dirent, Object> sortedResultMap) {
306 Dirent dirent = Preconditions.checkNotNull(subdirMap.get(keyAndValue.getKey()), keyAndValue);
307 String fileName = dirent.getName();
308 Object dirMatches = getSubdirMatchesFromSkyValue(fileName, glob, keyAndValue.getValue());
309 if (dirMatches != null) {
310 sortedResultMap.put(dirent, dirMatches);
311 }
312 }
313
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000314 /** Returns true if the given pattern contains globs. */
315 private static boolean containsGlobs(String pattern) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100316 return pattern.contains("*") || pattern.contains("?");
317 }
318
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000319 @SuppressWarnings("unchecked") // cast to NestedSet<PathFragment>
320 private static void addToMatches(Object toAdd, NestedSetBuilder<PathFragment> matches) {
321 if (toAdd instanceof PathFragment) {
322 matches.add((PathFragment) toAdd);
323 } else {
324 matches.addTransitive((NestedSet<PathFragment>) toAdd);
325 }
326 }
327
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100328 /**
329 * Includes the given file/directory in the glob.
330 *
331 * <p>{@code fileName} must exist.
332 *
333 * <p>{@code isDirectory} must be true iff the file is a directory.
334 *
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000335 * <p>Returns a {@link SkyKey} for a value that is needed to compute the files that will be added
336 * to {@code matches}, or {@code null} if no additional value is needed. The returned value should
337 * be opaquely passed to {@link #getSubdirMatchesFromSkyValue}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100338 */
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000339 private static SkyKey getSkyKeyForSubdir(
340 String fileName, GlobDescriptor glob, String subdirPattern) {
341 if (subdirPattern == null) {
342 if (glob.excludeDirs()) {
343 return null;
344 } else {
345 return PackageLookupValue.key(
346 PackageIdentifier.create(
347 glob.getPackageId().getRepository(),
348 glob.getPackageId()
349 .getPackageFragment()
350 .getRelative(glob.getSubdir())
351 .getRelative(fileName)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100352 }
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000353 } else {
354 // There is some more pattern to match. Get the glob for the subdirectory. Note that this
355 // directory may also match directly in the case of a pattern that starts with "**", but that
356 // match will be found in the subdirectory glob.
357 return GlobValue.internalKey(
358 glob.getPackageId(),
359 glob.getPackageRoot(),
360 glob.getSubdir().getRelative(fileName),
361 subdirPattern,
362 glob.excludeDirs());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100363 }
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000364 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100365
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000366 /**
367 * Returns matches coming from the directory {@code fileName} if appropriate, either an individual
368 * file or a nested set of files.
369 *
370 * <p>{@code valueRequested} must be the SkyValue whose key was returned by
371 * {@link #getSkyKeyForSubdir} for these parameters.
372 */
373 @Nullable
374 private static Object getSubdirMatchesFromSkyValue(
375 String fileName,
376 GlobDescriptor glob,
377 SkyValue valueRequested) {
378 if (valueRequested instanceof GlobValue) {
379 return ((GlobValue) valueRequested).getMatches();
380 } else {
381 Preconditions.checkState(
382 valueRequested instanceof PackageLookupValue,
383 "%s is not a GlobValue or PackageLookupValue (%s %s)",
384 valueRequested,
385 fileName,
386 glob);
John Cater4117c862017-11-20 08:09:02 -0800387 PackageLookupValue packageLookupValue = (PackageLookupValue) valueRequested;
388 if (packageLookupValue.packageExists()) {
389 // This is a separate package, so ignore it.
390 return null;
391 } else if (packageLookupValue
392 instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) {
393 // This is a separate repository, so ignore it.
394 return null;
395 } else {
Janak Ramakrishnance372c32016-03-14 16:19:18 +0000396 return glob.getSubdir().getRelative(fileName);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100397 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100398 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 }
400
401 @Nullable
402 @Override
403 public String extractTag(SkyKey skyKey) {
404 return null;
405 }
406
407 /**
408 * Used to declare all the exception types that can be wrapped in the exception thrown by
409 * {@link GlobFunction#compute}.
410 */
411 private static final class GlobFunctionException extends SkyFunctionException {
412 public GlobFunctionException(InconsistentFilesystemException e, Transience transience) {
413 super(e, transience);
414 }
415 }
416}