Allow SkyValues to be marked not "comparable". Such values are not compared for the purpose of change pruning.

--
MOS_MIGRATED_REVID=108203369
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
index 58753ad..be934a5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageValue.java
@@ -20,8 +20,8 @@
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.Package;
 import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.skyframe.NotComparableSkyValue;
 import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -31,7 +31,7 @@
  */
 @Immutable
 @ThreadSafe
-public class PackageValue implements SkyValue {
+public class PackageValue implements NotComparableSkyValue {
 
   private final Package pkg;
 
diff --git a/src/main/java/com/google/devtools/build/skyframe/BuildingState.java b/src/main/java/com/google/devtools/build/skyframe/BuildingState.java
index 1f72484..d0d2573 100644
--- a/src/main/java/com/google/devtools/build/skyframe/BuildingState.java
+++ b/src/main/java/com/google/devtools/build/skyframe/BuildingState.java
@@ -300,6 +300,9 @@
    */
   boolean unchangedFromLastBuild(SkyValue newValue) {
     checkFinishedBuildingWhenAboutToSetValue();
+    if (newValue instanceof NotComparableSkyValue) {
+      return false;
+    }
     return getLastBuildValue().equals(newValue) && lastBuildDirectDeps.equals(directDeps);
   }
 
diff --git a/src/main/java/com/google/devtools/build/skyframe/NotComparableSkyValue.java b/src/main/java/com/google/devtools/build/skyframe/NotComparableSkyValue.java
new file mode 100644
index 0000000..5cb211b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skyframe/NotComparableSkyValue.java
@@ -0,0 +1,18 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.skyframe;
+
+/** A SkyValue which, after a fresh evaluation, can never be equal to its last value. */
+public interface NotComparableSkyValue extends SkyValue {
+}
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueWithMetadata.java b/src/main/java/com/google/devtools/build/skyframe/ValueWithMetadata.java
index 6eab026..b702433 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueWithMetadata.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueWithMetadata.java
@@ -60,7 +60,7 @@
     if (errorInfo == null) {
       return transitiveEvents.isEmpty()
           ? value
-          : new ValueWithEvents(value, transitiveEvents);
+          : ValueWithEvents.createValueWithEvents(value, transitiveEvents);
     }
     return new ErrorInfoValue(errorInfo, value, transitiveEvents);
   }
@@ -75,15 +75,24 @@
   public abstract NestedSet<TaggedEvents> getTransitiveEvents();
 
   /** Implementation of {@link ValueWithMetadata} for the value case. */
-  public static final class ValueWithEvents extends ValueWithMetadata {
+  public static class ValueWithEvents extends ValueWithMetadata {
 
     private final NestedSet<TaggedEvents> transitiveEvents;
 
-    public ValueWithEvents(SkyValue value, NestedSet<TaggedEvents> transitiveEvents) {
+    private ValueWithEvents(SkyValue value, NestedSet<TaggedEvents> transitiveEvents) {
       super(Preconditions.checkNotNull(value));
       this.transitiveEvents = Preconditions.checkNotNull(transitiveEvents);
     }
 
+    public static ValueWithEvents createValueWithEvents(SkyValue value,
+            NestedSet<TaggedEvents> transitiveEvents) {
+      if (value instanceof NotComparableSkyValue) {
+        return new NotComparableValueWithEvents(value, transitiveEvents);
+      } else {
+        return new ValueWithEvents(value, transitiveEvents);
+      }
+    }
+
     @Nullable
     @Override
     ErrorInfo getErrorInfo() { return null; }
@@ -124,8 +133,21 @@
     public String toString() { return value.toString(); }
   }
 
-  /** Implementation of {@link ValueWithMetadata} for the error case. */
-  private static final class ErrorInfoValue extends ValueWithMetadata {
+  private static final class NotComparableValueWithEvents extends ValueWithEvents
+          implements NotComparableSkyValue {
+    private NotComparableValueWithEvents(SkyValue value,
+            NestedSet<TaggedEvents> transitiveEvents) {
+      super(value, transitiveEvents);
+    }
+  }
+
+  /**
+   * Implementation of {@link ValueWithMetadata} for the error case.
+   *
+   * ErorInfo does not override equals(), so it may as well be marked NotComparableSkyValue.
+   */
+  private static final class ErrorInfoValue extends ValueWithMetadata
+          implements NotComparableSkyValue {
 
     private final ErrorInfo errorInfo;
     private final NestedSet<TaggedEvents> transitiveEvents;
@@ -198,7 +220,7 @@
     if (value instanceof ValueWithMetadata) {
       return (ValueWithMetadata) value;
     }
-    return new ValueWithEvents(value, NO_EVENTS);
+    return ValueWithEvents.createValueWithEvents(value, NO_EVENTS);
   }
 
   @Nullable
diff --git a/src/test/java/com/google/devtools/build/skyframe/GraphTester.java b/src/test/java/com/google/devtools/build/skyframe/GraphTester.java
index 2d44e9d..2a3fc1b 100644
--- a/src/test/java/com/google/devtools/build/skyframe/GraphTester.java
+++ b/src/test/java/com/google/devtools/build/skyframe/GraphTester.java
@@ -291,7 +291,7 @@
    * Simple value class that stores strings.
    */
   public static class StringValue implements SkyValue {
-    private final String value;
+    protected final String value;
 
     public StringValue(String value) {
       this.value = value;
@@ -329,6 +329,24 @@
     }
   }
 
+  /** A StringValue that is also a NotComparableSkyValue. */
+  public static class NotComparableStringValue extends StringValue
+          implements NotComparableSkyValue {
+    public NotComparableStringValue(String value) {
+      super(value);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
+    }
+
+    @Override
+    public int hashCode() {
+      throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
+    }
+  }
+
   /**
    * A callback interface to provide the value computation.
    */
diff --git a/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java b/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
index 8b7c0d2..0304513 100644
--- a/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
@@ -492,7 +492,7 @@
   }
 
   @Test
-  public void pruneErrorValue() {
+  public void errorInfoCannotBePruned() {
     NodeEntry entry = new InMemoryNodeEntry();
     entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
     SkyKey dep = key("dep");
@@ -514,7 +514,8 @@
     assertThat(entry.markRebuildingAndGetAllRemainingDirtyDirectDeps()).isEmpty();
     setValue(entry, /*value=*/null, errorInfo, /*graphVersion=*/1L);
     assertTrue(entry.isDone());
-    assertEquals(new IntVersion(0L), entry.getVersion());
+    // ErrorInfo is treated as a NotComparableSkyValue, so it is not pruned.
+    assertEquals(new IntVersion(1L), entry.getVersion());
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java b/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
index fdae6b2..a24e93c 100644
--- a/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
@@ -45,12 +45,14 @@
 import com.google.devtools.build.lib.events.EventKind;
 import com.google.devtools.build.lib.testutil.TestThread;
 import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.skyframe.GraphTester.NotComparableStringValue;
 import com.google.devtools.build.skyframe.GraphTester.StringValue;
 import com.google.devtools.build.skyframe.GraphTester.TestFunction;
 import com.google.devtools.build.skyframe.GraphTester.ValueComputer;
 import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.EventType;
 import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.Listener;
 import com.google.devtools.build.skyframe.NotifyingInMemoryGraph.Order;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 
 import org.junit.After;
@@ -3113,6 +3115,63 @@
   }
 
   @Test
+  public void notComparableNotPrunedNoEvent() throws Exception {
+    checkNotComparableNotPruned(false);
+  }
+
+  @Test
+  public void notComparableNotPrunedEvent() throws Exception {
+    checkNotComparableNotPruned(true);
+  }
+
+  private void checkNotComparableNotPruned(boolean hasEvent) throws Exception {
+    initializeTester();
+    SkyKey parent = GraphTester.toSkyKey("parent");
+    SkyKey child = GraphTester.toSkyKey("child");
+    NotComparableStringValue notComparableString = new NotComparableStringValue("not comparable");
+    if (hasEvent) {
+      tester.getOrCreate(child).setConstantValue(notComparableString).setWarning("shmoop");
+    } else {
+      tester.getOrCreate(child).setConstantValue(notComparableString);
+    }
+    final AtomicInteger parentEvaluated = new AtomicInteger();
+    final String val = "some val";
+    tester
+        .getOrCreate(parent)
+        .addDependency(child)
+        .setComputedValue(new ValueComputer() {
+          @Override
+          public SkyValue compute(Map<SkyKey, SkyValue> deps, Environment env)
+              throws InterruptedException {
+            parentEvaluated.incrementAndGet();
+            return new StringValue(val);
+          }
+        });
+    assertStringValue(val, tester.evalAndGet( /*keepGoing=*/false, parent));
+    assertThat(parentEvaluated.get()).isEqualTo(1);
+    if (hasEvent) {
+      assertContainsEvent(eventCollector, "shmoop");
+    } else {
+      assertEventCount(0, eventCollector);
+    }
+
+    tester.resetPlayedEvents();
+    tester.getOrCreate(child, /*markAsModified=*/true);
+    tester.invalidate();
+    assertStringValue(val, tester.evalAndGet( /*keepGoing=*/false, parent));
+    assertThat(parentEvaluated.get()).isEqualTo(2);
+    if (hasEvent) {
+      assertContainsEvent(eventCollector, "shmoop");
+    } else {
+      assertEventCount(0, eventCollector);
+    }
+  }
+
+  private static void assertStringValue(String expected, SkyValue val) {
+    assertThat(((StringValue) val).getValue()).isEqualTo(expected);
+  }
+
+  @Test
   public void changePruningWithEvent() throws Exception {
     initializeTester();
     SkyKey parent = GraphTester.toSkyKey("parent");