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