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