Fix non-determinism in the `FailureDetail` produced for a package with multiple label crosses subpackage boundary errors.

While I'm here, also make the code a tiny bit simpler:
* Explicitly create the empty inner `ArrayList` on each loop iteration. There's no need to use `#computeIfAbsent`; we know no other loop iteration will be processing the same `Target`.
* Use a `List<Pair<Target, List<PackageLookupValue.Key>>>` instead of a `Map<Target, List<PackageLookupValue.Key>>` since we don't need lookup semantics.

PiperOrigin-RevId: 533585562
Change-Id: Iaa3c3d9302a0eef533b55d1f17c33de3ec23666b
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index ae8ba7b..30d84e8 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -805,7 +805,8 @@
     PathFragment pkgDir = pkgId.getPackageFragment();
     // Contains a key for each package whose label that might have a presence of a subpackage.
     // Values are all potential subpackages of the label.
-    Map<Target, List<PackageLookupValue.Key>> targetSubpackagePackageLookupKeyMap = new HashMap<>();
+    List<Pair<Target, List<PackageLookupValue.Key>>> targetsAndSubpackagePackageLookupKeys =
+        new ArrayList<>();
     Set<PackageLookupValue.Key> allPackageLookupKeys = new HashSet<>();
     for (Target target : pkgBuilder.getTargets()) {
       Label label = target.getLabel();
@@ -813,6 +814,7 @@
       if (dir.equals(pkgDir)) {
         continue;
       }
+      List<PackageLookupValue.Key> subpackagePackageLookupKeys = new ArrayList<>();
       String labelName = label.getName();
       PathFragment labelAsRelativePath = PathFragment.create(labelName).getParentDirectory();
       PathFragment subpackagePath = pkgDir;
@@ -821,14 +823,13 @@
         subpackagePath = subpackagePath.getRelative(segment);
         PackageLookupValue.Key currentPackageLookupKey =
             PackageLookupValue.key(PackageIdentifier.create(pkgId.getRepository(), subpackagePath));
-        targetSubpackagePackageLookupKeyMap
-            .computeIfAbsent(target, t -> new ArrayList<>())
-            .add(currentPackageLookupKey);
+        subpackagePackageLookupKeys.add(currentPackageLookupKey);
         allPackageLookupKeys.add(currentPackageLookupKey);
       }
+      targetsAndSubpackagePackageLookupKeys.add(Pair.of(target, subpackagePackageLookupKeys));
     }
 
-    if (targetSubpackagePackageLookupKeyMap.isEmpty()) {
+    if (targetsAndSubpackagePackageLookupKeys.isEmpty()) {
       return;
     }
 
@@ -837,9 +838,11 @@
       return;
     }
 
-    for (Map.Entry<Target, List<PackageLookupValue.Key>> entry :
-        targetSubpackagePackageLookupKeyMap.entrySet()) {
-      List<PackageLookupValue.Key> targetPackageLookupKeys = entry.getValue();
+    for (Pair<Target, List<PackageLookupValue.Key>> targetAndSubpackagePackageLookupKeys :
+        targetsAndSubpackagePackageLookupKeys) {
+      Target target = targetAndSubpackagePackageLookupKeys.getFirst();
+      List<PackageLookupValue.Key> targetPackageLookupKeys =
+          targetAndSubpackagePackageLookupKeys.getSecond();
       // Iterate from the deepest potential subpackage to the shallowest in that we only want to
       // display the deepest subpackage in the error message for each target.
       for (PackageLookupValue.Key packageLookupKey : Lists.reverse(targetPackageLookupKeys)) {
@@ -858,10 +861,9 @@
           throw new InternalInconsistentFilesystemException(pkgId, e);
         }
 
-        Target target = entry.getKey();
         if (maybeAddEventAboutLabelCrossingSubpackage(
             pkgBuilder, pkgRoot, target, packageLookupKey.argument(), packageLookupValue)) {
-          pkgBuilder.getTargets().remove(entry.getKey());
+          pkgBuilder.getTargets().remove(target);
           pkgBuilder.setContainsErrors();
           break;
         }
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/BUILD b/src/test/java/com/google/devtools/build/lib/pkgcache/BUILD
index a654c05..a127446 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/BUILD
@@ -181,6 +181,7 @@
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/java/net/starlark/java/syntax",
+        "//src/main/protobuf:failure_details_java_proto",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/testutil",
         "//src/test/java/com/google/devtools/build/lib/testutil:JunitUtils",
diff --git a/src/test/java/com/google/devtools/build/lib/pkgcache/PackageLoadingTest.java b/src/test/java/com/google/devtools/build/lib/pkgcache/PackageLoadingTest.java
index 56cb0a2..6b5d620 100644
--- a/src/test/java/com/google/devtools/build/lib/pkgcache/PackageLoadingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/pkgcache/PackageLoadingTest.java
@@ -40,6 +40,7 @@
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.runtime.QuiescingExecutorsImpl;
+import com.google.devtools.build.lib.server.FailureDetails.PackageLoading;
 import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
@@ -578,4 +579,18 @@
     InputFile f = (InputFile) p.getTarget("f.sh");
     assertThat(f.getLocation().line()).isEqualTo(1);
   }
+
+  @Test
+  public void testDeterminismOfFailureDetailOnMultipleLabelCrossingSubpackageBoundaryErrors()
+      throws Exception {
+    reporter.removeHandler(failFastHandler);
+    scratch.file("p/sub/BUILD");
+    scratch.file("p/BUILD", "sh_library(name = 'sub/a')", "sh_library(name = 'sub/b')");
+    Package p = getPackage("p");
+    assertThat(p.getFailureDetail().getPackageLoading().getCode())
+        .isEqualTo(PackageLoading.Code.LABEL_CROSSES_PACKAGE_BOUNDARY);
+    // We used to non-deterministically pick a target whose label crossed a subpackage boundary, but
+    // now we deterministically pick the first one (alphabetically by target name).
+    assertThat(p.getFailureDetail().getMessage()).startsWith("Label '//p:sub/a' is invalid");
+  }
 }