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.plugins.invoker;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.maven.plugin.MojoFailureException;
30  import org.apache.maven.plugin.logging.Log;
31  import org.apache.maven.plugins.invoker.model.BuildJob;
32  import org.codehaus.plexus.util.IOUtil;
33  
34  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
35  
36  /**
37   * Tracks a set of build jobs and their results.
38   *
39   * @author Benjamin Bentmann
40   */
41  class InvokerSession {
42      private static final String SEPARATOR =
43              buffer().strong("-------------------------------------------------").build();
44  
45      private List<BuildJob> buildJobs;
46  
47      private List<BuildJob> failedJobs;
48  
49      private List<BuildJob> errorJobs;
50  
51      private List<BuildJob> successfulJobs;
52  
53      private List<BuildJob> skippedJobs;
54  
55      /**
56       * Creates a new empty session.
57       */
58      InvokerSession() {
59          buildJobs = new ArrayList<>();
60      }
61  
62      /**
63       * Creates a session that initially contains the specified build jobs.
64       *
65       * @param buildJobs The build jobs to set, must not be <code>null</code>.
66       */
67      InvokerSession(List<BuildJob> buildJobs) {
68          this.buildJobs = new ArrayList<>(buildJobs);
69      }
70  
71      /**
72       * Adds the specified build job to this session.
73       *
74       * @param buildJob The build job to add, must not be <code>null</code>.
75       */
76      public void addJob(BuildJob buildJob) {
77          buildJobs.add(buildJob);
78  
79          resetStats();
80      }
81  
82      /**
83       * Sets the build jobs of this session.
84       *
85       * @param buildJobs The build jobs to set, must not be <code>null</code>.
86       */
87      public void setJobs(List<? extends BuildJob> buildJobs) {
88          this.buildJobs = new ArrayList<>(buildJobs);
89  
90          resetStats();
91      }
92  
93      /**
94       * Gets the build jobs in this session.
95       *
96       * @return The build jobs in this session, can be empty but never <code>null</code>.
97       */
98      public List<BuildJob> getJobs() {
99          return buildJobs;
100     }
101 
102     /**
103      * Gets the successful build jobs in this session.
104      *
105      * @return The successful build jobs in this session, can be empty but never <code>null</code>.
106      */
107     public List<BuildJob> getSuccessfulJobs() {
108         updateStats();
109 
110         return successfulJobs;
111     }
112 
113     /**
114      * Gets the failed build jobs in this session.
115      *
116      * @return The failed build jobs in this session, can be empty but never <code>null</code>.
117      */
118     public List<BuildJob> getFailedJobs() {
119         updateStats();
120 
121         return failedJobs;
122     }
123 
124     /**
125      * Gets the build jobs which had errors for this session.
126      *
127      * @return The build jobs in error for this session, can be empty but never <code>null</code>.
128      */
129     public List<BuildJob> getErrorJobs() {
130         updateStats();
131 
132         return errorJobs;
133     }
134 
135     /**
136      * Gets the skipped build jobs in this session.
137      *
138      * @return The skipped build jobs in this session, can be empty but never <code>null</code>.
139      */
140     public List<BuildJob> getSkippedJobs() {
141         updateStats();
142 
143         return skippedJobs;
144     }
145 
146     private void resetStats() {
147         successfulJobs = null;
148         failedJobs = null;
149         skippedJobs = null;
150         errorJobs = null;
151     }
152 
153     private void updateStats() {
154         if (successfulJobs != null && skippedJobs != null && failedJobs != null && errorJobs != null) {
155             return;
156         }
157 
158         successfulJobs = new ArrayList<>();
159         failedJobs = new ArrayList<>();
160         skippedJobs = new ArrayList<>();
161         errorJobs = new ArrayList<>();
162 
163         for (BuildJob buildJob : buildJobs) {
164             if (BuildJob.Result.SUCCESS.equals(buildJob.getResult())) {
165                 successfulJobs.add(buildJob);
166             } else if (BuildJob.Result.SKIPPED.equals(buildJob.getResult())) {
167                 skippedJobs.add(buildJob);
168             } else if (BuildJob.Result.ERROR.equals(buildJob.getResult())) {
169                 errorJobs.add(buildJob);
170             } else if (buildJob.getResult() != null) {
171                 failedJobs.add(buildJob);
172             }
173         }
174     }
175 
176     /**
177      * Prints a summary of this session to the specified logger.
178      *
179      * @param logger The mojo logger to output messages to, must not be <code>null</code>.
180      * @param ignoreFailures A flag whether failures should be ignored or whether a build failure should be signaled.
181      */
182     public void logSummary(Log logger, boolean ignoreFailures) {
183         updateStats();
184 
185         logger.info(SEPARATOR);
186         logger.info("Build Summary:");
187         logger.info("  Passed: " + successfulJobs.size()
188                 + ", Failed: " + failedJobs.size()
189                 + ", Errors: " + errorJobs.size()
190                 + ", Skipped: " + skippedJobs.size());
191         logger.info(SEPARATOR);
192 
193         logBuildJobList(logger, ignoreFailures, "The following builds failed:", failedJobs);
194         logBuildJobList(logger, ignoreFailures, "The following builds finished with error:", errorJobs);
195         logBuildJobList(logger, true, "The following builds were skipped:", skippedJobs);
196     }
197 
198     public void logFailedBuildLog(Log logger, boolean ignoreFailures) throws MojoFailureException {
199         updateStats();
200 
201         List<BuildJob> jobToLogs = new ArrayList<>(failedJobs);
202         jobToLogs.addAll(errorJobs);
203 
204         for (BuildJob buildJob : jobToLogs) {
205             Path buildLogPath = buildJob.getBuildlog() != null ? new File(buildJob.getBuildlog()).toPath() : null;
206             if (buildLogPath != null && Files.exists(buildLogPath)) {
207                 try {
208                     // prepare message with build.log in one string to omit begin [ERROR], [WARN]
209                     // so whole log will be displayed without decoration
210                     StringBuilder buildLogMessage = new StringBuilder();
211                     buildLogMessage.append(System.lineSeparator());
212                     buildLogMessage.append(System.lineSeparator());
213                     buildLogMessage
214                             .append("*** begin build.log for: ")
215                             .append(buildJob.getProject())
216                             .append(" ***")
217                             .append(System.lineSeparator());
218 
219                     if (buildJob.getExecutionCount() > 1) {
220                         // try to read build log from each execution
221                         for (int executionCount = 1;
222                                 executionCount <= buildJob.getExecutionCount() - 1;
223                                 executionCount++) {
224                             // log file name is hardcoded in whole project
225                             buildLogMessage
226                                     .append("*** build.log for execution: ")
227                                     .append(executionCount)
228                                     .append(" ***")
229                                     .append(System.lineSeparator());
230                             Path path = buildLogPath
231                                     .getParent()
232                                     .resolve(buildLogPath.getFileName().toString() + "." + executionCount);
233                             appendLogToMessage(path, buildLogMessage);
234                         }
235 
236                         buildLogMessage
237                                 .append("*** build.log for execution: ")
238                                 .append(buildJob.getExecutionCount())
239                                 .append(" ***")
240                                 .append(System.lineSeparator());
241                     }
242 
243                     appendLogToMessage(buildLogPath, buildLogMessage);
244 
245                     buildLogMessage
246                             .append("*** end build.log for: ")
247                             .append(buildJob.getProject())
248                             .append(" ***")
249                             .append(System.lineSeparator());
250 
251                     logWithLevel(logger, ignoreFailures, SEPARATOR);
252                     logWithLevel(logger, ignoreFailures, buildLogMessage.toString());
253                     logWithLevel(logger, ignoreFailures, SEPARATOR);
254                     logWithLevel(logger, ignoreFailures, "");
255 
256                 } catch (IOException e) {
257                     throw new MojoFailureException(e.getMessage(), e);
258                 }
259             }
260         }
261     }
262 
263     private void appendLogToMessage(Path logPath, StringBuilder buildLogMessage) throws IOException {
264         if (logPath != null && Files.exists(logPath)) {
265             try (Reader buildLogReader = Files.newBufferedReader(logPath)) {
266                 buildLogMessage.append(IOUtil.toString(buildLogReader));
267             }
268         }
269     }
270 
271     /**
272      * Handles the build failures in this session.
273      *
274      * @param logger The mojo logger to output messages to, must not be <code>null</code>.
275      * @param ignoreFailures A flag whether failures should be ignored or whether a build failure should be signaled.
276      * @throws MojoFailureException If failures are present and not ignored.
277      */
278     public void handleFailures(Log logger, boolean ignoreFailures) throws MojoFailureException {
279         updateStats();
280 
281         if (!failedJobs.isEmpty()) {
282             String message = failedJobs.size() + " build" + (failedJobs.size() == 1 ? "" : "s") + " failed.";
283 
284             if (ignoreFailures) {
285                 logger.warn("Ignoring that " + message);
286             } else {
287                 throw new MojoFailureException(message + " See console output above for details.");
288             }
289         }
290 
291         if (!errorJobs.isEmpty()) {
292             String message = errorJobs.size() + " build" + (errorJobs.size() == 1 ? "" : "s") + " in error.";
293 
294             if (ignoreFailures) {
295                 logger.warn("Ignoring that " + message);
296             } else {
297                 throw new MojoFailureException(message + " See console output above for details.");
298             }
299         }
300     }
301 
302     /**
303      * Log list of jobs.
304      *
305      * @param logger logger to write
306      * @param warn flag indicate log level
307      * @param buildJobs jobs to list
308      */
309     private void logBuildJobList(Log logger, boolean warn, String header, List<BuildJob> buildJobs) {
310         if (buildJobs.isEmpty()) {
311             return;
312         }
313 
314         logWithLevel(logger, warn, header);
315 
316         for (BuildJob buildJob : buildJobs) {
317             logWithLevel(logger, warn, "*  " + buildJob.getProject());
318         }
319 
320         logger.info(SEPARATOR);
321     }
322 
323     /**
324      * Log message in correct level depends on flag.
325      *
326      * @param logger logger to write
327      * @param warn flag indicate log level
328      * @param message message to write
329      */
330     private void logWithLevel(Log logger, boolean warn, String message) {
331 
332         if (warn) {
333             logger.warn(message);
334         } else {
335             logger.error(message);
336         }
337     }
338 }