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