Encode additional environmental execution failures with FailureDetails

Ninja, dynamic execution, include scanning.

RELNOTES: None.
PiperOrigin-RevId: 315825179
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 55a60d1..82a64ed 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
@@ -56,9 +56,9 @@
     failureDetail = null;
   }
 
-  public EnvironmentalExecException(String message) {
-    super(message);
-    failureDetail = null;
+  public EnvironmentalExecException(FailureDetail failureDetail) {
+    super(failureDetail.getMessage());
+    this.failureDetail = failureDetail;
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/BUILD
index e9fa09a..a368f0c 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/BUILD
@@ -44,6 +44,7 @@
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/build/skyframe",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
+        "//src/main/protobuf:failure_details_java_proto",
         "//third_party:guava",
         "//third_party:jsr305",
     ],
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaAction.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaAction.java
index 453bfbc..06a8fd2 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaAction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaAction.java
@@ -38,6 +38,9 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
 import com.google.devtools.build.lib.rules.cpp.CppIncludeExtractionContext;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.NinjaAction.Code;
 import com.google.devtools.build.lib.skyframe.TrackSourceDirectoriesFlag;
 import com.google.devtools.build.lib.util.DependencySet;
 import com.google.devtools.build.lib.vfs.Path;
@@ -187,10 +190,12 @@
 
         if (inputArtifact == null) {
           throw new EnvironmentalExecException(
-              String.format(
-                  "depfile-declared dependency '%s' is invalid: it must either be "
-                      + "a source input, or a pre-declared generated input",
-                  execRelativePath));
+              createFailureDetail(
+                  String.format(
+                      "depfile-declared dependency '%s' is invalid: it must either be "
+                          + "a source input, or a pre-declared generated input",
+                      execRelativePath),
+                  Code.INVALID_DEPFILE_DECLARED_DEPENDENCY));
         }
 
         inputsBuilder.add(inputArtifact);
@@ -198,7 +203,9 @@
       updateInputs(inputsBuilder.build());
     } catch (IOException e) {
       // Some kind of IO or parse exception--wrap & rethrow it to stop the build.
-      throw new EnvironmentalExecException("error while parsing .d file: " + e.getMessage(), e);
+      String message = "error while parsing .d file: " + e.getMessage();
+      throw new EnvironmentalExecException(
+          e, createFailureDetail(message, Code.D_FILE_PARSE_FAILURE));
     }
   }
 
@@ -222,4 +229,11 @@
     updateInputs(inputs);
     return inputs;
   }
+
+  private static FailureDetail createFailureDetail(String message, Code detailedCode) {
+    return FailureDetail.newBuilder()
+        .setMessage(message)
+        .setNinjaAction(FailureDetails.NinjaAction.newBuilder().setCode(detailedCode))
+        .build();
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/dynamic/LegacyDynamicSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/dynamic/LegacyDynamicSpawnStrategy.java
index 550755a..6f919ac 100644
--- a/src/main/java/com/google/devtools/build/lib/dynamic/LegacyDynamicSpawnStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/dynamic/LegacyDynamicSpawnStrategy.java
@@ -35,6 +35,9 @@
 import com.google.devtools.build.lib.actions.UserExecException;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.exec.ExecutionPolicy;
+import com.google.devtools.build.lib.server.FailureDetails.DynamicExecution;
+import com.google.devtools.build.lib.server.FailureDetails.DynamicExecution.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.util.io.FileOutErr;
 import com.google.devtools.build.lib.vfs.Path;
 import java.io.IOException;
@@ -142,7 +145,7 @@
         && !options.availabilityInfoExempt.contains(spawn.getMnemonic())) {
       if (spawn.getExecutionInfo().containsKey(ExecutionRequirements.REQUIRES_DARWIN)
           && !spawn.getExecutionInfo().containsKey(ExecutionRequirements.REQUIREMENTS_SET)) {
-        throw new EnvironmentalExecException(
+        String message =
             String.format(
                 "The following spawn was missing Xcode-related execution requirements. Please"
                     + " let the Bazel team know if you encounter this issue. You can work around"
@@ -156,7 +159,9 @@
                 spawn.getMnemonic(),
                 spawn.getToolFiles(),
                 spawn.getExecutionPlatform(),
-                spawn.getExecutionInfo()));
+                spawn.getExecutionInfo());
+        throw new EnvironmentalExecException(
+            createFailureDetail(message, Code.XCODE_RELATED_PREREQ_UNMET));
       }
     }
     ExecutionPolicy executionPolicy = getExecutionPolicy.apply(spawn);
