| // Copyright 2014 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.skyframe; | |
| import static com.google.common.collect.ImmutableList.toImmutableList; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.ResolvedTargets; | |
| import com.google.devtools.build.lib.cmdline.SignedTargetPattern; | |
| import com.google.devtools.build.lib.cmdline.SignedTargetPattern.Sign; | |
| import com.google.devtools.build.lib.cmdline.TargetPattern; | |
| import com.google.devtools.build.lib.cmdline.TargetPattern.TargetsBelowDirectory; | |
| import com.google.devtools.build.lib.cmdline.TargetPattern.TargetsBelowDirectory.ContainsResult; | |
| import com.google.devtools.build.lib.cmdline.TargetPattern.Type; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; | |
| import com.google.devtools.build.lib.pkgcache.FilteringPolicies; | |
| import com.google.devtools.build.lib.pkgcache.FilteringPolicy; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.skyframe.SkyFunctionName; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| /** | |
| * A value referring to a computed set of resolved targets. This is used for the results of target | |
| * pattern parsing. | |
| */ | |
| @Immutable | |
| @ThreadSafe | |
| public final class TargetPatternValue implements SkyValue { | |
| private final ResolvedTargets<Label> targets; | |
| TargetPatternValue(ResolvedTargets<Label> targets) { | |
| this.targets = Preconditions.checkNotNull(targets); | |
| } | |
| /** | |
| * Create a target pattern {@link SkyKey}. | |
| * | |
| * @param pattern The pattern, eg "-foo/biz...". | |
| * @param policy The filtering policy, eg "only return test targets" | |
| */ | |
| @ThreadSafe | |
| public static TargetPatternKey key(SignedTargetPattern pattern, FilteringPolicy policy) { | |
| return new TargetPatternKey( | |
| pattern, pattern.sign() == Sign.POSITIVE ? policy : FilteringPolicies.NO_FILTER); | |
| } | |
| /** | |
| * Returns an iterable of {@link TargetPatternKey}s, in the same order as the list of patterns | |
| * provided as input. | |
| * | |
| * @param patterns The list of patterns, eg "-foo/biz...". | |
| * @param policy The filtering policy, eg "only return test targets" | |
| */ | |
| @ThreadSafe | |
| public static Iterable<TargetPatternKey> keys( | |
| List<SignedTargetPattern> patterns, FilteringPolicy policy) { | |
| return patterns.stream().map(pattern -> key(pattern, policy)).collect(toImmutableList()); | |
| } | |
| @ThreadSafe | |
| public static ImmutableList<TargetPatternKey> combineTargetsBelowDirectoryWithNegativePatterns( | |
| List<TargetPatternKey> keys, boolean excludeSingleTargets) { | |
| ImmutableList.Builder<TargetPatternKey> builder = ImmutableList.builder(); | |
| // We use indicesOfNegativePatternsThatNeedToBeIncluded to avoid adding negative TBD or single | |
| // target patterns that have already been combined with previous patterns as an excluded | |
| // directory or excluded single target. | |
| HashSet<Integer> indicesOfNegativePatternsThatNeedToBeIncluded = new HashSet<>(); | |
| boolean positivePatternSeen = false; | |
| for (int i = 0; i < keys.size(); i++) { | |
| TargetPatternKey targetPatternKey = keys.get(i); | |
| if (targetPatternKey.isNegative()) { | |
| if (indicesOfNegativePatternsThatNeedToBeIncluded.contains(i) || !positivePatternSeen) { | |
| builder.add(targetPatternKey); | |
| } | |
| } else { | |
| positivePatternSeen = true; | |
| TargetPatternKeyWithExclusionsResult result = | |
| computeTargetPatternKeyWithExclusions(targetPatternKey, i, keys, excludeSingleTargets); | |
| result.targetPatternKeyMaybe.ifPresent(builder::add); | |
| indicesOfNegativePatternsThatNeedToBeIncluded.addAll( | |
| result.indicesOfNegativePatternsThatNeedToBeIncluded); | |
| } | |
| } | |
| return builder.build(); | |
| } | |
| private static TargetPatternKey setExcludedDirectoriesAndTargets( | |
| TargetPatternKey original, | |
| ImmutableSet<PathFragment> excludedSubdirectories, | |
| ImmutableSet<Label> excludedSingleTargets) { | |
| FilteringPolicy policy = original.getPolicy(); | |
| if (!excludedSingleTargets.isEmpty()) { | |
| policy = | |
| FilteringPolicies.and(policy, new TargetExcludingFilteringPolicy(excludedSingleTargets)); | |
| } | |
| return new TargetPatternKey(original.getSignedParsedPattern(), policy, excludedSubdirectories); | |
| } | |
| private static class TargetPatternKeyWithExclusionsResult { | |
| private final Optional<TargetPatternKey> targetPatternKeyMaybe; | |
| private final ImmutableList<Integer> indicesOfNegativePatternsThatNeedToBeIncluded; | |
| private TargetPatternKeyWithExclusionsResult( | |
| Optional<TargetPatternKey> targetPatternKeyMaybe, | |
| ImmutableList<Integer> indicesOfNegativePatternsThatNeedToBeIncluded) { | |
| this.targetPatternKeyMaybe = targetPatternKeyMaybe; | |
| this.indicesOfNegativePatternsThatNeedToBeIncluded = | |
| indicesOfNegativePatternsThatNeedToBeIncluded; | |
| } | |
| } | |
| private static TargetPatternKeyWithExclusionsResult computeTargetPatternKeyWithExclusions( | |
| TargetPatternKey targetPatternKey, | |
| int position, | |
| List<TargetPatternKey> keys, | |
| boolean excludeSingleTargets) { | |
| TargetPattern targetPattern = targetPatternKey.getParsedPattern(); | |
| ImmutableSet.Builder<PathFragment> excludedDirectoriesBuilder = ImmutableSet.builder(); | |
| ImmutableSet.Builder<Label> excludedSingleTargetsBuilder = ImmutableSet.builder(); | |
| ImmutableList.Builder<Integer> indicesOfNegativePatternsThatNeedToBeIncludedBuilder = | |
| ImmutableList.builder(); | |
| for (int j = position + 1; j < keys.size(); j++) { | |
| TargetPatternKey laterTargetPatternKey = keys.get(j); | |
| TargetPattern laterParsedPattern = laterTargetPatternKey.getParsedPattern(); | |
| if (!laterTargetPatternKey.isNegative()) { | |
| continue; | |
| } | |
| if (laterParsedPattern.getType() == Type.TARGETS_BELOW_DIRECTORY) { | |
| TargetsBelowDirectory laterParsedTargetsBelowDirectory = | |
| (TargetsBelowDirectory) laterParsedPattern; | |
| if (targetPattern.getType() == Type.TARGETS_BELOW_DIRECTORY) { | |
| TargetsBelowDirectory targetsBelowDirectory = (TargetsBelowDirectory) targetPattern; | |
| if (laterParsedTargetsBelowDirectory.contains(targetsBelowDirectory) | |
| == ContainsResult.DIRECTORY_EXCLUSION_WOULD_BE_EXACT) { | |
| return new TargetPatternKeyWithExclusionsResult(Optional.empty(), ImmutableList.of()); | |
| } else { | |
| switch (targetsBelowDirectory.contains(laterParsedTargetsBelowDirectory)) { | |
| case DIRECTORY_EXCLUSION_WOULD_BE_EXACT: | |
| excludedDirectoriesBuilder.add( | |
| laterParsedTargetsBelowDirectory.getDirectory().getPackageFragment()); | |
| break; | |
| case DIRECTORY_EXCLUSION_WOULD_BE_TOO_BROAD: | |
| indicesOfNegativePatternsThatNeedToBeIncludedBuilder.add(j); | |
| break; | |
| case NOT_CONTAINED: | |
| // Nothing to do with this pattern. | |
| } | |
| } | |
| } | |
| } else if (excludeSingleTargets && laterParsedPattern.getType() == Type.SINGLE_TARGET) { | |
| excludedSingleTargetsBuilder.add(laterParsedPattern.getSingleTargetLabel()); | |
| } else { | |
| indicesOfNegativePatternsThatNeedToBeIncludedBuilder.add(j); | |
| } | |
| } | |
| return new TargetPatternKeyWithExclusionsResult( | |
| Optional.of( | |
| setExcludedDirectoriesAndTargets( | |
| targetPatternKey, | |
| excludedDirectoriesBuilder.build(), | |
| excludedSingleTargetsBuilder.build())), | |
| indicesOfNegativePatternsThatNeedToBeIncludedBuilder.build()); | |
| } | |
| public ResolvedTargets<Label> getTargets() { | |
| return targets; | |
| } | |
| /** | |
| * A TargetPatternKey is a tuple of pattern (eg, "foo/..."), filtering policy, a relative pattern | |
| * offset, whether it is a positive or negative match, and a set of excluded subdirectories. | |
| */ | |
| @ThreadSafe | |
| public static class TargetPatternKey implements SkyKey { | |
| private final SignedTargetPattern signedParsedPattern; | |
| private final FilteringPolicy policy; | |
| /** | |
| * Must be "compatible" with {@link #signedParsedPattern}: if {@link #signedParsedPattern} is a | |
| * {@link TargetsBelowDirectory} object, then {@link TargetsBelowDirectory#containedIn} is false | |
| * for every element of {@code excludedSubdirectories}. | |
| */ | |
| private final ImmutableSet<PathFragment> excludedSubdirectories; | |
| public TargetPatternKey(SignedTargetPattern signedParsedPattern, FilteringPolicy policy) { | |
| this(signedParsedPattern, policy, ImmutableSet.of()); | |
| } | |
| private TargetPatternKey( | |
| SignedTargetPattern signedParsedPattern, | |
| FilteringPolicy policy, | |
| ImmutableSet<PathFragment> excludedSubdirectories) { | |
| this.signedParsedPattern = Preconditions.checkNotNull(signedParsedPattern); | |
| this.policy = Preconditions.checkNotNull(policy); | |
| this.excludedSubdirectories = Preconditions.checkNotNull(excludedSubdirectories); | |
| } | |
| @Override | |
| public SkyFunctionName functionName() { | |
| return SkyFunctions.TARGET_PATTERN; | |
| } | |
| public String getPattern() { | |
| return signedParsedPattern.pattern().getOriginalPattern(); | |
| } | |
| public TargetPattern getParsedPattern() { | |
| return signedParsedPattern.pattern(); | |
| } | |
| private SignedTargetPattern getSignedParsedPattern() { | |
| return signedParsedPattern; | |
| } | |
| public boolean isNegative() { | |
| return signedParsedPattern.sign() == Sign.NEGATIVE; | |
| } | |
| public FilteringPolicy getPolicy() { | |
| return policy; | |
| } | |
| public ImmutableSet<PathFragment> getExcludedSubdirectories() { | |
| return excludedSubdirectories; | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format( | |
| "%s, excludedSubdirs=%s, filteringPolicy=%s", | |
| (isNegative() ? "-" : "") + signedParsedPattern.pattern().getOriginalPattern(), | |
| excludedSubdirectories, | |
| policy); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(signedParsedPattern, policy, excludedSubdirectories); | |
| } | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (!(obj instanceof TargetPatternKey)) { | |
| return false; | |
| } | |
| TargetPatternKey other = (TargetPatternKey) obj; | |
| return other.signedParsedPattern.equals(this.signedParsedPattern) | |
| && other.policy.equals(this.policy) | |
| && other.excludedSubdirectories.equals(this.excludedSubdirectories); | |
| } | |
| } | |
| } |