Allow tree artifacts to be omitted, take 2.

PiperOrigin-RevId: 314357168
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
index b465a6e..1c63e3e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
@@ -73,8 +73,12 @@
     }
 
     for (Map.Entry<Artifact, TreeArtifactValue> tree : treeArtifactData.entrySet()) {
+      TreeArtifactValue treeArtifact = tree.getValue();
+      if (TreeArtifactValue.OMITTED_TREE_MARKER.equals(treeArtifact)) {
+        continue;
+      }
       for (Map.Entry<TreeFileArtifact, FileArtifactValue> file :
-          tree.getValue().getChildValues().entrySet()) {
+          treeArtifact.getChildValues().entrySet()) {
         // We should only have RegularFileValue instances in here, but apparently tree artifacts
         // sometimes store their own root directory in here. Sad.
         // https://github.com/bazelbuild/bazel/issues/9058
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
index 9c7a570..03fed1a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
@@ -157,6 +157,10 @@
       Map<Artifact, Collection<Artifact>> expandedArtifacts,
       ActionInputMapSink inputMap,
       Artifact depOwner) {
+    if (TreeArtifactValue.OMITTED_TREE_MARKER.equals(value)) {
+      inputMap.put(treeArtifact, FileArtifactValue.OMITTED_FILE_MARKER, depOwner);
+      return;
+    }
     ImmutableSet.Builder<Artifact> children = ImmutableSet.builder();
     for (Map.Entry<Artifact.TreeFileArtifact, FileArtifactValue> child :
         value.getChildValues().entrySet()) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index 0d9de61..1a65a9c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -424,9 +424,19 @@
 
   @Override
   public void markOmitted(Artifact output) {
-    Preconditions.checkState(executionMode.get());
-    Preconditions.checkState(omittedOutputs.add(output), "%s marked as omitted twice", output);
-    store.putArtifactData(output, FileArtifactValue.OMITTED_FILE_MARKER);
+    Preconditions.checkState(
+        executionMode.get(), "Tried to mark %s omitted outside of execution", output);
+    boolean newlyOmitted = omittedOutputs.add(output);
+    if (output.isTreeArtifact()) {
+      // Tolerate marking a tree artifact as omitted multiple times so that callers don't have to
+      // deduplicate when a tree artifact has several omitted children.
+      if (newlyOmitted) {
+        store.putTreeArtifactData((SpecialArtifact) output, TreeArtifactValue.OMITTED_TREE_MARKER);
+      }
+    } else {
+      Preconditions.checkState(newlyOmitted, "%s marked as omitted twice", output);
+      store.putArtifactData(output, FileArtifactValue.OMITTED_FILE_MARKER);
+    }
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
index e2c4621..d9f50b4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
@@ -161,7 +161,7 @@
     }
   }
 
-  private static SkyValue createTreeArtifactValueFromActionKey(
+  private static TreeArtifactValue createTreeArtifactValueFromActionKey(
       ArtifactDependencies artifactDependencies, Environment env) throws InterruptedException {
     // Request the list of expanded actions from the ActionTemplate.
     ActionTemplateExpansion actionTemplateExpansion =
@@ -232,7 +232,7 @@
           children.isEmpty(),
           "Action template expansion has some but not all outputs omitted, present outputs: %s",
           children);
-      return FileArtifactValue.OMITTED_FILE_MARKER;
+      return TreeArtifactValue.OMITTED_TREE_MARKER;
     }
     return TreeArtifactValue.create(children);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 26b90ab..baf24a3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -2634,6 +2634,7 @@
     deps = [
         "//src/main/java/com/google/devtools/build/lib/actions",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
+        "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:serialization-constant",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
index 7ac6c27..01920ca 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.actions.HasDigest;
 import com.google.devtools.build.lib.actions.cache.DigestUtils;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
 import com.google.devtools.build.lib.vfs.Dirent;
 import com.google.devtools.build.lib.vfs.Dirent.Type;
 import com.google.devtools.build.lib.vfs.Path;
@@ -43,14 +44,12 @@
  * Value for TreeArtifacts, which contains a digest and the {@link FileArtifactValue}s of its child
  * {@link TreeFileArtifact}s.
  */
-@AutoCodec
 public class TreeArtifactValue implements HasDigest, SkyValue {
 
-  private static final TreeArtifactValue EMPTY =
+  @SerializationConstant @AutoCodec.VisibleForSerialization
+  static final TreeArtifactValue EMPTY =
       new TreeArtifactValue(
-          DigestUtils.fromMetadata(ImmutableMap.of()),
-          ImmutableSortedMap.of(),
-          /* remote= */ false);
+          DigestUtils.fromMetadata(ImmutableMap.of()), ImmutableSortedMap.of(), /*remote=*/ false);
 
   private final byte[] digest;
   private final ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> childData;
@@ -161,57 +160,67 @@
   }
 
   /**
+   * Represents a tree artifact that was intentionally omitted, similar to {@link
+   * FileArtifactValue#OMITTED_FILE_MARKER}.
+   */
+  @SerializationConstant
+  public static final TreeArtifactValue OMITTED_TREE_MARKER = createMarker("OMITTED_TREE_MARKER");
+
+  /**
    * A TreeArtifactValue that represents a missing TreeArtifact. This is occasionally useful because
    * Java's concurrent collections disallow null members.
    */
-  static final TreeArtifactValue MISSING_TREE_ARTIFACT =
-      new TreeArtifactValue(null, ImmutableSortedMap.of(), /* remote= */ false) {
-        @Override
-        FileArtifactValue getSelfData() {
-          throw new UnsupportedOperationException();
-        }
+  static final TreeArtifactValue MISSING_TREE_ARTIFACT = createMarker("MISSING_TREE_ARTIFACT");
 
-        @Override
-        public ImmutableSet<TreeFileArtifact> getChildren() {
-          throw new UnsupportedOperationException();
-        }
+  private static TreeArtifactValue createMarker(String toStringRepresentation) {
+    return new TreeArtifactValue(null, ImmutableSortedMap.of(), /*remote=*/ false) {
+      @Override
+      FileArtifactValue getSelfData() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Override
-        ImmutableMap<TreeFileArtifact, FileArtifactValue> getChildValues() {
-          throw new UnsupportedOperationException();
-        }
+      @Override
+      public ImmutableSet<TreeFileArtifact> getChildren() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Override
-        FileArtifactValue getMetadata() {
-          throw new UnsupportedOperationException();
-        }
+      @Override
+      ImmutableMap<TreeFileArtifact, FileArtifactValue> getChildValues() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Override
-        ImmutableSet<PathFragment> getChildPaths() {
-          throw new UnsupportedOperationException();
-        }
+      @Override
+      FileArtifactValue getMetadata() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Nullable
-        @Override
-        public byte[] getDigest() {
-          throw new UnsupportedOperationException();
-        }
+      @Override
+      ImmutableSet<PathFragment> getChildPaths() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Override
-        public int hashCode() {
-          return 24; // my favorite number
-        }
+      @Nullable
+      @Override
+      public byte[] getDigest() {
+        throw new UnsupportedOperationException(toString());
+      }
 
-        @Override
-        public boolean equals(Object other) {
-          return this == other;
-        }
+      @Override
+      public int hashCode() {
+        return System.identityHashCode(this);
+      }
 
-        @Override
-        public String toString() {
-          return "MISSING_TREE_ARTIFACT";
-        }
-      };
+      @Override
+      public boolean equals(Object other) {
+        return this == other;
+      }
+
+      @Override
+      public String toString() {
+        return toStringRepresentation;
+      }
+    };
+  }
 
   private static void explodeDirectory(
       Path treeArtifactPath,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
index 91b15b5..70e9eba 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.ActionInput;
 import com.google.devtools.build.lib.actions.ActionInputHelper;
 import com.google.devtools.build.lib.actions.ActionInputMap;
@@ -516,6 +517,67 @@
     assertThat(handler.getMetadata(ActionInputHelper.fromPath("/does/not/exist"))).isNull();
   }
 
+  @Test
+  public void omitRegularArtifact() {
+    OutputStore store = new MinimalOutputStore();
+    Artifact omitted =
+        ActionsTestUtil.createArtifactWithRootRelativePath(
+            outputRoot, PathFragment.create("omitted"));
+    Artifact consumed =
+        ActionsTestUtil.createArtifactWithRootRelativePath(
+            outputRoot, PathFragment.create("consumed"));
+    ActionMetadataHandler handler =
+        new ActionMetadataHandler(
+            new ActionInputMap(1),
+            /*expandedFilesets=*/ ImmutableMap.of(),
+            /*missingArtifactsAllowed=*/ false,
+            ImmutableSet.of(omitted, consumed),
+            /*tsgm=*/ null,
+            ArtifactPathResolver.IDENTITY,
+            store,
+            outputRoot.getRoot().asPath());
+
+    handler.discardOutputMetadata();
+    handler.markOmitted(omitted);
+
+    assertThat(handler.artifactOmitted(omitted)).isTrue();
+    assertThat(handler.artifactOmitted(consumed)).isFalse();
+    assertThat(store.getAllArtifactData())
+        .containsExactly(omitted, FileArtifactValue.OMITTED_FILE_MARKER);
+    assertThat(store.getAllTreeArtifactData()).isEmpty();
+  }
+
+  @Test
+  public void omitTreeArtifact() {
+    OutputStore store = new MinimalOutputStore();
+    SpecialArtifact omittedTree =
+        ActionsTestUtil.createTreeArtifactWithGeneratingAction(
+            outputRoot, PathFragment.create("omitted"));
+    SpecialArtifact consumedTree =
+        ActionsTestUtil.createTreeArtifactWithGeneratingAction(
+            outputRoot, PathFragment.create("consumed"));
+    ActionMetadataHandler handler =
+        new ActionMetadataHandler(
+            new ActionInputMap(1),
+            /*expandedFilesets=*/ ImmutableMap.of(),
+            /*missingArtifactsAllowed=*/ false,
+            ImmutableSet.of(omittedTree, consumedTree),
+            /*tsgm=*/ null,
+            ArtifactPathResolver.IDENTITY,
+            store,
+            outputRoot.getRoot().asPath());
+
+    handler.discardOutputMetadata();
+    handler.markOmitted(omittedTree);
+    handler.markOmitted(omittedTree); // Marking a tree artifact as omitted twice is tolerated.
+
+    assertThat(handler.artifactOmitted(omittedTree)).isTrue();
+    assertThat(handler.artifactOmitted(consumedTree)).isFalse();
+    assertThat(store.getAllTreeArtifactData())
+        .containsExactly(omittedTree, TreeArtifactValue.OMITTED_TREE_MARKER);
+    assertThat(store.getAllArtifactData()).isEmpty();
+  }
+
   private ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> createFilesetOutputSymlinkMap(
       HasDigest... digests) {
     int index = 1;
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index 8017fef..5607782 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -278,7 +278,7 @@
     omittedOutputs.add(treeFileArtifact2);
 
     SkyValue value = evaluateArtifactValue(artifact2);
-    assertThat(value).isEqualTo(FileArtifactValue.OMITTED_FILE_MARKER);
+    assertThat(value).isEqualTo(TreeArtifactValue.OMITTED_TREE_MARKER);
   }
 
   @Test