Stream results of targets below directory to a callback rather than returning a ResolvedTargets set.

This is the first step in a series to allow processing large sets of targets in query target patterns via streaming batches rather than all at once. This should be a functional no-op.

--
MOS_MIGRATED_REVID=111609309
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java
index d3f758f..2641056 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursivePackageProviderBackedTargetPatternResolver.java
@@ -33,8 +33,11 @@
 import com.google.devtools.build.lib.pkgcache.FilteringPolicy;
 import com.google.devtools.build.lib.pkgcache.RecursivePackageProvider;
 import com.google.devtools.build.lib.pkgcache.TargetPatternResolverUtil;
+import com.google.devtools.build.lib.util.BatchCallback;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -43,6 +46,9 @@
 public class RecursivePackageProviderBackedTargetPatternResolver
     implements TargetPatternResolver<Target> {
 
+  // TODO(janakr): Move this to a more generic place and unify with SkyQueryEnvironment's value?
+  private static final int MAX_PACKAGES_BULK_GET = 10000;
+
   private final RecursivePackageProvider recursivePackageProvider;
   private final EventHandler eventHandler;
   private final FilteringPolicy policy;
@@ -154,17 +160,20 @@
   }
 
   @Override
-  public ResolvedTargets<Target> findTargetsBeneathDirectory(final RepositoryName repository,
-      String originalPattern, String directory, boolean rulesOnly,
-      ImmutableSet<String> excludedSubdirectories)
-      throws TargetParsingException, InterruptedException {
+  public <E extends Exception> void findTargetsBeneathDirectory(
+      final RepositoryName repository,
+      String originalPattern,
+      String directory,
+      boolean rulesOnly,
+      ImmutableSet<String> excludedSubdirectories,
+      BatchCallback<Target, E> callback)
+      throws TargetParsingException, E, InterruptedException {
     FilteringPolicy actualPolicy = rulesOnly
         ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy)
         : policy;
     ImmutableSet<PathFragment> excludedPathFragments =
         TargetPatternResolverUtil.getPathFragments(excludedSubdirectories);
     PathFragment pathFragment = TargetPatternResolverUtil.getPathFragment(directory);
-    ResolvedTargets.Builder<Target> targetBuilder = ResolvedTargets.builder();
     Iterable<PathFragment> packagesUnderDirectory =
         recursivePackageProvider.getPackagesUnderDirectory(
             repository, pathFragment, excludedPathFragments);
@@ -176,28 +185,30 @@
                 return PackageIdentifier.create(repository, path);
               }
             });
-    for (ResolvedTargets<Target> targets : bulkGetTargetsInPackage(originalPattern, pkgIds,
-            FilteringPolicies.NO_FILTER).values()) {
-      targetBuilder.merge(targets);
-    }
-
-    // Perform the no-targets-found check before applying the filtering policy so we only return the
-    // error if the input directory's subtree really contains no targets.
-    if (targetBuilder.isEmpty()) {
-      throw new TargetParsingException("no targets found beneath '" + pathFragment + "'");
-    }
-    ResolvedTargets<Target> prefilteredTargets = targetBuilder.build();
-     
-    ResolvedTargets.Builder<Target> filteredBuilder = ResolvedTargets.builder();
-    if (prefilteredTargets.hasError()) {
-      filteredBuilder.setError();
-    }
-    for (Target target : prefilteredTargets.getTargets()) {
-      if (actualPolicy.shouldRetain(target, false)) {
-        filteredBuilder.add(target);
+    boolean foundTarget = false;
+    // For very large sets of packages, we may not want to process all of them at once, so we split
+    // into batches.
+    for (Iterable<PackageIdentifier> pkgIdBatch :
+        Iterables.partition(pkgIds, MAX_PACKAGES_BULK_GET)) {
+      for (ResolvedTargets<Target> targets :
+          bulkGetTargetsInPackage(originalPattern, pkgIdBatch, FilteringPolicies.NO_FILTER)
+              .values()) {
+        List<Target> filteredTargets = new ArrayList<>(targets.getTargets().size());
+        for (Target target : targets.getTargets()) {
+          // Perform the no-targets-found check before applying the filtering policy so we only
+          // return the error if the input directory's subtree really contains no targets.
+          foundTarget = true;
+          if (actualPolicy.shouldRetain(target, false)) {
+            filteredTargets.add(target);
+          }
+        }
+        callback.process(filteredTargets);
       }
     }
-    return filteredBuilder.build();
+
+    if (!foundTarget) {
+      throw new TargetParsingException("no targets found beneath '" + pathFragment + "'");
+    }
   }
 }