blob: cf58d779a5e7ccca92867c0c5464478406ad6696 [file] [log] [blame]
mschallerd8a121e2020-02-28 16:41:11 -08001// Copyright 2020 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.util;
16
mschallerbb4d4cc2020-03-30 10:54:13 -070017import static com.google.common.base.Preconditions.checkArgument;
mschaller854bb902020-03-03 10:56:14 -080018import static com.google.common.base.Preconditions.checkNotNull;
19
mschallerbb4d4cc2020-03-30 10:54:13 -070020import com.google.devtools.build.lib.server.FailureDetails;
mschallerd8a121e2020-02-28 16:41:11 -080021import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
mschallerbb4d4cc2020-03-30 10:54:13 -070022import com.google.protobuf.Descriptors.EnumValueDescriptor;
23import com.google.protobuf.Descriptors.FieldDescriptor;
24import com.google.protobuf.MessageOrBuilder;
janakr5d667212020-04-03 10:45:41 -070025import java.util.Comparator;
mschallerbb4d4cc2020-03-30 10:54:13 -070026import java.util.Map;
janakr86e95382020-04-10 09:00:47 -070027import java.util.Objects;
mschallerd8a121e2020-02-28 16:41:11 -080028import javax.annotation.Nullable;
29
mschaller859c9ac2020-09-25 16:09:19 -070030/** An {@link ExitCode} that has a {@link FailureDetail} unless it's {@link ExitCode#SUCCESS}. */
mschallerd8a121e2020-02-28 16:41:11 -080031public class DetailedExitCode {
32 private final ExitCode exitCode;
33 @Nullable private final FailureDetail failureDetail;
34
35 private DetailedExitCode(ExitCode exitCode, @Nullable FailureDetail failureDetail) {
36 this.exitCode = exitCode;
37 this.failureDetail = failureDetail;
38 }
39
40 public ExitCode getExitCode() {
41 return exitCode;
42 }
43
mschallerbb4d4cc2020-03-30 10:54:13 -070044 /** Returns the registered {@link ExitCode} associated with a {@link FailureDetail} message. */
janakr40f2a122020-10-06 08:53:15 -070045 public static ExitCode getExitCode(FailureDetail failureDetail) {
mschallerbb4d4cc2020-03-30 10:54:13 -070046 // TODO(mschaller): Consider specializing for unregistered exit codes here, if absolutely
47 // necessary.
48 int numericExitCode = getNumericExitCode(failureDetail);
49 return checkNotNull(
50 ExitCode.forCode(numericExitCode), "No ExitCode for numericExitCode %s", numericExitCode);
51 }
52
mschallerd8a121e2020-02-28 16:41:11 -080053 @Nullable
54 public FailureDetail getFailureDetail() {
55 return failureDetail;
56 }
57
mschaller854bb902020-03-03 10:56:14 -080058 public boolean isSuccess() {
59 return exitCode.equals(ExitCode.SUCCESS);
60 }
61
mschaller07cbdce2020-05-12 22:45:26 -070062 /** Returns a {@link DetailedExitCode} specifying success (i.e. exit code 0). */
63 public static DetailedExitCode success() {
64 return new DetailedExitCode(ExitCode.SUCCESS, null);
65 }
66
mschaller854bb902020-03-03 10:56:14 -080067 /**
mschaller854bb902020-03-03 10:56:14 -080068 * Returns a {@link DetailedExitCode} combining the provided {@link FailureDetail} and {@link
69 * ExitCode}.
70 *
71 * <p>This method exists in order to allow for the introduction of new {@link
72 * FailureDetail)-handling code infrastructure without requiring any simultaneous change in exit
73 * code behavior.
74 *
mschaller859c9ac2020-09-25 16:09:19 -070075 * <p>Unless contrained by backwards-compatibility needs, callers should use {@link
76 * #of(FailureDetail)} instead.
mschaller854bb902020-03-03 10:56:14 -080077 */
mschaller854bb902020-03-03 10:56:14 -080078 public static DetailedExitCode of(ExitCode exitCode, FailureDetail failureDetail) {
79 return new DetailedExitCode(checkNotNull(exitCode), checkNotNull(failureDetail));
mschallerd8a121e2020-02-28 16:41:11 -080080 }
81
82 /**
83 * Returns a {@link DetailedExitCode} whose {@link ExitCode} is chosen referencing {@link
84 * FailureDetail}'s metadata.
85 */
86 public static DetailedExitCode of(FailureDetail failureDetail) {
mschaller859c9ac2020-09-25 16:09:19 -070087 return new DetailedExitCode(getExitCode(failureDetail), checkNotNull(failureDetail));
mschallerd8a121e2020-02-28 16:41:11 -080088 }
89
90 @Override
janakr86e95382020-04-10 09:00:47 -070091 public int hashCode() {
92 return Objects.hash(exitCode, failureDetail);
93 }
94
95 @Override
96 public boolean equals(Object obj) {
97 if (obj == this) {
98 return true;
99 }
100 if (!(obj instanceof DetailedExitCode)) {
101 return false;
102 }
103 DetailedExitCode that = (DetailedExitCode) obj;
104 return this.exitCode.equals(that.exitCode)
105 && Objects.equals(this.failureDetail, that.failureDetail);
106 }
107
108 @Override
mschallerd8a121e2020-02-28 16:41:11 -0800109 public String toString() {
110 return String.format(
111 "DetailedExitCode{exitCode=%s, failureDetail=%s}", exitCode, failureDetail);
112 }
mschallerbb4d4cc2020-03-30 10:54:13 -0700113
114 /** Returns the numeric exit code associated with a {@link FailureDetail} message. */
Googler2b7c2052022-01-21 06:00:02 -0800115 public static int getNumericExitCode(FailureDetail failureDetail) {
mschallerbb4d4cc2020-03-30 10:54:13 -0700116 MessageOrBuilder categoryMsg = getCategorySubmessage(failureDetail);
117 EnumValueDescriptor subcategoryDescriptor =
118 getSubcategoryDescriptor(failureDetail, categoryMsg);
119 return getNumericExitCode(subcategoryDescriptor);
120 }
121
122 /**
123 * Returns the numeric exit code associated with a {@link FailureDetail} submessage's subcategory
124 * enum value.
125 */
janakr40f2a122020-10-06 08:53:15 -0700126 public static int getNumericExitCode(EnumValueDescriptor subcategoryDescriptor) {
mschallerbb4d4cc2020-03-30 10:54:13 -0700127 checkArgument(
128 subcategoryDescriptor.getOptions().hasExtension(FailureDetails.metadata),
129 "Enum value %s has no FailureDetails.metadata",
130 subcategoryDescriptor);
131 return subcategoryDescriptor.getOptions().getExtension(FailureDetails.metadata).getExitCode();
132 }
133
134 /**
135 * Returns the category submessage, i.e. the message in {@link FailureDetail}'s oneof. Throws if
136 * none of those fields are set.
137 */
138 private static MessageOrBuilder getCategorySubmessage(FailureDetail failureDetail) {
139 MessageOrBuilder categoryMsg = null;
140 for (Map.Entry<FieldDescriptor, Object> entry : failureDetail.getAllFields().entrySet()) {
141 FieldDescriptor fieldDescriptor = entry.getKey();
142 if (isCategoryField(fieldDescriptor)) {
143 categoryMsg = (MessageOrBuilder) entry.getValue();
144 break;
145 }
146 }
147 return checkNotNull(
148 categoryMsg, "FailureDetail missing category submessage: %s", failureDetail);
149 }
150
151 /**
152 * Returns whether the {@link FieldDescriptor} describes a field in {@link FailureDetail}'s oneof.
153 *
154 * <p>Uses the field number criteria described in failure_details.proto.
155 */
156 private static boolean isCategoryField(FieldDescriptor fieldDescriptor) {
157 int fieldNum = fieldDescriptor.getNumber();
158 return 100 < fieldNum && fieldNum <= 10_000;
159 }
160
161 /**
162 * Returns the enum value descriptor for the enum field with field number 1 in the {@link
163 * FailureDetail}'s category submessage.
164 */
165 private static EnumValueDescriptor getSubcategoryDescriptor(
166 FailureDetail failureDetail, MessageOrBuilder categoryMsg) {
167 FieldDescriptor fieldNumberOne = categoryMsg.getDescriptorForType().findFieldByNumber(1);
168 checkNotNull(
169 fieldNumberOne, "FailureDetail category submessage has no field #1: %s", failureDetail);
170 Object fieldNumberOneVal = categoryMsg.getField(fieldNumberOne);
171 checkArgument(
172 fieldNumberOneVal instanceof EnumValueDescriptor,
173 "FailureDetail category submessage has non-enum field #1: %s",
174 failureDetail);
175 return (EnumValueDescriptor) fieldNumberOneVal;
176 }
janakr5d667212020-04-03 10:45:41 -0700177
178 /**
179 * A comparator to determine the reporting priority of {@link DetailedExitCode}.
180 *
181 * <p>Priority: infrastructure exit codes > non-infrastructure exit codes > null exit codes, with
182 * exit codes that contain failure details taking priority within each class.
183 */
184 public static class DetailedExitCodeComparator implements Comparator<DetailedExitCode> {
185 public static final DetailedExitCodeComparator INSTANCE = new DetailedExitCodeComparator();
186
187 private DetailedExitCodeComparator() {}
188
189 @Nullable
190 public static DetailedExitCode chooseMoreImportantWithFirstIfTie(
191 @Nullable DetailedExitCode first, @Nullable DetailedExitCode second) {
192 return INSTANCE.compare(first, second) >= 0 ? first : second;
193 }
194
195 @Override
196 public int compare(DetailedExitCode c1, DetailedExitCode c2) {
197 // returns POSITIVE result when the priority of c1 is HIGHER than the priority of c2
198 return getPriority(c1) - getPriority(c2);
199 }
200
201 private static int getPriority(DetailedExitCode code) {
202 if (code == null) {
203 return 0;
204 } else {
205 int codeClass = code.getExitCode().isInfrastructureFailure() ? 4 : 2;
206 return codeClass + (code.getFailureDetail() != null ? 1 : 0);
207 }
208 }
209 }
mschallerd8a121e2020-02-28 16:41:11 -0800210}