Provide an early display of build failures (fixes #361)

Also fix ordering of messages when projects are skipped at the end
This commit is contained in:
Guillaume Nodet
2021-09-01 15:55:38 +02:00
committed by GitHub
parent c98813f433
commit 230edac5ae
7 changed files with 173 additions and 17 deletions

View File

@@ -55,6 +55,7 @@ public abstract class Message {
public static final int TRANSFER_CORRUPTED = 21;
public static final int TRANSFER_SUCCEEDED = 22;
public static final int TRANSFER_FAILED = 23;
public static final int EXECUTION_FAILURE = 24;
final int type;
@@ -103,6 +104,8 @@ public abstract class Message {
case TRANSFER_SUCCEEDED:
case TRANSFER_FAILED:
return TransferEvent.read(type, input);
case EXECUTION_FAILURE:
return ExecutionFailureEvent.read(input);
}
throw new IllegalStateException("Unexpected message type: " + type);
}
@@ -821,6 +824,56 @@ public abstract class Message {
}
}
public static class ExecutionFailureEvent extends Message {
final String projectId;
final boolean halted;
final String exception;
private ExecutionFailureEvent(String projectId, boolean halted, String exception) {
super(EXECUTION_FAILURE);
this.projectId = projectId;
this.halted = halted;
this.exception = exception;
}
public String getProjectId() {
return projectId;
}
public boolean isHalted() {
return halted;
}
public String getException() {
return exception;
}
@Override
public String toString() {
return "ExecutionFailure{" +
"projectId='" + projectId + '\'' +
", halted=" + halted +
", exception='" + exception + '\'' +
'}';
}
@Override
public void write(DataOutputStream output) throws IOException {
super.write(output);
writeUTF(output, projectId);
output.writeBoolean(halted);
writeUTF(output, exception);
}
public static ExecutionFailureEvent read(DataInputStream input) throws IOException {
String projectId = readUTF(input);
boolean halted = input.readBoolean();
String exception = readUTF(input);
return new ExecutionFailureEvent(projectId, halted, exception);
}
}
public static class TransferEvent extends Message {
public static final int INITIATED = 0;
@@ -982,6 +1035,10 @@ public abstract class Message {
return new StringMessage(PROJECT_STOPPED, projectId);
}
public static ExecutionFailureEvent executionFailure(String projectId, boolean halted, String exception) {
return new ExecutionFailureEvent(projectId, halted, exception);
}
public static Message mojoStarted(String artifactId, String pluginGroupId, String pluginArtifactId,
String pluginVersion, String mojo, String executionId) {
return new MojoStartedEvent(artifactId, pluginGroupId, pluginArtifactId, pluginVersion, mojo, executionId);

View File

@@ -46,6 +46,7 @@ import org.jline.utils.Display;
import org.mvndaemon.mvnd.common.Message;
import org.mvndaemon.mvnd.common.Message.BuildException;
import org.mvndaemon.mvnd.common.Message.BuildStarted;
import org.mvndaemon.mvnd.common.Message.ExecutionFailureEvent;
import org.mvndaemon.mvnd.common.Message.MojoStartedEvent;
import org.mvndaemon.mvnd.common.Message.ProjectEvent;
import org.mvndaemon.mvnd.common.Message.StringMessage;
@@ -93,6 +94,7 @@ public class TerminalOutput implements ClientOutput {
private final Terminal.SignalHandler previousIntHandler;
private final Display display;
private final Map<String, Map<String, TransferEvent>> transfers = new LinkedHashMap<>();
private final ArrayList<ExecutionFailureEvent> failures = new ArrayList<>();
private final LinkedHashMap<String, Project> projects = new LinkedHashMap<>();
private final ClientLog log;
private final Thread reader;
@@ -409,6 +411,11 @@ public class TerminalOutput implements ClientOutput {
.remove(te.getResourceName());
break;
}
case Message.EXECUTION_FAILURE: {
final ExecutionFailureEvent efe = (ExecutionFailureEvent) entry;
failures.add(efe);
break;
}
default:
throw new IllegalStateException("Unexpected message " + entry);
}
@@ -521,12 +528,18 @@ public class TerminalOutput implements ClientOutput {
// so keep one more line empty at the end
dispLines--;
AttributedString globalTransfer = formatTransfers("");
dispLines -= globalTransfer != null ? 1 : 0;
addStatusLine(lines, dispLines, projectsCount);
AttributedString globalFailure = formatFailures();
if (globalFailure != null) {
lines.add(globalFailure);
dispLines--;
}
AttributedString globalTransfer = formatTransfers("");
if (globalTransfer != null) {
lines.add(globalTransfer);
dispLines--;
}
if (projectsCount <= dispLines) {
@@ -561,6 +574,43 @@ public class TerminalOutput implements ClientOutput {
display.update(trimmed, -1);
}
private AttributedString formatFailures() {
if (failures.isEmpty()) {
return null;
}
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.RED).bold());
if (failures.stream().anyMatch(ExecutionFailureEvent::isHalted)) {
asb.append("ABORTING ");
}
asb.append("FAILURE: ");
asb.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
if (failures.size() == 1) {
ExecutionFailureEvent efe = failures.iterator().next();
asb.append(efe.getProjectId());
String exception = efe.getException();
if (exception != null) {
if (exception.startsWith("org.apache.maven.lifecycle.LifecycleExecutionException: ")) {
exception = exception
.substring("org.apache.maven.lifecycle.LifecycleExecutionException: ".length());
}
asb.append(": ").append(exception);
}
} else {
asb.append(String.valueOf(failures.size())).append(" projects failed: ");
asb.append(failures.stream().map(ExecutionFailureEvent::getProjectId).collect(Collectors.joining(", ")));
}
AttributedString as = asb.toAttributedString();
if (as.columnLength() >= getTerminalWidth() - 1) {
asb = new AttributedStringBuilder();
asb.append(as.columnSubSequence(0, getTerminalWidth() - 2));
asb.style(AttributedStyle.DEFAULT);
asb.append("");
as = asb.toAttributedString();
}
return as;
}
private AttributedString formatTransfers(String projectId) {
Collection<TransferEvent> transfers = this.transfers.getOrDefault(projectId, Collections.emptyMap()).values();
if (transfers.isEmpty()) {

View File

@@ -66,7 +66,6 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.exception.DefaultExceptionHandler;
import org.apache.maven.exception.ExceptionHandler;
import org.apache.maven.exception.ExceptionSummary;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulationException;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
@@ -177,6 +176,8 @@ public class DaemonMavenCli {
private final Map<String, ConfigurationProcessor> configurationProcessors;
private final LoggingExecutionListener executionListener;
/** Non-volatile, assuming that it is accessed only from the main thread */
private BuildEventListener buildEventListener = BuildEventListener.dummy();
@@ -197,6 +198,7 @@ public class DaemonMavenCli {
configurationProcessors = container.lookupMap(ConfigurationProcessor.class);
toolchainsBuilder = container.lookup(ToolchainsBuilder.class);
dispatcher = (DefaultSecDispatcher) container.lookup(SecDispatcher.class, "maven");
executionListener = container.lookup(LoggingExecutionListener.class);
}
@@ -579,8 +581,10 @@ public class DaemonMavenCli {
final EventSpyDispatcher eventSpyDispatcher = container.lookup(EventSpyDispatcher.class);
properties(cliRequest);
configure(cliRequest, eventSpyDispatcher, configurationProcessors);
LoggingExecutionListener executionListener = container.lookup(LoggingExecutionListener.class);
populateRequest(cliRequest, cliRequest.request, slf4jLogger, eventSpyDispatcher,
container.lookup(ModelProcessor.class), createTransferListener(cliRequest), buildEventListener);
container.lookup(ModelProcessor.class), createTransferListener(cliRequest), buildEventListener,
executionListener);
executionRequestPopulator.populateDefaults(cliRequest.request);
BootstrapCoreExtensionManager resolver = container.lookup(BootstrapCoreExtensionManager.class);
return Collections
@@ -1110,7 +1114,7 @@ public class DaemonMavenCli {
private void populateRequest(CliRequest cliRequest) {
populateRequest(cliRequest, cliRequest.request, slf4jLogger, eventSpyDispatcher, modelProcessor,
createTransferListener(cliRequest), buildEventListener);
createTransferListener(cliRequest), buildEventListener, executionListener);
}
private static void populateRequest(
@@ -1120,7 +1124,8 @@ public class DaemonMavenCli {
EventSpyDispatcher eventSpyDispatcher,
ModelProcessor modelProcessor,
TransferListener transferListener,
BuildEventListener buildEventListener) {
BuildEventListener buildEventListener,
LoggingExecutionListener executionListener) {
CommandLine commandLine = cliRequest.commandLine;
String workingDirectory = cliRequest.workingDirectory;
boolean showErrors = cliRequest.showErrors;
@@ -1219,12 +1224,10 @@ public class DaemonMavenCli {
}
}
ExecutionListener executionListener = new ExecutionEventLogger();
if (eventSpyDispatcher != null) {
executionListener = new LoggingExecutionListener(
eventSpyDispatcher.chainListener(executionListener),
buildEventListener);
}
ExecutionEventLogger executionEventLogger = new ExecutionEventLogger();
executionListener.init(
eventSpyDispatcher.chainListener(executionEventLogger),
buildEventListener);
String alternatePomFile = null;
if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE)) {

View File

@@ -92,6 +92,8 @@ public class SmartBuilder implements Builder {
ProjectBuildList projectBuilds, final List<TaskSegment> taskSegments,
ReactorBuildStatus reactorBuildStatus) throws ExecutionException, InterruptedException {
session.getRepositorySession().getData().set(ReactorBuildStatus.class, reactorBuildStatus);
DependencyGraph<MavenProject> graph = (DependencyGraph<MavenProject>) session.getRequest().getData()
.get(DependencyGraph.class.getName());

View File

@@ -101,6 +101,11 @@ public class ClientDispatcher extends BuildEventListener {
queue.add(Message.projectStopped(projectId));
}
public void executionFailure(String projectId, boolean halted, String exception) {
projects.put(projectId, Boolean.FALSE);
queue.add(Message.executionFailure(projectId, halted, exception));
}
public void mojoStarted(ExecutionEvent event) {
final MojoExecution execution = event.getMojoExecution();
queue.add(Message.mojoStarted(

View File

@@ -36,6 +36,9 @@ public abstract class BuildEventListener {
public void projectFinished(String projectId) {
}
public void executionFailure(String projectId, boolean halted, String exception) {
}
public void mojoStarted(ExecutionEvent event) {
}
@@ -70,6 +73,8 @@ public abstract class BuildEventListener {
public abstract void projectFinished(String projectId);
public abstract void executionFailure(String projectId, boolean halted, String exception);
public abstract void mojoStarted(ExecutionEvent event);
public abstract void finish(int exitCode) throws Exception;

View File

@@ -15,19 +15,52 @@
*/
package org.mvndaemon.mvnd.logging.smart;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectExecutionEvent;
import org.apache.maven.execution.ProjectExecutionListener;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
import org.eclipse.sisu.Typed;
public class LoggingExecutionListener implements ExecutionListener {
@Singleton
@Named
@Typed({ LoggingExecutionListener.class, ExecutionListener.class, ProjectExecutionListener.class })
public class LoggingExecutionListener implements ExecutionListener, ProjectExecutionListener {
private final ExecutionListener delegate;
private final BuildEventListener buildEventListener;
private ExecutionListener delegate;
private BuildEventListener buildEventListener;
public LoggingExecutionListener(ExecutionListener delegate, BuildEventListener buildEventListener) {
public void init(ExecutionListener delegate, BuildEventListener buildEventListener) {
this.delegate = delegate;
this.buildEventListener = buildEventListener;
}
@Override
public void beforeProjectExecution(ProjectExecutionEvent projectExecutionEvent) throws LifecycleExecutionException {
}
@Override
public void beforeProjectLifecycleExecution(ProjectExecutionEvent projectExecutionEvent)
throws LifecycleExecutionException {
}
@Override
public void afterProjectExecutionSuccess(ProjectExecutionEvent projectExecutionEvent) throws LifecycleExecutionException {
}
@Override
public void afterProjectExecutionFailure(ProjectExecutionEvent projectExecutionEvent) {
MavenSession session = projectExecutionEvent.getSession();
ReactorBuildStatus status = (ReactorBuildStatus) session.getRepositorySession().getData().get(ReactorBuildStatus.class);
Throwable cause = projectExecutionEvent.getCause();
buildEventListener.executionFailure(projectExecutionEvent.getProject().getArtifactId(),
status.isHalted(), cause != null ? cause.toString() : null);
}
@Override
public void projectDiscoveryStarted(ExecutionEvent event) {
setMdc(event);
@@ -71,6 +104,7 @@ public class LoggingExecutionListener implements ExecutionListener {
@Override
public void projectSkipped(ExecutionEvent event) {
setMdc(event);
buildEventListener.projectStarted(event.getProject().getArtifactId());
delegate.projectSkipped(event);
buildEventListener.projectFinished(event.getProject().getArtifactId());
}