Encode remaining environmental execution failures with FailureDetails

EnvironmentalExecException now always has details. Applicable failure
modes in SymlinkTree management, include scanning, remote execution, and
CPP actions now use FailureDetails.

RELNOTES: None.
PiperOrigin-RevId: 315833077
diff --git a/src/main/java/com/google/devtools/build/lib/actions/EnvironmentalExecException.java b/src/main/java/com/google/devtools/build/lib/actions/EnvironmentalExecException.java
index 82a64ed..1c0f7c6 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/EnvironmentalExecException.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/EnvironmentalExecException.java
@@ -19,12 +19,10 @@
 import com.google.devtools.build.lib.server.FailureDetails.Execution;
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.util.DetailedExitCode;
-import com.google.devtools.build.lib.util.ExitCode;
 import java.io.IOException;
-import javax.annotation.Nullable;
 
 /**
- * An ExecException which is reports an issue executing an action due to an external problem on the
+ * An ExecException which reports an issue executing an action due to an external problem on the
  * local system.
  *
  * <p>This exception will result in an exit code regarded as a system error; avoid using this for
@@ -37,8 +35,7 @@
  * directory or denied file system access.
  */
 public class EnvironmentalExecException extends ExecException {
-  // TODO(b/138456686): Make this not nullable.
-  @Nullable private final FailureDetail failureDetail;
+  private final FailureDetail failureDetail;
 
   public EnvironmentalExecException(IOException cause, FailureDetails.Execution.Code code) {
     super("unexpected I/O exception", cause);
@@ -46,16 +43,11 @@
         FailureDetail.newBuilder().setExecution(Execution.newBuilder().setCode(code)).build();
   }
 
-  public EnvironmentalExecException(IOException cause, FailureDetail failureDetail) {
+  public EnvironmentalExecException(Exception cause, FailureDetail failureDetail) {
     super(failureDetail.getMessage(), cause);
     this.failureDetail = failureDetail;
   }
 
-  public EnvironmentalExecException(String message, Throwable cause) {
-    super(message, cause);
-    failureDetail = null;
-  }
-
   public EnvironmentalExecException(FailureDetail failureDetail) {
     super(failureDetail.getMessage());
     this.failureDetail = failureDetail;
@@ -70,10 +62,8 @@
             messagePrefix,
             getMessage(),
             getCause() == null ? "" : ("\n" + Throwables.getStackTraceAsString(getCause())));
-    DetailedExitCode detailedExitCode =
-        failureDetail == null
-            ? DetailedExitCode.justExitCode(ExitCode.LOCAL_ENVIRONMENTAL_ERROR)
-            : DetailedExitCode.of(failureDetail.toBuilder().setMessage(message).build());
-    return new ActionExecutionException(message, action, isCatastrophic(), detailedExitCode);
+    FailureDetail failureDetailWithPrefix = failureDetail.toBuilder().setMessage(message).build();
+    return new ActionExecutionException(
+        message, action, isCatastrophic(), DetailedExitCode.of(failureDetailWithPrefix));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
index c8aeb0b..fab0276 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
@@ -23,7 +23,9 @@
 import com.google.devtools.build.lib.actions.EnvironmentalExecException;
 import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
+import com.google.devtools.build.lib.server.FailureDetails.Execution;
 import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.shell.Command;
 import com.google.devtools.build.lib.shell.CommandException;
 import com.google.devtools.build.lib.util.CommandBuilder;
@@ -163,7 +165,13 @@
         command.execute();
       }
     } catch (CommandException e) {
-      throw new EnvironmentalExecException(CommandUtils.describeCommandFailure(true, e), e);
+      throw new EnvironmentalExecException(
+          e,
+          FailureDetail.newBuilder()
+              .setMessage(CommandUtils.describeCommandFailure(true, e))
+              .setExecution(
+                  Execution.newBuilder().setCode(Code.SYMLINK_TREE_CREATION_COMMAND_EXCEPTION))
+              .build());
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
index 5ff0a40..31a924f 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
@@ -27,7 +27,9 @@
 import com.google.devtools.build.lib.analysis.actions.SymlinkTreeActionContext;
 import com.google.devtools.build.lib.profiler.AutoProfiler;
 import com.google.devtools.build.lib.profiler.GoogleAutoProfilerUtils;
+import com.google.devtools.build.lib.server.FailureDetails.Execution;
 import com.google.devtools.build.lib.server.FailureDetails.Execution.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.OutputService;
@@ -155,8 +157,7 @@
       try {
         FileSystemUtils.writeContentAsLatin1(outputManifest, hexDigest);
       } catch (IOException e) {
-        throw new EnvironmentalExecException(
-            "Failed to link output manifest '" + outputManifest.getPathString() + "'", e);
+        throw createLinkFailureException(outputManifest, e);
       }
     } else {
       // Link output manifest on success. We avoid a file copy as these manifests may be
@@ -165,8 +166,7 @@
       try {
         outputManifest.createSymbolicLink(inputManifest);
       } catch (IOException e) {
-        throw new EnvironmentalExecException(
-            "Failed to link output manifest '" + outputManifest.getPathString() + "'", e);
+        throw createLinkFailureException(outputManifest, e);
       }
     }
   }
