Modify SkyQueryEnvironment#evalTargetPatternKey to take into account the filtering policy specified in the TargetPatternKey.
Add a TargetExcludingFilteringPolicy that can filter multiple single targets
Allow for TargetPatternValue.combineTargetsBelowDirectoryWithNegativePatterns the option of excluding single targets.
Improve toString of some filtering policies.

PiperOrigin-RevId: 230566477
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
index 9936f33..a4644a5 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/FilteringPolicies.java
@@ -33,6 +33,12 @@
 
   /** Returns the result of applying y, if target passes x. */
   public static FilteringPolicy and(final FilteringPolicy x, final FilteringPolicy y) {
+    if (x.equals(NO_FILTER)) {
+      return y;
+    }
+    if (y.equals(NO_FILTER)) {
+      return x;
+    }
     return new AndFilteringPolicy(x, y);
   }
 
@@ -69,6 +75,11 @@
     public boolean shouldRetain(Target target, boolean explicit) {
       return true;
     }
+
+    @Override
+    public String toString() {
+      return "[]";
+    }
   }
 
   private static class FilterManual extends AbstractFilteringPolicy {
@@ -157,5 +168,10 @@
       AndFilteringPolicy other = (AndFilteringPolicy) obj;
       return other.firstPolicy.equals(firstPolicy) && other.secondPolicy.equals(secondPolicy);
     }
+
+    @Override
+    public String toString() {
+      return String.format("and_filter(%s, %s)", firstPolicy, secondPolicy);
+    }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index 30fc480..08c44e9 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -15,6 +15,7 @@
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static com.google.devtools.build.lib.pkgcache.FilteringPolicies.NO_FILTER;
 
 import com.google.common.base.Ascii;
 import com.google.common.base.Function;
@@ -725,13 +726,24 @@
           reportBuildFileError(owner, exn.getMessage());
           return Futures.immediateFuture(null);
         };
