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