Return REMOTE_CACHE_EVICTED exit code on ThinLTO imports cache eviction.

ThinLTO backend compilation reads `.imports` files during Skyframe input discovery prior to spawn execution. When remote cache eviction drops an imports file, `LtoBackendAction.computeImports` caught the cache miss as a generic `IMPORTS_READ_IO_EXCEPTION` (exit code 1). Because Bazel only triggers `--experimental_remote_cache_eviction_retries` when an action returns `REMOTE_CACHE_EVICTED` (exit code 39), builds fail permanently instead of retrying.

In `LtoBackendAction.computeImports`, check if an `IOException` reading the imports file is a `CacheNotFoundException` or `BulkTransferException` cache miss. If so, return `Spawn.Code.REMOTE_CACHE_EVICTED` so that input discovery failures from evicted cache entries trigger build retries.

Tested:
Added `discoverInputs_cacheNotFoundException_throwsRemoteCacheEvictedCode` and `discoverInputs_bulkTransferException_throwsRemoteCacheEvictedCode` in `LtoBackendActionTest`.
PiperOrigin-RevId: 971771054
Change-Id: I6c9b84c9a55d727cd930319178a946332df2d977
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
index 5a2e91c..2e15200 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -90,6 +90,8 @@
         "//src/main/java/com/google/devtools/build/lib/packages:provider",
         "//src/main/java/com/google/devtools/build/lib/packages/semantics",
         "//src/main/java/com/google/devtools/build/lib/profiler",
+        "//src/main/java/com/google/devtools/build/lib/remote/common:bulk_transfer_exception",
+        "//src/main/java/com/google/devtools/build/lib/remote/common:cache_not_found_exception",
         "//src/main/java/com/google/devtools/build/lib/rules:alias",
         "//src/main/java/com/google/devtools/build/lib/skyframe:action_execution_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
index b3291af..4b7959f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
@@ -38,9 +38,12 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.remote.common.BulkTransferException;
+import com.google.devtools.build.lib.remote.common.CacheNotFoundException;
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.server.FailureDetails.LtoAction;
 import com.google.devtools.build.lib.server.FailureDetails.LtoAction.Code;
+import com.google.devtools.build.lib.server.FailureDetails.Spawn;
 import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.util.DetailedExitCode;
@@ -219,7 +222,18 @@
           String.format(
               "error reading imports file %s: %s",
               actionExecutionContext.getInputPath(imports), e.getMessage());
-      DetailedExitCode code = createDetailedExitCode(message, Code.IMPORTS_READ_IO_EXCEPTION);
+      DetailedExitCode code;
+      if (e instanceof CacheNotFoundException
+          || BulkTransferException.allCausedByCacheNotFoundException(e)) {
+        code =
+            DetailedExitCode.of(
+                FailureDetail.newBuilder()
+                    .setMessage(message)
+                    .setSpawn(Spawn.newBuilder().setCode(Spawn.Code.REMOTE_CACHE_EVICTED))
+                    .build());
+      } else {
+        code = createDetailedExitCode(message, Code.IMPORTS_READ_IO_EXCEPTION);
+      }
       throw new ActionExecutionException(message, e, this, false, code);
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
index 092d71c..a72fa10 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -409,20 +409,25 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/remote/common:bulk_transfer_exception",
+        "//src/main/java/com/google/devtools/build/lib/remote/common:cache_not_found_exception",
         "//src/main/java/com/google/devtools/build/lib/rules/cpp",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:depsutils",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:dumper",
+        "//src/main/java/com/google/devtools/build/lib/util:exit_code",
         "//src/main/java/com/google/devtools/build/lib/util/io",
         "//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/lib/vfs/inmemoryfs",
         "//src/test/java/com/google/devtools/build/lib/actions/util",
         "//src/test/java/com/google/devtools/build/lib/analysis/util",
         "//src/test/java/com/google/devtools/build/lib/exec/util",
         "//third_party/java/guava:collect",
         "//third_party/java/junit",
         "//third_party/java/truth",
+        "@remoteapis//:build_bazel_remote_execution_v2_remote_execution_java_proto",
     ],
 )
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java
index 31b61b0..c84f964 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java
@@ -18,6 +18,7 @@
 import static com.google.devtools.build.lib.skyframe.serialization.testutils.Dumper.dumpStructureWithEquivalenceReduction;
 import static org.junit.Assert.assertThrows;
 