@@ -403,6 +408,13 @@
         "executorCreated not yet called or no default dynamic_remote_strategy set");
   }
 
+  private static FailureDetail createFailureDetail(String message, Code detailedCode) {
+    return FailureDetail.newBuilder()
+        .setMessage(message)
+        .setDynamicExecution(DynamicExecution.newBuilder().setCode(detailedCode))
+        .build();
+  }
+
   private abstract static class DynamicExecutionCallable
       implements Callable<DynamicExecutionResult> {
     private final Phaser taskFinished;
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 076bf91..63bcfdd 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
@@ -18,6 +18,9 @@
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.includescanning.IncludeParser.Hints;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.IncludeScanning;
+import com.google.devtools.build.lib.server.FailureDetails.IncludeScanning.Code;
 import com.google.devtools.build.lib.skyframe.ContainingPackageLookupValue;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -61,14 +64,14 @@
       }
       if (!hintsLookupValue.hasContainingPackage()) {
         String reasonForNoContainingPackage = hintsLookupValue.getReasonForNoContainingPackage();
+        String message =
+            String.format(
+                "INCLUDE_HINTS file %s was not in a package%s",
+                hintsFile,
+                reasonForNoContainingPackage != null ? ": " + reasonForNoContainingPackage : "");
         throw new IncludeHintsFunctionException(
             new EnvironmentalExecException(
-                "INCLUDE_HINTS file "
-                    + hintsFile
-                    + " was not in a package"
-                    + (reasonForNoContainingPackage != null
-                        ? ": " + reasonForNoContainingPackage
-                        : "")));
+                createFailureDetail(message, Code.INCLUDE_HINTS_FILE_NOT_IN_PACKAGE)));
       }
       hintsPackageRoot = hintsLookupValue.getContainingPackageRoot();
       env.getValueOrThrow(FileValue.key(RootedPath.toRootedPath(hintsPackageRoot, hintsFile)),
@@ -94,6 +97,13 @@
     return null;
   }
 
+  private static FailureDetail createFailureDetail(String message, Code detailedCode) {
+    return FailureDetail.newBuilder()
+        .setMessage(message)
+        .setIncludeScanning(IncludeScanning.newBuilder().setCode(detailedCode))
+        .build();
+  }
+
   /**
    * Used to declare the exception type that can be wrapped in the exception thrown by
    * {@link IncludeHintsFunction#compute}.
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index afac067..69f1477 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -133,6 +133,8 @@
     ActionRewinding action_rewinding = 160;
     CppCompile cpp_compile = 161;
     StarlarkAction starlark_action = 162;
+    NinjaAction ninja_action = 163;
+    DynamicExecution dynamic_execution = 164;
   }
 
   reserved 102; // For internal use
@@ -603,6 +605,7 @@
     INCLUDE_SCANNING_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
     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 }];
   }
 
   Code code = 1;
@@ -838,3 +841,22 @@
 
   Code code = 1;
 }
+
+message NinjaAction {
+  enum Code {
+    NINJA_ACTION_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    INVALID_DEPFILE_DECLARED_DEPENDENCY = 1 [(metadata) = { exit_code: 36 }];
+    D_FILE_PARSE_FAILURE = 2 [(metadata) = { exit_code: 36 }];
+  }
+
+  Code code = 1;
+}
+
+message DynamicExecution {
+  enum Code {
+    DYNAMIC_EXECUTION_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+    XCODE_RELATED_PREREQ_UNMET = 1 [(metadata) = { exit_code: 36 }];
+  }
+
+  Code code = 1;
+}