Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractInvokerMojo |
|
| 7.21875;7,219 | ||||
AbstractInvokerMojo$1 |
|
| 7.21875;7,219 |
1 | package org.apache.maven.plugin.invoker; | |
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 org.apache.maven.artifact.Artifact; | |
23 | import org.apache.maven.model.Model; | |
24 | import org.apache.maven.model.Profile; | |
25 | import org.apache.maven.plugin.AbstractMojo; | |
26 | import org.apache.maven.plugin.MojoExecutionException; | |
27 | import org.apache.maven.plugin.MojoFailureException; | |
28 | import org.apache.maven.plugin.invoker.model.BuildJob; | |
29 | import org.apache.maven.plugin.invoker.model.io.xpp3.BuildJobXpp3Writer; | |
30 | import org.apache.maven.plugin.registry.TrackableBase; | |
31 | import org.apache.maven.project.MavenProject; | |
32 | import org.apache.maven.settings.Settings; | |
33 | import org.apache.maven.settings.SettingsUtils; | |
34 | import org.apache.maven.settings.io.xpp3.SettingsXpp3Reader; | |
35 | import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer; | |
36 | import org.apache.maven.shared.invoker.CommandLineConfigurationException; | |
37 | import org.apache.maven.shared.invoker.DefaultInvocationRequest; | |
38 | import org.apache.maven.shared.invoker.InvocationRequest; | |
39 | import org.apache.maven.shared.invoker.InvocationResult; | |
40 | import org.apache.maven.shared.invoker.Invoker; | |
41 | import org.apache.maven.shared.invoker.MavenCommandLineBuilder; | |
42 | import org.apache.maven.shared.invoker.MavenInvocationException; | |
43 | import org.apache.maven.shared.scriptinterpreter.RunErrorException; | |
44 | import org.apache.maven.shared.scriptinterpreter.RunFailureException; | |
45 | import org.apache.maven.shared.scriptinterpreter.ScriptRunner; | |
46 | import org.codehaus.plexus.interpolation.InterpolationException; | |
47 | import org.codehaus.plexus.interpolation.Interpolator; | |
48 | import org.codehaus.plexus.interpolation.MapBasedValueSource; | |
49 | import org.codehaus.plexus.interpolation.RegexBasedInterpolator; | |
50 | import org.codehaus.plexus.util.DirectoryScanner; | |
51 | import org.codehaus.plexus.util.FileUtils; | |
52 | import org.codehaus.plexus.util.IOUtil; | |
53 | import org.codehaus.plexus.util.InterpolationFilterReader; | |
54 | import org.codehaus.plexus.util.ReaderFactory; | |
55 | import org.codehaus.plexus.util.StringUtils; | |
56 | import org.codehaus.plexus.util.WriterFactory; | |
57 | import org.codehaus.plexus.util.xml.XmlStreamReader; | |
58 | import org.codehaus.plexus.util.xml.pull.XmlPullParserException; | |
59 | ||
60 | import java.io.BufferedReader; | |
61 | import java.io.File; | |
62 | import java.io.FileInputStream; | |
63 | import java.io.FileOutputStream; | |
64 | import java.io.FileWriter; | |
65 | import java.io.IOException; | |
66 | import java.io.InputStream; | |
67 | import java.io.OutputStreamWriter; | |
68 | import java.io.Reader; | |
69 | import java.io.Writer; | |
70 | import java.text.DecimalFormat; | |
71 | import java.text.DecimalFormatSymbols; | |
72 | import java.util.ArrayList; | |
73 | import java.util.Arrays; | |
74 | import java.util.Collection; | |
75 | import java.util.Collections; | |
76 | import java.util.HashMap; | |
77 | import java.util.LinkedHashMap; | |
78 | import java.util.LinkedHashSet; | |
79 | import java.util.List; | |
80 | import java.util.Locale; | |
81 | import java.util.Map; | |
82 | import java.util.Properties; | |
83 | import java.util.Set; | |
84 | import java.util.StringTokenizer; | |
85 | import java.util.TreeSet; | |
86 | import java.util.concurrent.ExecutorService; | |
87 | import java.util.concurrent.Executors; | |
88 | import java.util.concurrent.TimeUnit; | |
89 | ||
90 | /** | |
91 | * Provides common code for mojos invoking sub builds. | |
92 | * | |
93 | * @author Stephen Connolly | |
94 | * @since 15-Aug-2009 09:09:29 | |
95 | */ | |
96 | 9 | public abstract class AbstractInvokerMojo |
97 | extends AbstractMojo | |
98 | { | |
99 | ||
100 | /** | |
101 | * Flag used to suppress certain invocations. This is useful in tailoring the build using profiles. | |
102 | * | |
103 | * @parameter expression="${invoker.skip}" default-value="false" | |
104 | * @since 1.1 | |
105 | */ | |
106 | private boolean skipInvocation; | |
107 | ||
108 | /** | |
109 | * Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>, the | |
110 | * only indication of the build's success or failure will be the effect it has on the main build (if it fails, the | |
111 | * main build should fail as well). If {@link #streamLogs} is enabled, the sub-build summary will also provide an | |
112 | * indication. | |
113 | * | |
114 | * @parameter default-value="false" | |
115 | */ | |
116 | protected boolean suppressSummaries; | |
117 | ||
118 | /** | |
119 | * Flag used to determine whether the build logs should be output to the normal mojo log. | |
120 | * | |
121 | * @parameter expression="${invoker.streamLogs}" default-value="false" | |
122 | */ | |
123 | private boolean streamLogs; | |
124 | ||
125 | /** | |
126 | * The local repository for caching artifacts. It is strongly recommended to specify a path to an isolated | |
127 | * repository like <code>${project.build.directory}/it-repo</code>. Otherwise, your ordinary local repository will | |
128 | * be used, potentially soiling it with broken artifacts. | |
129 | * | |
130 | * @parameter expression="${invoker.localRepositoryPath}" default-value="${settings.localRepository}" | |
131 | */ | |
132 | private File localRepositoryPath; | |
133 | ||
134 | /** | |
135 | * Directory to search for integration tests. | |
136 | * | |
137 | * @parameter expression="${invoker.projectsDirectory}" default-value="${basedir}/src/it/" | |
138 | */ | |
139 | private File projectsDirectory; | |
140 | ||
141 | /** | |
142 | * Base directory where all build reports are written to. | |
143 | * Every execution of an integration test will produce an XML file which contains the information | |
144 | * about success or failure of that particular build job. The format of the resulting XML | |
145 | * file is documented in the given <a href="./build-job.html">build-job</a> reference. | |
146 | * | |
147 | * @parameter expression="${invoker.reportsDirectory}" default-value="${project.build.directory}/invoker-reports" | |
148 | * @since 1.4 | |
149 | */ | |
150 | private File reportsDirectory; | |
151 | ||
152 | /** | |
153 | * A flag to disable the generation of build reports. | |
154 | * | |
155 | * @parameter expression="${invoker.disableReports}" default-value="false" | |
156 | * @since 1.4 | |
157 | */ | |
158 | private boolean disableReports; | |
159 | ||
160 | /** | |
161 | * Directory to which projects should be cloned prior to execution. If not specified, each integration test will be | |
162 | * run in the directory in which the corresponding IT POM was found. In this case, you most likely want to configure | |
163 | * your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory. | |
164 | * | |
165 | * @parameter | |
166 | * @since 1.1 | |
167 | */ | |
168 | private File cloneProjectsTo; | |
169 | ||
170 | /** | |
171 | * Some files are normally excluded when copying the IT projects from the directory specified by the parameter | |
172 | * projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>, | |
173 | * <code>*~</code>, etc). Setting this parameter to <code>true</code> will cause all files to be copied to the | |
174 | * cloneProjectsTo directory. | |
175 | * | |
176 | * @parameter default-value="false" | |
177 | * @since 1.2 | |
178 | */ | |
179 | private boolean cloneAllFiles; | |
180 | ||
181 | /** | |
182 | * Ensure the {@link #cloneProjectsTo} directory is not polluted with files from earlier invoker runs. | |
183 | * | |
184 | * @parameter default-value="false" | |
185 | * @since 1.6 | |
186 | */ | |
187 | private boolean cloneClean; | |
188 | ||
189 | /** | |
190 | * A single POM to build, skipping any scanning parameters and behavior. | |
191 | * | |
192 | * @parameter expression="${invoker.pom}" | |
193 | */ | |
194 | private File pom; | |
195 | ||
196 | /** | |
197 | * Include patterns for searching the integration test directory for projects. This parameter is meant to be set | |
198 | * from the POM. If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one | |
199 | * directory below {@link #projectsDirectory} (i.e. <code>*/pom.xml</code>).<br> | |
200 | * <br> | |
201 | * Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include | |
202 | * pattern <code>*</code> will run Maven builds on all immediate sub directories of {@link #projectsDirectory}, | |
203 | * regardless if they contain a <code>pom.xml</code>. This allows to perform builds that need/should not depend on | |
204 | * the existence of a POM. | |
205 | * | |
206 | * @parameter | |
207 | */ | |
208 | 9 | private List<String> pomIncludes = Collections.singletonList( "*/pom.xml" ); |
209 | ||
210 | /** | |
211 | * Exclude patterns for searching the integration test directory. This parameter is meant to be set from the POM. By | |
212 | * default, no POM files are excluded. For the convenience of using an include pattern like <code>*</code>, the | |
213 | * custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically. | |
214 | * | |
215 | * @parameter | |
216 | */ | |
217 | 9 | private List<String> pomExcludes = Collections.emptyList(); |
218 | ||
219 | /** | |
220 | * Include patterns for searching the projects directory for projects that need to be run before the other projects. | |
221 | * This parameter allows to declare projects that perform setup tasks like installing utility artifacts into the | |
222 | * local repository. Projects matched by these patterns are implicitly excluded from the scan for ordinary projects. | |
223 | * Also, the exclusions defined by the parameter {@link #pomExcludes} apply to the setup projects, too. Default | |
224 | * value is: <code>setup*/pom.xml</code>. | |
225 | * | |
226 | * @parameter | |
227 | * @since 1.3 | |
228 | */ | |
229 | 9 | private List<String> setupIncludes = Collections.singletonList( "setup*/pom.xml" ); |
230 | ||
231 | /** | |
232 | * The list of goals to execute on each project. Default value is: <code>package</code>. | |
233 | * | |
234 | * @parameter | |
235 | */ | |
236 | 9 | private List<String> goals = Collections.singletonList( "package" ); |
237 | ||
238 | /** | |
239 | * The name of the project-specific file that contains the enumeration of goals to execute for that test. | |
240 | * | |
241 | * @parameter expression="${invoker.goalsFile}" default-value="goals.txt" | |
242 | * @deprecated As of version 1.2, the key <code>invoker.goals</code> from the properties file specified by the | |
243 | * parameter {@link #invokerPropertiesFile} should be used instead. | |
244 | */ | |
245 | private String goalsFile; | |
246 | ||
247 | /** | |
248 | * @component | |
249 | */ | |
250 | private Invoker invoker; | |
251 | ||
252 | /** | |
253 | * Relative path of a selector script to run prior in order to decide if the build should be executed. This script | |
254 | * may be written with either BeanShell or Groovy. If the file extension is omitted (e.g. <code>selector</code>), | |
255 | * the plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. | |
256 | * If this script exists for a particular project but returns any non-null value different from <code>true</code>, | |
257 | * the corresponding build is flagged as skipped. In this case, none of the pre-build hook script, | |
258 | * Maven nor the post-build hook script will be invoked. If this script throws an exception, the corresponding | |
259 | * build is flagged as in error, and none of the pre-build hook script, Maven not the post-build hook script will | |
260 | * be invoked. | |
261 | * | |
262 | * @parameter expression="${invoker.selectorScript}" default-value="selector" | |
263 | * @since 1.5 | |
264 | */ | |
265 | private String selectorScript; | |
266 | ||
267 | /** | |
268 | * Relative path of a pre-build hook script to run prior to executing the build. This script may be written with | |
269 | * either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>prebuild</code>), the plugin | |
270 | * searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. If this | |
271 | * script exists for a particular project but returns any non-null value different from <code>true</code> or throws | |
272 | * an exception, the corresponding build is flagged as a failure. In this case, neither Maven nor the post-build | |
273 | * hook script will be invoked. | |
274 | * | |
275 | * @parameter expression="${invoker.preBuildHookScript}" default-value="prebuild" | |
276 | */ | |
277 | private String preBuildHookScript; | |
278 | ||
279 | /** | |
280 | * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written | |
281 | * with either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>verify</code>), the | |
282 | * plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. | |
283 | * If this script exists for a particular project but returns any non-null value different from <code>true</code> or | |
284 | * throws an exception, the corresponding build is flagged as a failure. | |
285 | * | |
286 | * @parameter expression="${invoker.postBuildHookScript}" default-value="postbuild" | |
287 | */ | |
288 | private String postBuildHookScript; | |
289 | ||
290 | /** | |
291 | * Location of a properties file that defines CLI properties for the test. | |
292 | * | |
293 | * @parameter expression="${invoker.testPropertiesFile}" default-value="test.properties" | |
294 | */ | |
295 | private String testPropertiesFile; | |
296 | ||
297 | /** | |
298 | * Common set of test properties to pass in on each IT's command line, via -D parameters. | |
299 | * | |
300 | * @parameter | |
301 | * @deprecated As of version 1.1, use the {@link #properties} parameter instead. | |
302 | */ | |
303 | private Properties testProperties; | |
304 | ||
305 | /** | |
306 | * Common set of properties to pass in on each project's command line, via -D parameters. | |
307 | * | |
308 | * @parameter | |
309 | * @since 1.1 | |
310 | */ | |
311 | private Map<String, String> properties; | |
312 | ||
313 | /** | |
314 | * Whether to show errors in the build output. | |
315 | * | |
316 | * @parameter expression="${invoker.showErrors}" default-value="false" | |
317 | */ | |
318 | private boolean showErrors; | |
319 | ||
320 | /** | |
321 | * Whether to show debug statements in the build output. | |
322 | * | |
323 | * @parameter expression="${invoker.debug}" default-value="false" | |
324 | */ | |
325 | private boolean debug; | |
326 | ||
327 | /** | |
328 | * Suppress logging to the <code>build.log</code> file. | |
329 | * | |
330 | * @parameter expression="${invoker.noLog}" default-value="false" | |
331 | */ | |
332 | private boolean noLog; | |
333 | ||
334 | /** | |
335 | * List of profile identifiers to explicitly trigger in the build. | |
336 | * | |
337 | * @parameter | |
338 | * @since 1.1 | |
339 | */ | |
340 | private List<String> profiles; | |
341 | ||
342 | /** | |
343 | * List of properties which will be used to interpolate goal files. | |
344 | * | |
345 | * @parameter | |
346 | * @since 1.1 | |
347 | * @deprecated As of version 1.3, the parameter {@link #filterProperties} should be used instead. | |
348 | */ | |
349 | private Properties interpolationsProperties; | |
350 | ||
351 | /** | |
352 | * A list of additional properties which will be used to filter tokens in POMs and goal files. | |
353 | * | |
354 | * @parameter | |
355 | * @since 1.3 | |
356 | */ | |
357 | private Map<String, String> filterProperties; | |
358 | ||
359 | /** | |
360 | * The Maven Project Object | |
361 | * | |
362 | * @parameter expression="${project}" | |
363 | * @required | |
364 | * @readonly | |
365 | * @since 1.1 | |
366 | */ | |
367 | private MavenProject project; | |
368 | ||
369 | /** | |
370 | * A comma separated list of project names to run. Specify this parameter to run individual tests by file name, | |
371 | * overriding the {@link #setupIncludes}, {@link #pomIncludes} and {@link #pomExcludes} parameters. Each pattern you | |
372 | * specify here will be used to create an include pattern formatted like | |
373 | * <code>${projectsDirectory}/<i>pattern</i></code>, so you can just type | |
374 | * <code>-Dinvoker.test=FirstTest,SecondTest</code> to run builds in <code>${projectsDirectory}/FirstTest</code> and | |
375 | * <code>${projectsDirectory}/SecondTest</code>. | |
376 | * | |
377 | * @parameter expression="${invoker.test}" | |
378 | * @since 1.1 | |
379 | */ | |
380 | private String invokerTest; | |
381 | ||
382 | /** | |
383 | * The name of the project-specific file that contains the enumeration of profiles to use for that test. <b>If the | |
384 | * file exists and is empty no profiles will be used even if the parameter {@link #profiles} is set.</b> | |
385 | * | |
386 | * @parameter expression="${invoker.profilesFile}" default-value="profiles.txt" | |
387 | * @since 1.1 | |
388 | * @deprecated As of version 1.2, the key <code>invoker.profiles</code> from the properties file specified by the | |
389 | * parameter {@link #invokerPropertiesFile} should be used instead. | |
390 | */ | |
391 | private String profilesFile; | |
392 | ||
393 | /** | |
394 | * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the | |
395 | * <code><localRepository></code> element of this settings file is always ignored, i.e. the path given by the | |
396 | * parameter {@link #localRepositoryPath} is dominant. | |
397 | * | |
398 | * @parameter expression="${invoker.settingsFile}" | |
399 | * @since 1.2 | |
400 | */ | |
401 | private File settingsFile; | |
402 | ||
403 | /** | |
404 | * The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for | |
405 | * individual integration tests by using {@link #invokerPropertiesFile}. | |
406 | * | |
407 | * @parameter expression="${invoker.mavenOpts}" | |
408 | * @since 1.2 | |
409 | */ | |
410 | private String mavenOpts; | |
411 | ||
412 | /** | |
413 | * The home directory of the Maven installation to use for the forked builds. Defaults to the current Maven | |
414 | * installation. | |
415 | * | |
416 | * @parameter expression="${invoker.mavenHome}" | |
417 | * @since 1.3 | |
418 | */ | |
419 | private File mavenHome; | |
420 | ||
421 | /** | |
422 | * The <code>JAVA_HOME</code> environment variable to use for forked Maven invocations. Defaults to the current Java | |
423 | * home directory. | |
424 | * | |
425 | * @parameter expression="${invoker.javaHome}" | |
426 | * @since 1.3 | |
427 | */ | |
428 | private File javaHome; | |
429 | ||
430 | /** | |
431 | * The file encoding for the pre-/post-build scripts and the list files for goals and profiles. | |
432 | * | |
433 | * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}" | |
434 | * @since 1.2 | |
435 | */ | |
436 | private String encoding; | |
437 | ||
438 | /** | |
439 | * The current user system settings for use in Maven. | |
440 | * | |
441 | * @parameter expression="${settings}" | |
442 | * @required | |
443 | * @readonly | |
444 | * @since 1.2 | |
445 | */ | |
446 | private Settings settings; | |
447 | ||
448 | /** | |
449 | * A flag whether the test class path of the project under test should be included in the class path of the | |
450 | * pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of | |
451 | * the <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the | |
452 | * <code>true</code>, the project's test class path will be prepended to the interpreter class path. Among | |
453 | * others, this feature allows the scripts to access utility classes from the test sources of your project. | |
454 | * | |
455 | * @parameter expression="${invoker.addTestClassPath}" default-value="false" | |
456 | * @since 1.2 | |
457 | */ | |
458 | private boolean addTestClassPath; | |
459 | ||
460 | /** | |
461 | * The test class path of the project under test. | |
462 | * | |
463 | * @parameter default-value="${project.testClasspathElements}" | |
464 | * @readonly | |
465 | */ | |
466 | private List<String> testClassPath; | |
467 | ||
468 | /** | |
469 | * The name of an optional project-specific file that contains properties used to specify settings for an individual | |
470 | * Maven invocation. Any property present in the file will override the corresponding setting from the plugin | |
471 | * configuration. The values of the properties are filtered and may use expressions like | |
472 | * <code>${project.version}</code> to reference project properties or values from the parameter | |
473 | * {@link #filterProperties}. The snippet below describes the supported properties: | |
474 | * <p/> | |
475 | * <pre> | |
476 | * # A comma or space separated list of goals/phases to execute, may | |
477 | * # specify an empty list to execute the default goal of the IT project | |
478 | * invoker.goals = clean install | |
479 | * | |
480 | * # Optionally, a list of goals to run during further invocations of Maven | |
481 | * invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:run | |
482 | * | |
483 | * # A comma or space separated list of profiles to activate | |
484 | * invoker.profiles = its,jdk15 | |
485 | * | |
486 | * # The path to an alternative POM or base directory to invoke Maven on, defaults to the | |
487 | * # project that was originally specified in the plugin configuration | |
488 | * # Since plugin version 1.4 | |
489 | * invoker.project = sub-module | |
490 | * | |
491 | * # The value for the environment variable MAVEN_OPTS | |
492 | * invoker.mavenOpts = -Dfile.encoding=UTF-16 -Xms32m -Xmx256m | |
493 | * | |
494 | * # Possible values are "fail-fast" (default), "fail-at-end" and "fail-never" | |
495 | * invoker.failureBehavior = fail-never | |
496 | * | |
497 | * # The expected result of the build, possible values are "success" (default) and "failure" | |
498 | * invoker.buildResult = failure | |
499 | * | |
500 | * # A boolean value controlling the aggregator mode of Maven, defaults to "false" | |
501 | * invoker.nonRecursive = true | |
502 | * | |
503 | * # A boolean value controlling the network behavior of Maven, defaults to "false" | |
504 | * # Since plugin version 1.4 | |
505 | * invoker.offline = true | |
506 | * | |
507 | * # The path to the properties file from which to load system properties, defaults to the | |
508 | * # filename given by the plugin parameter testPropertiesFile | |
509 | * # Since plugin version 1.4 | |
510 | * invoker.systemPropertiesFile = test.properties | |
511 | * | |
512 | * # An optional human friendly name for this build job to be included in the build reports. | |
513 | * # Since plugin version 1.4 | |
514 | * invoker.name = Test Build 01 | |
515 | * | |
516 | * # An optional description for this build job to be included in the build reports. | |
517 | * # Since plugin version 1.4 | |
518 | * invoker.description = Checks the support for build reports. | |
519 | * | |
520 | * # A comma separated list of JRE versions on which this build job should be run. | |
521 | * # Since plugin version 1.4 | |
522 | * invoker.java.version = 1.4+, !1.4.1, 1.7- | |
523 | * | |
524 | * # A comma separated list of OS families on which this build job should be run. | |
525 | * # Since plugin version 1.4 | |
526 | * invoker.os.family = !windows, unix, mac | |
527 | * | |
528 | * # A comma separated list of Maven versions on which this build should be run. | |
529 | * # Since plugin version 1.5 | |
530 | * invoker.maven.version = 2.0.10+, !2.1.0, !2.2.0 | |
531 | * </pre> | |
532 | * | |
533 | * @parameter expression="${invoker.invokerPropertiesFile}" default-value="invoker.properties" | |
534 | * @since 1.2 | |
535 | */ | |
536 | private String invokerPropertiesFile; | |
537 | ||
538 | /** | |
539 | * flag to enable show mvn version used for running its (cli option : -V,--show-version ) | |
540 | * | |
541 | * @parameter expression="${invoker.showVersion}" default-value="false" | |
542 | * @since 1.4 | |
543 | */ | |
544 | private boolean showVersion; | |
545 | ||
546 | /** | |
547 | * number of threads for running tests in parallel. | |
548 | * This will be the number of maven forked process in parallel. | |
549 | * | |
550 | * @parameter expression="${invoker.parallelThreads}" default-value="1" | |
551 | * @since 1.6 | |
552 | */ | |
553 | private int parallelThreads; | |
554 | ||
555 | /** | |
556 | * @parameter expression="${plugin.artifacts}" | |
557 | * @required | |
558 | * @readonly | |
559 | * @since 1.6 | |
560 | */ | |
561 | private List<Artifact> pluginArtifacts; | |
562 | ||
563 | ||
564 | /** | |
565 | * If enable and if you have a settings file configured for the execution, it will be merged with your user settings. | |
566 | * | |
567 | * @parameter expression="${invoker.mergeUserSettings}" default-value="false" | |
568 | * @since 1.6 | |
569 | */ | |
570 | private boolean mergeUserSettings; | |
571 | ||
572 | /** | |
573 | * The scripter runner that is responsible to execute hook scripts. | |
574 | */ | |
575 | private ScriptRunner scriptRunner; | |
576 | ||
577 | /** | |
578 | * A string used to prefix the file name of the filtered POMs in case the POMs couldn't be filtered in-place (i.e. | |
579 | * the projects were not cloned to a temporary directory), can be <code>null</code>. This will be set to | |
580 | * <code>null</code> if the POMs have already been filtered during cloning. | |
581 | */ | |
582 | 9 | private String filteredPomPrefix = "interpolated-"; |
583 | ||
584 | /** | |
585 | * The format for elapsed build time. | |
586 | */ | |
587 | 9 | private final DecimalFormat secFormat = new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) ); |
588 | ||
589 | /** | |
590 | * Invokes Maven on the configured test projects. | |
591 | * | |
592 | * @throws org.apache.maven.plugin.MojoExecutionException | |
593 | * If the goal encountered severe errors. | |
594 | * @throws org.apache.maven.plugin.MojoFailureException | |
595 | * If any of the Maven builds failed. | |
596 | */ | |
597 | public void execute() | |
598 | throws MojoExecutionException, MojoFailureException | |
599 | { | |
600 | 0 | if ( skipInvocation ) |
601 | { | |
602 | 0 | getLog().info( "Skipping invocation per configuration." |
603 | + " If this is incorrect, ensure the skipInvocation parameter is not set to true." ); | |
604 | 0 | return; |
605 | } | |
606 | ||
607 | // done it here to prevent issues with concurrent access in case of parallel run | |
608 | 0 | if ( !disableReports && !reportsDirectory.exists() ) |
609 | { | |
610 | 0 | reportsDirectory.mkdirs(); |
611 | } | |
612 | ||
613 | BuildJob[] buildJobs; | |
614 | 0 | if ( pom != null ) |
615 | { | |
616 | try | |
617 | { | |
618 | 0 | projectsDirectory = pom.getCanonicalFile().getParentFile(); |
619 | } | |
620 | 0 | catch ( IOException e ) |
621 | { | |
622 | 0 | throw new MojoExecutionException( |
623 | "Failed to discover projectsDirectory from pom File parameter." + " Reason: " + e.getMessage(), e ); | |
624 | 0 | } |
625 | ||
626 | 0 | buildJobs = new BuildJob[]{ new BuildJob( pom.getName(), BuildJob.Type.NORMAL ) }; |
627 | } | |
628 | else | |
629 | { | |
630 | try | |
631 | { | |
632 | 0 | buildJobs = getBuildJobs(); |
633 | } | |
634 | 0 | catch ( final IOException e ) |
635 | { | |
636 | 0 | throw new MojoExecutionException( |
637 | "Error retrieving POM list from includes, excludes, " + "and projects directory. Reason: " | |
638 | + e.getMessage(), e ); | |
639 | 0 | } |
640 | } | |
641 | ||
642 | 0 | if ( ( buildJobs == null ) || ( buildJobs.length < 1 ) ) |
643 | { | |
644 | 0 | getLog().info( "No projects were selected for execution." ); |
645 | 0 | return; |
646 | } | |
647 | ||
648 | 0 | if ( StringUtils.isEmpty( encoding ) ) |
649 | { | |
650 | 0 | getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING |
651 | + ", i.e. build is platform dependent!" ); | |
652 | } | |
653 | ||
654 | final List<String> scriptClassPath; | |
655 | 0 | if ( addTestClassPath ) |
656 | { | |
657 | 0 | scriptClassPath = new ArrayList<String>( testClassPath ); |
658 | 0 | for ( Artifact pluginArtifact : pluginArtifacts ) |
659 | { | |
660 | 0 | scriptClassPath.remove( pluginArtifact.getFile().getAbsolutePath() ); |
661 | } | |
662 | } | |
663 | else | |
664 | { | |
665 | 0 | scriptClassPath = null; |
666 | } | |
667 | 0 | scriptRunner = new ScriptRunner( getLog() ); |
668 | 0 | scriptRunner.setScriptEncoding( encoding ); |
669 | 0 | scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath ); |
670 | 0 | scriptRunner.setClassPath( scriptClassPath ); |
671 | ||
672 | 0 | Collection<String> collectedProjects = new LinkedHashSet<String>(); |
673 | 0 | for ( int i = 0; i < buildJobs.length; i++ ) |
674 | { | |
675 | 0 | collectProjects( projectsDirectory, buildJobs[i].getProject(), collectedProjects, true ); |
676 | } | |
677 | ||
678 | 0 | File projectsDir = projectsDirectory; |
679 | ||
680 | 0 | if ( cloneProjectsTo != null ) |
681 | { | |
682 | 0 | cloneProjects( collectedProjects ); |
683 | 0 | projectsDir = cloneProjectsTo; |
684 | } | |
685 | else | |
686 | { | |
687 | 0 | getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" ); |
688 | } | |
689 | ||
690 | 0 | runBuilds( projectsDir, buildJobs ); |
691 | ||
692 | 0 | processResults( new InvokerSession( buildJobs ) ); |
693 | 0 | } |
694 | ||
695 | /** | |
696 | * Processes the results of invoking the build jobs. | |
697 | * | |
698 | * @param invokerSession The session with the build jobs, must not be <code>null</code>. | |
699 | * @throws MojoFailureException If the mojo had failed as a result of invoking the build jobs. | |
700 | * @since 1.4 | |
701 | */ | |
702 | abstract void processResults( InvokerSession invokerSession ) | |
703 | throws MojoFailureException; | |
704 | ||
705 | /** | |
706 | * Creates a new reader for the specified file, using the plugin's {@link #encoding} parameter. | |
707 | * | |
708 | * @param file The file to create a reader for, must not be <code>null</code>. | |
709 | * @return The reader for the file, never <code>null</code>. | |
710 | * @throws java.io.IOException If the specified file was not found or the configured encoding is not supported. | |
711 | */ | |
712 | private Reader newReader( File file ) | |
713 | throws IOException | |
714 | { | |
715 | 4 | if ( StringUtils.isNotEmpty( encoding ) ) |
716 | { | |
717 | 0 | return ReaderFactory.newReader( file, encoding ); |
718 | } | |
719 | else | |
720 | { | |
721 | 4 | return ReaderFactory.newPlatformReader( file ); |
722 | } | |
723 | } | |
724 | ||
725 | /** | |
726 | * Collects all projects locally reachable from the specified project. The method will as such try to read the POM | |
727 | * and recursively follow its parent/module elements. | |
728 | * | |
729 | * @param projectsDir The base directory of all projects, must not be <code>null</code>. | |
730 | * @param projectPath The relative path of the current project, can denote either the POM or its base directory, | |
731 | * must not be <code>null</code>. | |
732 | * @param projectPaths The set of already collected projects to add new projects to, must not be <code>null</code>. | |
733 | * This set will hold the relative paths to either a POM file or a project base directory. | |
734 | * @param included A flag indicating whether the specified project has been explicitly included via the parameter | |
735 | * {@link #pomIncludes}. Such projects will always be added to the result set even if there is no | |
736 | * corresponding POM. | |
737 | * @throws org.apache.maven.plugin.MojoExecutionException | |
738 | * If the project tree could not be traversed. | |
739 | */ | |
740 | private void collectProjects( File projectsDir, String projectPath, Collection<String> projectPaths, | |
741 | boolean included ) | |
742 | throws MojoExecutionException | |
743 | { | |
744 | 0 | projectPath = projectPath.replace( '\\', '/' ); |
745 | 0 | File pomFile = new File( projectsDir, projectPath ); |
746 | 0 | if ( pomFile.isDirectory() ) |
747 | { | |
748 | 0 | pomFile = new File( pomFile, "pom.xml" ); |
749 | 0 | if ( !pomFile.exists() ) |
750 | { | |
751 | 0 | if ( included ) |
752 | { | |
753 | 0 | projectPaths.add( projectPath ); |
754 | } | |
755 | 0 | return; |
756 | } | |
757 | 0 | if ( !projectPath.endsWith( "/" ) ) |
758 | { | |
759 | 0 | projectPath += '/'; |
760 | } | |
761 | 0 | projectPath += "pom.xml"; |
762 | } | |
763 | 0 | else if ( !pomFile.isFile() ) |
764 | { | |
765 | 0 | return; |
766 | } | |
767 | 0 | if ( !projectPaths.add( projectPath ) ) |
768 | { | |
769 | 0 | return; |
770 | } | |
771 | 0 | getLog().debug( "Collecting parent/child projects of " + projectPath ); |
772 | ||
773 | 0 | Model model = PomUtils.loadPom( pomFile ); |
774 | ||
775 | try | |
776 | { | |
777 | 0 | String projectsRoot = projectsDir.getCanonicalPath(); |
778 | 0 | String projectDir = pomFile.getParent(); |
779 | ||
780 | 0 | String parentPath = "../pom.xml"; |
781 | 0 | if ( model.getParent() != null && StringUtils.isNotEmpty( model.getParent().getRelativePath() ) ) |
782 | { | |
783 | 0 | parentPath = model.getParent().getRelativePath(); |
784 | } | |
785 | 0 | String parent = relativizePath( new File( projectDir, parentPath ), projectsRoot ); |
786 | 0 | if ( parent != null ) |
787 | { | |
788 | 0 | collectProjects( projectsDir, parent, projectPaths, false ); |
789 | } | |
790 | ||
791 | 0 | Collection<String> modulePaths = new LinkedHashSet<String>(); |
792 | ||
793 | 0 | modulePaths.addAll( (List<String>) model.getModules() ); |
794 | ||
795 | 0 | for ( Profile profile : (List<Profile>) model.getProfiles() ) |
796 | { | |
797 | 0 | modulePaths.addAll( (List<String>) profile.getModules() ); |
798 | } | |
799 | ||
800 | 0 | for ( String modulePath : modulePaths ) |
801 | { | |
802 | 0 | String module = relativizePath( new File( projectDir, modulePath ), projectsRoot ); |
803 | 0 | if ( module != null ) |
804 | { | |
805 | 0 | collectProjects( projectsDir, module, projectPaths, false ); |
806 | } | |
807 | 0 | } |
808 | } | |
809 | 0 | catch ( IOException e ) |
810 | { | |
811 | 0 | throw new MojoExecutionException( "Failed to analyze POM: " + pomFile, e ); |
812 | 0 | } |
813 | 0 | } |
814 | ||
815 | /** | |
816 | * Copies the specified projects to the directory given by {@link #cloneProjectsTo}. A project may either be denoted | |
817 | * by a path to a POM file or merely by a path to a base directory. During cloning, the POM files will be filtered. | |
818 | * | |
819 | * @param projectPaths The paths to the projects to clone, relative to the projects directory, must not be | |
820 | * <code>null</code> nor contain <code>null</code> elements. | |
821 | * @throws org.apache.maven.plugin.MojoExecutionException | |
822 | * If the the projects could not be copied/filtered. | |
823 | */ | |
824 | private void cloneProjects( Collection<String> projectPaths ) | |
825 | throws MojoExecutionException | |
826 | { | |
827 | 0 | if ( !cloneProjectsTo.mkdirs() && cloneClean ) |
828 | { | |
829 | try | |
830 | { | |
831 | 0 | FileUtils.cleanDirectory( cloneProjectsTo ); |
832 | } | |
833 | 0 | catch ( IOException e ) |
834 | { | |
835 | 0 | throw new MojoExecutionException( |
836 | "Could not clean the cloneProjectsTo directory. Reason: " + e.getMessage(), e ); | |
837 | 0 | } |
838 | } | |
839 | ||
840 | // determine project directories to clone | |
841 | 0 | Collection<String> dirs = new LinkedHashSet<String>(); |
842 | 0 | for ( String projectPath : projectPaths ) |
843 | { | |
844 | 0 | if ( !new File( projectsDirectory, projectPath ).isDirectory() ) |
845 | { | |
846 | 0 | projectPath = getParentPath( projectPath ); |
847 | } | |
848 | 0 | dirs.add( projectPath ); |
849 | } | |
850 | ||
851 | 0 | boolean filter = false; |
852 | ||
853 | // clone project directories | |
854 | try | |
855 | { | |
856 | 0 | filter = !cloneProjectsTo.getCanonicalFile().equals( projectsDirectory.getCanonicalFile() ); |
857 | ||
858 | 0 | List<String> clonedSubpaths = new ArrayList<String>(); |
859 | ||
860 | 0 | for ( String subpath : dirs ) |
861 | { | |
862 | // skip this project if its parent directory is also scheduled for cloning | |
863 | 0 | if ( !".".equals( subpath ) && dirs.contains( getParentPath( subpath ) ) ) |
864 | { | |
865 | 0 | continue; |
866 | } | |
867 | ||
868 | // avoid copying subdirs that are already cloned. | |
869 | 0 | if ( !alreadyCloned( subpath, clonedSubpaths ) ) |
870 | { | |
871 | // avoid creating new files that point to dir/. | |
872 | 0 | if ( ".".equals( subpath ) ) |
873 | { | |
874 | 0 | String cloneSubdir = relativizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() ); |
875 | ||
876 | // avoid infinite recursion if the cloneTo path is a subdirectory. | |
877 | 0 | if ( cloneSubdir != null ) |
878 | { | |
879 | 0 | File temp = File.createTempFile( "pre-invocation-clone.", "" ); |
880 | 0 | temp.delete(); |
881 | 0 | temp.mkdirs(); |
882 | ||
883 | 0 | copyDirectoryStructure( projectsDirectory, temp ); |
884 | ||
885 | 0 | FileUtils.deleteDirectory( new File( temp, cloneSubdir ) ); |
886 | ||
887 | 0 | copyDirectoryStructure( temp, cloneProjectsTo ); |
888 | 0 | } |
889 | else | |
890 | { | |
891 | 0 | copyDirectoryStructure( projectsDirectory, cloneProjectsTo ); |
892 | } | |
893 | 0 | } |
894 | else | |
895 | { | |
896 | 0 | File srcDir = new File( projectsDirectory, subpath ); |
897 | 0 | File dstDir = new File( cloneProjectsTo, subpath ); |
898 | 0 | copyDirectoryStructure( srcDir, dstDir ); |
899 | } | |
900 | ||
901 | 0 | clonedSubpaths.add( subpath ); |
902 | } | |
903 | } | |
904 | } | |
905 | 0 | catch ( IOException e ) |
906 | { | |
907 | 0 | throw new MojoExecutionException( |
908 | "Failed to clone projects from: " + projectsDirectory + " to: " + cloneProjectsTo + ". Reason: " | |
909 | + e.getMessage(), e ); | |
910 | 0 | } |
911 | ||
912 | // filter cloned POMs | |
913 | 0 | if ( filter ) |
914 | { | |
915 | 0 | for ( String projectPath : projectPaths ) |
916 | { | |
917 | 0 | File pomFile = new File( cloneProjectsTo, projectPath ); |
918 | 0 | if ( pomFile.isFile() ) |
919 | { | |
920 | 0 | buildInterpolatedFile( pomFile, pomFile ); |
921 | } | |
922 | 0 | } |
923 | 0 | filteredPomPrefix = null; |
924 | } | |
925 | 0 | } |
926 | ||
927 | /** | |
928 | * Gets the parent path of the specified relative path. | |
929 | * | |
930 | * @param path The relative path whose parent should be retrieved, must not be <code>null</code>. | |
931 | * @return The parent path or "." if the specified path has no parent, never <code>null</code>. | |
932 | */ | |
933 | private String getParentPath( String path ) | |
934 | { | |
935 | 0 | int lastSep = Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ) ); |
936 | 0 | return ( lastSep < 0 ) ? "." : path.substring( 0, lastSep ); |
937 | } | |
938 | ||
939 | /** | |
940 | * Copied a directory structure with deafault exclusions (.svn, CVS, etc) | |
941 | * | |
942 | * @param sourceDir The source directory to copy, must not be <code>null</code>. | |
943 | * @param destDir The target directory to copy to, must not be <code>null</code>. | |
944 | * @throws java.io.IOException If the directory structure could not be copied. | |
945 | */ | |
946 | private void copyDirectoryStructure( File sourceDir, File destDir ) | |
947 | throws IOException | |
948 | { | |
949 | 0 | DirectoryScanner scanner = new DirectoryScanner(); |
950 | 0 | scanner.setBasedir( sourceDir ); |
951 | 0 | if ( !cloneAllFiles ) |
952 | { | |
953 | 0 | scanner.addDefaultExcludes(); |
954 | } | |
955 | 0 | scanner.scan(); |
956 | ||
957 | /* | |
958 | * NOTE: Make sure the destination directory is always there (even if empty) to support POM-less ITs. | |
959 | */ | |
960 | 0 | destDir.mkdirs(); |
961 | 0 | String[] includedDirs = scanner.getIncludedDirectories(); |
962 | 0 | for ( int i = 0; i < includedDirs.length; ++i ) |
963 | { | |
964 | 0 | File clonedDir = new File( destDir, includedDirs[i] ); |
965 | 0 | clonedDir.mkdirs(); |
966 | } | |
967 | ||
968 | 0 | String[] includedFiles = scanner.getIncludedFiles(); |
969 | 0 | for ( int i = 0; i < includedFiles.length; ++i ) |
970 | { | |
971 | 0 | File sourceFile = new File( sourceDir, includedFiles[i] ); |
972 | 0 | File destFile = new File( destDir, includedFiles[i] ); |
973 | 0 | FileUtils.copyFile( sourceFile, destFile ); |
974 | } | |
975 | 0 | } |
976 | ||
977 | /** | |
978 | * Determines whether the specified sub path has already been cloned, i.e. whether one of its ancestor directories | |
979 | * was already cloned. | |
980 | * | |
981 | * @param subpath The sub path to check, must not be <code>null</code>. | |
982 | * @param clonedSubpaths The list of already cloned paths, must not be <code>null</code> nor contain | |
983 | * <code>null</code> elements. | |
984 | * @return <code>true</code> if the specified path has already been cloned, <code>false</code> otherwise. | |
985 | */ | |
986 | static boolean alreadyCloned( String subpath, List<String> clonedSubpaths ) | |
987 | { | |
988 | 4 | for ( String path : clonedSubpaths ) |
989 | { | |
990 | 3 | if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) ) |
991 | { | |
992 | 2 | return true; |
993 | } | |
994 | } | |
995 | ||
996 | 2 | return false; |
997 | } | |
998 | ||
999 | /** | |
1000 | * Runs the specified build jobs. | |
1001 | * | |
1002 | * @param projectsDir The base directory of all projects, must not be <code>null</code>. | |
1003 | * @param buildJobs The build jobs to run must not be <code>null</code> nor contain <code>null</code> elements. | |
1004 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1005 | * If any build could not be launched. | |
1006 | */ | |
1007 | private void runBuilds( final File projectsDir, BuildJob[] buildJobs ) | |
1008 | throws MojoExecutionException | |
1009 | { | |
1010 | 0 | if ( !localRepositoryPath.exists() ) |
1011 | { | |
1012 | 0 | localRepositoryPath.mkdirs(); |
1013 | } | |
1014 | ||
1015 | //----------------------------------------------- | |
1016 | // interpolate settings file | |
1017 | //----------------------------------------------- | |
1018 | ||
1019 | 0 | File interpolatedSettingsFile = null; |
1020 | 0 | if ( settingsFile != null ) |
1021 | { | |
1022 | 0 | if ( cloneProjectsTo != null ) |
1023 | { | |
1024 | 0 | interpolatedSettingsFile = new File( cloneProjectsTo, "interpolated-" + settingsFile.getName() ); |
1025 | } | |
1026 | else | |
1027 | { | |
1028 | 0 | interpolatedSettingsFile = |
1029 | new File( settingsFile.getParentFile(), "interpolated-" + settingsFile.getName() ); | |
1030 | } | |
1031 | 0 | buildInterpolatedFile( settingsFile, interpolatedSettingsFile ); |
1032 | } | |
1033 | ||
1034 | //----------------------------------------------- | |
1035 | // merge settings file | |
1036 | //----------------------------------------------- | |
1037 | ||
1038 | 0 | SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer(); |
1039 | ||
1040 | File mergedSettingsFile; | |
1041 | 0 | Settings mergedSettings = this.settings; |
1042 | 0 | if ( mergeUserSettings ) |
1043 | { | |
1044 | 0 | if ( interpolatedSettingsFile != null ) |
1045 | { | |
1046 | // Have to merge the specified settings file (dominant) and the one of the invoking Maven process | |
1047 | 0 | Reader reader = null; |
1048 | try | |
1049 | { | |
1050 | 0 | reader = new XmlStreamReader( interpolatedSettingsFile ); |
1051 | 0 | SettingsXpp3Reader settingsReader = new SettingsXpp3Reader(); |
1052 | 0 | Settings dominantSettings = settingsReader.read( reader ); |
1053 | 0 | Settings recessiveSettings = this.settings; |
1054 | ||
1055 | 0 | SettingsUtils.merge( dominantSettings, recessiveSettings, TrackableBase.USER_LEVEL ); |
1056 | ||
1057 | 0 | mergedSettings = dominantSettings; |
1058 | 0 | getLog().debug( "Merged specified settings file with settings of invoking process" ); |
1059 | } | |
1060 | 0 | catch ( XmlPullParserException e ) |
1061 | { | |
1062 | 0 | throw new MojoExecutionException( "Could not read specified settings file", e ); |
1063 | } | |
1064 | 0 | catch ( IOException e ) |
1065 | { | |
1066 | 0 | throw new MojoExecutionException( "Could not read specified settings file", e ); |
1067 | } | |
1068 | finally | |
1069 | { | |
1070 | 0 | IOUtil.close( reader ); |
1071 | 0 | } |
1072 | } | |
1073 | } | |
1074 | 0 | if ( this.settingsFile != null && !mergeUserSettings ) |
1075 | { | |
1076 | 0 | mergedSettingsFile = interpolatedSettingsFile; |
1077 | } | |
1078 | else | |
1079 | { | |
1080 | try | |
1081 | { | |
1082 | 0 | mergedSettingsFile = File.createTempFile( "invoker-settings", ".xml" ); |
1083 | ||
1084 | 0 | FileWriter fileWriter = null; |
1085 | try | |
1086 | { | |
1087 | 0 | fileWriter = new FileWriter( mergedSettingsFile ); |
1088 | 0 | settingsWriter.write( fileWriter, mergedSettings ); |
1089 | } | |
1090 | finally | |
1091 | { | |
1092 | 0 | IOUtil.close( fileWriter ); |
1093 | 0 | } |
1094 | ||
1095 | 0 | if ( getLog().isDebugEnabled() ) |
1096 | { | |
1097 | 0 | getLog().debug( |
1098 | "Created temporary file for invoker settings.xml: " + mergedSettingsFile.getAbsolutePath() ); | |
1099 | } | |
1100 | ||
1101 | } | |
1102 | 0 | catch ( IOException e ) |
1103 | { | |
1104 | 0 | throw new MojoExecutionException( "Could not create temporary file for invoker settings.xml", e ); |
1105 | 0 | } |
1106 | } | |
1107 | 0 | final File finalSettingsFile = mergedSettingsFile; |
1108 | ||
1109 | try | |
1110 | { | |
1111 | 0 | if ( isParallelRun() ) |
1112 | { | |
1113 | 0 | getLog().info( "use parallelThreads " + parallelThreads ); |
1114 | ||
1115 | 0 | ExecutorService executorService = Executors.newFixedThreadPool( parallelThreads ); |
1116 | 0 | for ( int i = 0; i < buildJobs.length; i++ ) |
1117 | { | |
1118 | 0 | final BuildJob project = buildJobs[i]; |
1119 | 0 | executorService.execute( new Runnable() |
1120 | 0 | { |
1121 | ||
1122 | public void run() | |
1123 | { | |
1124 | try | |
1125 | { | |
1126 | 0 | runBuild( projectsDir, project, finalSettingsFile ); |
1127 | } | |
1128 | 0 | catch ( MojoExecutionException e ) |
1129 | { | |
1130 | 0 | throw new RuntimeException( e.getMessage(), e ); |
1131 | 0 | } |
1132 | 0 | } |
1133 | } ); | |
1134 | } | |
1135 | ||
1136 | try | |
1137 | { | |
1138 | 0 | executorService.shutdown(); |
1139 | // TODO add a configurable time out | |
1140 | 0 | executorService.awaitTermination( Long.MAX_VALUE, TimeUnit.MILLISECONDS ); |
1141 | } | |
1142 | 0 | catch ( InterruptedException e ) |
1143 | { | |
1144 | 0 | throw new MojoExecutionException( e.getMessage(), e ); |
1145 | 0 | } |
1146 | ||
1147 | 0 | } |
1148 | else | |
1149 | { | |
1150 | 0 | for ( int i = 0; i < buildJobs.length; i++ ) |
1151 | { | |
1152 | 0 | BuildJob project = buildJobs[i]; |
1153 | 0 | runBuild( projectsDir, project, finalSettingsFile ); |
1154 | } | |
1155 | } | |
1156 | } | |
1157 | finally | |
1158 | { | |
1159 | 0 | if ( interpolatedSettingsFile != null && cloneProjectsTo == null ) |
1160 | { | |
1161 | 0 | interpolatedSettingsFile.delete(); |
1162 | } | |
1163 | 0 | if ( mergedSettingsFile != null && mergedSettingsFile.exists() ) |
1164 | { | |
1165 | 0 | mergedSettingsFile.delete(); |
1166 | } | |
1167 | ||
1168 | } | |
1169 | 0 | } |
1170 | ||
1171 | /** | |
1172 | * Runs the specified project. | |
1173 | * | |
1174 | * @param projectsDir The base directory of all projects, must not be <code>null</code>. | |
1175 | * @param buildJob The build job to run, must not be <code>null</code>. | |
1176 | * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code> to use | |
1177 | * the current user settings. | |
1178 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1179 | * If the project could not be launched. | |
1180 | */ | |
1181 | private void runBuild( File projectsDir, BuildJob buildJob, File settingsFile ) | |
1182 | throws MojoExecutionException | |
1183 | { | |
1184 | 0 | File pomFile = new File( projectsDir, buildJob.getProject() ); |
1185 | File basedir; | |
1186 | 0 | if ( pomFile.isDirectory() ) |
1187 | { | |
1188 | 0 | basedir = pomFile; |
1189 | 0 | pomFile = new File( basedir, "pom.xml" ); |
1190 | 0 | if ( !pomFile.exists() ) |
1191 | { | |
1192 | 0 | pomFile = null; |
1193 | } | |
1194 | else | |
1195 | { | |
1196 | 0 | buildJob.setProject( buildJob.getProject() + File.separator + "pom.xml" ); |
1197 | } | |
1198 | } | |
1199 | else | |
1200 | { | |
1201 | 0 | basedir = pomFile.getParentFile(); |
1202 | } | |
1203 | ||
1204 | 0 | getLog().info( "Building: " + buildJob.getProject() ); |
1205 | ||
1206 | 0 | File interpolatedPomFile = null; |
1207 | 0 | if ( pomFile != null ) |
1208 | { | |
1209 | 0 | if ( filteredPomPrefix != null ) |
1210 | { | |
1211 | 0 | interpolatedPomFile = new File( basedir, filteredPomPrefix + pomFile.getName() ); |
1212 | 0 | buildInterpolatedFile( pomFile, interpolatedPomFile ); |
1213 | } | |
1214 | else | |
1215 | { | |
1216 | 0 | interpolatedPomFile = pomFile; |
1217 | } | |
1218 | } | |
1219 | ||
1220 | 0 | InvokerProperties invokerProperties = getInvokerProperties( basedir ); |
1221 | ||
1222 | // let's set what details we can | |
1223 | 0 | buildJob.setName( invokerProperties.getJobName() ); |
1224 | 0 | buildJob.setDescription( invokerProperties.getJobDescription() ); |
1225 | ||
1226 | try | |
1227 | { | |
1228 | 0 | if ( isSelected( invokerProperties ) ) |
1229 | { | |
1230 | 0 | long milliseconds = System.currentTimeMillis(); |
1231 | boolean executed; | |
1232 | try | |
1233 | { | |
1234 | 0 | executed = runBuild( basedir, interpolatedPomFile, settingsFile, invokerProperties ); |
1235 | } | |
1236 | finally | |
1237 | { | |
1238 | 0 | milliseconds = System.currentTimeMillis() - milliseconds; |
1239 | 0 | buildJob.setTime( milliseconds / 1000.0 ); |
1240 | 0 | } |
1241 | ||
1242 | 0 | if ( executed ) |
1243 | { | |
1244 | ||
1245 | 0 | buildJob.setResult( BuildJob.Result.SUCCESS ); |
1246 | ||
1247 | 0 | if ( !suppressSummaries ) |
1248 | { | |
1249 | 0 | getLog().info( "..SUCCESS " + formatTime( buildJob.getTime() ) ); |
1250 | } | |
1251 | } | |
1252 | else | |
1253 | { | |
1254 | 0 | buildJob.setResult( BuildJob.Result.SKIPPED ); |
1255 | ||
1256 | 0 | if ( !suppressSummaries ) |
1257 | { | |
1258 | 0 | getLog().info( "..SKIPPED " + formatTime( buildJob.getTime() ) ); |
1259 | } | |
1260 | } | |
1261 | 0 | } |
1262 | else | |
1263 | { | |
1264 | 0 | buildJob.setResult( BuildJob.Result.SKIPPED ); |
1265 | ||
1266 | 0 | if ( !suppressSummaries ) |
1267 | { | |
1268 | 0 | getLog().info( "..SKIPPED " ); |
1269 | } | |
1270 | } | |
1271 | } | |
1272 | 0 | catch ( RunErrorException e ) |
1273 | { | |
1274 | 0 | buildJob.setResult( BuildJob.Result.ERROR ); |
1275 | 0 | buildJob.setFailureMessage( e.getMessage() ); |
1276 | ||
1277 | 0 | if ( !suppressSummaries ) |
1278 | { | |
1279 | 0 | getLog().info( "..ERROR " + formatTime( buildJob.getTime() ) ); |
1280 | 0 | getLog().info( " " + e.getMessage() ); |
1281 | } | |
1282 | } | |
1283 | 0 | catch ( RunFailureException e ) |
1284 | { | |
1285 | 0 | buildJob.setResult( e.getType() ); |
1286 | 0 | buildJob.setFailureMessage( e.getMessage() ); |
1287 | ||
1288 | 0 | if ( !suppressSummaries ) |
1289 | { | |
1290 | 0 | getLog().info( "..FAILED " + formatTime( buildJob.getTime() ) ); |
1291 | 0 | getLog().info( " " + e.getMessage() ); |
1292 | } | |
1293 | } | |
1294 | finally | |
1295 | { | |
1296 | 0 | if ( interpolatedPomFile != null && StringUtils.isNotEmpty( filteredPomPrefix ) ) |
1297 | { | |
1298 | 0 | interpolatedPomFile.delete(); |
1299 | } | |
1300 | 0 | writeBuildReport( buildJob ); |
1301 | 0 | } |
1302 | 0 | } |
1303 | ||
1304 | /** | |
1305 | * Determines whether selector conditions of the specified invoker properties match the current environment. | |
1306 | * | |
1307 | * @param invokerProperties The invoker properties to check, must not be <code>null</code>. | |
1308 | * @return <code>true</code> if the job corresponding to the properties should be run, <code>false</code> otherwise. | |
1309 | */ | |
1310 | private boolean isSelected( InvokerProperties invokerProperties ) | |
1311 | { | |
1312 | 0 | if ( !SelectorUtils.isMavenVersion( invokerProperties.getMavenVersion() ) ) |
1313 | { | |
1314 | 0 | return false; |
1315 | } | |
1316 | ||
1317 | 0 | if ( !SelectorUtils.isJreVersion( invokerProperties.getJreVersion() ) ) |
1318 | { | |
1319 | 0 | return false; |
1320 | } | |
1321 | ||
1322 | 0 | if ( !SelectorUtils.isOsFamily( invokerProperties.getOsFamily() ) ) |
1323 | { | |
1324 | 0 | return false; |
1325 | } | |
1326 | ||
1327 | 0 | return true; |
1328 | } | |
1329 | ||
1330 | /** | |
1331 | * Writes the XML report for the specified build job unless report generation has been disabled. | |
1332 | * | |
1333 | * @param buildJob The build job whose report should be written, must not be <code>null</code>. | |
1334 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1335 | * If the report could not be written. | |
1336 | */ | |
1337 | private void writeBuildReport( BuildJob buildJob ) | |
1338 | throws MojoExecutionException | |
1339 | { | |
1340 | 0 | if ( disableReports ) |
1341 | { | |
1342 | 0 | return; |
1343 | } | |
1344 | ||
1345 | 0 | String safeFileName = buildJob.getProject().replace( '/', '_' ).replace( '\\', '_' ).replace( ' ', '_' ); |
1346 | 0 | if ( safeFileName.endsWith( "_pom.xml" ) ) |
1347 | { | |
1348 | 0 | safeFileName = safeFileName.substring( 0, safeFileName.length() - "_pom.xml".length() ); |
1349 | } | |
1350 | ||
1351 | 0 | File reportFile = new File( reportsDirectory, "BUILD-" + safeFileName + ".xml" ); |
1352 | try | |
1353 | { | |
1354 | 0 | FileOutputStream fos = new FileOutputStream( reportFile ); |
1355 | try | |
1356 | { | |
1357 | 0 | Writer osw = new OutputStreamWriter( fos, buildJob.getModelEncoding() ); |
1358 | 0 | BuildJobXpp3Writer writer = new BuildJobXpp3Writer(); |
1359 | 0 | writer.write( osw, buildJob ); |
1360 | 0 | osw.close(); |
1361 | } | |
1362 | finally | |
1363 | { | |
1364 | 0 | fos.close(); |
1365 | 0 | } |
1366 | } | |
1367 | 0 | catch ( IOException e ) |
1368 | { | |
1369 | 0 | throw new MojoExecutionException( "Failed to write build report " + reportFile, e ); |
1370 | 0 | } |
1371 | 0 | } |
1372 | ||
1373 | /** | |
1374 | * Formats the specified build duration time. | |
1375 | * | |
1376 | * @param seconds The duration of the build. | |
1377 | * @return The formatted time, never <code>null</code>. | |
1378 | */ | |
1379 | private String formatTime( double seconds ) | |
1380 | { | |
1381 | 0 | return secFormat.format( seconds ); |
1382 | } | |
1383 | ||
1384 | /** | |
1385 | * Runs the specified project. | |
1386 | * | |
1387 | * @param basedir The base directory of the project, must not be <code>null</code>. | |
1388 | * @param pomFile The (already interpolated) POM file, may be <code>null</code> for a POM-less Maven invocation. | |
1389 | * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code>. Will be | |
1390 | * merged with the settings file of the invoking Maven process. | |
1391 | * @param invokerProperties The properties to use. | |
1392 | * @return <code>true</code> if the project was launched or <code>false</code> if the selector script indicated that | |
1393 | * the project should be skipped. | |
1394 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1395 | * If the project could not be launched. | |
1396 | * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException | |
1397 | * If either a hook script or the build itself failed. | |
1398 | */ | |
1399 | private boolean runBuild( File basedir, File pomFile, File settingsFile, InvokerProperties invokerProperties ) | |
1400 | throws MojoExecutionException, RunFailureException | |
1401 | { | |
1402 | 0 | if ( getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty() ) |
1403 | { | |
1404 | 0 | Properties props = invokerProperties.getProperties(); |
1405 | 0 | getLog().debug( "Using invoker properties:" ); |
1406 | 0 | for ( String key : new TreeSet<String>( (Set) props.keySet() ) ) |
1407 | { | |
1408 | 0 | String value = props.getProperty( key ); |
1409 | 0 | getLog().debug( " " + key + " = " + value ); |
1410 | 0 | } |
1411 | } | |
1412 | ||
1413 | 0 | List<String> goals = getGoals( basedir ); |
1414 | ||
1415 | 0 | List<String> profiles = getProfiles( basedir ); |
1416 | ||
1417 | 0 | Map<String, Object> context = new LinkedHashMap<String, Object>(); |
1418 | ||
1419 | 0 | FileLogger logger = setupLogger( basedir ); |
1420 | try | |
1421 | { | |
1422 | try | |
1423 | { | |
1424 | 0 | scriptRunner.run( "selector script", basedir, selectorScript, context, logger, BuildJob.Result.SKIPPED, |
1425 | false ); | |
1426 | } | |
1427 | 0 | catch ( RunErrorException e ) |
1428 | { | |
1429 | 0 | throw e; |
1430 | } | |
1431 | 0 | catch ( RunFailureException e ) |
1432 | { | |
1433 | 0 | return false; |
1434 | 0 | } |
1435 | ||
1436 | 0 | scriptRunner.run( "pre-build script", basedir, preBuildHookScript, context, logger, |
1437 | BuildJob.Result.FAILURE_PRE_HOOK, false ); | |
1438 | ||
1439 | 0 | final InvocationRequest request = new DefaultInvocationRequest(); |
1440 | ||
1441 | 0 | request.setLocalRepositoryDirectory( localRepositoryPath ); |
1442 | ||
1443 | 0 | request.setInteractive( false ); |
1444 | ||
1445 | 0 | request.setShowErrors( showErrors ); |
1446 | ||
1447 | 0 | request.setDebug( debug ); |
1448 | ||
1449 | 0 | request.setShowVersion( showVersion ); |
1450 | ||
1451 | 0 | if ( logger != null ) |
1452 | { | |
1453 | 0 | request.setErrorHandler( logger ); |
1454 | ||
1455 | 0 | request.setOutputHandler( logger ); |
1456 | } | |
1457 | ||
1458 | 0 | if ( mavenHome != null ) |
1459 | { | |
1460 | 0 | invoker.setMavenHome( mavenHome ); |
1461 | 0 | request.addShellEnvironment( "M2_HOME", mavenHome.getAbsolutePath() ); |
1462 | } | |
1463 | ||
1464 | 0 | if ( javaHome != null ) |
1465 | { | |
1466 | 0 | request.setJavaHome( javaHome ); |
1467 | } | |
1468 | ||
1469 | 0 | for ( int invocationIndex = 1; ; invocationIndex++ ) |
1470 | { | |
1471 | 0 | if ( invocationIndex > 1 && !invokerProperties.isInvocationDefined( invocationIndex ) ) |
1472 | { | |
1473 | 0 | break; |
1474 | } | |
1475 | ||
1476 | 0 | request.setBaseDirectory( basedir ); |
1477 | ||
1478 | 0 | request.setPomFile( pomFile ); |
1479 | ||
1480 | 0 | request.setGoals( goals ); |
1481 | ||
1482 | 0 | request.setProfiles( profiles ); |
1483 | ||
1484 | 0 | request.setMavenOpts( mavenOpts ); |
1485 | ||
1486 | 0 | request.setOffline( false ); |
1487 | ||
1488 | 0 | request.setUserSettingsFile( settingsFile ); |
1489 | ||
1490 | 0 | Properties systemProperties = |
1491 | getSystemProperties( basedir, invokerProperties.getSystemPropertiesFile( invocationIndex ) ); | |
1492 | 0 | request.setProperties( systemProperties ); |
1493 | ||
1494 | 0 | invokerProperties.configureInvocation( request, invocationIndex ); |
1495 | ||
1496 | 0 | if ( getLog().isDebugEnabled() ) |
1497 | { | |
1498 | try | |
1499 | { | |
1500 | 0 | getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() ); |
1501 | 0 | getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) ); |
1502 | } | |
1503 | 0 | catch ( CommandLineConfigurationException e ) |
1504 | { | |
1505 | 0 | getLog().debug( "Failed to display command line: " + e.getMessage() ); |
1506 | 0 | } |
1507 | } | |
1508 | ||
1509 | InvocationResult result; | |
1510 | ||
1511 | try | |
1512 | { | |
1513 | 0 | result = invoker.execute( request ); |
1514 | } | |
1515 | 0 | catch ( final MavenInvocationException e ) |
1516 | { | |
1517 | 0 | getLog().debug( "Error invoking Maven: " + e.getMessage(), e ); |
1518 | 0 | throw new RunFailureException( "Maven invocation failed. " + e.getMessage(), |
1519 | BuildJob.Result.FAILURE_BUILD ); | |
1520 | 0 | } |
1521 | ||
1522 | 0 | verify( result, invocationIndex, invokerProperties, logger ); |
1523 | } | |
1524 | ||
1525 | 0 | scriptRunner.run( "post-build script", basedir, postBuildHookScript, context, logger, |
1526 | BuildJob.Result.FAILURE_POST_HOOK, true ); | |
1527 | } | |
1528 | 0 | catch ( IOException e ) |
1529 | { | |
1530 | 0 | throw new MojoExecutionException( e.getMessage(), e ); |
1531 | } | |
1532 | finally | |
1533 | { | |
1534 | 0 | if ( logger != null ) |
1535 | { | |
1536 | 0 | logger.close(); |
1537 | } | |
1538 | } | |
1539 | 0 | return true; |
1540 | } | |
1541 | ||
1542 | /** | |
1543 | * Initializes the build logger for the specified project. | |
1544 | * | |
1545 | * @param basedir The base directory of the project, must not be <code>null</code>. | |
1546 | * @return The build logger or <code>null</code> if logging has been disabled. | |
1547 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1548 | * If the log file could not be created. | |
1549 | */ | |
1550 | private FileLogger setupLogger( File basedir ) | |
1551 | throws MojoExecutionException | |
1552 | { | |
1553 | 0 | FileLogger logger = null; |
1554 | ||
1555 | 0 | if ( !noLog ) |
1556 | { | |
1557 | 0 | File outputLog = new File( basedir, "build.log" ); |
1558 | try | |
1559 | { | |
1560 | 0 | if ( streamLogs ) |
1561 | { | |
1562 | 0 | logger = new FileLogger( outputLog, getLog() ); |
1563 | } | |
1564 | else | |
1565 | { | |
1566 | 0 | logger = new FileLogger( outputLog ); |
1567 | } | |
1568 | ||
1569 | 0 | getLog().debug( "build log initialized in: " + outputLog ); |
1570 | } | |
1571 | 0 | catch ( IOException e ) |
1572 | { | |
1573 | 0 | throw new MojoExecutionException( "Error initializing build logfile in: " + outputLog, e ); |
1574 | 0 | } |
1575 | } | |
1576 | ||
1577 | 0 | return logger; |
1578 | } | |
1579 | ||
1580 | /** | |
1581 | * Gets the system properties to use for the specified project. | |
1582 | * | |
1583 | * @param basedir The base directory of the project, must not be <code>null</code>. | |
1584 | * @param filename The filename to the properties file to load, may be <code>null</code> to use the default path | |
1585 | * given by {@link #testPropertiesFile}. | |
1586 | * @return The system properties to use, may be empty but never <code>null</code>. | |
1587 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1588 | * If the properties file exists but could not be read. | |
1589 | */ | |
1590 | private Properties getSystemProperties( final File basedir, final String filename ) | |
1591 | throws MojoExecutionException | |
1592 | { | |
1593 | 0 | Properties collectedTestProperties = new Properties(); |
1594 | ||
1595 | 0 | if ( testProperties != null ) |
1596 | { | |
1597 | 0 | collectedTestProperties.putAll( testProperties ); |
1598 | } | |
1599 | ||
1600 | 0 | if ( properties != null ) |
1601 | { | |
1602 | 0 | collectedTestProperties.putAll( properties ); |
1603 | } | |
1604 | ||
1605 | 0 | File propertiesFile = null; |
1606 | 0 | if ( filename != null ) |
1607 | { | |
1608 | 0 | propertiesFile = new File( basedir, filename ); |
1609 | } | |
1610 | 0 | else if ( testPropertiesFile != null ) |
1611 | { | |
1612 | 0 | propertiesFile = new File( basedir, testPropertiesFile ); |
1613 | } | |
1614 | ||
1615 | 0 | if ( propertiesFile != null && propertiesFile.isFile() ) |
1616 | { | |
1617 | 0 | InputStream fin = null; |
1618 | try | |
1619 | { | |
1620 | 0 | fin = new FileInputStream( propertiesFile ); |
1621 | ||
1622 | 0 | Properties loadedProperties = new Properties(); |
1623 | 0 | loadedProperties.load( fin ); |
1624 | 0 | collectedTestProperties.putAll( loadedProperties ); |
1625 | } | |
1626 | 0 | catch ( IOException e ) |
1627 | { | |
1628 | 0 | throw new MojoExecutionException( "Error reading system properties from " + propertiesFile ); |
1629 | } | |
1630 | finally | |
1631 | { | |
1632 | 0 | IOUtil.close( fin ); |
1633 | 0 | } |
1634 | } | |
1635 | ||
1636 | 0 | return collectedTestProperties; |
1637 | } | |
1638 | ||
1639 | /** | |
1640 | * Verifies the invocation result. | |
1641 | * | |
1642 | * @param result The invocation result to check, must not be <code>null</code>. | |
1643 | * @param invocationIndex The index of the invocation for which to check the exit code, must not be negative. | |
1644 | * @param invokerProperties The invoker properties used to check the exit code, must not be <code>null</code>. | |
1645 | * @param logger The build logger, may be <code>null</code> if logging is disabled. | |
1646 | * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException | |
1647 | * If the invocation result indicates a build failure. | |
1648 | */ | |
1649 | private void verify( InvocationResult result, int invocationIndex, InvokerProperties invokerProperties, | |
1650 | FileLogger logger ) | |
1651 | throws RunFailureException | |
1652 | { | |
1653 | 0 | if ( result.getExecutionException() != null ) |
1654 | { | |
1655 | 0 | throw new RunFailureException( |
1656 | "The Maven invocation failed. " + result.getExecutionException().getMessage(), BuildJob.Result.ERROR ); | |
1657 | } | |
1658 | 0 | else if ( !invokerProperties.isExpectedResult( result.getExitCode(), invocationIndex ) ) |
1659 | { | |
1660 | 0 | StringBuffer buffer = new StringBuffer( 256 ); |
1661 | 0 | buffer.append( "The build exited with code " ).append( result.getExitCode() ).append( ". " ); |
1662 | 0 | if ( logger != null ) |
1663 | { | |
1664 | 0 | buffer.append( "See " ); |
1665 | 0 | buffer.append( logger.getOutputFile().getAbsolutePath() ); |
1666 | 0 | buffer.append( " for details." ); |
1667 | } | |
1668 | else | |
1669 | { | |
1670 | 0 | buffer.append( "See console output for details." ); |
1671 | } | |
1672 | 0 | throw new RunFailureException( buffer.toString(), BuildJob.Result.FAILURE_BUILD ); |
1673 | } | |
1674 | 0 | } |
1675 | ||
1676 | /** | |
1677 | * Gets the goal list for the specified project. | |
1678 | * | |
1679 | * @param basedir The base directory of the project, must not be <code>null</code>. | |
1680 | * @return The list of goals to run when building the project, may be empty but never <code>null</code>. | |
1681 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1682 | * If the profile file could not be read. | |
1683 | */ | |
1684 | List<String> getGoals( final File basedir ) | |
1685 | throws MojoExecutionException | |
1686 | { | |
1687 | try | |
1688 | { | |
1689 | 5 | return getTokens( basedir, goalsFile, goals ); |
1690 | } | |
1691 | 0 | catch ( IOException e ) |
1692 | { | |
1693 | 0 | throw new MojoExecutionException( "error reading goals", e ); |
1694 | } | |
1695 | } | |
1696 | ||
1697 | /** | |
1698 | * Gets the profile list for the specified project. | |
1699 | * | |
1700 | * @param basedir The base directory of the project, must not be <code>null</code>. | |
1701 | * @return The list of profiles to activate when building the project, may be empty but never <code>null</code>. | |
1702 | * @throws org.apache.maven.plugin.MojoExecutionException | |
1703 | * If the profile file could not be read. | |
1704 | */ | |
1705 | List<String> getProfiles( File basedir ) | |
1706 | throws MojoExecutionException | |
1707 | { | |
1708 | try | |
1709 | { | |
1710 | 3 | return getTokens( basedir, profilesFile, profiles ); |
1711 | } | |
1712 | 0 | catch ( IOException e ) |
1713 | { | |
1714 | 0 | throw new MojoExecutionException( "error reading profiles", e ); |
1715 | } | |
1716 | } | |
1717 | ||
1718 | /** | |
1719 | * Gets the build jobs that should be processed. Note that the order of the returned build jobs is significant. | |
1720 | * | |
1721 | * @return The build jobs to process, may be empty but never <code>null</code>. | |
1722 | * @throws java.io.IOException If the projects directory could not be scanned. | |
1723 | */ | |
1724 | BuildJob[] getBuildJobs() | |
1725 | throws IOException | |
1726 | { | |
1727 | BuildJob[] buildJobs; | |
1728 | ||
1729 | 3 | if ( ( pom != null ) && pom.exists() ) |
1730 | { | |
1731 | 0 | buildJobs = new BuildJob[]{ new BuildJob( pom.getAbsolutePath(), BuildJob.Type.NORMAL ) }; |
1732 | } | |
1733 | 3 | else if ( invokerTest != null ) |
1734 | { | |
1735 | 3 | String[] testRegexes = StringUtils.split( invokerTest, "," ); |
1736 | 3 | List<String> includes = new ArrayList<String>( testRegexes.length ); |
1737 | ||
1738 | 7 | for ( int i = 0, size = testRegexes.length; i < size; i++ ) |
1739 | { | |
1740 | // user just use -Dinvoker.test=MWAR191,MNG111 to use a directory thats the end is not pom.xml | |
1741 | 4 | includes.add( testRegexes[i] ); |
1742 | } | |
1743 | ||
1744 | // it would be nice if we could figure out what types these are... but perhaps | |
1745 | // not necessary for the -Dinvoker.test=xxx t | |
1746 | 3 | buildJobs = scanProjectsDirectory( includes, null, BuildJob.Type.DIRECT ); |
1747 | 3 | } |
1748 | else | |
1749 | { | |
1750 | 0 | List<String> excludes = |
1751 | ( pomExcludes != null ) ? new ArrayList<String>( pomExcludes ) : new ArrayList<String>(); | |
1752 | 0 | if ( this.settingsFile != null ) |
1753 | { | |
1754 | 0 | String exclude = relativizePath( this.settingsFile, projectsDirectory.getCanonicalPath() ); |
1755 | 0 | if ( exclude != null ) |
1756 | { | |
1757 | 0 | excludes.add( exclude.replace( '\\', '/' ) ); |
1758 | 0 | getLog().debug( "Automatically excluded " + exclude + " from project scanning" ); |
1759 | } | |
1760 | } | |
1761 | ||
1762 | 0 | BuildJob[] setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP ); |
1763 | 0 | getLog().debug( "Setup projects: " + Arrays.asList( setupPoms ) ); |
1764 | ||
1765 | 0 | BuildJob[] normalPoms = scanProjectsDirectory( pomIncludes, excludes, BuildJob.Type.NORMAL ); |
1766 | ||
1767 | 0 | Map<String, BuildJob> uniquePoms = new LinkedHashMap<String, BuildJob>(); |
1768 | 0 | for ( int i = 0; i < setupPoms.length; i++ ) |
1769 | { | |
1770 | 0 | uniquePoms.put( setupPoms[i].getProject(), setupPoms[i] ); |
1771 | } | |
1772 | 0 | for ( int i = 0; i < normalPoms.length; i++ ) |
1773 | { | |
1774 | 0 | if ( !uniquePoms.containsKey( normalPoms[i].getProject() ) ) |
1775 | { | |
1776 | 0 | uniquePoms.put( normalPoms[i].getProject(), normalPoms[i] ); |
1777 | } | |
1778 | } | |
1779 | ||
1780 | 0 | buildJobs = uniquePoms.values().toArray( new BuildJob[uniquePoms.size()] ); |
1781 | } | |
1782 | ||
1783 | 3 | relativizeProjectPaths( buildJobs ); |
1784 | ||
1785 | 3 | return buildJobs; |
1786 | } | |
1787 | ||
1788 | /** | |
1789 | * Scans the projects directory for projects to build. Both (POM) files and mere directories will be matched by the | |
1790 | * scanner patterns. If the patterns match a directory which contains a file named "pom.xml", the results will | |
1791 | * include the path to this file rather than the directory path in order to avoid duplicate invocations of the same | |
1792 | * project. | |
1793 | * | |
1794 | * @param includes The include patterns for the scanner, may be <code>null</code>. | |
1795 | * @param excludes The exclude patterns for the scanner, may be <code>null</code> to exclude nothing. | |
1796 | * @param type The type to assign to the resulting build jobs, must not be <code>null</code>. | |
1797 | * @return The build jobs matching the patterns, never <code>null</code>. | |
1798 | * @throws java.io.IOException If the project directory could not be scanned. | |
1799 | */ | |
1800 | private BuildJob[] scanProjectsDirectory( List<String> includes, List<String> excludes, String type ) | |
1801 | throws IOException | |
1802 | { | |
1803 | 3 | if ( !projectsDirectory.isDirectory() ) |
1804 | { | |
1805 | 0 | return new BuildJob[0]; |
1806 | } | |
1807 | ||
1808 | 3 | DirectoryScanner scanner = new DirectoryScanner(); |
1809 | 3 | scanner.setBasedir( projectsDirectory.getCanonicalFile() ); |
1810 | 3 | scanner.setFollowSymlinks( false ); |
1811 | 3 | if ( includes != null ) |
1812 | { | |
1813 | 3 | scanner.setIncludes( includes.toArray( new String[includes.size()] ) ); |
1814 | } | |
1815 | 3 | if ( excludes != null ) |
1816 | { | |
1817 | 0 | scanner.setExcludes( excludes.toArray( new String[excludes.size()] ) ); |
1818 | } | |
1819 | 3 | scanner.addDefaultExcludes(); |
1820 | 3 | scanner.scan(); |
1821 | ||
1822 | 3 | Map<String, BuildJob> matches = new LinkedHashMap<String, BuildJob>(); |
1823 | ||
1824 | 3 | String[] includedFiles = scanner.getIncludedFiles(); |
1825 | 3 | for ( int i = 0; i < includedFiles.length; i++ ) |
1826 | { | |
1827 | 0 | matches.put( includedFiles[i], new BuildJob( includedFiles[i], type ) ); |
1828 | } | |
1829 | ||
1830 | 3 | String[] includedDirs = scanner.getIncludedDirectories(); |
1831 | 10 | for ( int i = 0; i < includedDirs.length; i++ ) |
1832 | { | |
1833 | 7 | String includedFile = includedDirs[i] + File.separatorChar + "pom.xml"; |
1834 | 7 | if ( new File( scanner.getBasedir(), includedFile ).isFile() ) |
1835 | { | |
1836 | 6 | matches.put( includedFile, new BuildJob( includedFile, type ) ); |
1837 | } | |
1838 | else | |
1839 | { | |
1840 | 1 | matches.put( includedDirs[i], new BuildJob( includedDirs[i], type ) ); |
1841 | } | |
1842 | } | |
1843 | ||
1844 | 3 | return matches.values().toArray( new BuildJob[matches.size()] ); |
1845 | } | |
1846 | ||
1847 | /** | |
1848 | * Relativizes the project paths of the specified build jobs against the directory specified by | |
1849 | * {@link #projectsDirectory} (if possible). If a project path does not denote a sub path of the projects directory, | |
1850 | * it is returned as is. | |
1851 | * | |
1852 | * @param buildJobs The build jobs whose project paths should be relativized, must not be <code>null</code> nor | |
1853 | * contain <code>null</code> elements. | |
1854 | * @throws java.io.IOException If any path could not be relativized. | |
1855 | */ | |
1856 | private void relativizeProjectPaths( BuildJob[] buildJobs ) | |
1857 | throws IOException | |
1858 | { | |
1859 | 3 | String projectsDirPath = projectsDirectory.getCanonicalPath(); |
1860 | ||
1861 | 10 | for ( int i = 0; i < buildJobs.length; i++ ) |
1862 | { | |
1863 | 7 | String projectPath = buildJobs[i].getProject(); |
1864 | ||
1865 | 7 | File file = new File( projectPath ); |
1866 | ||
1867 | 7 | if ( !file.isAbsolute() ) |
1868 | { | |
1869 | 7 | file = new File( projectsDirectory, projectPath ); |
1870 | } | |
1871 | ||
1872 | 7 | String relativizedPath = relativizePath( file, projectsDirPath ); |
1873 | ||
1874 | 7 | if ( relativizedPath == null ) |
1875 | { | |
1876 | 0 | relativizedPath = projectPath; |
1877 | } | |
1878 | ||
1879 | 7 | buildJobs[i].setProject( relativizedPath ); |
1880 | } | |
1881 | 3 | } |
1882 | ||
1883 | /** | |
1884 | * Relativizes the specified path against the given base directory. Besides relativization, the returned path will | |
1885 | * also be normalized, e.g. directory references like ".." will be removed. | |
1886 | * | |
1887 | * @param path The path to relativize, must not be <code>null</code>. | |
1888 | * @param basedir The (canonical path of the) base directory to relativize against, must not be <code>null</code>. | |
1889 | * @return The relative path in normal form or <code>null</code> if the input path does not denote a sub path of the | |
1890 | * base directory. | |
1891 | * @throws java.io.IOException If the path could not be relativized. | |
1892 | */ | |
1893 | private String relativizePath( File path, String basedir ) | |
1894 | throws IOException | |
1895 | { | |
1896 | 7 | String relativizedPath = path.getCanonicalPath(); |
1897 | ||
1898 | 7 | if ( relativizedPath.startsWith( basedir ) ) |
1899 | { | |
1900 | 7 | relativizedPath = relativizedPath.substring( basedir.length() ); |
1901 | 7 | if ( relativizedPath.startsWith( File.separator ) ) |
1902 | { | |
1903 | 7 | relativizedPath = relativizedPath.substring( File.separator.length() ); |
1904 | } | |
1905 | ||
1906 | 7 | return relativizedPath; |
1907 | } | |
1908 | else | |
1909 | { | |
1910 | 0 | return null; |
1911 | } | |
1912 | } | |
1913 | ||
1914 | /** | |
1915 | * Returns the map-based value source used to interpolate POMs and other stuff. | |
1916 | * | |
1917 | * @return The map-based value source for interpolation, never <code>null</code>. | |
1918 | */ | |
1919 | private Map<String, Object> getInterpolationValueSource() | |
1920 | { | |
1921 | 6 | Map<String, Object> props = new HashMap<String, Object>(); |
1922 | 6 | if ( interpolationsProperties != null ) |
1923 | { | |
1924 | 3 | props.putAll( (Map) interpolationsProperties ); |
1925 | } | |
1926 | 6 | if ( filterProperties != null ) |
1927 | { | |
1928 | 0 | props.putAll( filterProperties ); |
1929 | } | |
1930 | 6 | props.put( "basedir", this.project.getBasedir().getAbsolutePath() ); |
1931 | 6 | props.put( "baseurl", toUrl( this.project.getBasedir().getAbsolutePath() ) ); |
1932 | 6 | if ( settings.getLocalRepository() != null ) |
1933 | { | |
1934 | 0 | props.put( "localRepository", settings.getLocalRepository() ); |
1935 | 0 | props.put( "localRepositoryUrl", toUrl( settings.getLocalRepository() ) ); |
1936 | } | |
1937 | 6 | return new CompositeMap( this.project, props ); |
1938 | } | |
1939 | ||
1940 | /** | |
1941 | * Converts the specified filesystem path to a URL. The resulting URL has no trailing slash regardless whether the | |
1942 | * path denotes a file or a directory. | |
1943 | * | |
1944 | * @param filename The filesystem path to convert, must not be <code>null</code>. | |
1945 | * @return The <code>file:</code> URL for the specified path, never <code>null</code>. | |
1946 | */ | |
1947 | private static String toUrl( String filename ) | |
1948 | { | |
1949 | /* | |
1950 | * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here | |
1951 | * as-is but use the decoded path component in the URL. | |
1952 | */ | |
1953 | 6 | String url = "file://" + new File( filename ).toURI().getPath(); |
1954 | 6 | if ( url.endsWith( "/" ) ) |
1955 | { | |
1956 | 6 | url = url.substring( 0, url.length() - 1 ); |
1957 | } | |
1958 | 6 | return url; |
1959 | } | |
1960 | ||
1961 | /** | |
1962 | * Gets goal/profile names for the specified project, either directly from the plugin configuration or from an | |
1963 | * external token file. | |
1964 | * | |
1965 | * @param basedir The base directory of the test project, must not be <code>null</code>. | |
1966 | * @param filename The (simple) name of an optional file in the project base directory from which to read | |
1967 | * goals/profiles, may be <code>null</code>. | |
1968 | * @param defaultTokens The list of tokens to return in case the specified token file does not exist, may be | |
1969 | * <code>null</code>. | |
1970 | * @return The list of goal/profile names, may be empty but never <code>null</code>. | |
1971 | * @throws java.io.IOException If the token file exists but could not be parsed. | |
1972 | */ | |
1973 | private List<String> getTokens( File basedir, String filename, List<String> defaultTokens ) | |
1974 | throws IOException | |
1975 | { | |
1976 | 8 | List<String> tokens = ( defaultTokens != null ) ? defaultTokens : new ArrayList<String>(); |
1977 | ||
1978 | 8 | if ( StringUtils.isNotEmpty( filename ) ) |
1979 | { | |
1980 | 8 | File tokenFile = new File( basedir, filename ); |
1981 | ||
1982 | 8 | if ( tokenFile.exists() ) |
1983 | { | |
1984 | 4 | tokens = readTokens( tokenFile ); |
1985 | } | |
1986 | } | |
1987 | ||
1988 | 8 | return tokens; |
1989 | } | |
1990 | ||
1991 | /** | |
1992 | * Reads the tokens from the specified file. Tokens are separated either by line terminators or commas. During | |
1993 | * parsing, the file contents will be interpolated. | |
1994 | * | |
1995 | * @param tokenFile The file to read the tokens from, must not be <code>null</code>. | |
1996 | * @return The list of tokens, may be empty but never <code>null</code>. | |
1997 | * @throws java.io.IOException If the token file could not be read. | |
1998 | */ | |
1999 | private List<String> readTokens( final File tokenFile ) | |
2000 | throws IOException | |
2001 | { | |
2002 | 4 | List<String> result = new ArrayList<String>(); |
2003 | ||
2004 | 4 | BufferedReader reader = null; |
2005 | try | |
2006 | { | |
2007 | 4 | Map<String, Object> composite = getInterpolationValueSource(); |
2008 | 4 | reader = new BufferedReader( new InterpolationFilterReader( newReader( tokenFile ), composite ) ); |
2009 | ||
2010 | 4 | String line = null; |
2011 | 9 | while ( ( line = reader.readLine() ) != null ) |
2012 | { | |
2013 | 5 | result.addAll( collectListFromCSV( line ) ); |
2014 | } | |
2015 | } | |
2016 | finally | |
2017 | { | |
2018 | 4 | IOUtil.close( reader ); |
2019 | 4 | } |
2020 | ||
2021 | 4 | return result; |
2022 | } | |
2023 | ||
2024 | /** | |
2025 | * Gets a list of comma separated tokens from the specified line. | |
2026 | * | |
2027 | * @param csv The line with comma separated tokens, may be <code>null</code>. | |
2028 | * @return The list of tokens from the line, may be empty but never <code>null</code>. | |
2029 | */ | |
2030 | private List<String> collectListFromCSV( final String csv ) | |
2031 | { | |
2032 | 5 | final List<String> result = new ArrayList<String>(); |
2033 | ||
2034 | 5 | if ( ( csv != null ) && ( csv.trim().length() > 0 ) ) |
2035 | { | |
2036 | 5 | final StringTokenizer st = new StringTokenizer( csv, "," ); |
2037 | ||
2038 | 12 | while ( st.hasMoreTokens() ) |
2039 | { | |
2040 | 7 | result.add( st.nextToken().trim() ); |
2041 | } | |
2042 | } | |
2043 | ||
2044 | 5 | return result; |
2045 | } | |
2046 | ||
2047 | /** | |
2048 | * Interpolates the specified POM/settings file to a temporary file. The destination file may be same as the input | |
2049 | * file, i.e. interpolation can be performed in-place. | |
2050 | * | |
2051 | * @param originalFile The XML file to interpolate, must not be <code>null</code>. | |
2052 | * @param interpolatedFile The target file to write the interpolated contents of the original file to, must not be | |
2053 | * <code>null</code>. | |
2054 | * @throws org.apache.maven.plugin.MojoExecutionException | |
2055 | * If the target file could not be created. | |
2056 | */ | |
2057 | void buildInterpolatedFile( File originalFile, File interpolatedFile ) | |
2058 | throws MojoExecutionException | |
2059 | { | |
2060 | 2 | getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() ); |
2061 | ||
2062 | try | |
2063 | { | |
2064 | String xml; | |
2065 | ||
2066 | 2 | Reader reader = null; |
2067 | try | |
2068 | { | |
2069 | // interpolation with token @...@ | |
2070 | 2 | Map<String, Object> composite = getInterpolationValueSource(); |
2071 | 2 | reader = ReaderFactory.newXmlReader( originalFile ); |
2072 | 2 | reader = new InterpolationFilterReader( reader, composite, "@", "@" ); |
2073 | 2 | xml = IOUtil.toString( reader ); |
2074 | } | |
2075 | finally | |
2076 | { | |
2077 | 2 | IOUtil.close( reader ); |
2078 | 2 | } |
2079 | ||
2080 | 2 | Writer writer = null; |
2081 | try | |
2082 | { | |
2083 | 2 | interpolatedFile.getParentFile().mkdirs(); |
2084 | 2 | writer = WriterFactory.newXmlWriter( interpolatedFile ); |
2085 | 2 | writer.write( xml ); |
2086 | 2 | writer.flush(); |
2087 | } | |
2088 | finally | |
2089 | { | |
2090 | 2 | IOUtil.close( writer ); |
2091 | 2 | } |
2092 | } | |
2093 | 0 | catch ( IOException e ) |
2094 | { | |
2095 | 0 | throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e ); |
2096 | 2 | } |
2097 | 2 | } |
2098 | ||
2099 | /** | |
2100 | * Gets the (interpolated) invoker properties for an integration test. | |
2101 | * | |
2102 | * @param projectDirectory The base directory of the IT project, must not be <code>null</code>. | |
2103 | * @return The invoker properties, may be empty but never <code>null</code>. | |
2104 | * @throws org.apache.maven.plugin.MojoExecutionException | |
2105 | * If an I/O error occurred during reading the properties. | |
2106 | */ | |
2107 | private InvokerProperties getInvokerProperties( final File projectDirectory ) | |
2108 | throws MojoExecutionException | |
2109 | { | |
2110 | 0 | Properties props = new Properties(); |
2111 | 0 | if ( invokerPropertiesFile != null ) |
2112 | { | |
2113 | 0 | File propertiesFile = new File( projectDirectory, invokerPropertiesFile ); |
2114 | 0 | if ( propertiesFile.isFile() ) |
2115 | { | |
2116 | 0 | InputStream in = null; |
2117 | try | |
2118 | { | |
2119 | 0 | in = new FileInputStream( propertiesFile ); |
2120 | 0 | props.load( in ); |
2121 | } | |
2122 | 0 | catch ( IOException e ) |
2123 | { | |
2124 | 0 | throw new MojoExecutionException( "Failed to read invoker properties: " + propertiesFile, e ); |
2125 | } | |
2126 | finally | |
2127 | { | |
2128 | 0 | IOUtil.close( in ); |
2129 | 0 | } |
2130 | } | |
2131 | ||
2132 | 0 | Interpolator interpolator = new RegexBasedInterpolator(); |
2133 | 0 | interpolator.addValueSource( new MapBasedValueSource( getInterpolationValueSource() ) ); |
2134 | 0 | for ( String key : (Set<String>) ( (Map) props ).keySet() ) |
2135 | { | |
2136 | 0 | String value = props.getProperty( key ); |
2137 | try | |
2138 | { | |
2139 | 0 | value = interpolator.interpolate( value, "" ); |
2140 | } | |
2141 | 0 | catch ( InterpolationException e ) |
2142 | { | |
2143 | 0 | throw new MojoExecutionException( "Failed to interpolate invoker properties: " + propertiesFile, |
2144 | e ); | |
2145 | 0 | } |
2146 | 0 | props.setProperty( key, value ); |
2147 | 0 | } |
2148 | } | |
2149 | 0 | return new InvokerProperties( props ); |
2150 | } | |
2151 | ||
2152 | protected boolean isParallelRun() | |
2153 | { | |
2154 | 0 | return parallelThreads > 1; |
2155 | } | |
2156 | ||
2157 | } |