Remove error code for archived artifacts feature used when discarding orphaned
artifacts.

Change the method exposing information about archived artifact to include the
`ArchivedTreeArtifact` itself rather than just a boolean if it's present.

Add a `remove` method to `TreeArtifactValue.MultiBuilder` to allow deleting
tree artifacts from the builder after adding entries for them.

PiperOrigin-RevId: 333207607
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 093b840..e02e068 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
@@ -99,6 +99,17 @@
     }
 
     /**
+     * Removes all of collected data for a given tree artifact.
+     *
+     * <p>No-op if there is no data for a given tree artifact.
+     */
+    public MultiBuilder remove(SpecialArtifact treeArtifact) {
+      checkArgument(treeArtifact.isTreeArtifact(), "Not a tree artifact: %s", treeArtifact);
+      map.remove(treeArtifact);
+      return this;
+    }
+
+    /**
      * For each unique parent seen by this builder, passes the aggregated metadata to {@link
      * TreeArtifactInjector#injectTree}.
      */
@@ -189,8 +200,11 @@
   }
 
   @VisibleForTesting
-  public boolean hasArchivedArtifactForTesting() {
-    return archivedRepresentation != null;
+  @Nullable
+  public ArchivedTreeArtifact getArchivedArtifactForTesting() {
+    return archivedRepresentation != null
+        ? archivedRepresentation.archivedTreeFileArtifact()
+        : null;
   }
 
   ImmutableMap<TreeFileArtifact, FileArtifactValue> getChildValues() {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactValueTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactValueTest.java
index 5634913..e8bea2c 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactValueTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactValueTest.java
@@ -574,6 +574,79 @@
                 .build());
   }
 
+  @Test
+  public void multiBuilder_doesNotInjectRemovedValue() {
+    TreeArtifactValue.MultiBuilder builder = TreeArtifactValue.newMultiBuilder();
+    SpecialArtifact parent1 = createTreeArtifact("bin/tree1");
+    TreeFileArtifact parent1Child = TreeFileArtifact.createTreeOutput(parent1, "child");
+    FileArtifactValue parent1ChildMetadata = metadataWithId(1);
+    SpecialArtifact parent2 = createTreeArtifact("bin/tree2");
+    TreeFileArtifact parent2Child = TreeFileArtifact.createTreeOutput(parent2, "child");
+    FileArtifactValue parent2ChildMetadata = metadataWithId(2);
+    Map<SpecialArtifact, TreeArtifactValue> results = new HashMap<>();
+
+    builder
+        .putChild(parent1Child, parent1ChildMetadata)
+        .putChild(parent2Child, parent2ChildMetadata)
+        .remove(parent1)
+        .injectTo(results::put);
+
+    assertThat(results)
+        .containsExactly(
+            parent2,
+            TreeArtifactValue.newBuilder(parent2)
+                .putChild(parent2Child, parent2ChildMetadata)
+                .build());
+  }
+
+  @Test
+  public void multiBuilder_removeMissingTree_doesNothing() {
+    TreeArtifactValue.MultiBuilder builder = TreeArtifactValue.newMultiBuilder();
+    SpecialArtifact missingTree = createTreeArtifact("bin/tree");
+    Map<SpecialArtifact, TreeArtifactValue> results = new HashMap<>();
+
+    builder.remove(missingTree).injectTo(results::put);
+
+    assertThat(results).isEmpty();
+  }
+
+  @Test
+  public void multiBuilder_removeNotATreeArtifact_fails() {
+    TreeArtifactValue.MultiBuilder builder = TreeArtifactValue.newMultiBuilder();
+    SpecialArtifact notATreeArtifact =
+        new SpecialArtifact(
+            root,
+            root.getExecPath().getRelative("bin/artifact"),
+            ActionsTestUtil.NULL_ARTIFACT_OWNER,
+            SpecialArtifactType.FILESET);
+
+    assertThrows(IllegalArgumentException.class, () -> builder.remove(notATreeArtifact));
+  }
+
+  @Test
+  public void multiBuilder_removeAndRecreateValue_injectsValueAfterRemove() {
+    TreeArtifactValue.MultiBuilder builder = TreeArtifactValue.newMultiBuilder();
+    SpecialArtifact parent = createTreeArtifact("bin/tree");
+    TreeFileArtifact child1 = TreeFileArtifact.createTreeOutput(parent, "child1");
+    FileArtifactValue child1Metadata = metadataWithId(1);
+    ArchivedTreeArtifact archivedArtifact = createArchivedTreeArtifact(parent);
+    FileArtifactValue archivedArtifactMetadata = metadataWithId(2);
+    TreeFileArtifact child2 = TreeFileArtifact.createTreeOutput(parent, "child2");
+    FileArtifactValue child2Metadata = metadataWithId(3);
+    Map<SpecialArtifact, TreeArtifactValue> results = new HashMap<>();
+
+    builder
+        .putChild(child1, child1Metadata)
+        .setArchivedRepresentation(archivedArtifact, archivedArtifactMetadata)
+        .remove(parent)
+        .putChild(child2, child2Metadata)
+        .injectTo(results::put);
+
+    assertThat(results)
+        .containsExactly(
+            parent, TreeArtifactValue.newBuilder(parent).putChild(child2, child2Metadata).build());
+  }
+
   private static ArchivedTreeArtifact createArchivedTreeArtifact(SpecialArtifact specialArtifact) {
     return ArchivedTreeArtifact.create(specialArtifact, BIN_PATH);
   }