Teach TargetPatterns "getDirectory" and "contains"
Target patterns now know what directory they reside in, and the
TargetsBelowDirectory pattern now uses that information to determine
whether it matches a superset (not necessarily a strict superset) of
the targets belonging to another pattern.
This leads toward more efficient processing of target patterns in
target pattern sequence evaluation.
--
MOS_MIGRATED_REVID=94016481
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
index 2c9d4ad..ead03af 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
@@ -20,10 +20,13 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.cmdline.LabelValidator.BadLabelException;
+import com.google.devtools.build.lib.cmdline.LabelValidator.PackageAndTarget;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import javax.annotation.concurrent.Immutable;
@@ -41,7 +44,7 @@
*
* <p>See lib/blaze/commands/target-syntax.txt for details.
*/
-public abstract class TargetPattern {
+public abstract class TargetPattern implements Serializable {
private static final Splitter SLASH_SPLITTER = Splitter.on('/');
private static final Joiner SLASH_JOINER = Joiner.on('/');
@@ -106,7 +109,7 @@
}
/**
- * Return the type of the pattern. Examples include "below package" like "foo/..." and "single
+ * Return the type of the pattern. Examples include "below directory" like "foo/..." and "single
* target" like "//x:y".
*/
public Type getType() {
@@ -119,13 +122,34 @@
public abstract <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
throws TargetParsingException, InterruptedException;
+ /**
+ * Returns {@code true} iff this pattern has type {@code Type.TARGETS_BELOW_DIRECTORY} and
+ * {@code containedPattern} is contained by or equals this pattern. For example,
+ * returns {@code true} for {@code this = TargetPattern ("//...")} and {@code containedPattern
+ * = TargetPattern ("//foo/...")}.
+ */
+ public abstract boolean containsBelowDirectory(TargetPattern containedPattern);
+
+ /**
+ * Returns the most specific containing directory of the patterns that could be matched by this
+ * pattern.
+ *
+ * <p>For patterns of type {@code Type.TARGETS_BELOW_DIRECTORY}, this returns the referred-to
+ * directory. For example, for "//foo/bar/...", this returns "foo/bar".
+ *
+ * <p>The returned value always has no leading "//" and no trailing "/".
+ */
+ public abstract String getDirectory();
+
private static final class SingleTarget extends TargetPattern {
private final String targetName;
+ private final String directory;
- private SingleTarget(String targetName) {
+ private SingleTarget(String targetName, String directory) {
super(Type.SINGLE_TARGET);
- this.targetName = targetName;
+ this.targetName = Preconditions.checkNotNull(targetName);
+ this.directory = Preconditions.checkNotNull(directory);
}
@Override
@@ -133,6 +157,33 @@
throws TargetParsingException, InterruptedException {
return resolver.getExplicitTarget(targetName);
}
+
+ @Override
+ public boolean containsBelowDirectory(TargetPattern containedPattern) {
+ return false;
+ }
+
+ @Override
+ public String getDirectory() {
+ return directory;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SingleTarget)) {
+ return false;
+ }
+ SingleTarget that = (SingleTarget) o;
+ return targetName.equals(that.targetName) && directory.equals(that.directory);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getType(), targetName, directory);
+ }
}
private static final class InterpretPathAsTarget extends TargetPattern {
@@ -141,7 +192,7 @@
private InterpretPathAsTarget(String path) {
super(Type.PATH_AS_TARGET);
- this.path = normalize(path);
+ this.path = normalize(Preconditions.checkNotNull(path));
}
@Override
@@ -164,8 +215,35 @@
}
}
- throw new TargetParsingException(
- "couldn't determine target from filename '" + path + "'");
+ throw new TargetParsingException("couldn't determine target from filename '" + path + "'");
+ }
+
+ @Override
+ public boolean containsBelowDirectory(TargetPattern containedPattern) {
+ return false;
+ }
+
+ @Override
+ public String getDirectory() {
+ int lastSlashIndex = path.lastIndexOf('/');
+ return lastSlashIndex < 0 ? "" : path.substring(0, lastSlashIndex);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof InterpretPathAsTarget)) {
+ return false;
+ }
+ InterpretPathAsTarget that = (InterpretPathAsTarget) o;
+ return path.equals(that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getType(), path);
}
}
@@ -181,9 +259,9 @@
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.originalPattern = Preconditions.checkNotNull(originalPattern);
+ this.pattern = Preconditions.checkNotNull(pattern);
+ this.suffix = Preconditions.checkNotNull(suffix);
this.isAbsolute = isAbsolute;
this.rulesOnly = rulesOnly;
this.checkWildcardConflict = checkWildcardConflict;
@@ -202,6 +280,37 @@
rulesOnly);
}
+ @Override
+ public boolean containsBelowDirectory(TargetPattern containedPattern) {
+ return false;
+ }
+
+ @Override
+ public String getDirectory() {
+ return removeSuffix(pattern, suffix);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TargetsInPackage)) {
+ return false;
+ }
+ TargetsInPackage that = (TargetsInPackage) o;
+ return isAbsolute == that.isAbsolute && rulesOnly == that.rulesOnly
+ && checkWildcardConflict == that.checkWildcardConflict
+ && originalPattern.equals(that.originalPattern)
+ && pattern.equals(that.pattern) && suffix.equals(that.suffix);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getType(), originalPattern, pattern, suffix, isAbsolute, rulesOnly,
+ checkWildcardConflict);
+ }
+
/**
* 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.
@@ -236,23 +345,55 @@
}
}
- private static final class TargetsBelowPackage extends TargetPattern {
+ private static final class TargetsBelowDirectory extends TargetPattern {
private final String originalPattern;
- private final String pathPrefix;
+ private final String directory;
private final boolean rulesOnly;
- private TargetsBelowPackage(String originalPattern, String pathPrefix, boolean rulesOnly) {
- super(Type.TARGETS_BELOW_PACKAGE);
- this.originalPattern = originalPattern;
- this.pathPrefix = pathPrefix;
+ private TargetsBelowDirectory(String originalPattern, String directory, boolean rulesOnly) {
+ super(Type.TARGETS_BELOW_DIRECTORY);
+ this.originalPattern = Preconditions.checkNotNull(originalPattern);
+ this.directory = Preconditions.checkNotNull(directory);
this.rulesOnly = rulesOnly;
}
@Override
public <T> ResolvedTargets<T> eval(TargetPatternResolver<T> resolver)
throws TargetParsingException, InterruptedException {
- return resolver.findTargetsBeneathDirectory(originalPattern, pathPrefix, rulesOnly);
+ return resolver.findTargetsBeneathDirectory(originalPattern, directory, rulesOnly);
+ }
+
+ @Override
+ public boolean containsBelowDirectory(TargetPattern containedPattern) {
+ // Note that merely checking to see if the containedPattern's string expression beginsWith
+ // the TargetsBelowDirectory's directory is insufficient. "food" begins with "foo", but
+ // "//foo/..." does not contain "//food/...".
+ String containedDirectory = containedPattern.getDirectory() + "/";
+ return containedDirectory.startsWith(directory + "/");
+ }
+
+ @Override
+ public String getDirectory() {
+ return directory;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TargetsBelowDirectory)) {
+ return false;
+ }
+ TargetsBelowDirectory that = (TargetsBelowDirectory) o;
+ return rulesOnly == that.rulesOnly && originalPattern.equals(that.originalPattern)
+ && directory.equals(that.directory);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getType(), originalPattern, directory, rulesOnly);
}
}
@@ -362,9 +503,9 @@
if (packagePart.endsWith("/...")) {
String realPackagePart = removeSuffix(packagePart, "/...");
if (targetPart.isEmpty() || ALL_RULES_IN_SUFFIXES.contains(targetPart)) {
- return new TargetsBelowPackage(originalPattern, realPackagePart, true);
+ return new TargetsBelowDirectory(originalPattern, realPackagePart, true);
} else if (ALL_TARGETS_IN_SUFFIXES.contains(targetPart)) {
- return new TargetsBelowPackage(originalPattern, realPackagePart, false);
+ return new TargetsBelowDirectory(originalPattern, realPackagePart, false);
}
}
@@ -380,14 +521,15 @@
if (isAbsolute || pattern.contains(":")) {
+ PackageAndTarget packageAndTarget;
String fullLabel = "//" + pattern;
try {
- LabelValidator.validateAbsoluteLabel(fullLabel);
+ packageAndTarget = LabelValidator.validateAbsoluteLabel(fullLabel);
} catch (BadLabelException e) {
String error = "invalid target format '" + originalPattern + "': " + e.getMessage();
throw new TargetParsingException(error);
}
- return new SingleTarget(fullLabel);
+ return new SingleTarget(fullLabel, packageAndTarget.getPackageName());
}
// This is a stripped-down version of interpretPathAsTarget that does no I/O. We have a basic
@@ -444,8 +586,8 @@
PATH_AS_TARGET,
/** An explicit target, eg "//foo:bar." */
SINGLE_TARGET,
- /** Targets below a package, eg "foo/...". */
- TARGETS_BELOW_PACKAGE,
+ /** Targets below a directory, eg "foo/...". */
+ TARGETS_BELOW_DIRECTORY,
/** Target in a package, eg "foo:all". */
TARGETS_IN_PACKAGE;
}