blob: 3e3c1af99ffd3071982351d0f89c95f590591c02 [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.packages;
15
16import com.google.common.annotations.VisibleForTesting;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.base.Predicate;
19import com.google.common.base.Throwables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.Lists;
21import com.google.common.util.concurrent.SettableFuture;
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.concurrent.ThreadSafety;
Nathan Harmatac5a15d32016-02-04 23:14:29 +000024import com.google.devtools.build.lib.packages.Globber.BadGlobException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.util.Pair;
26import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.vfs.UnixGlob;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import java.io.IOException;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.HashMap;
Nathan Harmata44e1e3a2016-08-23 21:22:17 +000032import java.util.HashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.List;
34import java.util.Map;
35import java.util.Set;
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +000036import java.util.concurrent.CancellationException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import java.util.concurrent.ExecutionException;
djasper7567fb32019-02-23 00:36:52 -080038import java.util.concurrent.Executor;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039import java.util.concurrent.Future;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000040import java.util.concurrent.atomic.AtomicBoolean;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041import java.util.concurrent.atomic.AtomicReference;
42
43/**
44 * Caches the results of glob expansion for a package.
45 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046@ThreadSafety.ThreadCompatible
47public class GlobCache {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048 /**
49 * A mapping from glob expressions (e.g. "*.java") to the list of files it
50 * matched (in the order returned by VFS) at the time the package was
51 * constructed. Required for sound dependency analysis.
52 *
53 * We don't use a Multimap because it provides no way to distinguish "key not
54 * present" from (key -> {}).
55 */
56 private final Map<Pair<String, Boolean>, Future<List<Path>>> globCache = new HashMap<>();
57
58 /**
59 * The directory in which our package's BUILD file resides.
60 */
61 private final Path packageDirectory;
62
63 /**
64 * The name of the package we belong to.
65 */
66 private final PackageIdentifier packageId;
67
68 /**
69 * The package locator-based directory traversal predicate.
70 */
71 private final Predicate<Path> childDirectoryPredicate;
72
73 /**
74 * System call caching layer.
75 */
76 private AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000077 private final int maxDirectoriesToEagerlyVisit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078
djasper7567fb32019-02-23 00:36:52 -080079 /** The thread pool for glob evaluation. */
80 private final Executor globExecutor;
81
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000082 private final AtomicBoolean globalStarted = new AtomicBoolean(false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083
84 /**
85 * Create a glob expansion cache.
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000086 *
87 * @param packageDirectory globs will be expanded relatively to this directory.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010088 * @param packageId the name of the package this cache belongs to.
89 * @param locator the package locator.
90 * @param globExecutor thread pool for glob evaluation.
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000091 * @param maxDirectoriesToEagerlyVisit the number of directories to eagerly traverse on the first
92 * glob for a given package, in order to warm the filesystem. -1 means do no eager traversal.
93 * See {@code PackageCacheOptions#maxDirectoriesToEagerlyVisitInGlobbing}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094 */
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000095 public GlobCache(
96 final Path packageDirectory,
97 final PackageIdentifier packageId,
98 final CachingPackageLocator locator,
99 AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls,
djasper7567fb32019-02-23 00:36:52 -0800100 Executor globExecutor,
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000101 int maxDirectoriesToEagerlyVisit) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100102 this.packageDirectory = Preconditions.checkNotNull(packageDirectory);
103 this.packageId = Preconditions.checkNotNull(packageId);
104 this.globExecutor = Preconditions.checkNotNull(globExecutor);
105 this.syscalls = syscalls == null ? new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS) : syscalls;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000106 this.maxDirectoriesToEagerlyVisit = maxDirectoriesToEagerlyVisit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107
108 Preconditions.checkNotNull(locator);
laurentlb3d2a68c2017-06-30 00:32:04 +0200109 childDirectoryPredicate =
110 directory -> {
111 if (directory.equals(packageDirectory)) {
112 return true;
113 }
114 PackageIdentifier subPackageId =
115 PackageIdentifier.create(
116 packageId.getRepository(),
117 packageId
118 .getPackageFragment()
119 .getRelative(directory.relativeTo(packageDirectory)));
120 return locator.getBuildFileForPackage(subPackageId) == null;
121 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122 }
123
124 /**
125 * Returns the future result of evaluating glob "pattern" against this
126 * package's directory, using the package's cache of previously-started
127 * globs if possible.
128 *
129 * @return the list of paths matching the pattern, relative to the package's
130 * directory.
131 * @throws BadGlobException if the glob was syntactically invalid, or
132 * contained uplevel references.
133 */
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000134 Future<List<Path>> getGlobUnsortedAsync(String pattern, boolean excludeDirs)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100135 throws BadGlobException {
136 Future<List<Path>> cached = globCache.get(Pair.of(pattern, excludeDirs));
137 if (cached == null) {
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000138 if (maxDirectoriesToEagerlyVisit > -1
Googlerc804c662016-12-01 16:53:28 +0000139 && !globalStarted.getAndSet(true)) {
140 packageDirectory.prefetchPackageAsync(maxDirectoriesToEagerlyVisit);
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000141 }
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000142 cached = safeGlobUnsorted(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 setGlobPaths(pattern, excludeDirs, cached);
144 }
145 return cached;
146 }
147
148 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000149 List<String> getGlobUnsorted(String pattern)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100150 throws IOException, BadGlobException, InterruptedException {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000151 return getGlobUnsorted(pattern, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100152 }
153
154 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000155 protected List<String> getGlobUnsorted(String pattern, boolean excludeDirs)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 throws IOException, BadGlobException, InterruptedException {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000157 Future<List<Path>> futureResult = getGlobUnsortedAsync(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100158 List<Path> globPaths = fromFuture(futureResult);
159 // Replace the UnixGlob.GlobFuture with a completed future object, to allow
160 // garbage collection of the GlobFuture and GlobVisitor objects.
161 if (!(futureResult instanceof SettableFuture<?>)) {
162 SettableFuture<List<Path>> completedFuture = SettableFuture.create();
163 completedFuture.set(globPaths);
164 globCache.put(Pair.of(pattern, excludeDirs), completedFuture);
165 }
166
167 List<String> result = Lists.newArrayListWithCapacity(globPaths.size());
168 for (Path path : globPaths) {
169 String relative = path.relativeTo(packageDirectory).getPathString();
170 // Don't permit "" (meaning ".") in the glob expansion, since it's
171 // invalid as a label, plus users should say explicitly if they
172 // really want to name the package directory.
173 if (!relative.isEmpty()) {
twerthc4f2d802018-05-15 05:02:31 -0700174 if (relative.charAt(0) == '@') {
175 // Add explicit colon to disambiguate from external repository.
176 relative = ":" + relative;
177 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100178 result.add(relative);
179 }
180 }
181 return result;
182 }
183
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000184 /** Adds glob entries to the cache. */
185 private void setGlobPaths(String pattern, boolean excludeDirectories, Future<List<Path>> result) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100186 globCache.put(Pair.of(pattern, excludeDirectories), result);
187 }
188
189 /**
190 * Actually execute a glob against the filesystem. Otherwise similar to
191 * getGlob().
192 */
193 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000194 Future<List<Path>> safeGlobUnsorted(String pattern, boolean excludeDirs) throws BadGlobException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100195 // Forbidden patterns:
196 if (pattern.indexOf('?') != -1) {
197 throw new BadGlobException("glob pattern '" + pattern + "' contains forbidden '?' wildcard");
198 }
199 // Patterns forbidden by UnixGlob library:
200 String error = UnixGlob.checkPatternForError(pattern);
201 if (error != null) {
202 throw new BadGlobException(error + " (in glob pattern '" + pattern + "')");
203 }
204 return UnixGlob.forPath(packageDirectory)
205 .addPattern(pattern)
206 .setExcludeDirectories(excludeDirs)
207 .setDirectoryFilter(childDirectoryPredicate)
djasper7567fb32019-02-23 00:36:52 -0800208 .setExecutor(globExecutor)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 .setFilesystemCalls(syscalls)
fellyf4695742018-09-26 08:55:24 -0700210 .globAsync();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100211 }
212
213 /**
214 * Sanitize the future exceptions - the only expected checked exception
215 * is IOException.
216 */
217 private static List<Path> fromFuture(Future<List<Path>> future)
218 throws IOException, InterruptedException {
219 try {
220 return future.get();
221 } catch (ExecutionException e) {
222 Throwable cause = e.getCause();
223 Throwables.propagateIfPossible(cause,
224 IOException.class, InterruptedException.class);
225 throw new RuntimeException(e);
226 }
227 }
228
229 /**
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000230 * Helper for evaluating the build language expression "glob(includes, excludes)" in the
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100231 * context of this package.
232 *
233 * <p>Called by PackageFactory via Package.
234 */
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000235 public List<String> globUnsorted(
236 List<String> includes,
237 List<String> excludes,
238 boolean excludeDirs) throws IOException, BadGlobException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239 // Start globbing all patterns in parallel. The getGlob() calls below will
240 // block on an individual pattern's results, but the other globs can
241 // continue in the background.
djasper6371f552019-02-20 14:32:19 -0800242 for (String pattern : includes) {
Googler78cae6d2017-01-24 23:07:40 +0000243 @SuppressWarnings("unused")
244 Future<?> possiblyIgnoredError = getGlobUnsortedAsync(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100245 }
246
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000247 HashSet<String> results = new HashSet<>();
djasper6371f552019-02-20 14:32:19 -0800248 Preconditions.checkState(!results.contains(null), "glob returned null");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249 for (String pattern : includes) {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000250 results.addAll(getGlobUnsorted(pattern, excludeDirs));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100251 }
djasper6371f552019-02-20 14:32:19 -0800252 UnixGlob.removeExcludes(results, excludes);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 return new ArrayList<>(results);
254 }
255
256 public Set<Pair<String, Boolean>> getKeySet() {
257 return globCache.keySet();
258 }
259
260 /**
261 * Block on the completion of all potentially-abandoned background tasks.
262 */
263 public void finishBackgroundTasks() {
264 finishBackgroundTasks(globCache.values());
265 }
266
267 public void cancelBackgroundTasks() {
268 cancelBackgroundTasks(globCache.values());
269 }
270
271 private static void finishBackgroundTasks(Collection<Future<List<Path>>> tasks) {
272 for (Future<List<Path>> task : tasks) {
273 try {
274 fromFuture(task);
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000275 } catch (CancellationException | IOException | InterruptedException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 // Ignore: If this was still going on in the background, some other
277 // failure already occurred.
278 }
279 }
280 }
281
282 private static void cancelBackgroundTasks(Collection<Future<List<Path>>> tasks) {
283 for (Future<List<Path>> task : tasks) {
284 task.cancel(true);
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000285 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100286
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000287 for (Future<List<Path>> task : tasks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100288 try {
289 task.get();
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000290 } catch (CancellationException | ExecutionException | InterruptedException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 // We don't care. Point is, the task does not bother us anymore.
292 }
293 }
294 }
295
296 @Override
297 public String toString() {
298 return "GlobCache for " + packageId + " in " + packageDirectory;
299 }
300}