View Javadoc
1   package org.apache.maven.cli.event;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * Logs execution events to logger, eventually user-supplied.
47   *
48   * @author Benjamin Bentmann
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      // TODO should we deprecate?
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             // -------< groupId:artifactId >-------
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             // Building Project Name Version    [i/n]
316             String building = "Building " + event.getProject().getName() + " " + event.getProject().getVersion();
317 
318             if ( totalProjects <= 1 )
319             {
320                 infoMain( building );
321             }
322             else
323             {
324                 // display progress [i/n]
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             // path to pom.xml
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             // ----------[ packaging ]----------
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      * <pre>--- mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId ---</pre>
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     // CHECKSTYLE_OFF: LineLength
383     /**
384      * <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; :forked-goal @ project-artifactId &gt;&gt;&gt;</pre>
385      * <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; [lifecycle]phase @ project-artifactId &gt;&gt;&gt;</pre>
386      */
387     // CHECKSTYLE_ON: LineLength
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     // CHECKSTYLE_OFF: LineLength
407     /**
408      * <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; :forked-goal @ project-artifactId &lt;&lt;&lt;</pre>
409      * <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; [lifecycle]phase @ project-artifactId &lt;&lt;&lt;</pre>
410      */
411     // CHECKSTYLE_ON: LineLength
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             // forked phase
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             // forked goal
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 }