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