If EXTERNAL files are present, list them explicitly and check only these files, instead of triggering a search of all nodes in the graph.

This should improve performance for builds with a small number of external files and a large number of nodes in the graph, since it avoids a full graph traversal.

PiperOrigin-RevId: 399193720
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ExternalFilesHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/ExternalFilesHelper.java
index b8d9029..a4aebf2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ExternalFilesHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ExternalFilesHelper.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.skyframe;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
 import com.google.common.flogger.GoogleLogger;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.BundledFileSystem;
@@ -29,6 +30,7 @@
 import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.SkyFunction;
 import java.io.IOException;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -41,12 +43,17 @@
   private final BlazeDirectories directories;
   private final int maxNumExternalFilesToLog;
   private final AtomicInteger numExternalFilesLogged = new AtomicInteger(0);
+  private static final int MAX_EXTERNAL_FILES_TO_TRACK = 2500;
 
   // These variables are set to true from multiple threads, but only read in the main thread.
   // So volatility or an AtomicBoolean is not needed.
   private boolean anyOutputFilesSeen = false;
-  private boolean anyNonOutputExternalFilesSeen = false;
+  private boolean tooManyNonOutputExternalFilesSeen = false;
+  private boolean anyFilesInExternalReposSeen = false;
 
+  // This is a set of external files that are not in managed directories or
+  // external repositories.
+  private Set<RootedPath> nonOutputExternalFilesSeen = Sets.newConcurrentHashSet();
   private final ManagedDirectoriesKnowledge managedDirectoriesKnowledge;
 
   private ExternalFilesHelper(
@@ -190,24 +197,37 @@
 
   static class ExternalFilesKnowledge {
     final boolean anyOutputFilesSeen;
-    final boolean anyNonOutputExternalFilesSeen;
+    final Set<RootedPath> nonOutputExternalFilesSeen;
+    final boolean anyFilesInExternalReposSeen;
+    final boolean tooManyNonOutputExternalFilesSeen;
 
-    private ExternalFilesKnowledge(boolean anyOutputFilesSeen,
-        boolean anyNonOutputExternalFilesSeen) {
+    private ExternalFilesKnowledge(
+        boolean anyOutputFilesSeen,
+        Set<RootedPath> nonOutputExternalFilesSeen,
+        boolean anyFilesInExternalReposSeen,
+        boolean tooManyNonOutputExternalFilesSeen) {
       this.anyOutputFilesSeen = anyOutputFilesSeen;
-      this.anyNonOutputExternalFilesSeen = anyNonOutputExternalFilesSeen;
+      this.nonOutputExternalFilesSeen = nonOutputExternalFilesSeen;
+      this.anyFilesInExternalReposSeen = anyFilesInExternalReposSeen;
+      this.tooManyNonOutputExternalFilesSeen = tooManyNonOutputExternalFilesSeen;
     }
   }
 
   @ThreadCompatible
   ExternalFilesKnowledge getExternalFilesKnowledge() {
-    return new ExternalFilesKnowledge(anyOutputFilesSeen, anyNonOutputExternalFilesSeen);
+    return new ExternalFilesKnowledge(
+        anyOutputFilesSeen,
+        nonOutputExternalFilesSeen,
+        anyFilesInExternalReposSeen,
+        tooManyNonOutputExternalFilesSeen);
   }
 
   @ThreadCompatible
   void setExternalFilesKnowledge(ExternalFilesKnowledge externalFilesKnowledge) {
     anyOutputFilesSeen = externalFilesKnowledge.anyOutputFilesSeen;
-    anyNonOutputExternalFilesSeen = externalFilesKnowledge.anyNonOutputExternalFilesSeen;
+    nonOutputExternalFilesSeen = externalFilesKnowledge.nonOutputExternalFilesSeen;
+    anyFilesInExternalReposSeen = externalFilesKnowledge.anyFilesInExternalReposSeen;
+    tooManyNonOutputExternalFilesSeen = externalFilesKnowledge.tooManyNonOutputExternalFilesSeen;
   }
 
   ExternalFilesHelper cloneWithFreshExternalFilesKnowledge() {
@@ -227,12 +247,19 @@
     RepositoryName repositoryName =
         managedDirectoriesKnowledge.getOwnerRepository(rootedPath.getRootRelativePath());
     if (repositoryName != null) {
-      anyNonOutputExternalFilesSeen = true;
+      anyFilesInExternalReposSeen = true;
       return Pair.of(FileType.EXTERNAL_IN_MANAGED_DIRECTORY, repositoryName);
     }
     FileType fileType = detectFileType(rootedPath);
-    if (FileType.EXTERNAL == fileType || FileType.EXTERNAL_REPO == fileType) {
-      anyNonOutputExternalFilesSeen = true;
+    if (FileType.EXTERNAL == fileType) {
+      if (nonOutputExternalFilesSeen.size() >= MAX_EXTERNAL_FILES_TO_TRACK) {
+        tooManyNonOutputExternalFilesSeen = true;
+      } else {
+        nonOutputExternalFilesSeen.add(rootedPath);
+      }
+    }
+    if (FileType.EXTERNAL_REPO == fileType) {
+      anyFilesInExternalReposSeen = true;
     }
     if (FileType.OUTPUT == fileType) {
       anyOutputFilesSeen = true;
@@ -255,20 +282,16 @@
     // The outputBase may be null if we're not actually running a build.
     Path outputBase = packageLocator.getOutputBase();
     if (outputBase == null) {
-      anyNonOutputExternalFilesSeen = true;
       return FileType.EXTERNAL;
     }
     if (rootedPath.asPath().startsWith(outputBase)) {
       Path externalRepoDir = outputBase.getRelative(LabelConstants.EXTERNAL_REPOSITORY_LOCATION);
       if (rootedPath.asPath().startsWith(externalRepoDir)) {
-        anyNonOutputExternalFilesSeen = true;
         return FileType.EXTERNAL_REPO;
       } else {
-        anyOutputFilesSeen = true;
         return FileType.OUTPUT;
       }
     }
-    anyNonOutputExternalFilesSeen = true;
     return FileType.EXTERNAL;
   }