BuildEventStreamer supports status INCOMPLETE (--nokeep_going) and INTERNAL (catastrophe) - Add and Report AbortReason.INCOMPLETE when the build is incomplete due to an earlier build failure (--nokeep_going). - Report AbortReason.INTERNAL when the build is incomplete due to a catastrophic failure. - If multiple BuildEventStreamer AbortReasons are reported then the last one wins (no change), but all reasons are reported the Aborted event description (new behavior). RELNOTES: none PiperOrigin-RevId: 248037389
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto index 5941665..00d2592 100644 --- a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto +++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
@@ -16,12 +16,12 @@ package build_event_stream; +import "src/main/protobuf/command_line.proto"; +import "src/main/protobuf/invocation_policy.proto"; + option java_package = "com.google.devtools.build.lib.buildeventstream"; option java_outer_classname = "BuildEventStreamProtos"; -import "src/main/protobuf/invocation_policy.proto"; -import "src/main/protobuf/command_line.proto"; - // Identifier for a build event. It is deliberately structured to also provide // information about which build target etc the event is related to. // @@ -49,13 +49,11 @@ // Identifier of an event indicating the beginning of a build; this will // normally be the first event. - message BuildStartedId { - } + message BuildStartedId {} // Identifier on an event indicating the original commandline received by // the bazel server. - message UnstructuredCommandLineId { - } + message UnstructuredCommandLineId {} // Identifier on an event describing the commandline received by Bazel. message StructuredCommandLineId { @@ -67,13 +65,11 @@ } // Identifier of an event indicating the workspace status. - message WorkspaceStatusId { - } + message WorkspaceStatusId {} // Identifier on an event reporting on the options included in the command // line, both explicitly and implicitly. - message OptionsParsedId { - } + message OptionsParsedId {} // Identifier of an event reporting that an external resource was fetched // from. @@ -180,18 +176,15 @@ } // Identifier of the BuildFinished event, indicating the end of a build. - message BuildFinishedId { - } + message BuildFinishedId {} // Identifier of an event providing additional logs/statistics after // completion of the build. - message BuildToolLogsId { - } + message BuildToolLogsId {} // Identifier of an event providing build metrics after completion // of the build. - message BuildMetricsId { - } + message BuildMetricsId {} oneof id { UnknownBuildEventId unknown = 1; @@ -266,6 +259,10 @@ // Target build was skipped (e.g. due to incompatible CPU constraints). SKIPPED = 7; + + // Build incomplete due to an earlier build failure (e.g. --keep_going was + // set to false causing the build be ended upon failure). + INCOMPLETE = 10; } AbortReason reason = 1; @@ -357,8 +354,7 @@ // The main information is in the chaining part: the id will contain the // target pattern that was expanded and the children id will contain the // target or target pattern it was expanded to. -message PatternExpanded { -} +message PatternExpanded {} // Enumeration type characterizing the size of a test, as specified by the // test rule. @@ -499,7 +495,7 @@ REMOTE_FAILURE = 6; FAILED_TO_BUILD = 7; TOOL_HALTED_BEFORE_TESTING = 8; -}; +} // Payload on events reporting about individual test action. message TestResult { @@ -695,5 +691,5 @@ BuildFinished finished = 14; BuildToolLogs build_tool_logs = 23; BuildMetrics build_metrics = 24; - }; + } }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java index 742aa2b..4dbef12 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java
@@ -40,6 +40,7 @@ import com.google.devtools.build.lib.buildeventstream.BuildCompletingEvent; import com.google.devtools.build.lib.buildeventstream.BuildEvent; import com.google.devtools.build.lib.buildeventstream.BuildEventId; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason; import com.google.devtools.build.lib.buildeventstream.BuildEventTransport; import com.google.devtools.build.lib.buildeventstream.BuildEventWithConfiguration; @@ -66,6 +67,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.BiConsumer; @@ -100,7 +102,10 @@ private final CountingArtifactGroupNamer artifactGroupNamer; private OutErrProvider outErrProvider; - private volatile AbortReason abortReason = AbortReason.UNKNOWN; + + @GuardedBy("this") + private final Set<AbortReason> abortReasons = new LinkedHashSet<>(); + // Will be set to true if the build was invoked through "bazel test" or "bazel coverage". private boolean isTestCommand; @@ -293,7 +298,11 @@ .map(BuildEvent::getEventId) .collect(ImmutableSet.<BuildEventId>toImmutableSet())); buildEvent( - new AbortedEvent(BuildEventId.buildStartedId(), children.build(), abortReason, "")); + new AbortedEvent( + BuildEventId.buildStartedId(), + children.build(), + getLastAbortReason(), + getAbortReasonDetails())); } } @@ -301,7 +310,7 @@ private synchronized void clearPendingEvents() { while (!pendingEvents.isEmpty()) { BuildEventId id = pendingEvents.keySet().iterator().next(); - buildEvent(new AbortedEvent(id, abortReason, "")); + buildEvent(new AbortedEvent(id, getLastAbortReason(), getAbortReasonDetails())); } } @@ -319,7 +328,7 @@ } for (BuildEventId id : ids) { if (!dontclear.contains(id)) { - post(new AbortedEvent(id, abortReason, "")); + post(new AbortedEvent(id, getLastAbortReason(), getAbortReasonDetails())); } } } @@ -339,7 +348,7 @@ } closed = true; if (reason != null) { - abortReason = reason; + addAbortReason(reason); } if (finalEventsToCome == null) { @@ -403,17 +412,17 @@ @Subscribe public void buildInterrupted(BuildInterruptedEvent event) { - abortReason = AbortReason.USER_INTERRUPTED; + addAbortReason(AbortReason.USER_INTERRUPTED); } @Subscribe public void noAnalyze(NoAnalyzeEvent event) { - abortReason = AbortReason.NO_ANALYZE; + addAbortReason(AbortReason.NO_ANALYZE); } @Subscribe public void noExecution(NoExecutionEvent event) { - abortReason = AbortReason.NO_BUILD; + addAbortReason(AbortReason.NO_BUILD); } @Subscribe @@ -469,8 +478,15 @@ buildEvent(freedEvent); } - if (event instanceof BuildCompleteEvent && isCrash((BuildCompleteEvent) event)) { - abortReason = AbortReason.INTERNAL; + if (event instanceof BuildCompleteEvent) { + BuildCompleteEvent buildCompleteEvent = (BuildCompleteEvent) event; + if (isCrash(buildCompleteEvent)) { + addAbortReason(AbortReason.INTERNAL); + } else if (isCatastrophe(buildCompleteEvent)) { + addAbortReason(AbortReason.INTERNAL); + } else if (isIncomplete(buildCompleteEvent)) { + addAbortReason(AbortReason.INCOMPLETE); + } } if (event instanceof BuildCompletingEvent) { @@ -492,6 +508,16 @@ return event.getResult().getUnhandledThrowable() != null; } + private static boolean isCatastrophe(BuildCompleteEvent event) { + return event.getResult().wasCatastrophe(); + } + + private boolean isIncomplete(BuildCompleteEvent event) { + return !event.getResult().getSuccess() + && !event.getResult().wasCatastrophe() + && event.getResult().getStopOnFirstFailure(); + } + private synchronized BuildEvent flushStdoutStderrEvent(String out, String err) { BuildEvent updateEvent = ProgressEvent.progressUpdate(progressCount, out, err); progressCount++; @@ -713,6 +739,34 @@ return halfCloseFuturesMap; } + /** + * Stores the abort reason for later reporting on BEP pending events. In case of multiple abort + * reasons: + * + * <ul> + * <li>Only the most recent reason will be reported as the main AbortReason in BEP. + * <li>All previous AbortReason will appear in Aborted#getDescription message. + * </ul> + */ + private synchronized void addAbortReason(BuildEventStreamProtos.Aborted.AbortReason reason) { + abortReasons.add(reason); + } + + /** @return the most recent AbortReason or UNKNOWN if no value was set. */ + private synchronized AbortReason getLastAbortReason() { + return abortReasons.isEmpty() ? AbortReason.UNKNOWN : Iterables.getLast(abortReasons); + } + + /** + * @return Detailed message explaining the most recent AbortReason (and possibly previous + * reasons). + */ + private synchronized String getAbortReasonDetails() { + return abortReasons.size() <= 1 + ? "" + : String.format("Multiple abort reasons reported: %s", abortReasons); + } + /** A builder for {@link BuildEventStreamer}. */ public static class Builder { private Set<BuildEventTransport> buildEventTransports;
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java index 645f89e..6da3ca7 100644 --- a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java +++ b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
@@ -48,6 +48,7 @@ import com.google.devtools.build.lib.buildeventstream.BuildEventId; import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId; import com.google.devtools.build.lib.buildeventstream.BuildEventTransport; import com.google.devtools.build.lib.buildeventstream.BuildEventTransportClosedEvent; @@ -59,10 +60,12 @@ import com.google.devtools.build.lib.buildeventstream.transports.BuildEventStreamOptions; import com.google.devtools.build.lib.buildtool.BuildResult; import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent; +import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.NestedSetView; import com.google.devtools.build.lib.testutil.FoundationTestCase; +import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; @@ -79,6 +82,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; +import javax.annotation.Nullable; import org.apache.commons.lang.time.StopWatch; import org.junit.After; import org.junit.Before; @@ -1178,4 +1182,141 @@ assertThat(transportedEvents).contains(SUCCESSFUL_ACTION_EXECUTED_EVENT); assertThat(transportedEvents).contains(failedActionExecutedEvent); } + + @Test + public void testBuildIncomplete() { + BuildEventId buildEventId = testId("abort_expected"); + BuildEvent startEvent = + new GenericBuildEvent( + BuildEventId.buildStartedId(), + ImmutableSet.of( + buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished())); + BuildCompleteEvent buildCompleteEvent = + buildCompleteEvent(ExitCode.BUILD_FAILURE, true, null, false); + + streamer.buildEvent(startEvent); + streamer.buildEvent(buildCompleteEvent); + streamer.close(); + + BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId); + assertThat(aborted).isNotNull(); + assertThat(aborted.hasAborted()).isNotNull(); + assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INCOMPLETE); + assertThat(aborted.getAborted().getDescription()).isEmpty(); + } + + @Test + public void testBuildCrash() { + BuildEventId buildEventId = testId("abort_expected"); + BuildEvent startEvent = + new GenericBuildEvent( + BuildEventId.buildStartedId(), + ImmutableSet.of( + buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished())); + BuildCompleteEvent buildCompleteEvent = + buildCompleteEvent(ExitCode.BUILD_FAILURE, true, new RuntimeException(), false); + + streamer.buildEvent(startEvent); + streamer.buildEvent(buildCompleteEvent); + streamer.close(); + + BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId); + assertThat(aborted).isNotNull(); + assertThat(aborted.hasAborted()).isNotNull(); + assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL); + assertThat(aborted.getAborted().getDescription()).isEmpty(); + } + + @Test + public void testBuildCatastrophe() { + BuildEventId buildEventId = testId("abort_expected"); + BuildEvent startEvent = + new GenericBuildEvent( + BuildEventId.buildStartedId(), + ImmutableSet.of( + buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished())); + BuildCompleteEvent buildCompleteEvent = + buildCompleteEvent(ExitCode.BUILD_FAILURE, true, null, true); + + streamer.buildEvent(startEvent); + streamer.buildEvent(buildCompleteEvent); + streamer.close(); + + BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId); + assertThat(aborted).isNotNull(); + assertThat(aborted.hasAborted()).isNotNull(); + assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL); + assertThat(aborted.getAborted().getDescription()).isEmpty(); + } + + @Test + public void testStreamAbortedWithTimeout() { + BuildEventId buildEventId = testId("abort_expected"); + BuildEvent startEvent = + new GenericBuildEvent( + BuildEventId.buildStartedId(), + ImmutableSet.of( + buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished())); + + streamer.buildEvent(startEvent); + streamer.close(AbortReason.TIME_OUT); + + BuildEventStreamProtos.BuildEvent aborted0 = getBepEvent(buildEventId); + assertThat(aborted0).isNotNull(); + assertThat(aborted0.hasAborted()).isNotNull(); + assertThat(aborted0.getAborted().getReason()).isEqualTo(AbortReason.TIME_OUT); + assertThat(aborted0.getAborted().getDescription()).isEmpty(); + + BuildEventStreamProtos.BuildEvent aborted1 = getBepEvent(BuildEventId.buildFinished()); + assertThat(aborted1).isNotNull(); + assertThat(aborted1.hasAborted()).isNotNull(); + assertThat(aborted1.getAborted().getReason()).isEqualTo(AbortReason.TIME_OUT); + assertThat(aborted1.getAborted().getDescription()).isEmpty(); + } + + @Test + public void testBuildFailureMultipleReasons() { + BuildEventId buildEventId = testId("abort_expected"); + BuildEvent startEvent = + new GenericBuildEvent( + BuildEventId.buildStartedId(), + ImmutableSet.of( + buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished())); + BuildCompleteEvent buildCompleteEvent = + buildCompleteEvent(ExitCode.BUILD_FAILURE, false, new RuntimeException(), false); + + streamer.buildEvent(startEvent); + streamer.noAnalyze(new NoAnalyzeEvent()); + streamer.buildEvent(buildCompleteEvent); + streamer.close(); + + BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId); + assertThat(aborted).isNotNull(); + assertThat(aborted.hasAborted()).isNotNull(); + assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL); + assertThat(aborted.getAborted().getDescription()) + .isEqualTo("Multiple abort reasons reported: [NO_ANALYZE, INTERNAL]"); + } + + @Nullable + private BuildEventStreamProtos.BuildEvent getBepEvent(BuildEventId buildEventId) { + return transport.getEventProtos().stream() + .filter(e -> e.getId().equals(buildEventId.asStreamProto())) + .findFirst() + .orElse(null); + } + + private BuildCompleteEvent buildCompleteEvent( + ExitCode exitCode, boolean stopOnFailure, Throwable crash, boolean catastrophe) { + BuildResult result = new BuildResult(0); + result.setExitCondition(exitCode); + result.setStopOnFirstFailure(stopOnFailure); + if (catastrophe) { + result.setCatastrophe(); + } + if (crash != null) { + result.setUnhandledThrowable(crash); + } + return new BuildCompleteEvent(result); + } }