| // Copyright 2025 The Bazel Authors. 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.collect; |
| |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| |
| /** |
| * A simple matcher for checking whether a given label is part is a set of simple target patterns. |
| * |
| * <p>This does not implement full target patterns. Specifically, it handles: |
| * |
| * <ul> |
| * <li>Absolute labels, such as {@code //package:target} or {@code //package/subpackage} |
| * <li>Absolute package paths and all subpackages and targets, such as {@code //package/...} |
| * <li>Negative patterns of the above, such as {@code -//package:target} |
| * </ul> |
| * |
| * It does not handle: |
| * |
| * <ul> |
| * <li>Relative labels (all labels with no repository are assumed to be in the main repository) |
| * <li>The {@code :all} or {@code :*} qualifiers |
| * </ul> |
| * |
| * Patterns are processed in the order given, including negative patterns that override previous |
| * patterns. This means that if the patterns are |
| * |
| * <ul> |
| * <li>{@code //package/...} |
| * <li>{@code -//package/subpackage/...} |
| * <li>{@code //package/subpackage/further/...} |
| * </ul> |
| * |
| * then the labels {@code //package:something}, {@code //package/another} and {@code |
| * //package/subpackage/further:anything} all match, but the label {@code |
| * //package/subpackage:something} does not match. |
| * |
| * <p>Further note that this class does no loading of BUILD files and performs no verification that |
| * targets actually exist: it simply matches abstract labels against patterns. |
| */ |
| public class SimpleTargetPatternMatcher { |
| public static SimpleTargetPatternMatcher create(ImmutableList<String> patterns) |
| throws LabelSyntaxException { |
| ImmutableList.Builder<SinglePatternMatcher> singlePatternMatcherBuilder = |
| ImmutableList.builder(); |
| for (String pattern : patterns) { |
| SinglePatternMatcher matcher = SimpleTargetPatternMatcher.createSinglePatternMatcher(pattern); |
| singlePatternMatcherBuilder.add(matcher); |
| } |
| return new SimpleTargetPatternMatcher(singlePatternMatcherBuilder.build()); |
| } |
| |
| private final ImmutableList<SinglePatternMatcher> singlePatternMatchers; |
| |
| private SimpleTargetPatternMatcher(ImmutableList<SinglePatternMatcher> singlePatternMatchers) { |
| this.singlePatternMatchers = singlePatternMatchers; |
| } |
| |
| public boolean isEmpty() { |
| return this.singlePatternMatchers.isEmpty(); |
| } |
| |
| /** Returns {@code true} if the label matches all patterns in this matcher. */ |
| public boolean contains(Label label) { |
| if (this.singlePatternMatchers.isEmpty()) { |
| return false; |
| } |
| |
| // Check each sub-matcher. |
| MatchResult result = MatchResult.EXCLUDE; |
| for (SinglePatternMatcher matcher : this.singlePatternMatchers) { |
| MatchResult matchResult = matcher.matches(label); |
| if (matchResult == MatchResult.INCLUDE || matchResult == MatchResult.EXCLUDE) { |
| result = matchResult; |
| } |
| } |
| return result == MatchResult.INCLUDE; |
| } |
| |
| @Override |
| public String toString() { |
| String joined = |
| this.singlePatternMatchers.stream() |
| .map(SinglePatternMatcher::toString) |
| .collect(joining(",")); |
| return String.format("[%s]", joined); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other instanceof SimpleTargetPatternMatcher otherMatcher) { |
| return this.singlePatternMatchers.equals(otherMatcher.singlePatternMatchers); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.singlePatternMatchers.hashCode(); |
| } |
| |
| private static SinglePatternMatcher createSinglePatternMatcher(String pattern) |
| throws LabelSyntaxException { |
| if (pattern.startsWith("-")) { |
| // Strip off the leading '-' and create a matcher for what remains. This will technically |
| // handle a series of nested negative patterns (like `---//exact:target`), but isn't worth |
| // detecting and throwing an error. |
| pattern = pattern.substring(1); |
| SinglePatternMatcher inner = createSinglePatternMatcher(pattern); |
| return new NegativeMatcher(inner); |
| } else if (pattern.endsWith("/...")) { |
| return new WildcardMatcher(pattern); |
| } |
| |
| // Just match the pattern as an exact label. |
| return new ExactMatcher(pattern); |
| } |
| |
| private enum MatchResult { |
| INCLUDE, |
| EXCLUDE, |
| NOT_RELEVANT; |
| } |
| |
| private sealed interface SinglePatternMatcher |
| permits ExactMatcher, NegativeMatcher, WildcardMatcher { |
| MatchResult matches(Label label); |
| } |
| |
| /** Checks if the given label exactly matches the pattern. */ |
| private static final class ExactMatcher implements SinglePatternMatcher { |
| private final String rawPattern; |
| private final Label label; |
| |
| private ExactMatcher(String pattern) throws LabelSyntaxException { |
| this.rawPattern = pattern; |
| this.label = Label.parseCanonical(pattern); |
| } |
| |
| @Override |
| public MatchResult matches(Label label) { |
| if (this.label.equals(label)) { |
| return MatchResult.INCLUDE; |
| } |
| return MatchResult.NOT_RELEVANT; |
| } |
| |
| @Override |
| public String toString() { |
| return this.rawPattern; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other instanceof ExactMatcher otherMatcher) { |
| return this.rawPattern.equals(otherMatcher.rawPattern); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.rawPattern.hashCode(); |
| } |
| } |
| |
| /** Checks if the given label fails to match the pattern. */ |
| private static final class NegativeMatcher implements SinglePatternMatcher { |
| private final SinglePatternMatcher inner; |
| |
| private NegativeMatcher(SinglePatternMatcher inner) { |
| this.inner = inner; |
| } |
| |
| @Override |
| public MatchResult matches(Label label) { |
| return switch (this.inner.matches(label)) { |
| case INCLUDE -> MatchResult.EXCLUDE; |
| case EXCLUDE, NOT_RELEVANT -> MatchResult.NOT_RELEVANT; |
| }; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("-%s", this.inner); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other instanceof NegativeMatcher otherMatcher) { |
| return this.inner.equals(otherMatcher.inner); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 0x37 ^ this.inner.hashCode(); |
| } |
| } |
| |
| private static final class WildcardMatcher implements SinglePatternMatcher { |
| private final PathFragment packagePath; |
| |
| private WildcardMatcher(String pattern) { |
| // Strip off the leading "//" and the trailing "/..." and create the wildcard matcher. |
| this.packagePath = PathFragment.create(pattern.substring(2, pattern.lastIndexOf("..."))); |
| } |
| |
| @Override |
| public MatchResult matches(Label label) { |
| if (label.getPackageFragment().startsWith(this.packagePath)) { |
| return MatchResult.INCLUDE; |
| } |
| return MatchResult.NOT_RELEVANT; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("//%s/...", this.packagePath); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other instanceof WildcardMatcher otherMatcher) { |
| return this.packagePath.equals(otherMatcher.packagePath); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.packagePath.hashCode(); |
| } |
| } |
| } |