-    ListenableFuture<Void> evalFuture = patternToEval.evalAsync(
-        resolver,
-        blacklistedSubdirectoriesToExclude,
-        additionalSubdirectoriesToExclude,
-        callback,
-        QueryException.class,
-        executor);
+    Callback<Target> filteredCallback = callback;
+    if (!targetPatternKey.getPolicy().equals(NO_FILTER)) {
+      filteredCallback =
+          targets ->
+              callback.process(
+                  Iterables.filter(
+                      targets,
+                      target ->
+                          targetPatternKey.getPolicy().shouldRetain(target, /*explicit=*/ false)));
+    }
+    ListenableFuture<Void> evalFuture =
+        patternToEval.evalAsync(
+            resolver,
+            blacklistedSubdirectoriesToExclude,
+            additionalSubdirectoriesToExclude,
+            filteredCallback,
+            QueryException.class,
+            executor);
     return QueryTaskFutureImpl.ofDelegate(
         Futures.catchingAsync(
             evalFuture,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternValue.java
index 2c43726..bfbb45d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternValue.java
@@ -108,8 +108,8 @@
     // subdirectory exclusion) is only acceptable because all the use cases for query universe
     // preloading involve short (<10 items) pattern sequences.
     Iterable<TargetPatternKey> combinedTargetPatternKeys =
-        TargetPatternValue.combineNegativeTargetsBelowDirectoryPatterns(
-            targetPatternKeysBuilder.build());
+        TargetPatternValue.combineTargetsBelowDirectoryWithNegativePatterns(
+            targetPatternKeysBuilder.build(), /*excludeSingleTargets=*/ false);
     for (TargetPatternKey targetPatternKey : combinedTargetPatternKeys) {
       if (targetPatternKey.isNegative()
           && !targetPatternKey
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetExcludingFilteringPolicy.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetExcludingFilteringPolicy.java
new file mode 100644
index 0000000..4069ea6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetExcludingFilteringPolicy.java
@@ -0,0 +1,41 @@
+// Copyright 2018 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.collect.ImmutableSet;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
+
+/**
+ * A filtering policy that excludes multiple single targets. These are not expected to be a part of
+ * any SkyKey and it's expected that the number of targets is not too large.
+ */
+class TargetExcludingFilteringPolicy extends FilteringPolicy {
+  private final ImmutableSet<Label> excludedSingleTargets;
+
+  TargetExcludingFilteringPolicy(ImmutableSet<Label> excludedSingleTargets) {
+    this.excludedSingleTargets = excludedSingleTargets;
+  }
+
+  @Override
+  public boolean shouldRetain(Target target, boolean explicit) {
+    return !excludedSingleTargets.contains(target.getLabel());
+  }
+
+  @Override
+  public String toString() {
+    return String.format("excludedTargets%s", excludedSingleTargets);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternValue.java
index 9e3d0ef..0237af1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternValue.java
@@ -25,6 +25,7 @@
 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;
@@ -145,8 +146,8 @@
       TargetPatternKey targetPatternKey =
           new TargetPatternKey(
               targetPattern,
-              positive ? policy : FilteringPolicies.NO_FILTER, /*isNegative=*/
-              !positive,
+              positive ? policy : FilteringPolicies.NO_FILTER,
+              /*isNegative=*/ !positive,
               offset,
               ImmutableSet.<PathFragment>of());
       builder.add(new TargetPatternSkyKeyValue(targetPatternKey));
@@ -155,25 +156,24 @@
   }
 
   @ThreadSafe
-  public static ImmutableList<TargetPatternKey> combineNegativeTargetsBelowDirectoryPatterns(
-      List<TargetPatternKey> keys) {
+  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 (!targetPatternKey
-                .getParsedPattern()
-                .getType()
-                .equals(TargetPattern.Type.TARGETS_BELOW_DIRECTORY)
-            || indicesOfNegativePatternsThatNeedToBeIncluded.contains(i)) {
+        if (indicesOfNegativePatternsThatNeedToBeIncluded.contains(i) || !positivePatternSeen) {
           builder.add(targetPatternKey);
         }
-        // Otherwise it's a negative TBD pattern which was combined with previous patterns as an
-        // excluded directory.
       } else {
+        positivePatternSeen = true;
         TargetPatternKeyWithExclusionsResult result =
-            computeTargetPatternKeyWithExclusions(targetPatternKey, i, keys);
+            computeTargetPatternKeyWithExclusions(targetPatternKey, i, keys, excludeSingleTargets);
         result.targetPatternKeyMaybe.ifPresent(builder::add);
         indicesOfNegativePatternsThatNeedToBeIncluded.addAll(
             result.indicesOfNegativePatternsThatNeedToBeIncluded);
@@ -182,10 +182,21 @@
     return builder.build();
   }
 
-  private static TargetPatternKey setExcludedDirectories(
-      TargetPatternKey original, ImmutableSet<PathFragment> excludedSubdirectories) {
-    return new TargetPatternKey(original.getParsedPattern(), original.getPolicy(),
-        original.isNegative(), original.getOffset(), excludedSubdirectories);
+  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 {
@@ -204,16 +215,20 @@
   private static TargetPatternKeyWithExclusionsResult computeTargetPatternKeyWithExclusions(
       TargetPatternKey targetPatternKey,
       int position,
-      List<TargetPatternKey> keys) {
+      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()
-          && laterParsedPattern.getType() == TargetPattern.Type.TARGETS_BELOW_DIRECTORY) {
+      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());
@@ -230,12 +245,27 @@
             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(setExcludedDirectories(targetPatternKey, excludedDirectoriesBuilder.build())),
+        Optional.of(
+            setExcludedDirectoriesAndTargets(
+                targetPatternKey,
+                excludedDirectoriesBuilder.build(),
+                excludedSingleTargetsBuilder.build())),
         indicesOfNegativePatternsThatNeedToBeIncludedBuilder.build());
   }
 
@@ -312,7 +342,7 @@
         InterruptibleSupplier<? extends Iterable<PathFragment>> blacklistedPackagePrefixes)
         throws InterruptedException {
       ImmutableSet.Builder<PathFragment> blacklistedPathsBuilder = ImmutableSet.builder();
-      if (parsedPattern.getType() == TargetPattern.Type.TARGETS_BELOW_DIRECTORY) {
+      if (parsedPattern.getType() == Type.TARGETS_BELOW_DIRECTORY) {
         for (PathFragment blacklistedPackagePrefix : blacklistedPackagePrefixes.get()) {
           PackageIdentifier pkgIdForBlacklistedDirectorPrefix = PackageIdentifier.create(
               parsedPattern.getDirectoryForTargetsUnderDirectory().getRepository(),
@@ -328,7 +358,11 @@
 
     @Override
     public String toString() {
-      return (isNegative ? "-" : "") + parsedPattern.getOriginalPattern();
+      return String.format(
+          "%s, excludedSubdirs=%s, filteringPolicy=%s",
+          (isNegative ? "-" : "") + parsedPattern.getOriginalPattern(),
+          excludedSubdirectories,
+          getPolicy());
     }
 
     @Override
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
index 0c59fbe..03b7c51 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionSmartNegationTest.java
@@ -206,8 +206,8 @@
 
     // Then a event is published that says that negative non-TBD patterns are skipped.
     assertContainsEvent(
-        "Skipping '-//foo/bar': Negative target patterns of types other than \"targets below "
-            + "directory\" are not permitted.");
+        "Skipping '-//foo/bar, excludedSubdirs=[], filteringPolicy=[]': Negative target patterns of"
+            + " types other than \"targets below directory\" are not permitted.");
   }
 
   // Helpers: