Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 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. |
| 14 | package com.google.devtools.build.lib.skyframe; |
| 15 | |
tomlu | a155b53 | 2017-11-08 20:12:47 +0100 | [diff] [blame] | 16 | import com.google.common.base.Preconditions; |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 17 | import com.google.common.collect.ImmutableList; |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 18 | import com.google.common.collect.Maps; |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 19 | import com.google.common.collect.Sets; |
shahan | 602cc85 | 2018-06-06 20:09:57 -0700 | [diff] [blame] | 20 | import com.google.devtools.build.lib.actions.FileValue; |
Kristina Chodorow | 73fa203 | 2015-08-28 17:57:46 +0000 | [diff] [blame] | 21 | import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
Yun Peng | bf2c4d9 | 2019-12-13 09:21:28 -0800 | [diff] [blame] | 22 | import com.google.devtools.build.lib.cmdline.RepositoryName; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 23 | import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| 24 | import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
janakr | e2af68f | 2021-03-18 15:11:30 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionException; |
| 26 | import com.google.devtools.build.lib.io.FileSymlinkInfiniteExpansionUniquenessFunction; |
janakr | 3d7424a | 2021-03-18 11:44:25 -0700 | [diff] [blame] | 27 | import com.google.devtools.build.lib.io.InconsistentFilesystemException; |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 28 | import com.google.devtools.build.lib.packages.Globber; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 29 | import com.google.devtools.build.lib.vfs.Dirent; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 30 | import com.google.devtools.build.lib.vfs.PathFragment; |
| 31 | import com.google.devtools.build.lib.vfs.RootedPath; |
| 32 | import com.google.devtools.build.lib.vfs.UnixGlob; |
| 33 | import com.google.devtools.build.skyframe.SkyFunction; |
| 34 | import com.google.devtools.build.skyframe.SkyFunctionException; |
| 35 | import com.google.devtools.build.skyframe.SkyFunctionException.Transience; |
| 36 | import com.google.devtools.build.skyframe.SkyKey; |
| 37 | import com.google.devtools.build.skyframe.SkyValue; |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 38 | import com.google.devtools.build.skyframe.SkyframeIterableResult; |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 39 | import java.util.Map; |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 40 | import java.util.Set; |
Googler | d3501d8 | 2018-08-15 13:39:01 -0700 | [diff] [blame] | 41 | import java.util.concurrent.ConcurrentHashMap; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 42 | import java.util.regex.Pattern; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 43 | import javax.annotation.Nullable; |
| 44 | |
| 45 | /** |
| 46 | * A {@link SkyFunction} for {@link GlobValue}s. |
| 47 | * |
| 48 | * <p>This code drives the glob matching process. |
| 49 | */ |
Nathan Harmata | 029de3d | 2015-07-27 18:08:09 +0000 | [diff] [blame] | 50 | public final class GlobFunction implements SkyFunction { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 51 | |
Googler | f4bf6f6 | 2021-12-09 08:34:52 -0800 | [diff] [blame] | 52 | private ConcurrentHashMap<String, Pattern> regexPatternCache = new ConcurrentHashMap<>(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 53 | |
Eric Fellheimer | 434e473 | 2015-08-04 18:08:45 +0000 | [diff] [blame] | 54 | private final boolean alwaysUseDirListing; |
| 55 | |
| 56 | public GlobFunction(boolean alwaysUseDirListing) { |
| 57 | this.alwaysUseDirListing = alwaysUseDirListing; |
| 58 | } |
| 59 | |
Googler | f4bf6f6 | 2021-12-09 08:34:52 -0800 | [diff] [blame] | 60 | void complete() { |
| 61 | this.regexPatternCache = new ConcurrentHashMap<>(); |
| 62 | } |
| 63 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 64 | @Override |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 65 | public SkyValue compute(SkyKey skyKey, Environment env) |
| 66 | throws GlobFunctionException, InterruptedException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 67 | GlobDescriptor glob = (GlobDescriptor) skyKey.argument(); |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 68 | Globber.Operation globberOperation = glob.globberOperation(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 69 | |
Yun Peng | bf2c4d9 | 2019-12-13 09:21:28 -0800 | [diff] [blame] | 70 | RepositoryName repositoryName = glob.getPackageId().getRepository(); |
kkress | 1847a01 | 2020-06-24 12:30:11 -0700 | [diff] [blame] | 71 | IgnoredPackagePrefixesValue ignoredPackagePrefixes = |
| 72 | (IgnoredPackagePrefixesValue) env.getValue(IgnoredPackagePrefixesValue.key(repositoryName)); |
lberki | 7abdcb4 | 2019-10-22 02:17:13 -0700 | [diff] [blame] | 73 | if (env.valuesMissing()) { |
| 74 | return null; |
| 75 | } |
| 76 | |
| 77 | PathFragment globSubdir = glob.getSubdir(); |
| 78 | PathFragment dirPathFragment = glob.getPackageId().getPackageFragment().getRelative(globSubdir); |
| 79 | |
kkress | 1847a01 | 2020-06-24 12:30:11 -0700 | [diff] [blame] | 80 | for (PathFragment ignoredPrefix : ignoredPackagePrefixes.getPatterns()) { |
| 81 | if (dirPathFragment.startsWith(ignoredPrefix)) { |
lberki | 7abdcb4 | 2019-10-22 02:17:13 -0700 | [diff] [blame] | 82 | return GlobValue.EMPTY; |
| 83 | } |
| 84 | } |
| 85 | |
Nathan Harmata | b795e6b | 2016-02-04 01:10:19 +0000 | [diff] [blame] | 86 | // Note that the glob's package is assumed to exist which implies that the package's BUILD file |
| 87 | // exists which implies that the package's directory exists. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 88 | if (!globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) { |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 89 | PathFragment subDirFragment = |
| 90 | glob.getPackageId().getPackageFragment().getRelative(globSubdir); |
| 91 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 92 | PackageLookupValue globSubdirPkgLookupValue = |
| 93 | (PackageLookupValue) |
| 94 | env.getValue( |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 95 | PackageLookupValue.key(PackageIdentifier.create(repositoryName, subDirFragment))); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 96 | if (globSubdirPkgLookupValue == null) { |
| 97 | return null; |
| 98 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 99 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 100 | if (globSubdirPkgLookupValue.packageExists()) { |
| 101 | // We crossed the package boundary, that is, pkg/subdir contains a BUILD file and thus |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 102 | // defines another package, so glob expansion should not descend into |
| 103 | // that subdir. |
| 104 | // |
| 105 | // For SUBPACKAGES, we encounter this when the pattern is a recursive ** and we are a |
| 106 | // terminal package for that pattern. In that case we should include the subDirFragment |
| 107 | // PathFragment (relative to the glob's package) in the GlobValue.getMatches, |
| 108 | // otherwise for file/dir matching return EMPTY; |
| 109 | if (globberOperation == Globber.Operation.SUBPACKAGES) { |
| 110 | return new GlobValue( |
| 111 | NestedSetBuilder.<PathFragment>stableOrder() |
| 112 | .add(subDirFragment.relativeTo(glob.getPackageId().getPackageFragment())) |
| 113 | .build()); |
| 114 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 115 | return GlobValue.EMPTY; |
John Cater | 4117c86 | 2017-11-20 08:09:02 -0800 | [diff] [blame] | 116 | } else if (globSubdirPkgLookupValue |
| 117 | instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) { |
| 118 | // We crossed a repository boundary, so glob expansion should not descend into that subdir. |
| 119 | return GlobValue.EMPTY; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 120 | } |
| 121 | } |
| 122 | |
| 123 | String pattern = glob.getPattern(); |
| 124 | // Split off the first path component of the pattern. |
Ulf Adams | 07dba94 | 2015-03-05 14:47:37 +0000 | [diff] [blame] | 125 | int slashPos = pattern.indexOf('/'); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 126 | String patternHead; |
| 127 | String patternTail; |
| 128 | if (slashPos == -1) { |
| 129 | patternHead = pattern; |
| 130 | patternTail = null; |
| 131 | } else { |
| 132 | // Substrings will share the backing array of the original glob string. That should be fine. |
| 133 | patternHead = pattern.substring(0, slashPos); |
| 134 | patternTail = pattern.substring(slashPos + 1); |
| 135 | } |
| 136 | |
| 137 | NestedSetBuilder<PathFragment> matches = NestedSetBuilder.stableOrder(); |
| 138 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 139 | boolean globMatchesBareFile = patternTail == null; |
| 140 | |
Nathan Harmata | b795e6b | 2016-02-04 01:10:19 +0000 | [diff] [blame] | 141 | RootedPath dirRootedPath = RootedPath.toRootedPath(glob.getPackageRoot(), dirPathFragment); |
Eric Fellheimer | 434e473 | 2015-08-04 18:08:45 +0000 | [diff] [blame] | 142 | if (alwaysUseDirListing || containsGlobs(patternHead)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 143 | // Pattern contains globs, so a directory listing is required. |
| 144 | // |
| 145 | // Note that we have good reason to believe the directory exists: if this is the |
| 146 | // top-level directory of the package, the package's existence implies the directory's |
| 147 | // existence; if this is a lower-level directory in the package, then we got here from |
| 148 | // previous directory listings. Filesystem operations concurrent with build could mean the |
| 149 | // directory no longer exists, but DirectoryListingFunction handles that gracefully. |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 150 | SkyKey directoryListingKey = DirectoryListingValue.key(dirRootedPath); |
| 151 | DirectoryListingValue listingValue = null; |
| 152 | |
| 153 | boolean patternHeadIsStarStar = "**".equals(patternHead); |
| 154 | if (patternHeadIsStarStar) { |
| 155 | // "**" also matches an empty segment, so try the case where it is not present. |
| 156 | if (globMatchesBareFile) { |
| 157 | // Recursive globs aren't supposed to match the package's directory. |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 158 | if (globberOperation == Globber.Operation.FILES_AND_DIRS |
| 159 | && !globSubdir.equals(PathFragment.EMPTY_FRAGMENT)) { |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 160 | matches.add(globSubdir); |
| 161 | } |
| 162 | } else { |
| 163 | // Optimize away a Skyframe restart by requesting the DirectoryListingValue dep and |
| 164 | // recursive GlobValue dep in a single batch. |
| 165 | |
| 166 | SkyKey keyForRecursiveGlobInCurrentDirectory = |
| 167 | GlobValue.internalKey( |
| 168 | glob.getPackageId(), |
| 169 | glob.getPackageRoot(), |
| 170 | globSubdir, |
| 171 | patternTail, |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 172 | globberOperation); |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 173 | SkyframeIterableResult listingAndRecursiveGlobResult = |
| 174 | env.getOrderedValuesAndExceptions( |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 175 | ImmutableList.of(keyForRecursiveGlobInCurrentDirectory, directoryListingKey)); |
| 176 | if (env.valuesMissing()) { |
| 177 | return null; |
| 178 | } |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 179 | GlobValue globValue = (GlobValue) listingAndRecursiveGlobResult.next(); |
emilyguo | 8f0034c | 2022-04-09 10:09:52 -0700 | [diff] [blame] | 180 | if (globValue == null) { |
| 181 | // has exception, will be handled later. |
| 182 | return null; |
| 183 | } |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 184 | matches.addTransitive(globValue.getMatches()); |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 185 | listingValue = (DirectoryListingValue) listingAndRecursiveGlobResult.next(); |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 186 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 187 | } |
| 188 | |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 189 | if (listingValue == null) { |
| 190 | listingValue = (DirectoryListingValue) env.getValue(directoryListingKey); |
| 191 | if (listingValue == null) { |
| 192 | return null; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // Now that we have the directory listing, we do three passes over it so as to maximize |
| 197 | // skyframe batching: |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 198 | // (1) Process every dirent, keeping track of values we need to request if the dirent cannot |
| 199 | // be processed with current information (symlink targets and subdirectory globs/package |
| 200 | // lookups for some subdirectories). |
| 201 | // (2) Get those values and process the symlinks, keeping track of subdirectory globs/package |
| 202 | // lookups we may need to request in case the symlink's target is a directory. |
| 203 | // (3) Process the necessary subdirectories. |
| 204 | int direntsSize = listingValue.getDirents().size(); |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 205 | Map<SkyKey, Dirent> symlinkFileMap = Maps.newHashMapWithExpectedSize(direntsSize); |
| 206 | Map<SkyKey, Dirent> subdirMap = Maps.newHashMapWithExpectedSize(direntsSize); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 207 | Map<Dirent, Object> sortedResultMap = Maps.newTreeMap(); |
nharmata | 8494cc7 | 2019-02-28 13:09:20 -0800 | [diff] [blame] | 208 | String subdirPattern = patternHeadIsStarStar ? glob.getPattern() : patternTail; |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 209 | // First pass: do normal files and collect SkyKeys to request for subdirectories and symlinks. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 210 | for (Dirent dirent : listingValue.getDirents()) { |
jcater | cecb3a8 | 2018-05-01 14:37:48 -0700 | [diff] [blame] | 211 | Dirent.Type direntType = dirent.getType(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 212 | String fileName = dirent.getName(); |
laszlocsomor | 9efbb49 | 2019-07-04 08:36:29 -0700 | [diff] [blame] | 213 | if (!UnixGlob.matches(patternHead, fileName, regexPatternCache)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 214 | continue; |
| 215 | } |
| 216 | |
| 217 | if (direntType == Dirent.Type.SYMLINK) { |
| 218 | // TODO(bazel-team): Consider extracting the symlink resolution logic. |
| 219 | // For symlinks, look up the corresponding FileValue. This ensures that if the symlink |
| 220 | // changes and "switches types" (say, from a file to a directory), this value will be |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 221 | // invalidated. We also need the target's type to properly process the symlink. |
| 222 | symlinkFileMap.put( |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 223 | FileValue.key( |
| 224 | RootedPath.toRootedPath( |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 225 | glob.getPackageRoot(), dirPathFragment.getRelative(fileName))), |
| 226 | dirent); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 227 | continue; |
Janak Ramakrishnan | 4bf0018 | 2016-02-29 19:59:44 +0000 | [diff] [blame] | 228 | } |
Janak Ramakrishnan | 4bf0018 | 2016-02-29 19:59:44 +0000 | [diff] [blame] | 229 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 230 | if (direntType == Dirent.Type.DIRECTORY) { |
| 231 | SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, subdirPattern); |
| 232 | if (keyToRequest != null) { |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 233 | subdirMap.put(keyToRequest, dirent); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 234 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 235 | } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 236 | sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName)); |
| 237 | } |
| 238 | } |
| 239 | |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 240 | Set<SkyKey> subdirAndSymlinksKeys = Sets.union(subdirMap.keySet(), symlinkFileMap.keySet()); |
| 241 | SkyframeIterableResult subdirAndSymlinksResult = |
| 242 | env.getOrderedValuesAndExceptions(subdirAndSymlinksKeys); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 243 | if (env.valuesMissing()) { |
| 244 | return null; |
| 245 | } |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 246 | Map<SkyKey, Dirent> symlinkSubdirMap = Maps.newHashMapWithExpectedSize(symlinkFileMap.size()); |
| 247 | // Second pass: process the symlinks and subdirectories from the first pass, and maybe |
| 248 | // collect further SkyKeys if fully resolved symlink targets are themselves directories. |
| 249 | // Also process any known directories. |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 250 | for (SkyKey subdirAndSymlinksKey : subdirAndSymlinksKeys) { |
| 251 | if (symlinkFileMap.containsKey(subdirAndSymlinksKey)) { |
| 252 | FileValue symlinkFileValue = (FileValue) subdirAndSymlinksResult.next(); |
| 253 | if (symlinkFileValue == null) { |
| 254 | return null; |
| 255 | } |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 256 | if (!symlinkFileValue.isSymlink()) { |
| 257 | throw new GlobFunctionException( |
| 258 | new InconsistentFilesystemException( |
| 259 | "readdir and stat disagree about whether " |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 260 | + ((RootedPath) subdirAndSymlinksKey.argument()).asPath() |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 261 | + " is a symlink."), |
| 262 | Transience.TRANSIENT); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 263 | } |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 264 | if (!symlinkFileValue.exists()) { |
| 265 | continue; |
| 266 | } |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 267 | |
| 268 | // This check is more strict than necessary: we raise an error if globbing traverses into |
| 269 | // a directory for any reason, even though it's only necessary if that reason was the |
| 270 | // resolution of a recursive glob ("**"). Fixing this would require plumbing the ancestor |
| 271 | // symlink information through DirectoryListingValue. |
| 272 | if (symlinkFileValue.isDirectory() |
| 273 | && symlinkFileValue.unboundedAncestorSymlinkExpansionChain() != null) { |
| 274 | SkyKey uniquenessKey = |
| 275 | FileSymlinkInfiniteExpansionUniquenessFunction.key( |
| 276 | symlinkFileValue.unboundedAncestorSymlinkExpansionChain()); |
| 277 | env.getValue(uniquenessKey); |
| 278 | if (env.valuesMissing()) { |
| 279 | return null; |
| 280 | } |
| 281 | |
| 282 | FileSymlinkInfiniteExpansionException symlinkException = |
| 283 | new FileSymlinkInfiniteExpansionException( |
| 284 | symlinkFileValue.pathToUnboundedAncestorSymlinkExpansionChain(), |
| 285 | symlinkFileValue.unboundedAncestorSymlinkExpansionChain()); |
| 286 | throw new GlobFunctionException(symlinkException, Transience.PERSISTENT); |
| 287 | } |
| 288 | |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 289 | Dirent dirent = symlinkFileMap.get(subdirAndSymlinksKey); |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 290 | String fileName = dirent.getName(); |
| 291 | if (symlinkFileValue.isDirectory()) { |
| 292 | SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, subdirPattern); |
| 293 | if (keyToRequest != null) { |
| 294 | symlinkSubdirMap.put(keyToRequest, dirent); |
| 295 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 296 | } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) { |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 297 | sortedResultMap.put(dirent, glob.getSubdir().getRelative(fileName)); |
| 298 | } |
| 299 | } else { |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 300 | SkyValue value = subdirAndSymlinksResult.next(); |
| 301 | if (value == null) { |
| 302 | return null; |
| 303 | } |
| 304 | processSubdir(Map.entry(subdirAndSymlinksKey, value), subdirMap, glob, sortedResultMap); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 305 | } |
| 306 | } |
| 307 | |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 308 | Set<SkyKey> symlinkSubdirKeys = symlinkSubdirMap.keySet(); |
| 309 | SkyframeIterableResult symlinkSubdirResult = |
| 310 | env.getOrderedValuesAndExceptions(symlinkSubdirKeys); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 311 | if (env.valuesMissing()) { |
| 312 | return null; |
| 313 | } |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 314 | // Third pass: do needed subdirectories of symlinked directories discovered during the second |
| 315 | // pass. |
emilyguo | 38722db | 2022-03-31 15:31:21 -0700 | [diff] [blame] | 316 | for (SkyKey symlinkSubdirKey : symlinkSubdirKeys) { |
| 317 | SkyValue symlinkSubdirValue = symlinkSubdirResult.next(); |
| 318 | if (symlinkSubdirValue == null) { |
| 319 | return null; |
| 320 | } |
| 321 | processSubdir( |
| 322 | Map.entry(symlinkSubdirKey, symlinkSubdirValue), |
| 323 | symlinkSubdirMap, |
| 324 | glob, |
| 325 | sortedResultMap); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 326 | } |
| 327 | for (Map.Entry<Dirent, Object> fileMatches : sortedResultMap.entrySet()) { |
| 328 | addToMatches(fileMatches.getValue(), matches); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 329 | } |
| 330 | } else { |
| 331 | // Pattern does not contain globs, so a direct stat is enough. |
| 332 | String fileName = patternHead; |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 333 | RootedPath fileRootedPath = |
| 334 | RootedPath.toRootedPath(glob.getPackageRoot(), dirPathFragment.getRelative(fileName)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 335 | FileValue fileValue = (FileValue) env.getValue(FileValue.key(fileRootedPath)); |
| 336 | if (fileValue == null) { |
| 337 | return null; |
| 338 | } |
| 339 | if (fileValue.exists()) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 340 | if (fileValue.isDirectory()) { |
| 341 | SkyKey keyToRequest = getSkyKeyForSubdir(fileName, glob, patternTail); |
| 342 | if (keyToRequest != null) { |
| 343 | SkyValue valueRequested = env.getValue(keyToRequest); |
| 344 | if (env.valuesMissing()) { |
| 345 | return null; |
| 346 | } |
| 347 | Object fileMatches = getSubdirMatchesFromSkyValue(fileName, glob, valueRequested); |
| 348 | if (fileMatches != null) { |
| 349 | addToMatches(fileMatches, matches); |
| 350 | } |
| 351 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 352 | } else if (globMatchesBareFile && globberOperation != Globber.Operation.SUBPACKAGES) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 353 | matches.add(glob.getSubdir().getRelative(fileName)); |
| 354 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 355 | } |
| 356 | } |
| 357 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 358 | Preconditions.checkState(!env.valuesMissing(), skyKey); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 359 | |
| 360 | NestedSet<PathFragment> matchesBuilt = matches.build(); |
| 361 | // Use the same value to represent that we did not match anything. |
| 362 | if (matchesBuilt.isEmpty()) { |
| 363 | return GlobValue.EMPTY; |
| 364 | } |
| 365 | return new GlobValue(matchesBuilt); |
| 366 | } |
| 367 | |
Janak Ramakrishnan | bce6fc5 | 2016-08-18 16:46:09 +0000 | [diff] [blame] | 368 | private static void processSubdir( |
| 369 | Map.Entry<SkyKey, SkyValue> keyAndValue, |
| 370 | Map<SkyKey, Dirent> subdirMap, |
| 371 | GlobDescriptor glob, |
| 372 | Map<Dirent, Object> sortedResultMap) { |
| 373 | Dirent dirent = Preconditions.checkNotNull(subdirMap.get(keyAndValue.getKey()), keyAndValue); |
| 374 | String fileName = dirent.getName(); |
| 375 | Object dirMatches = getSubdirMatchesFromSkyValue(fileName, glob, keyAndValue.getValue()); |
| 376 | if (dirMatches != null) { |
| 377 | sortedResultMap.put(dirent, dirMatches); |
| 378 | } |
| 379 | } |
| 380 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 381 | /** Returns true if the given pattern contains globs. */ |
| 382 | private static boolean containsGlobs(String pattern) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 383 | return pattern.contains("*") || pattern.contains("?"); |
| 384 | } |
| 385 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 386 | @SuppressWarnings("unchecked") // cast to NestedSet<PathFragment> |
| 387 | private static void addToMatches(Object toAdd, NestedSetBuilder<PathFragment> matches) { |
| 388 | if (toAdd instanceof PathFragment) { |
| 389 | matches.add((PathFragment) toAdd); |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 390 | } else if (toAdd instanceof NestedSet) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 391 | matches.addTransitive((NestedSet<PathFragment>) toAdd); |
| 392 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 393 | // else Not actually a valid type and ignore. |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 394 | } |
| 395 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 396 | /** |
| 397 | * Includes the given file/directory in the glob. |
| 398 | * |
| 399 | * <p>{@code fileName} must exist. |
| 400 | * |
| 401 | * <p>{@code isDirectory} must be true iff the file is a directory. |
| 402 | * |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 403 | * <p>Returns a {@link SkyKey} for a value that is needed to compute the files that will be added |
| 404 | * to {@code matches}, or {@code null} if no additional value is needed. The returned value should |
| 405 | * be opaquely passed to {@link #getSubdirMatchesFromSkyValue}. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 406 | */ |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 407 | private static SkyKey getSkyKeyForSubdir( |
| 408 | String fileName, GlobDescriptor glob, String subdirPattern) { |
| 409 | if (subdirPattern == null) { |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 410 | if (glob.globberOperation() == Globber.Operation.FILES) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 411 | return null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 412 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 413 | |
| 414 | // For FILES_AND_DIRS and SUBPACKAGES we want to maybe inspect a |
| 415 | // PackageLookupValue for it. |
| 416 | return PackageLookupValue.key( |
| 417 | PackageIdentifier.create( |
| 418 | glob.getPackageId().getRepository(), |
| 419 | glob.getPackageId() |
| 420 | .getPackageFragment() |
| 421 | .getRelative(glob.getSubdir()) |
| 422 | .getRelative(fileName))); |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 423 | } else { |
| 424 | // There is some more pattern to match. Get the glob for the subdirectory. Note that this |
| 425 | // directory may also match directly in the case of a pattern that starts with "**", but that |
| 426 | // match will be found in the subdirectory glob. |
| 427 | return GlobValue.internalKey( |
| 428 | glob.getPackageId(), |
| 429 | glob.getPackageRoot(), |
| 430 | glob.getSubdir().getRelative(fileName), |
| 431 | subdirPattern, |
kkress | 7dbabb4 | 2022-01-11 14:24:38 -0800 | [diff] [blame] | 432 | glob.globberOperation()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 433 | } |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 434 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 435 | |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 436 | /** |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 437 | * Returns an Object indicating a match was found for the given fileName in the given |
| 438 | * valueRequested. The Object will be one of: |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 439 | * |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 440 | * <ul> |
| 441 | * <li>{@code null} if no matches for the given parameters exists |
| 442 | * <li>{@code NestedSet<PathFragment>} if a match exists, either because we are looking for |
| 443 | * files/directories or the SkyValue is a package and we're globbing for {@link |
| 444 | * Globber.Operation.SUBPACKAGES} |
| 445 | * </ul> |
| 446 | * |
| 447 | * <p>{@code valueRequested} must be the SkyValue whose key was returned by {@link |
| 448 | * #getSkyKeyForSubdir} for these parameters. |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 449 | */ |
| 450 | @Nullable |
| 451 | private static Object getSubdirMatchesFromSkyValue( |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 452 | String fileName, GlobDescriptor glob, SkyValue valueRequested) { |
Janak Ramakrishnan | ce372c3 | 2016-03-14 16:19:18 +0000 | [diff] [blame] | 453 | if (valueRequested instanceof GlobValue) { |
| 454 | return ((GlobValue) valueRequested).getMatches(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 455 | } |
kkress | 986d4bd | 2022-01-18 14:48:34 -0800 | [diff] [blame] | 456 | |
| 457 | Preconditions.checkState( |
| 458 | valueRequested instanceof PackageLookupValue, |
| 459 | "%s is not a GlobValue or PackageLookupValue (%s %s)", |
| 460 | valueRequested, |
| 461 | fileName, |
| 462 | glob); |
| 463 | |
| 464 | PackageLookupValue packageLookupValue = (PackageLookupValue) valueRequested; |
| 465 | if (packageLookupValue |
| 466 | instanceof PackageLookupValue.IncorrectRepositoryReferencePackageLookupValue) { |
| 467 | // This is a separate repository, so ignore it. |
| 468 | return null; |
| 469 | } |
| 470 | |
| 471 | boolean isSubpackagesOp = glob.globberOperation() == Globber.Operation.SUBPACKAGES; |
| 472 | boolean pkgExists = packageLookupValue.packageExists(); |
| 473 | |
| 474 | if (!isSubpackagesOp && pkgExists) { |
| 475 | // We're in our repo and fileName is a package. Since we're not doing SUBPACKAGES listing, we |
| 476 | // do not want to add it to the results. |
| 477 | return null; |
| 478 | } else if (isSubpackagesOp && !pkgExists) { |
| 479 | // We're in our repo and the package exists. Since we're doing SUBPACKAGES listing, we do |
| 480 | // want to add fileName to the results. |
| 481 | return null; |
| 482 | } |
| 483 | |
| 484 | // The fileName should be added to the results of the glob. |
| 485 | return glob.getSubdir().getRelative(fileName); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 486 | } |
| 487 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 488 | /** |
| 489 | * Used to declare all the exception types that can be wrapped in the exception thrown by |
| 490 | * {@link GlobFunction#compute}. |
| 491 | */ |
| 492 | private static final class GlobFunctionException extends SkyFunctionException { |
| 493 | public GlobFunctionException(InconsistentFilesystemException e, Transience transience) { |
| 494 | super(e, transience); |
| 495 | } |
lberki | 7598bc6 | 2020-10-02 01:33:14 -0700 | [diff] [blame] | 496 | |
| 497 | public GlobFunctionException(FileSymlinkInfiniteExpansionException e, Transience transience) { |
| 498 | super(e, transience); |
| 499 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 500 | } |
| 501 | } |