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