Use failure details in InfoCommand, InvalidConfigurationException
Adapts its usage of ExitCode and AbruptExitException to add FailureDetail
values. Failure modes detected there now have corresponding codes in
failure_details.proto.
InvalidConfigurationException now specifies a fine-grained code to more
precisely represent what configuration-related activity failed. This gets
its first use for platform mapping failures. (Platform mapping failures
may benefit from additional refinement.)
RELNOTES: None.
PiperOrigin-RevId: 309104771
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index aa46a17..29d2426 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1622,6 +1622,10 @@
java_library(
name = "config/invalid_configuration_exception",
srcs = ["config/InvalidConfigurationException.java"],
+ deps = [
+ "//src/main/protobuf:failure_details_java_proto",
+ "//third_party:jsr305",
+ ],
)
java_library(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
index 35bca2c..7f2a7f4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
@@ -13,21 +13,39 @@
// limitations under the License.
package com.google.devtools.build.lib.analysis.config;
+import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
+import javax.annotation.Nullable;
+
/**
* Thrown if the configuration options lead to an invalid configuration, or if any of the
* configuration labels cannot be loaded.
*/
public class InvalidConfigurationException extends Exception {
+ @Nullable private final Code detailedCode;
+
public InvalidConfigurationException(String message) {
super(message);
+ this.detailedCode = null;
}
public InvalidConfigurationException(String message, Throwable cause) {
super(message, cause);
+ this.detailedCode = null;
}
public InvalidConfigurationException(Throwable cause) {
- this(cause.getMessage(), cause);
+ super(cause.getMessage(), cause);
+ this.detailedCode = null;
+ }
+
+ public InvalidConfigurationException(Code detailedCode, Exception cause) {
+ super(cause.getMessage(), cause);
+ this.detailedCode = detailedCode;
+ }
+
+ @Nullable
+ public Code getDetailedCode() {
+ return detailedCode;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
index 9dc5cc8..f16e709 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.runtime.commands;
+import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
@@ -26,7 +27,12 @@
import com.google.devtools.build.lib.runtime.BlazeRuntime;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.server.FailureDetails.Interrupted;
import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.common.options.Option;
@@ -86,24 +92,19 @@
}
/**
- * Unchecked variant of ExitCausingException. Below, we need to throw from the Supplier interface,
- * which does not allow checked exceptions.
+ * Unchecked variant of {@link AbruptExitException}. Below, we need to throw from the Supplier
+ * interface, which does not allow checked exceptions.
*/
- public static class ExitCausingRuntimeException extends RuntimeException {
+ private static class AbruptExitRuntimeException extends RuntimeException {
- private final ExitCode exitCode;
+ private final DetailedExitCode detailedExitCode;
- public ExitCausingRuntimeException(String message, ExitCode exitCode) {
- super(message);
- this.exitCode = exitCode;
+ private AbruptExitRuntimeException(DetailedExitCode exitCode) {
+ this.detailedExitCode = exitCode;
}
- public ExitCausingRuntimeException(ExitCode exitCode) {
- this.exitCode = exitCode;
- }
-
- public ExitCode getExitCode() {
- return exitCode;
+ private DetailedExitCode getDetailedExitCode() {
+ return detailedExitCode;
}
}
@@ -133,13 +134,26 @@
/*keepGoing=*/ true);
} catch (InvalidConfigurationException e) {
env.getReporter().handle(Event.error(e.getMessage()));
- throw new ExitCausingRuntimeException(ExitCode.COMMAND_LINE_ERROR);
+ throw new AbruptExitRuntimeException(
+ DetailedExitCode.of(
+ ExitCode.COMMAND_LINE_ERROR,
+ FailureDetail.newBuilder()
+ .setMessage(Strings.nullToEmpty(e.getMessage()))
+ .setBuildConfiguration(
+ FailureDetails.BuildConfiguration.newBuilder()
+ .setCode(
+ e.getDetailedCode() == null
+ ? Code.BUILD_CONFIGURATION_UNKNOWN
+ : e.getDetailedCode()))
+ .build()));
} catch (AbruptExitException e) {
- throw new ExitCausingRuntimeException(
- "unknown error: " + e.getMessage(), e.getExitCode());
+ throw new AbruptExitRuntimeException(e.getDetailedExitCode());
} catch (InterruptedException e) {
env.getReporter().handle(Event.error("interrupted"));
- throw new ExitCausingRuntimeException(ExitCode.INTERRUPTED);
+ throw new AbruptExitRuntimeException(
+ createInterruptedExit(
+ "command interrupted while syncing package loading",
+ Interrupted.Code.PACKAGE_LOADING_SYNC));
}
});
@@ -156,8 +170,10 @@
List<String> residue = optionsParsingResult.getResidue();
if (residue.size() > 1) {
- env.getReporter().handle(Event.error("at most one key may be specified"));
- return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
+ String message = "at most one key may be specified";
+ env.getReporter().handle(Event.error(message));
+ return createFailureResult(
+ message, ExitCode.COMMAND_LINE_ERROR, FailureDetails.InfoCommand.Code.TOO_MANY_KEYS);
}
String key = residue.size() == 1 ? residue.get(0) : null;
@@ -167,15 +183,23 @@
if (items.containsKey(key)) {
value = items.get(key).get(configurationSupplier, env);
} else {
- env.getReporter().handle(Event.error("unknown key: '" + key + "'"));
- return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
+ String message = "unknown key: '" + key + "'";
+ env.getReporter().handle(Event.error(message));
+ return createFailureResult(
+ message,
+ ExitCode.COMMAND_LINE_ERROR,
+ FailureDetails.InfoCommand.Code.KEY_NOT_RECOGNIZED);
}
try {
outErr.getOutputStream().write(value);
outErr.getOutputStream().flush();
} catch (IOException e) {
- env.getReporter().handle(Event.error("Cannot write info block: " + e.getMessage()));
- return BlazeCommandResult.exitCode(ExitCode.ANALYSIS_FAILURE);
+ String message = "Cannot write info block: " + e.getMessage();
+ env.getReporter().handle(Event.error(message));
+ return createFailureResult(
+ message,
+ ExitCode.ANALYSIS_FAILURE,
+ FailureDetails.InfoCommand.Code.INFO_BLOCK_WRITE_FAILURE);
}
} else { // print them all
configurationSupplier.get(); // We'll need this later anyway
@@ -189,17 +213,42 @@
}
}
} catch (AbruptExitException e) {
- return BlazeCommandResult.exitCode(e.getExitCode());
- } catch (ExitCausingRuntimeException e) {
- return BlazeCommandResult.exitCode(e.getExitCode());
+ return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
+ } catch (AbruptExitRuntimeException e) {
+ return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
} catch (IOException e) {
- return BlazeCommandResult.exitCode(ExitCode.LOCAL_ENVIRONMENTAL_ERROR);
+ return createFailureResult(
+ "Cannot write info block: " + e.getMessage(),
+ ExitCode.LOCAL_ENVIRONMENTAL_ERROR,
+ FailureDetails.InfoCommand.Code.ALL_INFO_WRITE_FAILURE);
} catch (InterruptedException e) {
- return BlazeCommandResult.exitCode(ExitCode.INTERRUPTED);
+ return BlazeCommandResult.detailedExitCode(
+ createInterruptedExit("info interrupted", Interrupted.Code.INFO_ITEM));
}
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
}
+ private static DetailedExitCode createInterruptedExit(
+ String message, Interrupted.Code detailedCode) {
+ return DetailedExitCode.of(
+ ExitCode.INTERRUPTED,
+ FailureDetail.newBuilder()
+ .setMessage(message)
+ .setInterrupted(Interrupted.newBuilder().setCode(detailedCode))
+ .build());
+ }
+
+ private static BlazeCommandResult createFailureResult(
+ String message, ExitCode exitCode, FailureDetails.InfoCommand.Code detailedCode) {
+ return BlazeCommandResult.detailedExitCode(
+ DetailedExitCode.of(
+ exitCode,
+ FailureDetail.newBuilder()
+ .setMessage(message)
+ .setInfoCommand(FailureDetails.InfoCommand.newBuilder().setCode(detailedCode))
+ .build()));
+ }
+
private static Map<String, InfoItem> getHardwiredInfoItemMap(
OptionsParsingResult commandOptions, String productName) {
List<InfoItem> hardwiredInfoItems =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 915f58a..604874d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -141,6 +141,7 @@
import com.google.devtools.build.lib.rules.repository.ResolvedHashesFunction;
import com.google.devtools.build.lib.runtime.KeepGoingOption;
import com.google.devtools.build.lib.server.FailureDetails;
+import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.FileDirtinessChecker;
@@ -2143,7 +2144,8 @@
EvaluationResult<SkyValue> evaluationResult =
evaluateSkyKeys(eventHandler, ImmutableSet.of(platformMappingKey));
if (evaluationResult.hasError()) {
- throw new InvalidConfigurationException(evaluationResult.getError().getException());
+ throw new InvalidConfigurationException(
+ Code.PLATFORM_MAPPING_EVALUATION_FAILURE, evaluationResult.getError().getException());
}
return (PlatformMappingValue) evaluationResult.get(platformMappingKey);
}
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index 5954d55..51ecfe5 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -105,6 +105,8 @@
Spawn spawn = 123;
GrpcServer grpc_server = 124;
CanonicalizeFlags canonicalize_flags = 125;
+ BuildConfiguration build_configuration = 126;
+ InfoCommand info_command = 127;
}
reserved 102; // For internal use
@@ -124,6 +126,7 @@
PACKAGE_LOADING_SYNC = 6 [(metadata) = { exit_code: 8 }];
EXECUTOR_COMPLETION = 7 [(metadata) = { exit_code: 8 }];
COMMAND_DISPATCH = 8 [(metadata) = { exit_code: 8 }];
+ INFO_ITEM = 9 [(metadata) = { exit_code: 8 }];
}
Code code = 1;
@@ -385,3 +388,34 @@
Code code = 1;
}
+
+// Failure modes described by this category pertain to the Bazel invocation
+// configuration consumed by Bazel's analysis phase. This category is not
+// intended as a grab-bag for all Bazel flag value constraint violations, which
+// instead generally belong in the category for the subsystem whose flag values
+// participate in the constraint.
+message BuildConfiguration {
+ // TODO(mschaller): many more codes should be added to represent different
+ // configuration-related failure modes. See InvalidConfigurationException.
+ enum Code {
+ BUILD_CONFIGURATION_UNKNOWN = 0 [(metadata) = { exit_code: 2 }];
+ PLATFORM_MAPPING_EVALUATION_FAILURE = 1 [(metadata) = { exit_code: 2 }];
+ }
+
+ Code code = 1;
+}
+
+message InfoCommand {
+ // The distinction between a failure to write a single info item and a failure
+ // to write them all seems sketchy. Why do they have different exit codes?
+ // This reflects current Bazel behavior, but deserves more thought.
+ enum Code {
+ INFO_COMMAND_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+ TOO_MANY_KEYS = 1 [(metadata) = { exit_code: 2 }];
+ KEY_NOT_RECOGNIZED = 2 [(metadata) = { exit_code: 2 }];
+ INFO_BLOCK_WRITE_FAILURE = 3 [(metadata) = { exit_code: 7 }];
+ ALL_INFO_WRITE_FAILURE = 4 [(metadata) = { exit_code: 36 }];
+ }
+
+ Code code = 1;
+}
\ No newline at end of file