| // 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 com.google.common.base.Preconditions; | 
 | import com.google.common.collect.ImmutableList; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.common.collect.ImmutableSet; | 
 | import com.google.common.collect.Iterables; | 
 | import com.google.devtools.build.lib.cmdline.Label; | 
 | import com.google.devtools.build.lib.cmdline.LabelSyntaxException; | 
 | import com.google.devtools.build.lib.cmdline.PackageIdentifier; | 
 | import com.google.devtools.build.lib.cmdline.ResolvedTargets; | 
 | import com.google.devtools.build.lib.cmdline.TargetParsingException; | 
 | import com.google.devtools.build.lib.cmdline.TargetPattern; | 
 | import com.google.devtools.build.lib.cmdline.TargetPattern.ContainsTBDForTBDResult; | 
 | 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.InterruptibleSupplier; | 
 | import com.google.devtools.build.skyframe.SkyFunctionName; | 
 | import com.google.devtools.build.skyframe.SkyKey; | 
 | import com.google.devtools.build.skyframe.SkyValue; | 
 | import java.io.IOException; | 
 | import java.io.ObjectInputStream; | 
 | import java.io.ObjectOutputStream; | 
 | import java.io.Serializable; | 
 | import java.util.ArrayList; | 
 | 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 ResolvedTargets<Label> targets; | 
 |  | 
 |   TargetPatternValue(ResolvedTargets<Label> targets) { | 
 |     this.targets = Preconditions.checkNotNull(targets); | 
 |   } | 
 |  | 
 |   private void writeObject(ObjectOutputStream out) throws IOException { | 
 |     List<String> ts = new ArrayList<>(); | 
 |     List<String> filteredTs = new ArrayList<>(); | 
 |     for (Label target : targets.getTargets()) { | 
 |       ts.add(target.toString()); | 
 |     } | 
 |     for (Label target : targets.getFilteredTargets()) { | 
 |       filteredTs.add(target.toString()); | 
 |     } | 
 |  | 
 |     out.writeObject(ts); | 
 |     out.writeObject(filteredTs); | 
 |   } | 
 |  | 
 |   private Label labelFromString(String labelString) { | 
 |     try { | 
 |       return Label.parseAbsolute(labelString, ImmutableMap.of()); | 
 |     } catch (LabelSyntaxException e) { | 
 |       throw new IllegalStateException(e); | 
 |     } | 
 |   } | 
 |  | 
 |   @SuppressWarnings("unchecked") | 
 |   private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | 
 |     List<String> ts = (List<String>) in.readObject(); | 
 |     List<String> filteredTs = (List<String>) in.readObject(); | 
 |  | 
 |     ResolvedTargets.Builder<Label> builder = ResolvedTargets.<Label>builder(); | 
 |     for (String labelString : ts) { | 
 |       builder.add(labelFromString(labelString)); | 
 |     } | 
 |  | 
 |     for (String labelString : filteredTs) { | 
 |       builder.remove(labelFromString(labelString)); | 
 |     } | 
 |     this.targets = builder.build(); | 
 |   } | 
 |  | 
 |   @SuppressWarnings("unused") | 
 |   private void readObjectNoData() { | 
 |     throw new IllegalStateException(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Create a target pattern {@link SkyKey}. Throws {@link TargetParsingException} if the provided | 
 |    * {@code pattern} cannot be parsed. | 
 |    * | 
 |    * @param pattern The pattern, eg "-foo/biz...". If the first character is "-", the pattern is | 
 |    *     treated as a negative pattern. | 
 |    * @param policy The filtering policy, eg "only return test targets" | 
 |    * @param offset The offset to apply to relative target patterns. | 
 |    */ | 
 |   @ThreadSafe | 
 |   public static TargetPatternKey key(String pattern, FilteringPolicy policy, String offset) | 
 |       throws TargetParsingException { | 
 |     return Iterables.getOnlyElement(keys(ImmutableList.of(pattern), policy, offset)).getSkyKey(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Returns an iterable of {@link TargetPatternSkyKeyOrException}, with {@link TargetPatternKey} | 
 |    * arguments, in the same order as the list of patterns provided as input. If a provided pattern | 
 |    * fails to parse, the element in the returned iterable corresponding to it will throw when its | 
 |    * {@link TargetPatternSkyKeyOrException#getSkyKey} method is called. | 
 |    * | 
 |    * @param patterns The list of patterns, eg "-foo/biz...". If a pattern's first character is "-", | 
 |    *     it is treated as a negative pattern. | 
 |    * @param policy The filtering policy, eg "only return test targets" | 
 |    * @param offset The offset to apply to relative target patterns. | 
 |    */ | 
 |   @ThreadSafe | 
 |   public static Iterable<TargetPatternSkyKeyOrException> keys(List<String> patterns, | 
 |       FilteringPolicy policy, String offset) { | 
 |     TargetPattern.Parser parser = new TargetPattern.Parser(offset); | 
 |     ImmutableList.Builder<TargetPatternSkyKeyOrException> builder = ImmutableList.builder(); | 
 |     for (String pattern : patterns) { | 
 |       boolean positive = !pattern.startsWith("-"); | 
 |       String absoluteValueOfPattern = positive ? pattern : pattern.substring(1); | 
 |       TargetPattern targetPattern; | 
 |       try { | 
 |         targetPattern = parser.parse(absoluteValueOfPattern); | 
 |       } catch (TargetParsingException e) { | 
 |         builder.add(new TargetPatternSkyKeyException(e, absoluteValueOfPattern)); | 
 |         continue; | 
 |       } | 
 |       TargetPatternKey targetPatternKey = | 
 |           new TargetPatternKey( | 
 |               targetPattern, | 
 |               positive ? policy : FilteringPolicies.NO_FILTER, | 
 |               /*isNegative=*/ !positive, | 
 |               offset, | 
 |               ImmutableSet.<PathFragment>of()); | 
 |       builder.add(new TargetPatternSkyKeyValue(targetPatternKey)); | 
 |     } | 
 |     return builder.build(); | 
 |   } | 
 |  | 
 |   @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.getParsedPattern(), | 
 |         policy, | 
 |         original.isNegative(), | 
 |         original.getOffset(), | 
 |         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) { | 
 |         if (laterParsedPattern.containsTBDForTBD(targetPattern) | 
 |             == ContainsTBDForTBDResult.DIRECTORY_EXCLUSION_WOULD_BE_EXACT) { | 
 |           return new TargetPatternKeyWithExclusionsResult(Optional.empty(), ImmutableList.of()); | 
 |         } else { | 
 |           switch (targetPattern.containsTBDForTBD(laterParsedPattern)) { | 
 |             case DIRECTORY_EXCLUSION_WOULD_BE_EXACT: | 
 |               excludedDirectoriesBuilder.add( | 
 |                   laterParsedPattern.getDirectoryForTargetsUnderDirectory().getPackageFragment()); | 
 |               break; | 
 |             case DIRECTORY_EXCLUSION_WOULD_BE_TOO_BROAD: | 
 |               indicesOfNegativePatternsThatNeedToBeIncludedBuilder.add(j); | 
 |               break; | 
 |             case OTHER: | 
 |             default: | 
 |               // Nothing to do with this pattern. | 
 |           } | 
 |         } | 
 |       } else if (excludeSingleTargets && laterParsedPattern.getType() == Type.SINGLE_TARGET) { | 
 |         try { | 
 |           Label label = | 
 |               Label.parseAbsolute( | 
 |                   laterParsedPattern.getSingleTargetPath(), | 
 |                   /*repositoryMapping=*/ ImmutableMap.of()); | 
 |           excludedSingleTargetsBuilder.add(label); | 
 |         } catch (LabelSyntaxException e) { | 
 |           indicesOfNegativePatternsThatNeedToBeIncludedBuilder.add(j); | 
 |         } | 
 |       } 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, Serializable { | 
 |  | 
 |     private final TargetPattern parsedPattern; | 
 |     private final FilteringPolicy policy; | 
 |     private final boolean isNegative; | 
 |  | 
 |     private final String offset; | 
 |     private final ImmutableSet<PathFragment> excludedSubdirectories; | 
 |  | 
 |     public TargetPatternKey( | 
 |         TargetPattern parsedPattern, | 
 |         FilteringPolicy policy, | 
 |         boolean isNegative, | 
 |         String offset, | 
 |         ImmutableSet<PathFragment> excludedSubdirectories) { | 
 |       this.parsedPattern = Preconditions.checkNotNull(parsedPattern); | 
 |       this.policy = Preconditions.checkNotNull(policy); | 
 |       this.isNegative = isNegative; | 
 |       this.offset = offset; | 
 |       this.excludedSubdirectories = Preconditions.checkNotNull(excludedSubdirectories); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public SkyFunctionName functionName() { | 
 |       return SkyFunctions.TARGET_PATTERN; | 
 |     } | 
 |  | 
 |     public String getPattern() { | 
 |       return parsedPattern.getOriginalPattern(); | 
 |     } | 
 |  | 
 |     public TargetPattern getParsedPattern() { | 
 |       return parsedPattern; | 
 |     } | 
 |  | 
 |     public boolean isNegative() { | 
 |       return isNegative; | 
 |     } | 
 |  | 
 |     public FilteringPolicy getPolicy() { | 
 |       return policy; | 
 |     } | 
 |  | 
 |     public String getOffset() { | 
 |       return offset; | 
 |     } | 
 |  | 
 |     public ImmutableSet<PathFragment> getExcludedSubdirectories() { | 
 |       return excludedSubdirectories; | 
 |     } | 
 |  | 
 |     ImmutableSet<PathFragment> getAllSubdirectoriesToExclude( | 
 |         Iterable<PathFragment> blacklistedPackagePrefixes) throws InterruptedException { | 
 |       ImmutableSet.Builder<PathFragment> excludedPathsBuilder = ImmutableSet.builder(); | 
 |       excludedPathsBuilder.addAll(getExcludedSubdirectories()); | 
 |       excludedPathsBuilder.addAll( | 
 |           getAllBlacklistedSubdirectoriesToExclude(() -> blacklistedPackagePrefixes)); | 
 |       return excludedPathsBuilder.build(); | 
 |     } | 
 |  | 
 |     public ImmutableSet<PathFragment> getAllBlacklistedSubdirectoriesToExclude( | 
 |         InterruptibleSupplier<? extends Iterable<PathFragment>> blacklistedPackagePrefixes) | 
 |         throws InterruptedException { | 
 |       return getAllBlacklistedSubdirectoriesToExclude(parsedPattern, blacklistedPackagePrefixes); | 
 |     } | 
 |  | 
 |     public static ImmutableSet<PathFragment> getAllBlacklistedSubdirectoriesToExclude( | 
 |         TargetPattern pattern, | 
 |         InterruptibleSupplier<? extends Iterable<PathFragment>> blacklistedPackagePrefixes) | 
 |         throws InterruptedException { | 
 |       ImmutableSet.Builder<PathFragment> blacklistedPathsBuilder = ImmutableSet.builder(); | 
 |       if (pattern.getType() == Type.TARGETS_BELOW_DIRECTORY) { | 
 |         for (PathFragment blacklistedPackagePrefix : blacklistedPackagePrefixes.get()) { | 
 |           PackageIdentifier pkgIdForBlacklistedDirectorPrefix = | 
 |               PackageIdentifier.create( | 
 |                   pattern.getDirectoryForTargetsUnderDirectory().getRepository(), | 
 |                   blacklistedPackagePrefix); | 
 |           if (pattern.containsAllTransitiveSubdirectoriesForTBD( | 
 |               pkgIdForBlacklistedDirectorPrefix)) { | 
 |             blacklistedPathsBuilder.add(blacklistedPackagePrefix); | 
 |           } | 
 |         } | 
 |       } | 
 |       return blacklistedPathsBuilder.build(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String toString() { | 
 |       return String.format( | 
 |           "%s, excludedSubdirs=%s, filteringPolicy=%s", | 
 |           (isNegative ? "-" : "") + parsedPattern.getOriginalPattern(), | 
 |           excludedSubdirectories, | 
 |           getPolicy()); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int hashCode() { | 
 |       return Objects.hash(parsedPattern, isNegative, policy, offset, | 
 |           excludedSubdirectories); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean equals(Object obj) { | 
 |       if (!(obj instanceof TargetPatternKey)) { | 
 |         return false; | 
 |       } | 
 |       TargetPatternKey other = (TargetPatternKey) obj; | 
 |  | 
 |       return other.isNegative == this.isNegative && other.parsedPattern.equals(this.parsedPattern) | 
 |           && other.offset.equals(this.offset) && other.policy.equals(this.policy) | 
 |           && other.excludedSubdirectories.equals(this.excludedSubdirectories); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * Wrapper for a target pattern {@link SkyKey} or the {@link TargetParsingException} thrown when | 
 |    * trying to compute it. | 
 |    */ | 
 |   public interface TargetPatternSkyKeyOrException { | 
 |  | 
 |     /** | 
 |      * Returns the stored {@link SkyKey} or throws {@link TargetParsingException} if one was thrown | 
 |      * when computing the key. | 
 |      */ | 
 |     TargetPatternKey getSkyKey() throws TargetParsingException; | 
 |  | 
 |     /** | 
 |      * Returns the pattern that resulted in the stored {@link SkyKey} or {@link | 
 |      * TargetParsingException}. | 
 |      */ | 
 |     String getOriginalPattern(); | 
 |   } | 
 |  | 
 |   private static final class TargetPatternSkyKeyValue implements TargetPatternSkyKeyOrException { | 
 |  | 
 |     private final TargetPatternKey value; | 
 |  | 
 |     private TargetPatternSkyKeyValue(TargetPatternKey value) { | 
 |       this.value = value; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public TargetPatternKey getSkyKey() throws TargetParsingException { | 
 |       return value; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String getOriginalPattern() { | 
 |       return ((TargetPatternKey) value.argument()).getPattern(); | 
 |     } | 
 |   } | 
 |  | 
 |   private static final class TargetPatternSkyKeyException implements | 
 |       TargetPatternSkyKeyOrException { | 
 |  | 
 |     private final TargetParsingException exception; | 
 |     private final String originalPattern; | 
 |  | 
 |     private TargetPatternSkyKeyException(TargetParsingException exception, String originalPattern) { | 
 |       this.exception = exception; | 
 |       this.originalPattern = originalPattern; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public TargetPatternKey getSkyKey() throws TargetParsingException { | 
 |       throw exception; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String getOriginalPattern() { | 
 |       return originalPattern; | 
 |     } | 
 |   } | 
 | } |