blob: a486ddbf004b695be5139c69013b2b082be78050 [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;
20import com.google.common.collect.Iterables;
21import com.google.common.collect.Lists;
22import com.google.common.util.concurrent.SettableFuture;
Kristina Chodorow73fa2032015-08-28 17:57:46 +000023import com.google.devtools.build.lib.cmdline.PackageIdentifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.concurrent.ThreadSafety;
Nathan Harmatac5a15d32016-02-04 23:14:29 +000025import com.google.devtools.build.lib.packages.Globber.BadGlobException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.util.Pair;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.vfs.UnixGlob;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import java.io.IOException;
30import java.util.ArrayList;
31import java.util.Collection;
32import java.util.HashMap;
Nathan Harmata44e1e3a2016-08-23 21:22:17 +000033import java.util.HashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import java.util.List;
35import java.util.Map;
36import java.util.Set;
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +000037import java.util.concurrent.CancellationException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import java.util.concurrent.ExecutionException;
39import java.util.concurrent.Future;
40import java.util.concurrent.ThreadPoolExecutor;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000041import java.util.concurrent.atomic.AtomicBoolean;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import java.util.concurrent.atomic.AtomicReference;
43
44/**
45 * Caches the results of glob expansion for a package.
46 */
Googlercf80d7d2015-03-27 23:50:06 +000047 // Used outside of Bazel!
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048@ThreadSafety.ThreadCompatible
49public class GlobCache {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050 /**
51 * A mapping from glob expressions (e.g. "*.java") to the list of files it
52 * matched (in the order returned by VFS) at the time the package was
53 * constructed. Required for sound dependency analysis.
54 *
55 * We don't use a Multimap because it provides no way to distinguish "key not
56 * present" from (key -> {}).
57 */
58 private final Map<Pair<String, Boolean>, Future<List<Path>>> globCache = new HashMap<>();
59
60 /**
61 * The directory in which our package's BUILD file resides.
62 */
63 private final Path packageDirectory;
64
65 /**
66 * The name of the package we belong to.
67 */
68 private final PackageIdentifier packageId;
69
70 /**
71 * The package locator-based directory traversal predicate.
72 */
73 private final Predicate<Path> childDirectoryPredicate;
74
75 /**
76 * System call caching layer.
77 */
78 private AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000079 private final int maxDirectoriesToEagerlyVisit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080
81 /**
82 * The thread pool for glob evaluation.
83 */
84 private final ThreadPoolExecutor globExecutor;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000085 private final AtomicBoolean globalStarted = new AtomicBoolean(false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086
87 /**
88 * Create a glob expansion cache.
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000089 *
90 * @param packageDirectory globs will be expanded relatively to this directory.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091 * @param packageId the name of the package this cache belongs to.
92 * @param locator the package locator.
93 * @param globExecutor thread pool for glob evaluation.
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000094 * @param maxDirectoriesToEagerlyVisit the number of directories to eagerly traverse on the first
95 * glob for a given package, in order to warm the filesystem. -1 means do no eager traversal.
96 * See {@code PackageCacheOptions#maxDirectoriesToEagerlyVisitInGlobbing}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 */
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +000098 public GlobCache(
99 final Path packageDirectory,
100 final PackageIdentifier packageId,
101 final CachingPackageLocator locator,
102 AtomicReference<? extends UnixGlob.FilesystemCalls> syscalls,
103 ThreadPoolExecutor globExecutor,
104 int maxDirectoriesToEagerlyVisit) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105 this.packageDirectory = Preconditions.checkNotNull(packageDirectory);
106 this.packageId = Preconditions.checkNotNull(packageId);
107 this.globExecutor = Preconditions.checkNotNull(globExecutor);
108 this.syscalls = syscalls == null ? new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS) : syscalls;
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000109 this.maxDirectoriesToEagerlyVisit = maxDirectoriesToEagerlyVisit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100110
111 Preconditions.checkNotNull(locator);
laurentlb3d2a68c2017-06-30 00:32:04 +0200112 childDirectoryPredicate =
113 directory -> {
114 if (directory.equals(packageDirectory)) {
115 return true;
116 }
117 PackageIdentifier subPackageId =
118 PackageIdentifier.create(
119 packageId.getRepository(),
120 packageId
121 .getPackageFragment()
122 .getRelative(directory.relativeTo(packageDirectory)));
123 return locator.getBuildFileForPackage(subPackageId) == null;
124 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125 }
126
127 /**
128 * Returns the future result of evaluating glob "pattern" against this
129 * package's directory, using the package's cache of previously-started
130 * globs if possible.
131 *
132 * @return the list of paths matching the pattern, relative to the package's
133 * directory.
134 * @throws BadGlobException if the glob was syntactically invalid, or
135 * contained uplevel references.
136 */
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000137 Future<List<Path>> getGlobUnsortedAsync(String pattern, boolean excludeDirs)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100138 throws BadGlobException {
139 Future<List<Path>> cached = globCache.get(Pair.of(pattern, excludeDirs));
140 if (cached == null) {
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000141 if (maxDirectoriesToEagerlyVisit > -1
Googlerc804c662016-12-01 16:53:28 +0000142 && !globalStarted.getAndSet(true)) {
143 packageDirectory.prefetchPackageAsync(maxDirectoriesToEagerlyVisit);
Janak Ramakrishnan930e89c2016-10-04 20:51:42 +0000144 }
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000145 cached = safeGlobUnsorted(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100146 setGlobPaths(pattern, excludeDirs, cached);
147 }
148 return cached;
149 }
150
151 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000152 List<String> getGlobUnsorted(String pattern)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100153 throws IOException, BadGlobException, InterruptedException {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000154 return getGlobUnsorted(pattern, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100155 }
156
157 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000158 protected List<String> getGlobUnsorted(String pattern, boolean excludeDirs)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 throws IOException, BadGlobException, InterruptedException {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000160 Future<List<Path>> futureResult = getGlobUnsortedAsync(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100161 List<Path> globPaths = fromFuture(futureResult);
162 // Replace the UnixGlob.GlobFuture with a completed future object, to allow
163 // garbage collection of the GlobFuture and GlobVisitor objects.
164 if (!(futureResult instanceof SettableFuture<?>)) {
165 SettableFuture<List<Path>> completedFuture = SettableFuture.create();
166 completedFuture.set(globPaths);
167 globCache.put(Pair.of(pattern, excludeDirs), completedFuture);
168 }
169
170 List<String> result = Lists.newArrayListWithCapacity(globPaths.size());
171 for (Path path : globPaths) {
172 String relative = path.relativeTo(packageDirectory).getPathString();
173 // Don't permit "" (meaning ".") in the glob expansion, since it's
174 // invalid as a label, plus users should say explicitly if they
175 // really want to name the package directory.
176 if (!relative.isEmpty()) {
177 result.add(relative);
178 }
179 }
180 return result;
181 }
182
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000183 /** Adds glob entries to the cache. */
184 private void setGlobPaths(String pattern, boolean excludeDirectories, Future<List<Path>> result) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 globCache.put(Pair.of(pattern, excludeDirectories), result);
186 }
187
188 /**
189 * Actually execute a glob against the filesystem. Otherwise similar to
190 * getGlob().
191 */
192 @VisibleForTesting
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000193 Future<List<Path>> safeGlobUnsorted(String pattern, boolean excludeDirs) throws BadGlobException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 // Forbidden patterns:
195 if (pattern.indexOf('?') != -1) {
196 throw new BadGlobException("glob pattern '" + pattern + "' contains forbidden '?' wildcard");
197 }
198 // Patterns forbidden by UnixGlob library:
199 String error = UnixGlob.checkPatternForError(pattern);
200 if (error != null) {
201 throw new BadGlobException(error + " (in glob pattern '" + pattern + "')");
202 }
203 return UnixGlob.forPath(packageDirectory)
204 .addPattern(pattern)
205 .setExcludeDirectories(excludeDirs)
206 .setDirectoryFilter(childDirectoryPredicate)
207 .setThreadPool(globExecutor)
208 .setFilesystemCalls(syscalls)
209 .globAsync(true);
210 }
211
212 /**
213 * Sanitize the future exceptions - the only expected checked exception
214 * is IOException.
215 */
216 private static List<Path> fromFuture(Future<List<Path>> future)
217 throws IOException, InterruptedException {
218 try {
219 return future.get();
220 } catch (ExecutionException e) {
221 Throwable cause = e.getCause();
222 Throwables.propagateIfPossible(cause,
223 IOException.class, InterruptedException.class);
224 throw new RuntimeException(e);
225 }
226 }
227
228 /**
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000229 * Helper for evaluating the build language expression "glob(includes, excludes)" in the
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100230 * context of this package.
231 *
232 * <p>Called by PackageFactory via Package.
233 */
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000234 public List<String> globUnsorted(
235 List<String> includes,
236 List<String> excludes,
237 boolean excludeDirs) throws IOException, BadGlobException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 // Start globbing all patterns in parallel. The getGlob() calls below will
239 // block on an individual pattern's results, but the other globs can
240 // continue in the background.
241 for (String pattern : Iterables.concat(includes, excludes)) {
Googler78cae6d2017-01-24 23:07:40 +0000242 @SuppressWarnings("unused")
243 Future<?> possiblyIgnoredError = getGlobUnsortedAsync(pattern, excludeDirs);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100244 }
245
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000246 HashSet<String> results = new HashSet<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100247 for (String pattern : includes) {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000248 results.addAll(getGlobUnsorted(pattern, excludeDirs));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249 }
250 for (String pattern : excludes) {
Nathan Harmata44e1e3a2016-08-23 21:22:17 +0000251 for (String excludeMatch : getGlobUnsorted(pattern, excludeDirs)) {
Nathan Harmata3ae80a72016-03-16 19:04:26 +0000252 results.remove(excludeMatch);
253 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100254 }
255
256 Preconditions.checkState(!results.contains(null), "glob returned null");
257 return new ArrayList<>(results);
258 }
259
260 public Set<Pair<String, Boolean>> getKeySet() {
261 return globCache.keySet();
262 }
263
264 /**
265 * Block on the completion of all potentially-abandoned background tasks.
266 */
267 public void finishBackgroundTasks() {
268 finishBackgroundTasks(globCache.values());
269 }
270
271 public void cancelBackgroundTasks() {
272 cancelBackgroundTasks(globCache.values());
273 }
274
275 private static void finishBackgroundTasks(Collection<Future<List<Path>>> tasks) {
276 for (Future<List<Path>> task : tasks) {
277 try {
278 fromFuture(task);
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000279 } catch (CancellationException | IOException | InterruptedException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100280 // Ignore: If this was still going on in the background, some other
281 // failure already occurred.
282 }
283 }
284 }
285
286 private static void cancelBackgroundTasks(Collection<Future<List<Path>>> tasks) {
287 for (Future<List<Path>> task : tasks) {
288 task.cancel(true);
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000289 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100290
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000291 for (Future<List<Path>> task : tasks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 try {
293 task.get();
Eric Fellheimer20e2f6e2015-06-26 20:58:18 +0000294 } catch (CancellationException | ExecutionException | InterruptedException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295 // We don't care. Point is, the task does not bother us anymore.
296 }
297 }
298 }
299
300 @Override
301 public String toString() {
302 return "GlobCache for " + packageId + " in " + packageDirectory;
303 }
304}