blob: 21eb22f4f3b8470e02c18f142727937e8a124fac [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.
14
15package com.google.devtools.build.lib.cmdline;
16
janakr3c38c412021-01-21 08:49:01 -080017import static com.google.common.util.concurrent.Futures.immediateCancelledFuture;
janakr59f172e2021-01-22 11:22:00 -080018import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
janakr3c38c412021-01-21 08:49:01 -080019
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.annotations.VisibleForTesting;
21import com.google.common.base.Joiner;
tomlua155b532017-11-08 20:12:47 +010022import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.base.Splitter;
24import com.google.common.collect.ImmutableList;
dannark90e2b4b2018-06-27 13:35:04 -070025import com.google.common.collect.ImmutableMap;
Mark Schallerf6e32d62015-05-19 20:27:43 +000026import com.google.common.collect.ImmutableSet;
Nathan Harmata7a5a2362017-03-08 22:42:01 +000027import com.google.common.util.concurrent.Futures;
28import com.google.common.util.concurrent.ListenableFuture;
29import com.google.common.util.concurrent.ListeningExecutorService;
nharmata80ed2662019-04-30 13:17:47 -070030import com.google.devtools.build.lib.cmdline.LabelValidator.BadLabelException;
31import com.google.devtools.build.lib.cmdline.LabelValidator.PackageAndTarget;
adgarec7084d2019-08-06 17:02:41 -070032import com.google.devtools.build.lib.concurrent.BatchCallback;
Googler35b92d02020-08-03 07:42:11 -070033import com.google.devtools.build.lib.server.FailureDetails.TargetPatterns;
janakr3c38c412021-01-21 08:49:01 -080034import com.google.devtools.build.lib.supplier.InterruptibleSupplier;
Lukacs Berki960dc272015-09-24 07:48:36 +000035import com.google.devtools.build.lib.util.StringUtilities;
Lukacs Berki8096c422015-10-14 15:12:23 +000036import com.google.devtools.build.lib.vfs.PathFragment;
adgarb70e6362019-11-21 16:54:09 -080037import com.google.errorprone.annotations.CheckReturnValue;
38import com.google.errorprone.annotations.CompileTimeConstant;
Mark Schaller111634a52015-05-19 19:48:05 +000039import java.io.Serializable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040import java.util.ArrayList;
41import java.util.Iterator;
42import java.util.List;
Klaus Aehlig9a96f4c2019-07-08 05:05:05 -070043import java.util.Map;
Mark Schaller111634a52015-05-19 19:48:05 +000044import java.util.Objects;
Nathan Harmata985ed702016-03-01 23:09:29 +000045import java.util.regex.Pattern;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046import javax.annotation.concurrent.Immutable;
47
48/**
49 * Represents a target pattern. Target patterns are a generalization of labels to include
50 * wildcards for finding all packages recursively beneath some root, and for finding all targets
51 * within a package.
52 *
53 * <p>Note that this class does not handle negative patterns ("-//foo/bar"); these must be handled
54 * one level up. In particular, the query language comes with built-in support for negative
55 * patterns.
56 *
57 * <p>In order to resolve target patterns, you need an implementation of {@link
58 * TargetPatternResolver}. This class is thread-safe if the corresponding instance is thread-safe.
59 *
60 * <p>See lib/blaze/commands/target-syntax.txt for details.
61 */
Mark Schaller111634a52015-05-19 19:48:05 +000062public abstract class TargetPattern implements Serializable {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010063
64 private static final Splitter SLASH_SPLITTER = Splitter.on('/');
65 private static final Joiner SLASH_JOINER = Joiner.on('/');
66
janakra3652a32020-09-10 12:05:20 -070067 private static final Parser DEFAULT_PARSER = new Parser(PathFragment.EMPTY_FRAGMENT);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068
Mark Schaller66b35f32015-05-19 21:19:37 +000069 private final String originalPattern;
janakra3652a32020-09-10 12:05:20 -070070 private final PathFragment offset;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010071
72 /**
73 * Returns a parser with no offset. Note that the Parser class is immutable, so this method may
74 * return the same instance on subsequent calls.
75 */
76 public static Parser defaultParser() {
77 return DEFAULT_PARSER;
78 }
79
80 private static String removeSuffix(String s, String suffix) {
81 if (s.endsWith(suffix)) {
82 return s.substring(0, s.length() - suffix.length());
83 } else {
84 throw new IllegalArgumentException(s + ", " + suffix);
85 }
86 }
87
88 /**
89 * Normalizes the given relative path by resolving {@code //}, {@code /./} and {@code x/../}
90 * pieces. Note that leading {@code ".."} segments are not removed, so the returned string can
91 * have leading {@code ".."} segments.
92 *
93 * @throws IllegalArgumentException if the path is absolute, i.e. starts with a @{code '/'}
94 */
95 @VisibleForTesting
96 static String normalize(String path) {
97 Preconditions.checkArgument(!path.startsWith("/"));
Kristina Chodorow70d5dd02015-05-29 14:51:31 +000098 Preconditions.checkArgument(!path.startsWith("@"));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099 Iterator<String> it = SLASH_SPLITTER.split(path).iterator();
100 List<String> pieces = new ArrayList<>();
101 while (it.hasNext()) {
102 String piece = it.next();
103 if (".".equals(piece) || piece.isEmpty()) {
104 continue;
105 }
106 if ("..".equals(piece)) {
107 if (pieces.isEmpty()) {
108 pieces.add(piece);
109 continue;
110 }
111 String predecessor = pieces.remove(pieces.size() - 1);
112 if ("..".equals(predecessor)) {
113 pieces.add(piece);
114 pieces.add(piece);
115 }
116 continue;
117 }
118 pieces.add(piece);
119 }
120 return SLASH_JOINER.join(pieces);
121 }
122
janakr7b766832020-12-30 08:16:25 -0800123 private TargetPattern(String originalPattern, PathFragment offset) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100124 // Don't allow inheritance outside this class.
Mark Schaller66b35f32015-05-19 21:19:37 +0000125 this.originalPattern = Preconditions.checkNotNull(originalPattern);
Lukacs Berki550cfae2015-09-25 08:20:16 +0000126 this.offset = Preconditions.checkNotNull(offset);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127 }
128
129 /**
Mark Schaller111634a52015-05-19 19:48:05 +0000130 * Return the type of the pattern. Examples include "below directory" like "foo/..." and "single
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131 * target" like "//x:y".
132 */
janakr7b766832020-12-30 08:16:25 -0800133 public abstract Type getType();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100134
135 /**
Mark Schaller66b35f32015-05-19 21:19:37 +0000136 * Return the string that was parsed into this pattern.
137 */
138 public String getOriginalPattern() {
139 return originalPattern;
140 }
141
janakra3652a32020-09-10 12:05:20 -0700142 /** Returns the offset this target pattern was parsed with. */
143 public PathFragment getOffset() {
Lukacs Berki550cfae2015-09-25 08:20:16 +0000144 return offset;
145 }
146
147 /**
kkress1847a012020-06-24 12:30:11 -0700148 * Evaluates the current target pattern, excluding targets under directories in both {@code
149 * ignoredSubdirectories} and {@code excludedSubdirectories}, and returns the result.
Mark Schallerf6e32d62015-05-19 20:27:43 +0000150 *
kkress1847a012020-06-24 12:30:11 -0700151 * @throws IllegalArgumentException if either {@code ignoredSubdirectories} or {@code
152 * excludedSubdirectories} is nonempty and this pattern does not have type {@code
153 * Type.TARGETS_BELOW_DIRECTORY}.
Mark Schallerf6e32d62015-05-19 20:27:43 +0000154 */
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000155 public abstract <T, E extends Exception> void eval(
156 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800157 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Janak Ramakrishnan3d9441b2016-01-13 17:38:29 +0000158 ImmutableSet<PathFragment> excludedSubdirectories,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000159 BatchCallback<T, E> callback,
160 Class<E> exceptionClass)
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000161 throws TargetParsingException, E, InterruptedException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100162
Mark Schaller111634a52015-05-19 19:48:05 +0000163 /**
kkress1847a012020-06-24 12:30:11 -0700164 * Evaluates this {@link TargetPattern} synchronously, feeding the result to the given {@code
165 * callback}, and then returns an appropriate immediate {@link ListenableFuture}.
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000166 *
kkress1847a012020-06-24 12:30:11 -0700167 * <p>If the returned {@link ListenableFuture}'s {@link ListenableFuture#get} throws an {@link
168 * ExecutionException}, the cause will be an instance of either {@link TargetParsingException} or
169 * the given {@code exceptionClass}.
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000170 */
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000171 public final <T, E extends Exception> ListenableFuture<Void> evalAdaptedForAsync(
172 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800173 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000174 ImmutableSet<PathFragment> excludedSubdirectories,
Googler54be05a2020-06-29 09:20:32 -0700175 BatchCallback<T, E> callback,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000176 Class<E> exceptionClass) {
177 try {
kkress1847a012020-06-24 12:30:11 -0700178 eval(resolver, ignoredSubdirectories, excludedSubdirectories, callback, exceptionClass);
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000179 return Futures.immediateFuture(null);
180 } catch (TargetParsingException e) {
181 return Futures.immediateFailedFuture(e);
182 } catch (InterruptedException e) {
janakr3c38c412021-01-21 08:49:01 -0800183 return immediateCancelledFuture();
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000184 } catch (Exception e) {
185 if (exceptionClass.isInstance(e)) {
186 return Futures.immediateFailedFuture(exceptionClass.cast(e));
187 }
188 throw new IllegalStateException(e);
189 }
190 }
191
192 /**
kkress1847a012020-06-24 12:30:11 -0700193 * Returns a {@link ListenableFuture} representing the asynchronous evaluation of this {@link
194 * TargetPattern} that feeds the results to the given {@code callback}.
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000195 *
kkress1847a012020-06-24 12:30:11 -0700196 * <p>If the returned {@link ListenableFuture}'s {@link ListenableFuture#get} throws an {@link
197 * ExecutionException}, the cause will be an instance of either {@link TargetParsingException} or
198 * the given {@code exceptionClass}.
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000199 */
200 public <T, E extends Exception> ListenableFuture<Void> evalAsync(
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000201 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800202 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000203 ImmutableSet<PathFragment> excludedSubdirectories,
Googler54be05a2020-06-29 09:20:32 -0700204 BatchCallback<T, E> callback,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000205 Class<E> exceptionClass,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000206 ListeningExecutorService executor) {
nharmatade0c5352017-07-25 17:39:09 +0200207 return evalAdaptedForAsync(
kkress1847a012020-06-24 12:30:11 -0700208 resolver, ignoredSubdirectories, excludedSubdirectories, callback, exceptionClass);
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000209 }
210
211 /**
Nathan Harmata986e3b02017-01-13 23:54:10 +0000212 * For patterns of type {@link Type#PATH_AS_TARGET}, returns the path in question.
213 *
cparsonsdac21352019-05-03 11:41:17 -0700214 * <p>The interpretation of this path, of course, depends on the existence of packages.
215 * See {@link InterpretPathAsTarget#eval}.
Nathan Harmata986e3b02017-01-13 23:54:10 +0000216 */
217 public String getPathForPathAsTarget() {
218 throw new IllegalStateException();
219 }
220
nharmata80ed2662019-04-30 13:17:47 -0700221 /** For patterns of type {@link Type#SINGLE_TARGET}, returns the target path. */
222 public String getSingleTargetPath() {
Googler5e65c982017-08-17 05:21:54 +0200223 throw new IllegalStateException();
224 }
225
Nathan Harmata986e3b02017-01-13 23:54:10 +0000226 /**
227 * For patterns of type {@link Type#SINGLE_TARGET} and {@link Type#TARGETS_IN_PACKAGE}, returns
228 * the {@link PackageIdentifier} corresponding to the package that would contain the target(s)
229 * matched by this {@link TargetPattern}.
230 */
231 public PackageIdentifier getDirectoryForTargetOrTargetsInPackage() {
232 throw new IllegalStateException();
233 }
Mark Schaller111634a52015-05-19 19:48:05 +0000234
Yun Pengbf2c4d92019-12-13 09:21:28 -0800235 /** Returns the repository name of the target pattern. */
236 public abstract RepositoryName getRepository();
237
Mark Schallerd7311e02015-07-07 16:36:09 +0000238 /**
239 * Returns {@code true} iff this pattern has type {@code Type.TARGETS_BELOW_DIRECTORY} or
240 * {@code Type.TARGETS_IN_PACKAGE} and the target pattern suffix specified it should match
241 * rules only.
242 */
243 public abstract boolean getRulesOnly();
244
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100245 private static final class SingleTarget extends TargetPattern {
246
nharmata80ed2662019-04-30 13:17:47 -0700247 private final String targetName;
248 private final PackageIdentifier directory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249
nharmata80ed2662019-04-30 13:17:47 -0700250 private SingleTarget(
janakra3652a32020-09-10 12:05:20 -0700251 String targetName,
252 PackageIdentifier directory,
253 String originalPattern,
254 PathFragment offset) {
janakr7b766832020-12-30 08:16:25 -0800255 super(originalPattern, offset);
nharmata80ed2662019-04-30 13:17:47 -0700256 this.targetName = Preconditions.checkNotNull(targetName);
257 this.directory = Preconditions.checkNotNull(directory);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100258 }
259
260 @Override
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000261 public <T, E extends Exception> void eval(
262 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800263 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Janak Ramakrishnan3d9441b2016-01-13 17:38:29 +0000264 ImmutableSet<PathFragment> excludedSubdirectories,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000265 BatchCallback<T, E> callback,
kkress1847a012020-06-24 12:30:11 -0700266 Class<E> exceptionClass)
267 throws TargetParsingException, E, InterruptedException {
cparsonsdac21352019-05-03 11:41:17 -0700268 callback.process(resolver.getExplicitTarget(label(targetName)).getTargets());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100269 }
Mark Schaller111634a52015-05-19 19:48:05 +0000270
271 @Override
Nathan Harmata986e3b02017-01-13 23:54:10 +0000272 public PackageIdentifier getDirectoryForTargetOrTargetsInPackage() {
nharmata80ed2662019-04-30 13:17:47 -0700273 return directory;
Mark Schaller111634a52015-05-19 19:48:05 +0000274 }
275
276 @Override
Yun Pengbf2c4d92019-12-13 09:21:28 -0800277 public RepositoryName getRepository() {
278 return directory.getRepository();
279 }
280
281 @Override
Mark Schallerd7311e02015-07-07 16:36:09 +0000282 public boolean getRulesOnly() {
283 return false;
284 }
285
286 @Override
nharmata80ed2662019-04-30 13:17:47 -0700287 public String getSingleTargetPath() {
288 return targetName;
Googler5e65c982017-08-17 05:21:54 +0200289 }
290
291 @Override
janakr7b766832020-12-30 08:16:25 -0800292 public Type getType() {
293 return Type.SINGLE_TARGET;
294 }
295
296 @Override
Mark Schaller111634a52015-05-19 19:48:05 +0000297 public boolean equals(Object o) {
298 if (this == o) {
299 return true;
300 }
301 if (!(o instanceof SingleTarget)) {
302 return false;
303 }
304 SingleTarget that = (SingleTarget) o;
nharmata80ed2662019-04-30 13:17:47 -0700305 return targetName.equals(that.targetName) && directory.equals(that.directory);
Mark Schaller111634a52015-05-19 19:48:05 +0000306 }
307
308 @Override
309 public int hashCode() {
nharmata80ed2662019-04-30 13:17:47 -0700310 return Objects.hash(getType(), targetName, directory);
Mark Schaller111634a52015-05-19 19:48:05 +0000311 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100312 }
313
314 private static final class InterpretPathAsTarget extends TargetPattern {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100315 private final String path;
316
janakra3652a32020-09-10 12:05:20 -0700317 private InterpretPathAsTarget(String path, String originalPattern, PathFragment offset) {
janakr7b766832020-12-30 08:16:25 -0800318 super(originalPattern, offset);
Mark Schaller111634a52015-05-19 19:48:05 +0000319 this.path = normalize(Preconditions.checkNotNull(path));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100320 }
321
322 @Override
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000323 public <T, E extends Exception> void eval(
324 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800325 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Janak Ramakrishnan3d9441b2016-01-13 17:38:29 +0000326 ImmutableSet<PathFragment> excludedSubdirectories,
kkress1847a012020-06-24 12:30:11 -0700327 BatchCallback<T, E> callback,
328 Class<E> exceptionClass)
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000329 throws TargetParsingException, E, InterruptedException {
Brian Silvermand7d6d622016-03-17 09:53:39 +0000330 if (resolver.isPackage(PackageIdentifier.createInMainRepo(path))) {
Damien Martin-Guillerez52ac3562015-02-25 18:55:53 +0000331 // User has specified a package name. lookout for default target.
cparsonsdac21352019-05-03 11:41:17 -0700332 callback.process(resolver.getExplicitTarget(label("//" + path)).getTargets());
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000333 } else {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100334
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000335 List<String> pieces = SLASH_SPLITTER.splitToList(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100336
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000337 // Interprets the label as a file target. This loop stops as soon as the
338 // first BUILD file is found (i.e. longest prefix match).
Nathan Harmata1c155692016-04-22 18:32:11 +0000339 for (int i = pieces.size() - 1; i >= 0; i--) {
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000340 String packageName = SLASH_JOINER.join(pieces.subList(0, i));
Brian Silvermand7d6d622016-03-17 09:53:39 +0000341 if (resolver.isPackage(PackageIdentifier.createInMainRepo(packageName))) {
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000342 String targetName = SLASH_JOINER.join(pieces.subList(i, pieces.size()));
343 callback.process(
344 resolver
cparsonsdac21352019-05-03 11:41:17 -0700345 .getExplicitTarget(label("//" + packageName + ":" + targetName))
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000346 .getTargets());
347 return;
348 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100349 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350
Googler35b92d02020-08-03 07:42:11 -0700351 throw new TargetParsingException(
352 "couldn't determine target from filename '" + path + "'",
353 TargetPatterns.Code.CANNOT_DETERMINE_TARGET_FROM_FILENAME);
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000354 }
Mark Schaller111634a52015-05-19 19:48:05 +0000355 }
356
357 @Override
Nathan Harmata986e3b02017-01-13 23:54:10 +0000358 public String getPathForPathAsTarget() {
359 return path;
Mark Schaller111634a52015-05-19 19:48:05 +0000360 }
361
362 @Override
Yun Pengbf2c4d92019-12-13 09:21:28 -0800363 public RepositoryName getRepository() {
364 // InterpretPathAsTarget is validated by PackageIdentifier.createInMainRepo,
365 // therefore it must belong to the main repository.
366 return RepositoryName.MAIN;
367 }
368
369 @Override
Mark Schallerd7311e02015-07-07 16:36:09 +0000370 public boolean getRulesOnly() {
371 return false;
372 }
373
374 @Override
janakr7b766832020-12-30 08:16:25 -0800375 public Type getType() {
376 return Type.PATH_AS_TARGET;
377 }
378
379 @Override
Mark Schaller111634a52015-05-19 19:48:05 +0000380 public boolean equals(Object o) {
381 if (this == o) {
382 return true;
383 }
384 if (!(o instanceof InterpretPathAsTarget)) {
385 return false;
386 }
387 InterpretPathAsTarget that = (InterpretPathAsTarget) o;
388 return path.equals(that.path);
389 }
390
391 @Override
392 public int hashCode() {
393 return Objects.hash(getType(), path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100394 }
395 }
396
397 private static final class TargetsInPackage extends TargetPattern {
Lukacs Berki960dc272015-09-24 07:48:36 +0000398 private final PackageIdentifier packageIdentifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 private final String suffix;
Nathan Harmata985ed702016-03-01 23:09:29 +0000400 private final boolean wasOriginallyAbsolute;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100401 private final boolean rulesOnly;
402 private final boolean checkWildcardConflict;
403
janakra3652a32020-09-10 12:05:20 -0700404 private TargetsInPackage(
405 String originalPattern,
406 PathFragment offset,
407 PackageIdentifier packageIdentifier,
408 String suffix,
409 boolean wasOriginallyAbsolute,
410 boolean rulesOnly,
411 boolean checkWildcardConflict) {
janakr7b766832020-12-30 08:16:25 -0800412 super(originalPattern, offset);
Lukacs Berki14ab5672016-03-31 07:52:15 +0000413 Preconditions.checkArgument(!packageIdentifier.getRepository().isDefault());
Lukacs Berki960dc272015-09-24 07:48:36 +0000414 this.packageIdentifier = packageIdentifier;
Mark Schaller111634a52015-05-19 19:48:05 +0000415 this.suffix = Preconditions.checkNotNull(suffix);
Nathan Harmata985ed702016-03-01 23:09:29 +0000416 this.wasOriginallyAbsolute = wasOriginallyAbsolute;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100417 this.rulesOnly = rulesOnly;
418 this.checkWildcardConflict = checkWildcardConflict;
419 }
420
421 @Override
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000422 public <T, E extends Exception> void eval(
423 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800424 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Janak Ramakrishnan3d9441b2016-01-13 17:38:29 +0000425 ImmutableSet<PathFragment> excludedSubdirectories,
kkress1847a012020-06-24 12:30:11 -0700426 BatchCallback<T, E> callback,
427 Class<E> exceptionClass)
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000428 throws TargetParsingException, E, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100429 if (checkWildcardConflict) {
430 ResolvedTargets<T> targets = getWildcardConflict(resolver);
431 if (targets != null) {
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000432 callback.process(targets.getTargets());
433 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100434 }
435 }
Lukacs Berki10e3b2b2015-09-22 07:58:20 +0000436
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000437 callback.process(
ulfjack53a0ff12019-05-21 07:08:40 -0700438 resolver.getTargetsInPackage(getOriginalPattern(), packageIdentifier, rulesOnly));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100439 }
440
Mark Schaller111634a52015-05-19 19:48:05 +0000441 @Override
Nathan Harmata986e3b02017-01-13 23:54:10 +0000442 public PackageIdentifier getDirectoryForTargetOrTargetsInPackage() {
Lukacs Berki8096c422015-10-14 15:12:23 +0000443 return packageIdentifier;
Mark Schaller111634a52015-05-19 19:48:05 +0000444 }
445
446 @Override
Yun Pengbf2c4d92019-12-13 09:21:28 -0800447 public RepositoryName getRepository() {
448 return packageIdentifier.getRepository();
449 }
450
451 @Override
Mark Schallerd7311e02015-07-07 16:36:09 +0000452 public boolean getRulesOnly() {
453 return rulesOnly;
454 }
455
456 @Override
janakr7b766832020-12-30 08:16:25 -0800457 public Type getType() {
458 return Type.TARGETS_IN_PACKAGE;
459 }
460
461 @Override
Mark Schaller111634a52015-05-19 19:48:05 +0000462 public boolean equals(Object o) {
463 if (this == o) {
464 return true;
465 }
466 if (!(o instanceof TargetsInPackage)) {
467 return false;
468 }
469 TargetsInPackage that = (TargetsInPackage) o;
Nathan Harmata985ed702016-03-01 23:09:29 +0000470 return wasOriginallyAbsolute == that.wasOriginallyAbsolute && rulesOnly == that.rulesOnly
Mark Schaller111634a52015-05-19 19:48:05 +0000471 && checkWildcardConflict == that.checkWildcardConflict
Mark Schaller66b35f32015-05-19 21:19:37 +0000472 && getOriginalPattern().equals(that.getOriginalPattern())
Lukacs Berki960dc272015-09-24 07:48:36 +0000473 && packageIdentifier.equals(that.packageIdentifier) && suffix.equals(that.suffix);
Mark Schaller111634a52015-05-19 19:48:05 +0000474 }
475
476 @Override
477 public int hashCode() {
Nathan Harmata985ed702016-03-01 23:09:29 +0000478 return Objects.hash(getType(), getOriginalPattern(), packageIdentifier, suffix,
479 wasOriginallyAbsolute, rulesOnly, checkWildcardConflict);
Mark Schaller111634a52015-05-19 19:48:05 +0000480 }
481
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100482 /**
483 * There's a potential ambiguity if '//foo/bar:all' refers to an actual target. In this case, we
Benjamin Petersondac096c2019-03-22 10:29:40 -0700484 * use the target but print a warning.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100485 *
486 * @return the Target corresponding to the given pattern, if the pattern is absolute and there
Benjamin Petersondac096c2019-03-22 10:29:40 -0700487 * is such a target. Otherwise, return null.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100488 */
489 private <T> ResolvedTargets<T> getWildcardConflict(TargetPatternResolver<T> resolver)
Mark Schallerb889cf32015-03-17 20:55:30 +0000490 throws InterruptedException {
Nathan Harmata985ed702016-03-01 23:09:29 +0000491 if (!wasOriginallyAbsolute) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100492 return null;
493 }
494
Lukacs Berki960dc272015-09-24 07:48:36 +0000495 T target;
496 Label label;
497 try {
498 label = Label.create(packageIdentifier, suffix);
499 target = resolver.getTargetOrNull(label);
500 } catch (LabelSyntaxException e) {
501 return null;
502 }
503
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100504 if (target != null) {
Lukacs Berki960dc272015-09-24 07:48:36 +0000505 resolver.warn(String.format("The target pattern '%s' is ambiguous: '%s' is " +
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100506 "both a wildcard, and the name of an existing %s; " +
507 "using the latter interpretation",
Lukacs Berki960dc272015-09-24 07:48:36 +0000508 getOriginalPattern(), ":" + suffix,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100509 resolver.getTargetKind(target)));
510 try {
Lukacs Berki960dc272015-09-24 07:48:36 +0000511 return resolver.getExplicitTarget(label);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100512 } catch (TargetParsingException e) {
513 throw new IllegalStateException(
514 "getTargetOrNull() returned non-null, so target should exist", e);
515 }
516 }
517 return null;
518 }
519 }
520
janakr7b766832020-12-30 08:16:25 -0800521 /**
522 * Specialization of {@link TargetPattern} for {@link Type#TARGETS_BELOW_DIRECTORY}. Exposed
523 * because it has a considerable number of specific methods. If {@link TargetPattern#getType}
524 * returns {@link Type#TARGETS_BELOW_DIRECTORY} the instance can safely be cast to {@code
525 * TargetsBelowDirectory}.
526 */
527 public static final class TargetsBelowDirectory extends TargetPattern {
Lukacs Berki8096c422015-10-14 15:12:23 +0000528 private final PackageIdentifier directory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100529 private final boolean rulesOnly;
530
Lukacs Berki550cfae2015-09-25 08:20:16 +0000531 private TargetsBelowDirectory(
janakra3652a32020-09-10 12:05:20 -0700532 String originalPattern,
533 PathFragment offset,
534 PackageIdentifier directory,
535 boolean rulesOnly) {
janakr7b766832020-12-30 08:16:25 -0800536 super(originalPattern, offset);
Lukacs Berki14ab5672016-03-31 07:52:15 +0000537 Preconditions.checkArgument(!directory.getRepository().isDefault());
Mark Schaller111634a52015-05-19 19:48:05 +0000538 this.directory = Preconditions.checkNotNull(directory);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100539 this.rulesOnly = rulesOnly;
540 }
541
542 @Override
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000543 public <T, E extends Exception> void eval(
544 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800545 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Janak Ramakrishnan3d9441b2016-01-13 17:38:29 +0000546 ImmutableSet<PathFragment> excludedSubdirectories,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000547 BatchCallback<T, E> callback,
548 Class<E> exceptionClass)
Janak Ramakrishnan006ab492016-01-07 17:39:41 +0000549 throws TargetParsingException, E, InterruptedException {
janakr3c38c412021-01-21 08:49:01 -0800550 Preconditions.checkState(
551 !excludedSubdirectories.contains(directory.getPackageFragment()),
552 "Fully excluded target pattern %s should have already been filtered out (%s)",
553 this,
554 excludedSubdirectories);
janakr59f172e2021-01-22 11:22:00 -0800555 IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection =
janakr3c38c412021-01-21 08:49:01 -0800556 getAllIgnoredSubdirectoriesToExclude(ignoredSubdirectories);
janakr59f172e2021-01-22 11:22:00 -0800557 if (warnIfFiltered(ignoredIntersection, resolver)) {
558 return;
559 }
Janak Ramakrishnan5b5f22a2016-01-07 17:07:29 +0000560 resolver.findTargetsBeneathDirectory(
561 directory.getRepository(),
562 getOriginalPattern(),
563 directory.getPackageFragment().getPathString(),
564 rulesOnly,
janakr59f172e2021-01-22 11:22:00 -0800565 ignoredIntersection.ignoredPathFragments(),
Janak Ramakrishnan5b5f22a2016-01-07 17:07:29 +0000566 excludedSubdirectories,
Eric Fellheimer6f8b7ce2016-01-29 00:15:44 +0000567 callback,
568 exceptionClass);
Mark Schaller111634a52015-05-19 19:48:05 +0000569 }
570
571 @Override
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000572 public <T, E extends Exception> ListenableFuture<Void> evalAsync(
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000573 TargetPatternResolver<T> resolver,
janakr3c38c412021-01-21 08:49:01 -0800574 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000575 ImmutableSet<PathFragment> excludedSubdirectories,
Googler54be05a2020-06-29 09:20:32 -0700576 BatchCallback<T, E> callback,
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000577 Class<E> exceptionClass,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000578 ListeningExecutorService executor) {
janakr3c38c412021-01-21 08:49:01 -0800579 Preconditions.checkState(
580 !excludedSubdirectories.contains(directory.getPackageFragment()),
581 "Fully excluded target pattern %s should have already been filtered out (%s)",
582 this,
583 excludedSubdirectories);
janakr59f172e2021-01-22 11:22:00 -0800584 IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection;
janakr3c38c412021-01-21 08:49:01 -0800585 try {
janakr59f172e2021-01-22 11:22:00 -0800586 ignoredIntersection = getAllIgnoredSubdirectoriesToExclude(ignoredSubdirectories);
janakr3c38c412021-01-21 08:49:01 -0800587 } catch (InterruptedException e) {
588 return immediateCancelledFuture();
589 }
janakr59f172e2021-01-22 11:22:00 -0800590 if (warnIfFiltered(ignoredIntersection, resolver)) {
591 return immediateVoidFuture();
592 }
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000593 return resolver.findTargetsBeneathDirectoryAsync(
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000594 directory.getRepository(),
595 getOriginalPattern(),
596 directory.getPackageFragment().getPathString(),
597 rulesOnly,
janakr59f172e2021-01-22 11:22:00 -0800598 ignoredIntersection.ignoredPathFragments(),
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000599 excludedSubdirectories,
600 callback,
601 exceptionClass,
Nathan Harmata7a5a2362017-03-08 22:42:01 +0000602 executor);
Nathan Harmata5bd26b22016-11-14 19:55:50 +0000603 }
604
janakr59f172e2021-01-22 11:22:00 -0800605 private boolean warnIfFiltered(
606 IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection,
607 TargetPatternResolver<?> resolver) {
608 if (ignoredIntersection.wasFiltered()) {
609 resolver.warn(
610 "Pattern '"
611 + getOriginalPattern()
612 + "' was filtered out by ignored directory '"
613 + ignoredIntersection.filteringIgnorer().getPathString()
614 + "'");
615 return true;
616 }
617 return false;
618 }
619
620 public IgnoredPathFragmentsInScopeOrFilteringIgnorer getAllIgnoredSubdirectoriesToExclude(
janakr3c38c412021-01-21 08:49:01 -0800621 InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredPackagePrefixes)
622 throws InterruptedException {
janakr3c38c412021-01-21 08:49:01 -0800623 ImmutableSet.Builder<PathFragment> ignoredPathsBuilder =
624 ImmutableSet.builderWithExpectedSize(0);
janakr59f172e2021-01-22 11:22:00 -0800625 for (PathFragment ignoredPackagePrefix : ignoredPackagePrefixes.get()) {
626 if (this.containedIn(ignoredPackagePrefix)) {
627 return new IgnoredPathFragmentsInScopeOrFilteringIgnorer.FilteringIgnorer(
628 ignoredPackagePrefix);
629 }
janakr3c38c412021-01-21 08:49:01 -0800630 PackageIdentifier pkgIdForIgnoredDirectorPrefix =
631 PackageIdentifier.create(this.getDirectory().getRepository(), ignoredPackagePrefix);
632 if (this.containsAllTransitiveSubdirectories(pkgIdForIgnoredDirectorPrefix)) {
633 ignoredPathsBuilder.add(ignoredPackagePrefix);
634 }
635 }
janakr59f172e2021-01-22 11:22:00 -0800636 return IgnoredPathFragmentsInScopeOrFilteringIgnorer.IgnoredPathFragments.of(
637 ignoredPathsBuilder.build());
638 }
639
640 /**
641 * Morally an {@code Either<ImmutableSet<PathFragment>, PathFragment>}, saying whether the given
642 * set of ignored directories intersected a directory (in which case the directories that were
643 * in the intersection are returned) or completely contained it (in which case a containing
644 * directory is returned).
645 */
646 public abstract static class IgnoredPathFragmentsInScopeOrFilteringIgnorer {
647 public abstract boolean wasFiltered();
648
649 public abstract ImmutableSet<PathFragment> ignoredPathFragments();
650
651 public abstract PathFragment filteringIgnorer();
652
653 private static class IgnoredPathFragments
654 extends IgnoredPathFragmentsInScopeOrFilteringIgnorer {
655 private static final IgnoredPathFragments EMPTYSET_IGNORED =
656 new IgnoredPathFragments(ImmutableSet.of());
657
658 private final ImmutableSet<PathFragment> ignoredPathFragments;
659
660 private IgnoredPathFragments(ImmutableSet<PathFragment> ignoredPathFragments) {
661 this.ignoredPathFragments = ignoredPathFragments;
662 }
663
664 static IgnoredPathFragments of(ImmutableSet<PathFragment> ignoredPathFragments) {
665 if (ignoredPathFragments.isEmpty()) {
666 return EMPTYSET_IGNORED;
667 }
668 return new IgnoredPathFragments(ignoredPathFragments);
669 }
670
671 @Override
672 public boolean wasFiltered() {
673 return false;
674 }
675
676 @Override
677 public ImmutableSet<PathFragment> ignoredPathFragments() {
678 return ignoredPathFragments;
679 }
680
681 @Override
682 public PathFragment filteringIgnorer() {
683 throw new UnsupportedOperationException("No filter: " + ignoredPathFragments);
684 }
685 }
686
687 private static class FilteringIgnorer extends IgnoredPathFragmentsInScopeOrFilteringIgnorer {
688 private final PathFragment filteringIgnorer;
689
690 FilteringIgnorer(PathFragment filteringIgnorer) {
691 this.filteringIgnorer = filteringIgnorer;
692 }
693
694 @Override
695 public boolean wasFiltered() {
696 return true;
697 }
698
699 @Override
700 public ImmutableSet<PathFragment> ignoredPathFragments() {
701 throw new UnsupportedOperationException("was filtered: " + filteringIgnorer);
702 }
703
704 @Override
705 public PathFragment filteringIgnorer() {
706 return filteringIgnorer;
707 }
708 }
709 }
710
711 /** Is {@code containingDirectory} an ancestor of or equal to this {@link #directory}? */
712 public boolean containedIn(PathFragment containingDirectory) {
713 return directory.getPackageFragment().startsWith(containingDirectory);
janakr3c38c412021-01-21 08:49:01 -0800714 }
715
janakr7b766832020-12-30 08:16:25 -0800716 /**
717 * Returns true if {@code containedDirectory} is contained by or equals this pattern's
718 * directory.
719 *
720 * <p>For example, returns {@code true} for {@code this = TargetPattern ("//...")} and {@code
721 * directory = "foo")}.
722 */
723 public boolean containsAllTransitiveSubdirectories(PackageIdentifier containedDirectory) {
Mark Schallerd7311e02015-07-07 16:36:09 +0000724 // Note that merely checking to see if the directory startsWith the TargetsBelowDirectory's
725 // directory is insufficient. "food" begins with "foo", but "//foo/..." does not contain
726 // "//food/...".
Lukacs Berki8096c422015-10-14 15:12:23 +0000727 return containedDirectory.getRepository().equals(directory.getRepository())
728 && containedDirectory.getPackageFragment().startsWith(directory.getPackageFragment());
Mark Schaller111634a52015-05-19 19:48:05 +0000729 }
730
janakr7b766832020-12-30 08:16:25 -0800731 /**
732 * Determines how, if it all, the evaluation of this pattern with a directory exclusion of the
733 * given {@code containedPattern}'s directory relates to the evaluation of the subtraction of
734 * the given {@code containedPattern} from this one.
735 */
736 public ContainsResult contains(TargetsBelowDirectory containedPattern) {
737 if (containsAllTransitiveSubdirectories(containedPattern.getDirectory())) {
738 return !getRulesOnly() && containedPattern.getRulesOnly()
739 ? ContainsResult.DIRECTORY_EXCLUSION_WOULD_BE_TOO_BROAD
740 : ContainsResult.DIRECTORY_EXCLUSION_WOULD_BE_EXACT;
741 } else {
742 return ContainsResult.NOT_CONTAINED;
743 }
744 }
745
746 /** A tristate return value for {@link #contains}. */
747 public enum ContainsResult {
748 /**
749 * Evaluating this pattern with a directory exclusion of the other pattern's directory would
750 * result in exactly the same set of targets as evaluating the subtraction of the other
751 * pattern from this one.
752 */
753 DIRECTORY_EXCLUSION_WOULD_BE_EXACT,
754 /**
755 * A directory exclusion of the other pattern's directory would be too broad because this
756 * pattern is not "rules only" and the other one is, meaning that this pattern potentially
757 * matches more targets underneath the directory in question than the other one does. Thus, a
758 * directory exclusion would incorrectly exclude non-rule targets.
759 */
760 DIRECTORY_EXCLUSION_WOULD_BE_TOO_BROAD,
761 /** None of the above. The other pattern isn't contained by this pattern. */
762 NOT_CONTAINED,
763 }
764
765 /**
766 * Returns a {@link PackageIdentifier} identifying the most specific containing directory of the
767 * patterns that could be matched by this pattern.
768 *
769 * <p>Note that we are using the {@link PackageIdentifier} type as a convenience; there may not
770 * actually be a package corresponding to this directory!
771 *
772 * <p>This returns a {@link PackageIdentifier} that identifies the referred-to directory. For
773 * example, for "//foo/bar/...", this method returns a {@link PackageIdentifier} for "foo/bar".
774 */
775 public PackageIdentifier getDirectory() {
Mark Schaller111634a52015-05-19 19:48:05 +0000776 return directory;
777 }
778
779 @Override
Yun Pengbf2c4d92019-12-13 09:21:28 -0800780 public RepositoryName getRepository() {
781 return directory.getRepository();
782 }
783
784 @Override
Mark Schallerd7311e02015-07-07 16:36:09 +0000785 public boolean getRulesOnly() {
786 return rulesOnly;
787 }
788
789 @Override
janakr7b766832020-12-30 08:16:25 -0800790 public Type getType() {
791 return Type.TARGETS_BELOW_DIRECTORY;
792 }
793
794 @Override
Mark Schaller111634a52015-05-19 19:48:05 +0000795 public boolean equals(Object o) {
796 if (this == o) {
797 return true;
798 }
799 if (!(o instanceof TargetsBelowDirectory)) {
800 return false;
801 }
802 TargetsBelowDirectory that = (TargetsBelowDirectory) o;
Mark Schaller66b35f32015-05-19 21:19:37 +0000803 return rulesOnly == that.rulesOnly && getOriginalPattern().equals(that.getOriginalPattern())
Mark Schaller111634a52015-05-19 19:48:05 +0000804 && directory.equals(that.directory);
805 }
806
807 @Override
808 public int hashCode() {
Mark Schaller66b35f32015-05-19 21:19:37 +0000809 return Objects.hash(getType(), getOriginalPattern(), directory, rulesOnly);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100810 }
811 }
812
Klaus Aehlig9a96f4c2019-07-08 05:05:05 -0700813 /**
814 * Apply a renaming to the repository part of a pattern string, returning the renamed pattern
815 * string. This function only looks at the repository part of the pattern string, not the rest; so
816 * any syntactic errors will not be handled here, but simply remain. Similarly, if the repository
817 * part of the pattern is not syntactically valid, the renaming simply does not match and the
818 * string is returned unchanged.
819 */
820 public static String renameRepository(
821 String pattern, Map<RepositoryName, RepositoryName> renaming) {
822 if (!pattern.startsWith("@")) {
823 return pattern;
824 }
825 int pkgStart = pattern.indexOf("//");
826 if (pkgStart < 0) {
827 return pattern;
828 }
829 RepositoryName repository;
830 try {
831 repository = RepositoryName.create(pattern.substring(0, pkgStart));
832 } catch (LabelSyntaxException e) {
833 return pattern;
834 }
835 RepositoryName newRepository = renaming.get(repository);
836 if (newRepository == null) {
837 // No renaming required
838 return pattern;
839 }
840 return newRepository.getName() + pattern.substring(pkgStart);
841 }
842
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100843 @Immutable
844 public static final class Parser {
Nathan Harmata985ed702016-03-01 23:09:29 +0000845 // A valid pattern either starts with exactly 0 slashes (relative pattern) or exactly two
846 // slashes (absolute pattern).
847 private static final Pattern VALID_SLASH_PREFIX = Pattern.compile("(//)?([^/]|$)");
848
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100849 // TODO(bazel-team): Merge the Label functionality that requires similar constants into this
850 // class.
851 /**
852 * The set of target-pattern suffixes which indicate wildcards over all <em>rules</em> in a
853 * single package.
854 */
Googler04cb3d32017-03-09 15:55:42 +0000855 private static final ImmutableList<String> ALL_RULES_IN_SUFFIXES = ImmutableList.of("all");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100856
857 /**
858 * The set of target-pattern suffixes which indicate wildcards over all <em>targets</em> in a
859 * single package.
860 */
Googler04cb3d32017-03-09 15:55:42 +0000861 private static final ImmutableList<String> ALL_TARGETS_IN_SUFFIXES =
862 ImmutableList.of("*", "all-targets");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100863
864 private static final List<String> SUFFIXES;
865
866 static {
867 SUFFIXES = ImmutableList.<String>builder()
868 .addAll(ALL_RULES_IN_SUFFIXES)
869 .addAll(ALL_TARGETS_IN_SUFFIXES)
870 .add("/...")
871 .build();
872 }
873
874 /**
875 * Returns whether the given pattern is simple, i.e., not starting with '-' and using none of
876 * the target matching suffixes.
877 */
878 public static boolean isSimpleTargetPattern(String pattern) {
879 if (pattern.startsWith("-")) {
880 return false;
881 }
882
883 for (String suffix : SUFFIXES) {
884 if (pattern.endsWith(":" + suffix)) {
885 return false;
886 }
887 }
888 return true;
889 }
890
891 /**
892 * Directory prefix to use when resolving relative labels (rather than absolute ones). For
janakra3652a32020-09-10 12:05:20 -0700893 * example, if the working directory is "<workspace root>/foo", then this should be "foo", which
894 * will make patterns such as "bar:bar" be resolved as "//foo/bar:bar". This makes the command
895 * line a bit more convenient to use.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100896 */
janakra3652a32020-09-10 12:05:20 -0700897 private final PathFragment relativeDirectory;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100898
janakra3652a32020-09-10 12:05:20 -0700899 /** Creates a new parser with the given offset for relative patterns. */
900 public Parser(PathFragment relativeDirectory) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100901 this.relativeDirectory = relativeDirectory;
902 }
903
904 /**
905 * Parses the given pattern, and throws an exception if the pattern is invalid.
906 *
907 * @return a target pattern corresponding to the pattern parsed
908 * @throws TargetParsingException if the pattern is invalid
909 */
910 public TargetPattern parse(String pattern) throws TargetParsingException {
911 // The structure of this method is by cases, according to the usage string
912 // constant (see lib/blaze/commands/target-syntax.txt).
913
914 String originalPattern = pattern;
Kristina Chodorow70d5dd02015-05-29 14:51:31 +0000915 final boolean includesRepo = pattern.startsWith("@");
Brian Silvermand7d6d622016-03-17 09:53:39 +0000916 RepositoryName repository = null;
Kristina Chodorow70d5dd02015-05-29 14:51:31 +0000917 if (includesRepo) {
918 int pkgStart = pattern.indexOf("//");
919 if (pkgStart < 0) {
Googler35b92d02020-08-03 07:42:11 -0700920 throw new TargetParsingException(
921 "Couldn't find package in target " + pattern, TargetPatterns.Code.PACKAGE_NOT_FOUND);
Kristina Chodorow70d5dd02015-05-29 14:51:31 +0000922 }
Lukacs Berki960dc272015-09-24 07:48:36 +0000923 try {
924 repository = RepositoryName.create(pattern.substring(0, pkgStart));
925 } catch (LabelSyntaxException e) {
Googler35b92d02020-08-03 07:42:11 -0700926 throw new TargetParsingException(e.getMessage(), TargetPatterns.Code.LABEL_SYNTAX_ERROR);
Lukacs Berki960dc272015-09-24 07:48:36 +0000927 }
928
Kristina Chodorow70d5dd02015-05-29 14:51:31 +0000929 pattern = pattern.substring(pkgStart);
930 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100931
Nathan Harmata985ed702016-03-01 23:09:29 +0000932 if (!VALID_SLASH_PREFIX.matcher(pattern).lookingAt()) {
Googler35b92d02020-08-03 07:42:11 -0700933 throw new TargetParsingException(
934 "not a valid absolute pattern (absolute target patterns "
935 + "must start with exactly two slashes): '"
936 + pattern
937 + "'",
938 TargetPatterns.Code.ABSOLUTE_TARGET_PATTERN_INVALID);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100939 }
Nathan Harmata985ed702016-03-01 23:09:29 +0000940
941 final boolean wasOriginallyAbsolute = pattern.startsWith("//");
942 // We now ensure the relativeDirectory is applied to relative patterns.
943 pattern = absolutize(pattern).substring(2);
944
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100945 if (pattern.isEmpty()) {
Googler35b92d02020-08-03 07:42:11 -0700946 throw new TargetParsingException(
947 "the empty string is not a valid target",
948 TargetPatterns.Code.TARGET_CANNOT_BE_EMPTY_STRING);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100949 }
950
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100951 int colonIndex = pattern.lastIndexOf(':');
952 String packagePart = colonIndex < 0 ? pattern : pattern.substring(0, colonIndex);
953 String targetPart = colonIndex < 0 ? "" : pattern.substring(colonIndex + 1);
954
955 if (packagePart.equals("...")) {
956 packagePart = "/..."; // special case this for easier parsing
957 }
958
959 if (packagePart.endsWith("/")) {
Googler35b92d02020-08-03 07:42:11 -0700960 throw new TargetParsingException(
961 "The package part of '" + originalPattern + "' should not end in a slash",
962 TargetPatterns.Code.PACKAGE_PART_CANNOT_END_IN_SLASH);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100963 }
964
Brian Silvermand7d6d622016-03-17 09:53:39 +0000965 if (repository == null) {
Kristina Chodorowa1a31ff2016-07-27 16:34:27 +0000966 repository = RepositoryName.MAIN;
Brian Silvermand7d6d622016-03-17 09:53:39 +0000967 }
968
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100969 if (packagePart.endsWith("/...")) {
970 String realPackagePart = removeSuffix(packagePart, "/...");
Lukacs Berki8096c422015-10-14 15:12:23 +0000971 PackageIdentifier packageIdentifier;
972 try {
Lukacs Berkic7106d42015-10-15 07:45:54 +0000973 packageIdentifier = PackageIdentifier.parse(
974 repository.getName() + "//" + realPackagePart);
Lukacs Berki8096c422015-10-14 15:12:23 +0000975 } catch (LabelSyntaxException e) {
976 throw new TargetParsingException(
Googler35b92d02020-08-03 07:42:11 -0700977 "Invalid package name '" + realPackagePart + "': " + e.getMessage(),
978 TargetPatterns.Code.LABEL_SYNTAX_ERROR);
Lukacs Berki8096c422015-10-14 15:12:23 +0000979 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100980 if (targetPart.isEmpty() || ALL_RULES_IN_SUFFIXES.contains(targetPart)) {
Lukacs Berki550cfae2015-09-25 08:20:16 +0000981 return new TargetsBelowDirectory(
Lukacs Berki8096c422015-10-14 15:12:23 +0000982 originalPattern, relativeDirectory, packageIdentifier, true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100983 } else if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) {
Lukacs Berki550cfae2015-09-25 08:20:16 +0000984 return new TargetsBelowDirectory(
Lukacs Berki8096c422015-10-14 15:12:23 +0000985 originalPattern, relativeDirectory, packageIdentifier, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100986 }
987 }
988
989 if (ALL_RULES_IN_SUFFIXES.contains(targetPart)) {
Lukacs Berki960dc272015-09-24 07:48:36 +0000990 PackageIdentifier packageIdentifier;
991 try {
992 packageIdentifier = PackageIdentifier.parse(repository.getName() + "//" + packagePart);
993 } catch (LabelSyntaxException e) {
994 throw new TargetParsingException(
Googler35b92d02020-08-03 07:42:11 -0700995 "Invalid package name '" + packagePart + "': " + e.getMessage(),
996 TargetPatterns.Code.LABEL_SYNTAX_ERROR);
Lukacs Berki960dc272015-09-24 07:48:36 +0000997 }
Lukacs Berki550cfae2015-09-25 08:20:16 +0000998 return new TargetsInPackage(originalPattern, relativeDirectory, packageIdentifier,
Nathan Harmata985ed702016-03-01 23:09:29 +0000999 targetPart, wasOriginallyAbsolute, true, true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001000 }
1001
1002 if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) {
Lukacs Berki960dc272015-09-24 07:48:36 +00001003 PackageIdentifier packageIdentifier;
1004 try {
1005 packageIdentifier = PackageIdentifier.parse(repository.getName() + "//" + packagePart);
1006 } catch (LabelSyntaxException e) {
1007 throw new TargetParsingException(
Googler35b92d02020-08-03 07:42:11 -07001008 "Invalid package name '" + packagePart + "': " + e.getMessage(),
1009 TargetPatterns.Code.LABEL_SYNTAX_ERROR);
Lukacs Berki960dc272015-09-24 07:48:36 +00001010 }
Lukacs Berki550cfae2015-09-25 08:20:16 +00001011 return new TargetsInPackage(originalPattern, relativeDirectory, packageIdentifier,
Nathan Harmata985ed702016-03-01 23:09:29 +00001012 targetPart, wasOriginallyAbsolute, false, true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001013 }
1014
Nathan Harmata985ed702016-03-01 23:09:29 +00001015 if (includesRepo || wasOriginallyAbsolute || pattern.contains(":")) {
nharmata80ed2662019-04-30 13:17:47 -07001016 PackageIdentifier packageIdentifier;
1017 String fullLabel = repository.getName() + "//" + pattern;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001018 try {
nharmata80ed2662019-04-30 13:17:47 -07001019 PackageAndTarget packageAndTarget = LabelValidator.validateAbsoluteLabel(fullLabel);
1020 packageIdentifier =
1021 PackageIdentifier.create(
1022 repository, PathFragment.create(packageAndTarget.getPackageName()));
1023 } catch (BadLabelException e) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001024 String error = "invalid target format '" + originalPattern + "': " + e.getMessage();
Googler35b92d02020-08-03 07:42:11 -07001025 throw new TargetParsingException(error, TargetPatterns.Code.TARGET_FORMAT_INVALID);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001026 }
nharmata80ed2662019-04-30 13:17:47 -07001027 return new SingleTarget(fullLabel, packageIdentifier, originalPattern, relativeDirectory);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001028 }
1029
1030 // This is a stripped-down version of interpretPathAsTarget that does no I/O. We have a basic
1031 // relative path. e.g. "foo/bar/Wiz.java". The strictest correct check we can do here (without
1032 // I/O) is just to ensure that there is *some* prefix that is a valid package-name. It's
1033 // sufficient to test the first segment. This is really a rather weak check; perhaps we should
1034 // just eliminate it.
1035 int slashIndex = pattern.indexOf('/');
Damien Martin-Guillerez52ac3562015-02-25 18:55:53 +00001036 String packageName = pattern;
1037 if (slashIndex > 0) {
1038 packageName = pattern.substring(0, slashIndex);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001039 }
Lukacs Berki8096c422015-10-14 15:12:23 +00001040 try {
1041 PackageIdentifier.parse("//" + packageName);
1042 } catch (LabelSyntaxException e) {
1043 throw new TargetParsingException(
Googler35b92d02020-08-03 07:42:11 -07001044 "Bad target pattern '" + originalPattern + "': " + e.getMessage(),
1045 TargetPatterns.Code.LABEL_SYNTAX_ERROR);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001046 }
Lukacs Berki550cfae2015-09-25 08:20:16 +00001047 return new InterpretPathAsTarget(pattern, originalPattern, relativeDirectory);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001048 }
1049
1050 /**
adgarb70e6362019-11-21 16:54:09 -08001051 * Parses a constant string TargetPattern, throwing IllegalStateException on invalid pattern.
1052 */
1053 @CheckReturnValue
1054 public TargetPattern parseConstantUnchecked(@CompileTimeConstant String pattern) {
1055 try {
1056 return parse(pattern);
1057 } catch (TargetParsingException e) {
1058 throw new IllegalStateException(e);
1059 }
1060 }
1061
1062 /**
1063 * Absolutizes the target pattern to the offset. Patterns starting with "//" are absolute and
1064 * not modified. Assumes the given pattern is not invalid wrt leading "/"s.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001065 *
adgarb70e6362019-11-21 16:54:09 -08001066 * <p>If the offset is "foo": absolutize(":bar") --> "//foo:bar" absolutize("bar") -->
1067 * "//foo/bar" absolutize("//biz/bar") --> "//biz/bar" (absolute) absolutize("biz:bar") -->
1068 * "//foo/biz:bar"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001069 *
1070 * @param pattern The target pattern to parse.
1071 * @return the pattern, absolutized to the offset if approprate.
1072 */
Nathan Harmata985ed702016-03-01 23:09:29 +00001073 public String absolutize(String pattern) {
1074 if (pattern.startsWith("//")) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001075 return pattern;
1076 }
1077
janakra3652a32020-09-10 12:05:20 -07001078 // PathFragment#getRelative doesn't work when the pattern starts with ":".
1079 // "foo".getRelative(":all") would return "foo/:all", where we really want "foo:all".
Nathan Harmata985ed702016-03-01 23:09:29 +00001080 return pattern.startsWith(":") || relativeDirectory.isEmpty()
janakra3652a32020-09-10 12:05:20 -07001081 ? "//" + relativeDirectory.getPathString() + pattern
1082 : "//" + relativeDirectory.getPathString() + "/" + pattern;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001083 }
1084 }
1085
Lukacs Berki960dc272015-09-24 07:48:36 +00001086 // Parse 'label' as a Label, mapping LabelSyntaxException into
1087 // TargetParsingException.
cparsonsdac21352019-05-03 11:41:17 -07001088 private static Label label(String label) throws TargetParsingException {
Lukacs Berki960dc272015-09-24 07:48:36 +00001089 try {
cparsonsdac21352019-05-03 11:41:17 -07001090 return Label.parseAbsolute(label, ImmutableMap.of());
Lukacs Berki960dc272015-09-24 07:48:36 +00001091 } catch (LabelSyntaxException e) {
Googler35b92d02020-08-03 07:42:11 -07001092 throw new TargetParsingException(
1093 "invalid target format: '"
1094 + StringUtilities.sanitizeControlChars(label)
1095 + "'; "
1096 + StringUtilities.sanitizeControlChars(e.getMessage()),
1097 TargetPatterns.Code.TARGET_FORMAT_INVALID);
Lukacs Berki960dc272015-09-24 07:48:36 +00001098 }
1099 }
1100
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001101 /**
1102 * The target pattern type (targets below package, in package, explicit target, etc.)
1103 */
1104 public enum Type {
1105 /** A path interpreted as a target, eg "foo/bar/baz" */
1106 PATH_AS_TARGET,
1107 /** An explicit target, eg "//foo:bar." */
1108 SINGLE_TARGET,
Mark Schaller111634a52015-05-19 19:48:05 +00001109 /** Targets below a directory, eg "foo/...". */
1110 TARGETS_BELOW_DIRECTORY,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001111 /** Target in a package, eg "foo:all". */
1112 TARGETS_IN_PACKAGE;
1113 }
1114}