@@ -178,4 +178,15 @@
         actionExecutionContext.getInputPath(action.getOutputManifest()).getParentDirectory(),
         action.isFilesetTree());
   }
+
+  private static EnvironmentalExecException createLinkFailureException(
+      Path outputManifest, IOException e) {
+    return new EnvironmentalExecException(
+        e,
+        FailureDetail.newBuilder()
+            .setMessage("Failed to link output manifest '" + outputManifest.getPathString() + "'")
+            .setExecution(
+                Execution.newBuilder().setCode(Code.SYMLINK_TREE_MANIFEST_LINK_IO_EXCEPTION))
+            .build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/includescanning/IncludeHintsFunction.java b/src/main/java/com/google/devtools/build/lib/includescanning/IncludeHintsFunction.java
index 63bcfdd..a9f50d3 100644
--- a/src/main/java/com/google/devtools/build/lib/includescanning/IncludeHintsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/includescanning/IncludeHintsFunction.java
@@ -77,8 +77,11 @@
       env.getValueOrThrow(FileValue.key(RootedPath.toRootedPath(hintsPackageRoot, hintsFile)),
           IOException.class);
     } catch (IOException | BuildFileNotFoundException e) {
-      throw new IncludeHintsFunctionException(new EnvironmentalExecException(
-          "could not read INCLUDE_HINTS file", e));
+      throw new IncludeHintsFunctionException(
+          new EnvironmentalExecException(
+              e,
+              createFailureDetail(
+                  "could not read INCLUDE_HINTS file", Code.INCLUDE_HINTS_READ_FAILURE)));
     }
     if (env.valuesMissing()) {
       return null;
@@ -86,8 +89,11 @@
     try {
       return Hints.getRules(hintsPackageRoot.getRelative(hintsFile));
     } catch (IOException e) {
-      throw new IncludeHintsFunctionException(new EnvironmentalExecException(
-          "could not read INCLUDE_HINTS file", e));
+      throw new IncludeHintsFunctionException(
+          new EnvironmentalExecException(
+              e,
+              createFailureDetail(
+                  "could not read INCLUDE_HINTS file", Code.INCLUDE_HINTS_READ_FAILURE)));
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteCache.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteCache.java
index 0c881f4..92d1e32 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteCache.java
@@ -60,6 +60,9 @@
 import com.google.devtools.build.lib.remote.options.RemoteOptions;
 import com.google.devtools.build.lib.remote.util.DigestUtil;
 import com.google.devtools.build.lib.remote.util.Utils.InMemoryOutput;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.RemoteExecution;
+import com.google.devtools.build.lib.server.FailureDetails.RemoteExecution.Code;
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.util.io.OutErr;
 import com.google.devtools.build.lib.vfs.Dirent;
@@ -352,7 +355,13 @@
         // any subsequent local execution failure would likely be incomprehensible.
         ExecException execEx =
             new EnvironmentalExecException(
-                "Failed to delete output files after incomplete download", ioEx);
+                ioEx,
+                FailureDetail.newBuilder()
+                    .setMessage("Failed to delete output files after incomplete download")
+                    .setRemoteExecution(
+                        RemoteExecution.newBuilder()
+                            .setCode(Code.INCOMPLETE_OUTPUT_DOWNLOAD_CLEANUP_FAILURE))
+                    .build());
         execEx.addSuppressed(e);
         throw execEx;
       }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
index a5fef2f..01919c5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -1459,7 +1459,8 @@
         try {
           return ByteStreams.toByteArray(in);
         } catch (IOException e) {
-          throw new EnvironmentalExecException("Reading in-memory .d file failed", e);
+          throw new EnvironmentalExecException(
+              e, createFailureDetail("Reading in-memory .d file failed", Code.D_FILE_READ_FAILURE));
         }
       }
     }
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index d53717b..58d1844 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -337,6 +337,8 @@
         [(metadata) = { exit_code: 2 }];
     REMOTE_DOWNLOAD_OUTPUTS_MINIMAL_WITHOUT_INMEMORY_JDEPS = 12
         [(metadata) = { exit_code: 2 }];
