1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cli.event;
20
21 import java.io.File;
22 import java.nio.file.Path;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Objects;
26
27 import org.apache.maven.execution.AbstractExecutionListener;
28 import org.apache.maven.execution.BuildFailure;
29 import org.apache.maven.execution.BuildSuccess;
30 import org.apache.maven.execution.BuildSummary;
31 import org.apache.maven.execution.ExecutionEvent;
32 import org.apache.maven.execution.MavenExecutionResult;
33 import org.apache.maven.execution.MavenSession;
34 import org.apache.maven.jline.MessageUtils;
35 import org.apache.maven.message.MessageBuilder;
36 import org.apache.maven.plugin.MojoExecution;
37 import org.apache.maven.plugin.descriptor.MojoDescriptor;
38 import org.apache.maven.project.MavenProject;
39 import org.codehaus.plexus.util.StringUtils;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import static org.apache.maven.cli.CLIReportingUtils.formatDuration;
44 import static org.apache.maven.cli.CLIReportingUtils.formatTimestamp;
45 import static org.apache.maven.jline.MessageUtils.builder;
46
47
48
49
50
51
52 public class ExecutionEventLogger extends AbstractExecutionListener {
53 private final Logger logger;
54
55 private static final int LINE_LENGTH = 72;
56 private static final int MAX_PADDED_BUILD_TIME_DURATION_LENGTH = 9;
57 private static final int MAX_PROJECT_NAME_LENGTH = 52;
58
59 private int totalProjects;
60 private volatile int currentVisitedProjectCount;
61
62 public ExecutionEventLogger() {
63 logger = LoggerFactory.getLogger(ExecutionEventLogger.class);
64 }
65
66
67 public ExecutionEventLogger(Logger logger) {
68 this.logger = Objects.requireNonNull(logger, "logger cannot be null");
69 }
70
71 private static String chars(char c, int count) {
72 StringBuilder buffer = new StringBuilder(count);
73
74 for (int i = count; i > 0; i--) {
75 buffer.append(c);
76 }
77
78 return buffer.toString();
79 }
80
81 private void infoLine(char c) {
82 infoMain(chars(c, LINE_LENGTH));
83 }
84
85 private void infoMain(String msg) {
86 logger.info(builder().strong(msg).toString());
87 }
88
89 @Override
90 public void projectDiscoveryStarted(ExecutionEvent event) {
91 if (logger.isInfoEnabled()) {
92 logger.info("Scanning for projects...");
93 }
94 }
95
96 @Override
97 public void sessionStarted(ExecutionEvent event) {
98 if (logger.isInfoEnabled() && event.getSession().getProjects().size() > 1) {
99 infoLine('-');
100
101 infoMain("Reactor Build Order:");
102
103 logger.info("");
104
105 final List<MavenProject> projects = event.getSession().getProjects();
106 for (MavenProject project : projects) {
107 int len = LINE_LENGTH
108 - project.getName().length()
109 - project.getPackaging().length()
110 - 2;
111 logger.info("{}{}[{}]", project.getName(), chars(' ', (len > 0) ? len : 1), project.getPackaging());
112 }
113
114 totalProjects = projects.size();
115 }
116 }
117
118 @Override
119 public void sessionEnded(ExecutionEvent event) {
120 if (logger.isInfoEnabled()) {
121 if (event.getSession().getProjects().size() > 1) {
122 logReactorSummary(event.getSession());
123 }
124
125 logResult(event.getSession());
126
127 logStats(event.getSession());
128
129 infoLine('-');
130 }
131 }
132
133 private boolean isSingleVersionedReactor(MavenSession session) {
134 boolean result = true;
135
136 MavenProject topProject = session.getTopLevelProject();
137 List<MavenProject> sortedProjects = session.getProjectDependencyGraph().getSortedProjects();
138 for (MavenProject mavenProject : sortedProjects) {
139 if (!topProject.getVersion().equals(mavenProject.getVersion())) {
140 result = false;
141 break;
142 }
143 }
144
145 return result;
146 }
147
148 private void logReactorSummary(MavenSession session) {
149 boolean isSingleVersion = isSingleVersionedReactor(session);
150
151 infoLine('-');
152
153 StringBuilder summary = new StringBuilder("Reactor Summary");
154 if (isSingleVersion) {
155 summary.append(" for ");
156 summary.append(session.getTopLevelProject().getName());
157 summary.append(" ");
158 summary.append(session.getTopLevelProject().getVersion());
159 }
160 summary.append(":");
161 infoMain(summary.toString());
162
163 logger.info("");
164
165 MavenExecutionResult result = session.getResult();
166
167 List<MavenProject> projects = session.getProjects();
168
169 String skippedMessage = builder().warning("SKIPPED").build();
170 String successMessage = builder().success("SUCCESS").build();
171 String failureMessage = builder().failure("FAILURE").build();
172 String unknownMessage = builder().warning("UNKNOWN").build();
173
174 List<ReactorSummaryEntry> entries = new ArrayList<>(projects.size());
175 for (MavenProject project : projects) {
176 BuildSummary buildSummary = result.getBuildSummary(project);
177
178 String statusMessage;
179 int group;
180 if (buildSummary instanceof BuildSuccess) {
181 statusMessage = successMessage;
182 group = 1;
183 } else if (buildSummary instanceof BuildFailure) {
184 statusMessage = failureMessage;
185 group = 2;
186 } else if (buildSummary == null) {
187 statusMessage = skippedMessage;
188 group = 0;
189 } else {
190 statusMessage = unknownMessage;
191 group = 2;
192 }
193 entries.add(new ReactorSummaryEntry(project, buildSummary, group, statusMessage));
194 }
195
196 ReactorSummaryRequest request = new ReactorSummaryRequest(entries, isSingleVersion);
197
198 logReactorSummaryGroup(request, 0);
199 logReactorSummaryGroup(request, 1);
200 logReactorSummaryGroup(request, 2);
201 }
202
203 private void logReactorSummaryGroup(ReactorSummaryRequest request, int group) {
204 StringBuilder buffer = new StringBuilder(128);
205
206 for (ReactorSummaryEntry entry : request.getEntries()) {
207 if (entry.getGroup() != group) {
208 continue;
209 }
210
211 buffer.append(entry.getProject().getName());
212 buffer.append(' ');
213
214 if (!request.isSingleVersion()) {
215 buffer.append(entry.getProject().getVersion());
216 buffer.append(' ');
217 }
218
219 if (buffer.length() <= MAX_PROJECT_NAME_LENGTH) {
220 while (buffer.length() < MAX_PROJECT_NAME_LENGTH) {
221 buffer.append('.');
222 }
223 buffer.append(' ');
224 }
225
226 buffer.append(entry.getStatusMessage());
227 if (entry.getBuildSummary() != null) {
228 formatBuildTime(buffer, entry.getBuildSummary());
229 }
230
231 if (entry.getBuildSummary() instanceof BuildFailure) {
232 logger.error(buffer.toString());
233 } else {
234 logger.info(buffer.toString());
235 }
236 buffer.setLength(0);
237 }
238 }
239
240 private static final class ReactorSummaryRequest {
241 private final List<ReactorSummaryEntry> entries;
242 private final boolean singleVersion;
243
244 private ReactorSummaryRequest(List<ReactorSummaryEntry> entries, boolean singleVersion) {
245 this.entries = entries;
246 this.singleVersion = singleVersion;
247 }
248
249 private List<ReactorSummaryEntry> getEntries() {
250 return entries;
251 }
252
253 private boolean isSingleVersion() {
254 return singleVersion;
255 }
256 }
257
258 private static final class ReactorSummaryEntry {
259 private final MavenProject project;
260 private final BuildSummary buildSummary;
261 private final int group;
262 private final String statusMessage;
263
264 private ReactorSummaryEntry(MavenProject project, BuildSummary buildSummary, int group, String statusMessage) {
265 this.project = project;
266 this.buildSummary = buildSummary;
267 this.group = group;
268 this.statusMessage = statusMessage;
269 }
270
271 private MavenProject getProject() {
272 return project;
273 }
274
275 private BuildSummary getBuildSummary() {
276 return buildSummary;
277 }
278
279 private int getGroup() {
280 return group;
281 }
282
283 private String getStatusMessage() {
284 return statusMessage;
285 }
286 }
287
288 private void formatBuildTime(StringBuilder buffer, BuildSummary buildSummary) {
289 buffer.append(" [");
290 String buildTimeDuration = formatDuration(buildSummary.getTime());
291 int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
292 if (padSize > 0) {
293 buffer.append(chars(' ', padSize));
294 }
295 buffer.append(buildTimeDuration);
296 buffer.append(']');
297 }
298
299 private void logResult(MavenSession session) {
300 infoLine('-');
301 MessageBuilder buffer = MessageUtils.builder();
302
303 boolean failure = session.getResult().hasExceptions();
304 if (failure) {
305 buffer.failure("BUILD FAILURE");
306 } else {
307 buffer.success("BUILD SUCCESS");
308 }
309 if (failure) {
310 logger.error(buffer.toString());
311 } else {
312 logger.info(buffer.toString());
313 }
314 }
315
316 private void logStats(MavenSession session) {
317 infoLine('-');
318
319 long finish = System.currentTimeMillis();
320
321 long time = finish - session.getRequest().getStartTime().getTime();
322
323 String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : "";
324
325 logger.info("Total time: {}{}", formatDuration(time), wallClock);
326
327 logger.info("Finished at: {}", formatTimestamp(finish));
328 }
329
330 @Override
331 public void projectSkipped(ExecutionEvent event) {
332 if (logger.isInfoEnabled()) {
333 logger.info("");
334 infoLine('-');
335
336 infoMain("Skipping " + event.getProject().getName());
337 logger.info("This project has been banned from the build due to previous failures.");
338
339 infoLine('-');
340 }
341 }
342
343 @Override
344 public void projectStarted(ExecutionEvent event) {
345 if (logger.isInfoEnabled()) {
346 MavenProject project = event.getProject();
347
348 logger.info("");
349
350
351 String projectKey = project.getGroupId() + ':' + project.getArtifactId();
352
353 final String preHeader = "--< ";
354 final String postHeader = " >--";
355
356 final int headerLen = preHeader.length() + projectKey.length() + postHeader.length();
357
358 String prefix = chars('-', Math.max(0, (LINE_LENGTH - headerLen) / 2)) + preHeader;
359
360 String suffix = postHeader
361 + chars('-', Math.max(0, LINE_LENGTH - headerLen - prefix.length() + preHeader.length()));
362
363 logger.info(
364 builder().strong(prefix).project(projectKey).strong(suffix).toString());
365
366
367 String building = "Building " + event.getProject().getName() + " "
368 + event.getProject().getVersion();
369
370 if (totalProjects <= 1) {
371 infoMain(building);
372 } else {
373
374 int number;
375 synchronized (this) {
376 number = ++currentVisitedProjectCount;
377 }
378 String progress = " [" + number + '/' + totalProjects + ']';
379
380 int pad = LINE_LENGTH - building.length() - progress.length();
381
382 infoMain(building + ((pad > 0) ? chars(' ', pad) : "") + progress);
383 }
384
385
386 File currentPom = project.getFile();
387 if (currentPom != null) {
388 MavenSession session = event.getSession();
389 Path topLevelBasedir = session.getTopLevelProject().getBasedir().toPath();
390 Path current = currentPom.toPath().toAbsolutePath().normalize();
391 if (current.startsWith(topLevelBasedir)) {
392 current = topLevelBasedir.relativize(current);
393 }
394 logger.info(" from " + current);
395 }
396
397
398 prefix =
399 chars('-', Math.max(0, (LINE_LENGTH - project.getPackaging().length() - 4) / 2));
400 suffix = chars('-', Math.max(0, LINE_LENGTH - project.getPackaging().length() - 4 - prefix.length()));
401 infoMain(prefix + "[ " + project.getPackaging() + " ]" + suffix);
402 }
403 }
404
405 @Override
406 public void mojoSkipped(ExecutionEvent event) {
407 if (logger.isWarnEnabled()) {
408 logger.warn(
409 "Goal {} requires online mode for execution but Maven is currently offline, skipping",
410 event.getMojoExecution().getGoal());
411 }
412 }
413
414
415
416
417 @Override
418 public void mojoStarted(ExecutionEvent event) {
419 if (logger.isInfoEnabled()) {
420 logger.info("");
421
422 MessageBuilder buffer = builder().strong("--- ");
423 append(buffer, event.getMojoExecution());
424 append(buffer, event.getProject());
425 buffer.strong(" ---");
426
427 logger.info(buffer.toString());
428 }
429 }
430
431
432
433
434
435
436
437 @Override
438 public void forkStarted(ExecutionEvent event) {
439 if (logger.isInfoEnabled()) {
440 logger.info("");
441
442 MessageBuilder buffer = builder().strong(">>> ");
443 append(buffer, event.getMojoExecution());
444 buffer.strong(" > ");
445 appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor());
446 append(buffer, event.getProject());
447 buffer.strong(" >>>");
448
449 logger.info(buffer.toString());
450 }
451 }
452
453
454
455
456
457
458
459 @Override
460 public void forkSucceeded(ExecutionEvent event) {
461 if (logger.isInfoEnabled()) {
462 logger.info("");
463
464 MessageBuilder buffer = builder().strong("<<< ");
465 append(buffer, event.getMojoExecution());
466 buffer.strong(" < ");
467 appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor());
468 append(buffer, event.getProject());
469 buffer.strong(" <<<");
470
471 logger.info(buffer.toString());
472
473 logger.info("");
474 }
475 }
476
477 private void append(MessageBuilder buffer, MojoExecution me) {
478 String prefix = me.getMojoDescriptor().getPluginDescriptor().getGoalPrefix();
479 if (StringUtils.isEmpty(prefix)) {
480 prefix = me.getGroupId() + ":" + me.getArtifactId();
481 }
482 buffer.mojo(prefix + ':' + me.getVersion() + ':' + me.getGoal());
483 if (me.getExecutionId() != null) {
484 buffer.a(' ').strong('(' + me.getExecutionId() + ')');
485 }
486 }
487
488 private void appendForkInfo(MessageBuilder buffer, MojoDescriptor md) {
489 StringBuilder buff = new StringBuilder();
490 if (StringUtils.isNotEmpty(md.getExecutePhase())) {
491
492 if (StringUtils.isNotEmpty(md.getExecuteLifecycle())) {
493 buff.append('[');
494 buff.append(md.getExecuteLifecycle());
495 buff.append(']');
496 }
497 buff.append(md.getExecutePhase());
498 } else {
499
500 buff.append(':');
501 buff.append(md.getExecuteGoal());
502 }
503 buffer.strong(buff.toString());
504 }
505
506 private void append(MessageBuilder buffer, MavenProject project) {
507 buffer.a(" @ ").project(project.getArtifactId());
508 }
509
510 @Override
511 public void forkedProjectStarted(ExecutionEvent event) {
512 if (logger.isInfoEnabled()
513 && event.getMojoExecution().getForkedExecutions().size() > 1) {
514 logger.info("");
515 infoLine('>');
516
517 infoMain("Forking " + event.getProject().getName() + " "
518 + event.getProject().getVersion());
519
520 infoLine('>');
521 }
522 }
523 }