aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java464
1 files changed, 464 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
new file mode 100644
index 0000000000..6677970e2d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
@@ -0,0 +1,464 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.cmdline;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.cmdline.LabelValidator.BadLabelException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Represents a target pattern. Target patterns are a generalization of labels to include
+ * wildcards for finding all packages recursively beneath some root, and for finding all targets
+ * within a package.
+ *
+ * <p>Note that this class does not handle negative patterns ("-//foo/bar"); these must be handled
+ * one level up. In particular, the query language comes with built-in support for negative
+ * patterns.
+ *
+ * <p>In order to resolve target patterns, you need an implementation of {@link
+ * TargetPatternResolver}. This class is thread-safe if the corresponding instance is thread-safe.
+ *
+ * <p>See lib/blaze/commands/target-syntax.txt for details.
+ */
+public abstract class TargetPattern {
+
+ private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+ private static final Joiner SLASH_JOINER = Joiner.on('/');
+
+ private static final Parser DEFAULT_PARSER = new Parser("");
+
+ private final Type type;
+
+ /**
+ * Returns a parser with no offset. Note that the Parser class is immutable, so this method may
+ * return the same instance on subsequent calls.
+ */
+ public static Parser defaultParser() {
+ return DEFAULT_PARSER;
+ }
+
+ private static String removeSuffix(String s, String suffix) {
+ if (s.endsWith(suffix)) {
+ return s.substring(0, s.length() - suffix.length());
+ } else {
+ throw new IllegalArgumentException(s + ", " + suffix);
+ }
+ }
+
+ /**
+ * Normalizes the given relative path by resolving {@code //}, {@code /./} and {@code x/../}
+ * pieces. Note that leading {@code ".."} segments are not removed, so the returned string can
+ * have leading {@code ".."} segments.
+ *
+ * @throws IllegalArgumentException if the path is absolute, i.e. starts with a @{code '/'}
+ */
+ @VisibleForTesting
+ static String normalize(String path) {
+ Preconditions.checkArgument(!path.startsWith("/"));
+ Iterator<String> it = SLASH_SPLITTER.split(path).iterator();
+ List<String> pieces = new ArrayList<>();
+ while (it.hasNext()) {
+ String piece = it.next();
+ if (".".equals(piece) || piece.isEmpty()) {
+ continue;
+ }
+ if ("..".equals(piece)) {
+ if (pieces.isEmpty()) {
+ pieces.add(piece);
+ continue;
+ }
+ String predecessor = pieces.remove(pieces.size() - 1);
+ if ("..".equals(predecessor)) {
+ pieces.add(piece);
+ pieces.add(piece);
+ }
+ continue;
+ }
+ pieces.add(piece);
+ }
+ return SLASH_JOINER.join(pieces);
+ }
+
+ private TargetPattern(Type type) {
+ // Don't allow inheritance outside this class.
+ this.type = type;
+ }
+
+ /**
+ * Return the type of the pattern. Examples include "below package" like "foo/..." and "single
+ * target" like "//x:y".
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Evaluates the current target pattern and returns the result.
+ */
+ public abstract <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
+ throws TargetParsingException, InterruptedException,
+ TargetPatternResolver.MissingDepException;
+
+ private static final class SingleTarget extends TargetPattern {
+
+ private final String targetName;
+
+ private SingleTarget(String targetName) {
+ super(Type.SINGLE_TARGET);
+ this.targetName = targetName;
+ }
+
+ @Override
+ public <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
+ throws TargetParsingException, InterruptedException,
+ TargetPatternResolver.MissingDepException {
+ return resolver.getExplicitTarget(targetName);
+ }
+ }
+
+ private static final class InterpretPathAsTarget extends TargetPattern {
+
+ private final String path;
+
+ private InterpretPathAsTarget(String path) {
+ super(Type.PATH_AS_TARGET);
+ this.path = normalize(path);
+ }
+
+ @Override
+ public <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
+ throws TargetParsingException, InterruptedException,
+ TargetPatternResolver.MissingDepException {
+ if (resolver.isPackage(path)) {
+ // User has specified a package name. Issue a helpful error message.
+ throw new TargetParsingException("ambiguous target pattern: '" + path + "' is "
+ + "the name of a package; use '" + path + ":all' to mean \"all "
+ + "rules in this package\", '" + path + "/...' to mean \"all rules recursively "
+ + "beneath this package\", or '//" + path + "' to mean \"the default rule in this "
+ + "package\"");
+ }
+
+ List<String> pieces = SLASH_SPLITTER.splitToList(path);
+
+ // Interprets the label as a file target. This loop stops as soon as the
+ // first BUILD file is found (i.e. longest prefix match).
+ for (int i = pieces.size() - 1; i > 0; i--) {
+ String packageName = SLASH_JOINER.join(pieces.subList(0, i));
+ if (resolver.isPackage(packageName)) {
+ String targetName = SLASH_JOINER.join(pieces.subList(i, pieces.size()));
+ return resolver.getExplicitTarget("//" + packageName + ":" + targetName);
+ }
+ }
+
+ throw new TargetParsingException(
+ "couldn't determine target from filename '" + path + "'");
+ }
+ }
+
+ private static final class TargetsInPackage extends TargetPattern {
+
+ private final String originalPattern;
+ private final String pattern;
+ private final String suffix;
+ private final boolean isAbsolute;
+ private final boolean rulesOnly;
+ private final boolean checkWildcardConflict;
+
+ private TargetsInPackage(String originalPattern, String pattern, String suffix,
+ boolean isAbsolute, boolean rulesOnly, boolean checkWildcardConflict) {
+ super(Type.TARGETS_IN_PACKAGE);
+ this.originalPattern = originalPattern;
+ this.pattern = pattern;
+ this.suffix = suffix;
+ this.isAbsolute = isAbsolute;
+ this.rulesOnly = rulesOnly;
+ this.checkWildcardConflict = checkWildcardConflict;
+ }
+
+ @Override
+ public <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
+ throws TargetParsingException, InterruptedException,
+ TargetPatternResolver.MissingDepException {
+ if (checkWildcardConflict) {
+ ResolvedTargets<T> targets = getWildcardConflict(resolver);
+ if (targets != null) {
+ return targets;
+ }
+ }
+ return resolver.getTargetsInPackage(originalPattern, removeSuffix(pattern, suffix),
+ rulesOnly);
+ }
+
+ /**
+ * There's a potential ambiguity if '//foo/bar:all' refers to an actual target. In this case, we
+ * use the the target but print a warning.
+ *
+ * @return the Target corresponding to the given pattern, if the pattern is absolute and there
+ * is such a target. Otherwise, return null.
+ */
+ private <T> ResolvedTargets<T> getWildcardConflict(TargetPatternResolver<T> resolver)
+ throws InterruptedException, TargetPatternResolver.MissingDepException {
+ if (!isAbsolute) {
+ return null;
+ }
+
+ T target = resolver.getTargetOrNull("//" + pattern);
+ if (target != null) {
+ String name = pattern.lastIndexOf(':') != -1
+ ? pattern.substring(pattern.lastIndexOf(':') + 1)
+ : pattern.substring(pattern.lastIndexOf('/') + 1);
+ resolver.warn(String.format("The Blaze target pattern '%s' is ambiguous: '%s' is " +
+ "both a wildcard, and the name of an existing %s; " +
+ "using the latter interpretation",
+ "//" + pattern, ":" + name,
+ resolver.getTargetKind(target)));
+ try {
+ return resolver.getExplicitTarget("//" + pattern);
+ } catch (TargetParsingException e) {
+ throw new IllegalStateException(
+ "getTargetOrNull() returned non-null, so target should exist", e);
+ }
+ }
+ return null;
+ }
+ }
+
+ private static final class TargetsBelowPackage extends TargetPattern {
+
+ private final String originalPattern;
+ private final String pathPrefix;
+ private final boolean rulesOnly;
+
+ private TargetsBelowPackage(String originalPattern, String pathPrefix, boolean rulesOnly) {
+ super(Type.TARGETS_BELOW_PACKAGE);
+ this.originalPattern = originalPattern;
+ this.pathPrefix = pathPrefix;
+ this.rulesOnly = rulesOnly;
+ }
+
+ @Override
+ public <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
+ throws TargetParsingException, InterruptedException,
+ TargetPatternResolver.MissingDepException {
+ return resolver.findTargetsBeneathDirectory(originalPattern, pathPrefix, rulesOnly);
+ }
+ }
+
+ @Immutable
+ public static final class Parser {
+ // TODO(bazel-team): Merge the Label functionality that requires similar constants into this
+ // class.
+ /**
+ * The set of target-pattern suffixes which indicate wildcards over all <em>rules</em> in a
+ * single package.
+ */
+ private static final List<String> ALL_RULES_IN_SUFFIXES = ImmutableList.of(
+ "all");
+
+ /**
+ * The set of target-pattern suffixes which indicate wildcards over all <em>targets</em> in a
+ * single package.
+ */
+ private static final List<String> ALL_TARGETS_IN_SUFFIXES = ImmutableList.of(
+ "*",
+ "all-targets");
+
+ private static final List<String> SUFFIXES;
+
+ static {
+ SUFFIXES = ImmutableList.<String>builder()
+ .addAll(ALL_RULES_IN_SUFFIXES)
+ .addAll(ALL_TARGETS_IN_SUFFIXES)
+ .add("/...")
+ .build();
+ }
+
+ /**
+ * Returns whether the given pattern is simple, i.e., not starting with '-' and using none of
+ * the target matching suffixes.
+ */
+ public static boolean isSimpleTargetPattern(String pattern) {
+ if (pattern.startsWith("-")) {
+ return false;
+ }
+
+ for (String suffix : SUFFIXES) {
+ if (pattern.endsWith(":" + suffix)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Directory prefix to use when resolving relative labels (rather than absolute ones). For
+ * example, if the working directory is "<workspace root>/foo", then this should be "foo",
+ * which will make patterns such as "bar:bar" be resolved as "//foo/bar:bar". This makes the
+ * command line a bit more convenient to use.
+ */
+ private final String relativeDirectory;
+
+ /**
+ * Creates a new parser with the given offset for relative patterns.
+ */
+ public Parser(String relativeDirectory) {
+ this.relativeDirectory = relativeDirectory;
+ }
+
+ /**
+ * Parses the given pattern, and throws an exception if the pattern is invalid.
+ *
+ * @return a target pattern corresponding to the pattern parsed
+ * @throws TargetParsingException if the pattern is invalid
+ */
+ public TargetPattern parse(String pattern) throws TargetParsingException {
+ // The structure of this method is by cases, according to the usage string
+ // constant (see lib/blaze/commands/target-syntax.txt).
+
+ String originalPattern = pattern;
+ final boolean isAbsolute = pattern.startsWith("//");
+
+ // We now absolutize non-absolute target patterns.
+ pattern = isAbsolute ? pattern.substring(2) : absolutize(pattern);
+ // Check for common errors.
+ if (pattern.startsWith("/")) {
+ throw new TargetParsingException("not a relative path or label: '" + pattern + "'");
+ }
+ if (pattern.isEmpty()) {
+ throw new TargetParsingException("the empty string is not a valid target");
+ }
+
+ // Transform "/BUILD" suffix into ":BUILD" to accept //foo/bar/BUILD
+ // syntax as a synonym to //foo/bar:BUILD.
+ if (pattern.endsWith("/BUILD")) {
+ pattern = pattern.substring(0, pattern.length() - 6) + ":BUILD";
+ }
+
+ int colonIndex = pattern.lastIndexOf(':');
+ String packagePart = colonIndex < 0 ? pattern : pattern.substring(0, colonIndex);
+ String targetPart = colonIndex < 0 ? "" : pattern.substring(colonIndex + 1);
+
+ if (packagePart.equals("...")) {
+ packagePart = "/..."; // special case this for easier parsing
+ }
+
+ if (packagePart.endsWith("/")) {
+ throw new TargetParsingException("The package part of '" + originalPattern
+ + "' should not end in a slash");
+ }
+
+ if (packagePart.endsWith("/...")) {
+ String realPackagePart = removeSuffix(packagePart, "/...");
+ if (targetPart.isEmpty() || ALL_RULES_IN_SUFFIXES.contains(targetPart)) {
+ return new TargetsBelowPackage(originalPattern, realPackagePart, true);
+ } else if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) {
+ return new TargetsBelowPackage(originalPattern, realPackagePart, false);
+ }
+ }
+
+ if (ALL_RULES_IN_SUFFIXES.contains(targetPart)) {
+ return new TargetsInPackage(
+ originalPattern, pattern, ":" + targetPart, isAbsolute, true, true);
+ }
+
+ if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) {
+ return new TargetsInPackage(
+ originalPattern, pattern, ":" + targetPart, isAbsolute, false, true);
+ }
+
+
+ if (isAbsolute || pattern.contains(":")) {
+ String fullLabel = "//" + pattern;
+ try {
+ LabelValidator.validateAbsoluteLabel(fullLabel);
+ } catch (BadLabelException e) {
+ String error = "invalid target format '" + originalPattern + "': " + e.getMessage();
+ throw new TargetParsingException(error);
+ }
+ return new SingleTarget(fullLabel);
+ }
+
+ // This is a stripped-down version of interpretPathAsTarget that does no I/O. We have a basic
+ // relative path. e.g. "foo/bar/Wiz.java". The strictest correct check we can do here (without
+ // I/O) is just to ensure that there is *some* prefix that is a valid package-name. It's
+ // sufficient to test the first segment. This is really a rather weak check; perhaps we should
+ // just eliminate it.
+ int slashIndex = pattern.indexOf('/');
+ if (slashIndex < 0) {
+ throw new TargetParsingException("ambiguous target pattern: '" + pattern + "' could "
+ + "potentially be the name of a package; use '" + pattern + ":all' to mean \"all "
+ + "rules in this package\", '" + pattern + "/...' to mean \"all rules recursively "
+ + "beneath this package\", or '//" + pattern + "' to mean \"the default rule in this "
+ + "package\"");
+ }
+ String errorMessage = LabelValidator.validatePackageName(pattern.substring(0, slashIndex));
+ if (errorMessage != null) {
+ throw new TargetParsingException("Bad target pattern '" + originalPattern + "': " +
+ errorMessage);
+ }
+ return new InterpretPathAsTarget(pattern);
+ }
+
+ /**
+ * Absolutizes the target pattern to the offset.
+ * Patterns starting with "/" are absolute and not modified.
+ *
+ * If the offset is "foo":
+ * absolutize(":bar") --> "foo:bar"
+ * absolutize("bar") --> "foo/bar"
+ * absolutize("/biz/bar") --> "biz/bar" (absolute)
+ * absolutize("biz:bar") --> "foo/biz:bar"
+ *
+ * @param pattern The target pattern to parse.
+ * @return the pattern, absolutized to the offset if approprate.
+ */
+ private String absolutize(String pattern) {
+ if (relativeDirectory.isEmpty() || pattern.startsWith("/")) {
+ return pattern;
+ }
+
+ // It seems natural to use {@link PathFragment#getRelative()} here,
+ // but it doesn't work when the pattern starts with ":".
+ // "foo".getRelative(":all") would return "foo/:all", where we
+ // really want "foo:all".
+ return pattern.startsWith(":")
+ ? relativeDirectory + pattern
+ : relativeDirectory + "/" + pattern;
+ }
+ }
+
+ /**
+ * The target pattern type (targets below package, in package, explicit target, etc.)
+ */
+ public enum Type {
+ /** A path interpreted as a target, eg "foo/bar/baz" */
+ PATH_AS_TARGET,
+ /** An explicit target, eg "//foo:bar." */
+ SINGLE_TARGET,
+ /** Targets below a package, eg "foo/...". */
+ TARGETS_BELOW_PACKAGE,
+ /** Target in a package, eg "foo:all". */
+ TARGETS_IN_PACKAGE;
+ }
+}