+    INCOMPLETE_OUTPUT_DOWNLOAD_CLEANUP_FAILURE = 13
+        [(metadata) = { exit_code: 36 }];
   }
 
   Code code = 1;
@@ -364,8 +366,12 @@
     TEST_OUT_ERR_IO_EXCEPTION = 14 [(metadata) = { exit_code: 36 }];
     SYMLINK_TREE_MANIFEST_COPY_IO_EXCEPTION = 15
         [(metadata) = { exit_code: 36 }];
-    SYMLINK_TREE_CREATION_IO_EXCEPTION = 16 [(metadata) = { exit_code: 36 }];
-    ACTION_INPUT_READ_IO_EXCEPTION = 17 [(metadata) = { exit_code: 36 }];
+    SYMLINK_TREE_MANIFEST_LINK_IO_EXCEPTION = 16
+        [(metadata) = { exit_code: 36 }];
+    SYMLINK_TREE_CREATION_IO_EXCEPTION = 17 [(metadata) = { exit_code: 36 }];
+    SYMLINK_TREE_CREATION_COMMAND_EXCEPTION = 18
+        [(metadata) = { exit_code: 36 }];
+    ACTION_INPUT_READ_IO_EXCEPTION = 19 [(metadata) = { exit_code: 36 }];
   }
 
   Code code = 1;
@@ -608,6 +614,7 @@
     INITIALIZE_INCLUDE_HINTS_ERROR = 1 [(metadata) = { exit_code: 36 }];
     SCANNING_IO_EXCEPTION = 2 [(metadata) = { exit_code: 36 }];
     INCLUDE_HINTS_FILE_NOT_IN_PACKAGE = 3 [(metadata) = { exit_code: 36 }];
+    INCLUDE_HINTS_READ_FAILURE = 4 [(metadata) = { exit_code: 36 }];
   }
 
   Code code = 1;
@@ -831,6 +838,7 @@
     CPP_COMPILE_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
     FIND_USED_HEADERS_IO_EXCEPTION = 1 [(metadata) = { exit_code: 36 }];
     COPY_OUT_ERR_FAILURE = 2 [(metadata) = { exit_code: 36 }];
+    D_FILE_READ_FAILURE = 3 [(metadata) = { exit_code: 36 }];
   }
 
   Code code = 1;