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