Make deterministic the error that is stored in TransitiveTraversalValue. When there are multiple errors, we don't want non-determinism.

PiperOrigin-RevId: 240208756
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
index 3f72cf9..b7d8b43 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
@@ -21,13 +21,13 @@
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.NoSuchThingException;
-import com.google.devtools.build.lib.skyframe.TransitiveTraversalFunction.FirstErrorMessageAccumulator;
 import com.google.devtools.build.lib.util.GroupedList;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.ValueOrException2;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
@@ -35,12 +35,13 @@
 /**
  * This class is like {@link TransitiveTargetFunction}, but the values it returns do not contain
  * {@link NestedSet}s. It performs the side-effects of {@link TransitiveTargetFunction} (i.e.,
- * ensuring that transitive targets and their packages have been loaded). It evaluates to a
- * {@link TransitiveTraversalValue} that contains the first error message it encountered, and a
- * set of names of providers if the target is a rule.
+ * ensuring that transitive targets and their packages have been loaded). It evaluates to a {@link
+ * TransitiveTraversalValue} that contains the first error message it encountered, and a set of
+ * names of providers if the target is a rule.
  */
 public class TransitiveTraversalFunction
-    extends TransitiveBaseTraversalFunction<FirstErrorMessageAccumulator> {
+    extends TransitiveBaseTraversalFunction<
+        TransitiveTraversalFunction.DeterministicErrorMessageAccumulator> {
 
   @Override
   Label argumentFromKey(SkyKey key) {
@@ -53,15 +54,16 @@
   }
 
   @Override
-  FirstErrorMessageAccumulator processTarget(Label label, TargetAndErrorIfAny targetAndErrorIfAny) {
+  DeterministicErrorMessageAccumulator processTarget(
+      Label label, TargetAndErrorIfAny targetAndErrorIfAny) {
     NoSuchTargetException errorIfAny = targetAndErrorIfAny.getErrorLoadingTarget();
     String errorMessageIfAny = errorIfAny == null ? null : errorIfAny.getMessage();
-    return new FirstErrorMessageAccumulator(errorMessageIfAny);
+    return DeterministicErrorMessageAccumulator.create(errorMessageIfAny);
   }
 
   @Override
   void processDeps(
-      FirstErrorMessageAccumulator accumulator,
+      DeterministicErrorMessageAccumulator accumulator,
       EventHandler eventHandler,
       TargetAndErrorIfAny targetAndErrorIfAny,
       Iterable<Map.Entry<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>>
@@ -78,9 +80,9 @@
         accumulator.maybeSet(e.getMessage());
         continue;
       }
-      String firstErrorMessage = transitiveTraversalValue.getFirstErrorMessage();
-      if (firstErrorMessage != null) {
-        accumulator.maybeSet(firstErrorMessage);
+      String errorMessage = transitiveTraversalValue.getErrorMessage();
+      if (errorMessage != null) {
+        accumulator.maybeSet(errorMessage);
       }
     }
   }
@@ -104,14 +106,14 @@
   }
 
   @Override
-  SkyValue computeSkyValue(TargetAndErrorIfAny targetAndErrorIfAny,
-      FirstErrorMessageAccumulator accumulator) {
+  SkyValue computeSkyValue(
+      TargetAndErrorIfAny targetAndErrorIfAny, DeterministicErrorMessageAccumulator accumulator) {
     boolean targetLoadedSuccessfully = targetAndErrorIfAny.getErrorLoadingTarget() == null;
-    String firstErrorMessage = accumulator.getFirstErrorMessage();
+    String errorMessage = accumulator.getErrorMessage();
     return targetLoadedSuccessfully
-        ? TransitiveTraversalValue.forTarget(targetAndErrorIfAny.getTarget(), firstErrorMessage)
+        ? TransitiveTraversalValue.forTarget(targetAndErrorIfAny.getTarget(), errorMessage)
         : TransitiveTraversalValue.unsuccessfulTransitiveTraversal(
-            firstErrorMessage, targetAndErrorIfAny.getTarget());
+            errorMessage, targetAndErrorIfAny.getTarget());
   }
 
   @Override
@@ -178,28 +180,47 @@
   }
 
   /**
-   * Keeps track of the first error message encountered while traversing itself and its
-   * dependencies.
+   * Keeps track of a deterministic error message encountered while traversing itself and its
+   * dependencies: either the error it was initialized with, or the shortest error it encounters,
+   * with ties broken alphabetically.
+   *
+   * <p>This preserves the behavior that the local target's error is the most important, and is
+   * cheap (constant-time) to compute the comparison between strings, unless they have the same
+   * length, which is unlikely.
    */
-  static class FirstErrorMessageAccumulator {
-
-    @Nullable private String firstErrorMessage;
-
-    public FirstErrorMessageAccumulator(@Nullable String firstErrorMessage) {
-      this.firstErrorMessage = firstErrorMessage;
-    }
-
-    /** Remembers {@param errorMessage} if it is the first error message. */
-    void maybeSet(String errorMessage) {
-      Preconditions.checkNotNull(errorMessage);
-      if (firstErrorMessage == null) {
-        firstErrorMessage = errorMessage;
-      }
-    }
-
+  interface DeterministicErrorMessageAccumulator {
     @Nullable
-    String getFirstErrorMessage() {
-      return firstErrorMessage;
+    String getErrorMessage();
+
+    default void maybeSet(String errorMessage) {}
+
+    static DeterministicErrorMessageAccumulator create(@Nullable String errorMessage) {
+      if (errorMessage != null) {
+        return () -> errorMessage;
+      }
+      return new UpdateableErrorMessageAccumulator();
+    }
+
+    class UpdateableErrorMessageAccumulator implements DeterministicErrorMessageAccumulator {
+      private static final Comparator<String> LENGTH_THEN_ALPHABETICAL =
+          Comparator.nullsLast(
+              Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
+
+      @Nullable private String errorMessage;
+
+      @Override
+      public void maybeSet(String errorMessage) {
+        Preconditions.checkNotNull(errorMessage);
+        if (LENGTH_THEN_ALPHABETICAL.compare(this.errorMessage, errorMessage) > 0) {
+          this.errorMessage = errorMessage;
+        }
+      }
+
+      @Nullable
+      @Override
+      public String getErrorMessage() {
+        return errorMessage;
+      }
     }
   }
 }