Encode remaining ActionExecution failures with FailureDetails

This removes the last uses of undetailed ActionExecutionException
constructors by adding details to {Test,User}ExecException.

RELNOTES: None.
PiperOrigin-RevId: 318143448
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 92d1e32..afc1562 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
@@ -356,12 +356,9 @@
         ExecException execEx =
             new EnvironmentalExecException(
                 ioEx,
-                FailureDetail.newBuilder()
-                    .setMessage("Failed to delete output files after incomplete download")
-                    .setRemoteExecution(
-                        RemoteExecution.newBuilder()
-                            .setCode(Code.INCOMPLETE_OUTPUT_DOWNLOAD_CLEANUP_FAILURE))
-                    .build());
+                createFailureDetail(
+                    "Failed to delete output files after incomplete download",
+                    Code.INCOMPLETE_OUTPUT_DOWNLOAD_CLEANUP_FAILURE));
         execEx.addSuppressed(e);
         throw execEx;
       }
@@ -968,12 +965,13 @@
 
     private void illegalOutput(Path what) throws ExecException {
       String kind = what.isSymbolicLink() ? "symbolic link" : "special file";
-      throw new UserExecException(
+      String message =
           String.format(
               "Output %s is a %s. Only regular files and directories may be "
                   + "uploaded to a remote cache. "
                   + "Change the file type or use --remote_allow_symlink_upload.",
-              what.relativeTo(execRoot), kind));
+              what.relativeTo(execRoot), kind);
+      throw new UserExecException(createFailureDetail(message, Code.ILLEGAL_OUTPUT));
     }
   }
 
@@ -983,6 +981,13 @@
     cacheProtocol.close();
   }
 
+  private static FailureDetail createFailureDetail(String message, Code detailedCode) {
+    return FailureDetail.newBuilder()
+        .setMessage(message)
+        .setRemoteExecution(RemoteExecution.newBuilder().setCode(detailedCode))
+        .build();
+  }
+
   /**
    * Creates an {@link OutputStream} that isn't actually opened until the first data is written.
    * This is useful to only have as many open file descriptors as necessary at a time to avoid
diff --git a/src/main/java/com/google/devtools/build/lib/remote/options/BUILD b/src/main/java/com/google/devtools/build/lib/remote/options/BUILD
index 5a714d7..1e7be66 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/options/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/remote/options/BUILD
@@ -18,6 +18,7 @@
         "//src/main/java/com/google/devtools/build/lib/util",
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/common/options",
+        "//src/main/protobuf:failure_details_java_proto",
         "//third_party:guava",
         "//third_party/protobuf:protobuf_java",
         "@remoteapis//:build_bazel_remote_execution_v2_remote_execution_java_proto",
diff --git a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
index d31bfa7..0c400d1 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
@@ -19,6 +19,9 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.devtools.build.lib.actions.UserExecException;
+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.OptionsUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.common.options.Converters;
@@ -454,9 +457,11 @@
 
     if (hasExecProperties && hasPlatformProperties) {
       throw new UserExecException(
-          "Setting both --remote_default_platform_properties and "
-              + "--remote_default_exec_properties is not allowed. Prefer setting "
-              + "--remote_default_exec_properties.");
+          createFailureDetail(
+              "Setting both --remote_default_platform_properties and "
+                  + "--remote_default_exec_properties is not allowed. Prefer setting "
+                  + "--remote_default_exec_properties.",
+              Code.INVALID_EXEC_AND_PLATFORM_PROPERTIES));
     }
 
     if (hasExecProperties) {
@@ -470,10 +475,11 @@
         TextFormat.getParser().merge(remoteDefaultPlatformProperties, builder);
         platform = builder.build();
       } catch (ParseException e) {
-        throw new UserExecException(
+        String message =
             "Failed to parse --remote_default_platform_properties "
-                + remoteDefaultPlatformProperties,
-            e);
+                + remoteDefaultPlatformProperties;
+        throw new UserExecException(
+            e, createFailureDetail(message, Code.REMOTE_DEFAULT_PLATFORM_PROPERTIES_PARSE_FAILURE));
       }
 
       ImmutableSortedMap.Builder<String, String> builder = ImmutableSortedMap.naturalOrder();
@@ -485,4 +491,11 @@
 
     return ImmutableSortedMap.of();
   }
+
+  private static FailureDetail createFailureDetail(String message, Code detailedCode) {
+    return FailureDetail.newBuilder()
+        .setMessage(message)
+        .setRemoteExecution(RemoteExecution.newBuilder().setCode(detailedCode))
+        .build();
+  }
 }