+import build.bazel.remote.execution.v2.Digest;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -29,6 +30,7 @@
 import com.google.devtools.build.lib.actions.ActionExecutionException;
 import com.google.devtools.build.lib.actions.ActionInputPrefetcher;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.CommandLines;
 import com.google.devtools.build.lib.actions.DiscoveredModulesPruner;
 import com.google.devtools.build.lib.actions.Executor;
@@ -44,13 +46,22 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.StoredEventHandler;
 import com.google.devtools.build.lib.exec.util.TestExecutorBuilder;
+import com.google.devtools.build.lib.remote.common.BulkTransferException;
+import com.google.devtools.build.lib.remote.common.CacheNotFoundException;
 import com.google.devtools.build.lib.skyframe.serialization.ArrayCodec;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationDepsUtils;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
+import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
+import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.lib.vfs.SyscallCache;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 import org.junit.Before;
@@ -280,6 +291,76 @@
   }
 
   @Test
+  public void discoverInputs_cacheNotFoundException_throwsRemoteCacheEvictedCode()
+      throws Exception {
+    FileSystem customFs =
+        new InMemoryFileSystem(DigestHashFunction.SHA256) {
+          @Override
+          public synchronized InputStream getInputStream(PathFragment path) throws IOException {
+            if (path.getBaseName().endsWith(".imports")) {
+              throw new CacheNotFoundException(Digest.getDefaultInstance(), path);
+            }
+            return super.getInputStream(path);
+          }
+        };
+    ArtifactRoot root = ArtifactRoot.asSourceRoot(Root.fromPath(customFs.getPath("/")));
+    Artifact importsArtifact = ActionsTestUtil.createArtifact(root, "bitcode1.imports");
+    Artifact index1Artifact = getSourceArtifact("file2.o");
+    LtoBackendAction action =
+        LtoBackendAction.create(
+            ActionsTestUtil.NULL_ACTION_OWNER,
+            targetConfig,
+            NestedSetBuilder.create(Order.STABLE_ORDER, importsArtifact, index1Artifact),
+            new BitcodeFiles(NestedSetBuilder.create(Order.STABLE_ORDER, index1Artifact)),
+            importsArtifact,
+            ImmutableSet.of(destinationArtifact),
+            CommandLines.builder()
+                .addSingleArgument(scratch.file("/bin/clang").asFragment())
+                .build(),
+            ActionEnvironment.create(ImmutableMap.of()));
+
+    ActionExecutionException e =
+        assertThrows(ActionExecutionException.class, () -> action.discoverInputs(context));
+
+    assertThat(e.getExitCode()).isEqualTo(ExitCode.REMOTE_CACHE_EVICTED);
+  }
+
+  @Test
+  public void discoverInputs_bulkTransferException_throwsRemoteCacheEvictedCode() throws Exception {
+    FileSystem customFs =
+        new InMemoryFileSystem(DigestHashFunction.SHA256) {
+          @Override
+          public synchronized InputStream getInputStream(PathFragment path) throws IOException {
+            if (path.getBaseName().endsWith(".imports")) {
+              throw new BulkTransferException(
+                  new CacheNotFoundException(Digest.getDefaultInstance(), path));
+            }
+            return super.getInputStream(path);
+          }
+        };
+    ArtifactRoot root = ArtifactRoot.asSourceRoot(Root.fromPath(customFs.getPath("/")));
+    Artifact importsArtifact = ActionsTestUtil.createArtifact(root, "bitcode1.imports");
+    Artifact index1Artifact = getSourceArtifact("file2.o");
+    LtoBackendAction action =
+        LtoBackendAction.create(
+            ActionsTestUtil.NULL_ACTION_OWNER,
+            targetConfig,
+            NestedSetBuilder.create(Order.STABLE_ORDER, importsArtifact, index1Artifact),
+            new BitcodeFiles(NestedSetBuilder.create(Order.STABLE_ORDER, index1Artifact)),
+            importsArtifact,
+            ImmutableSet.of(destinationArtifact),
+            CommandLines.builder()
+                .addSingleArgument(scratch.file("/bin/clang").asFragment())
+                .build(),
+            ActionEnvironment.create(ImmutableMap.of()));
+
+    ActionExecutionException e =
+        assertThrows(ActionExecutionException.class, () -> action.discoverInputs(context));
+
+    assertThat(e.getExitCode()).isEqualTo(ExitCode.REMOTE_CACHE_EVICTED);
+  }
+
+  @Test
   public void serializationRoundTrip_resetsInputs() throws Exception {
     LtoBackendAction action =
         LtoBackendAction.create(