Introduce --strict_fileset_output which treats all output Artifacts encountered in a Fileset as a regular file.

PiperOrigin-RevId: 205152271
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
index bc91ed7..22f5b31 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
@@ -243,6 +243,7 @@
             traversal.getRoot(),
             traversal.isGenerated(),
             traversal.getPackageBoundaryMode(),
+            traversal.isStrictFilesetOutput(),
             traversal.isPackage(),
             errorInfo);
     RecursiveFilesystemTraversalValue v = (RecursiveFilesystemTraversalValue) env.getValue(depKey);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
index 070b831..c4a23b3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
@@ -245,33 +245,38 @@
         }
       }
 
-      // FileArtifactValue does not currently track symlinks. If it did, we could potentially remove
-      // some of the filesystem operations we're doing here.
-      Path path = traversal.root.asRootedPath().asPath();
-      FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
-      FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
-      FileType type;
-      PathFragment unresolvedLinkTarget = null;
       RootedPath realPath = traversal.root.asRootedPath();
-      if (followStat == null) {
-        type = FileType.DANGLING_SYMLINK;
-        if (!noFollowStat.isSymbolicLink()) {
-          throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
-        }
-        unresolvedLinkTarget = path.readSymbolicLink();
-      } else if (noFollowStat.isFile()) {
-        type = FileType.FILE;
-      } else if (noFollowStat.isDirectory()) {
-        type = FileType.DIRECTORY;
+      if (traversal.strictOutputFiles) {
+        Preconditions.checkNotNull(fsVal, "Strict Fileset output tree has null FileArtifactValue");
+        return new FileInfo(FileType.FILE, fsVal, realPath, null);
       } else {
-        unresolvedLinkTarget = path.readSymbolicLink();
-        realPath = RootedPath.toRootedPath(
-            Root.absoluteRoot(path.getFileSystem()),
-            path.resolveSymbolicLinks());
-        type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
+        // FileArtifactValue does not currently track symlinks. If it did, we could potentially
+        // remove some of the filesystem operations we're doing here.
+        Path path = traversal.root.asRootedPath().asPath();
+        FileStatus noFollowStat = path.stat(Symlinks.NOFOLLOW);
+        FileStatus followStat = path.statIfFound(Symlinks.FOLLOW);
+        FileType type;
+        PathFragment unresolvedLinkTarget = null;
+        if (followStat == null) {
+          type = FileType.DANGLING_SYMLINK;
+          if (!noFollowStat.isSymbolicLink()) {
+            throw new IOException("Expected symlink for " + path + ", but got: " + noFollowStat);
+          }
+          unresolvedLinkTarget = path.readSymbolicLink();
+        } else if (noFollowStat.isFile()) {
+          type = FileType.FILE;
+        } else if (noFollowStat.isDirectory()) {
+          type = FileType.DIRECTORY;
+        } else {
+          unresolvedLinkTarget = path.readSymbolicLink();
+          realPath =
+              RootedPath.toRootedPath(
+                  Root.absoluteRoot(path.getFileSystem()), path.resolveSymbolicLinks());
+          type = followStat.isFile() ? FileType.SYMLINK_TO_FILE : FileType.SYMLINK_TO_DIRECTORY;
+        }
+        return new FileInfo(
+            type, fsVal != null ? fsVal : noFollowStat.hashCode(), realPath, unresolvedLinkTarget);
       }
-      return new FileInfo(
-          type, fsVal != null ? fsVal : noFollowStat.hashCode(), realPath, unresolvedLinkTarget);
     } else {
       // Stat the file.
       FileValue fileValue =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
index 94041f9..c849f10 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalValue.java
@@ -124,6 +124,9 @@
     /** Whether traversal should descend into directories that are roots of subpackages. */
     final PackageBoundaryMode crossPkgBoundaries;
 
+    /** Whether Fileset assumes that output Artifacts are regular files. */
+    final boolean strictOutputFiles;
+
     /**
      * Whether to skip checking if the root (if it's a directory) contains a BUILD file.
      *
@@ -140,11 +143,13 @@
         DirectTraversalRoot root,
         boolean isRootGenerated,
         PackageBoundaryMode crossPkgBoundaries,
+        boolean strictOutputFiles,
         boolean skipTestingForSubpackage,
         @Nullable String errorInfo) {
       this.root = root;
       this.isRootGenerated = isRootGenerated;
       this.crossPkgBoundaries = crossPkgBoundaries;
+      this.strictOutputFiles = strictOutputFiles;
       this.skipTestingForSubpackage = skipTestingForSubpackage;
       this.errorInfo = errorInfo;
     }
@@ -155,17 +160,20 @@
         DirectTraversalRoot root,
         boolean isRootGenerated,
         PackageBoundaryMode crossPkgBoundaries,
+        boolean strictOutputFiles,
         boolean skipTestingForSubpackage,
         @Nullable String errorInfo) {
       return interner.intern(
           new TraversalRequest(
-              root, isRootGenerated, crossPkgBoundaries, skipTestingForSubpackage, errorInfo));
+              root, isRootGenerated, crossPkgBoundaries, strictOutputFiles,
+              skipTestingForSubpackage, errorInfo));
     }
 
     private TraversalRequest duplicate(DirectTraversalRoot newRoot,
         boolean newSkipTestingForSubpackage) {
       return create(
-          newRoot, isRootGenerated, crossPkgBoundaries, newSkipTestingForSubpackage, errorInfo);
+          newRoot, isRootGenerated, crossPkgBoundaries, strictOutputFiles,
+          newSkipTestingForSubpackage, errorInfo);
     }
 
     /** Creates a new request to traverse a child element in the current directory (the root). */
@@ -198,20 +206,23 @@
       return root.equals(o.root)
           && isRootGenerated == o.isRootGenerated
           && crossPkgBoundaries == o.crossPkgBoundaries
+          && strictOutputFiles == o.strictOutputFiles
           && skipTestingForSubpackage == o.skipTestingForSubpackage;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hashCode(root, isRootGenerated, crossPkgBoundaries, skipTestingForSubpackage);
+      return Objects.hashCode(root, isRootGenerated, crossPkgBoundaries, strictOutputFiles,
+          skipTestingForSubpackage);
     }
 
     @Override
     public String toString() {
       return String.format(
           "TraversalParams(root=%s, is_generated=%d, skip_testing_for_subpkg=%d,"
-              + " pkg_boundaries=%s)",
-          root, isRootGenerated ? 1 : 0, skipTestingForSubpackage ? 1 : 0, crossPkgBoundaries);
+              + " pkg_boundaries=%s, strictOutputFiles=%d)",
+          root, isRootGenerated ? 1 : 0, skipTestingForSubpackage ? 1 : 0, crossPkgBoundaries,
+          strictOutputFiles ? 1 : 0);
     }
 
     @Override