Properly report missing unused inputs files

These need to be reported as 'user error' rather than as environmental
error, as the most likely cause is that the action failed to generate
the file.

PiperOrigin-RevId: 278316373
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
index 5f735d5..aed88d9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -309,7 +309,7 @@
    */
   protected void afterExecute(
       ActionExecutionContext actionExecutionContext, List<SpawnResult> spawnResults)
-      throws IOException {}
+      throws IOException, ExecException {}
 
   @Override
   public final ActionContinuationOrResult beginExecution(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/StarlarkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/StarlarkAction.java
index ff21119..3f6bd9f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/StarlarkAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/StarlarkAction.java
@@ -26,14 +26,17 @@
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
 import com.google.devtools.build.lib.actions.CommandLines;
 import com.google.devtools.build.lib.actions.CommandLines.CommandLineLimits;
+import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.ExecutionRequirements;
 import com.google.devtools.build.lib.actions.ResourceSet;
 import com.google.devtools.build.lib.actions.RunfilesSupplier;
 import com.google.devtools.build.lib.actions.Spawn;
 import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.actions.UserExecException;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import java.io.BufferedReader;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -143,23 +146,34 @@
 
   private InputStream getUnusedInputListInputStream(
       ActionExecutionContext actionExecutionContext, List<SpawnResult> spawnResults)
-      throws IOException {
+      throws IOException, ExecException {
 
     // Check if the file is in-memory.
     // Note: SpawnActionContext guarantees that the first list entry exists and corresponds to the
     // executed spawn.
-    InputStream inputStream = spawnResults.get(0).getInMemoryOutput(unusedInputsList.get());
+    Artifact unusedInputsListArtifact = unusedInputsList.get();
+    InputStream inputStream = spawnResults.get(0).getInMemoryOutput(unusedInputsListArtifact);
     if (inputStream != null) {
       return inputStream;
     }
     // Fallback to reading from disk.
-    return actionExecutionContext.getPathResolver().toPath(unusedInputsList.get()).getInputStream();
+    try {
+      return actionExecutionContext
+          .getPathResolver()
+          .toPath(unusedInputsListArtifact)
+          .getInputStream();
+    } catch (FileNotFoundException e) {
+      throw new UserExecException(
+          "Action did not create expected output file listing unused inputs: "
+              + unusedInputsListArtifact.getExecPathString(),
+          e);
+    }
   }
 
   @Override
   protected void afterExecute(
       ActionExecutionContext actionExecutionContext, List<SpawnResult> spawnResults)
-      throws IOException {
+      throws IOException, ExecException {
     if (!unusedInputsList.isPresent()) {
       return;
     }
diff --git a/src/test/shell/integration/skylark_dependency_pruning_test.sh b/src/test/shell/integration/skylark_dependency_pruning_test.sh
index 868684c..10b0c45 100755
--- a/src/test/shell/integration/skylark_dependency_pruning_test.sh
+++ b/src/test/shell/integration/skylark_dependency_pruning_test.sh
@@ -280,6 +280,19 @@
   expect_log "Use --experimental_starlark_unused_inputs_list"
 }
 
+function test_missing_unused_inputs_list() {
+  cat > pkg/cat_unused.sh << 'EOF'
+#!/bin/sh
+exit 0
+EOF
+  chmod +x pkg/cat_unused.sh
+  bazel build --experimental_starlark_unused_inputs_list \
+      //pkg:output >& $TEST_log && fail "Expected failure"
+  exitcode=$?
+  assert_equals 1 "$exitcode"
+  expect_log "Action did not create expected output file listing unused inputs"
+}
+
 # Test with orphaned artifacts features.
 # This requires --experimental_inmemory_unused_inputs_list flag.
 function test_orphaned_artifacts() {
@@ -294,7 +307,7 @@
       //pkg:output >& $TEST_log && fail "Expected failure"
   exitcode=$?
   assert_equals 1 "$exitcode"
-  expect_log "bin/pkg/output.unused (No such file or directory)"
+  expect_log "Action did not create expected output file listing unused inputs"
 }
 
 # Test with orphaned artifacts features with host config.
@@ -312,7 +325,7 @@
       //pkg:output2 >& $TEST_log && fail "Expected failure"
   exitcode=$?
   assert_equals 1 "$exitcode"
-  expect_log "bin/pkg/output.unused (No such file or directory)"
+  expect_log "Action did not create expected output file listing unused inputs"
 }
 
 run_suite "Tests Skylark dependency pruning"