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.File;
23 import java.io.IOException;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashSet;
29 import java.util.Map;
30
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.artifact.factory.ArtifactFactory;
33 import org.apache.maven.artifact.installer.ArtifactInstaller;
34 import org.apache.maven.artifact.repository.ArtifactRepository;
35 import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
36 import org.apache.maven.model.Model;
37 import org.apache.maven.model.Parent;
38 import org.apache.maven.plugin.AbstractMojo;
39 import org.apache.maven.plugin.MojoExecutionException;
40 import org.apache.maven.project.MavenProject;
41 import org.codehaus.plexus.util.FileUtils;
42
43 /**
44 * Installs the project artifacts of the main build into the local repository as a preparation to run the sub projects.
45 * More precisely, all artifacts of the project itself, all its locally reachable parent POMs and all its dependencies
46 * from the reactor will be installed to the local repository.
47 *
48 * @goal install
49 * @phase pre-integration-test
50 * @requiresDependencyResolution runtime
51 * @since 1.2
52 * @author Paul Gier
53 * @author Benjamin Bentmann
54 * @version $Id: InstallMojo.java 814226 2009-09-12 20:01:59Z bentmann $
55 */
56 public class InstallMojo
57 extends AbstractMojo
58 {
59
60 /**
61 * Maven artifact install component to copy artifacts to the local repository.
62 *
63 * @component
64 */
65 private ArtifactInstaller installer;
66
67 /**
68 * The component used to create artifacts.
69 *
70 * @component
71 */
72 private ArtifactFactory artifactFactory;
73
74 /**
75 * The component used to create artifacts.
76 *
77 * @component
78 */
79 private ArtifactRepositoryFactory repositoryFactory;
80
81 /**
82 * @parameter expression="${localRepository}"
83 * @required
84 * @readonly
85 */
86 private ArtifactRepository localRepository;
87
88 /**
89 * The path to the local repository into which the project artifacts should be installed for the integration tests.
90 * If not set, the regular local repository will be used. To prevent soiling of your regular local repository with
91 * possibly broken artifacts, it is strongly recommended to use an isolated repository for the integration tests
92 * (e.g. <code>${project.build.directory}/it-repo</code>).
93 *
94 * @parameter expression="${invoker.localRepositoryPath}"
95 */
96 private File localRepositoryPath;
97
98 /**
99 * The current Maven project.
100 *
101 * @parameter expression="${project}"
102 * @required
103 * @readonly
104 */
105 private MavenProject project;
106
107 /**
108 * The set of Maven projects in the reactor build.
109 *
110 * @parameter default-value="${reactorProjects}"
111 * @readonly
112 */
113 private Collection reactorProjects;
114
115 /**
116 * A flag used to disable the installation procedure. This is primarily intended for usage from the command line to
117 * occasionally adjust the build.
118 *
119 * @parameter expression="${invoker.skip}" default-value="false"
120 * @since 1.4
121 */
122 private boolean skipInstallation;
123
124 /**
125 * The identifiers of already installed artifacts, used to avoid multiple installation of the same artifact.
126 */
127 private Collection installedArtifacts;
128
129 /**
130 * The identifiers of already copied artifacts, used to avoid multiple installation of the same artifact.
131 */
132 private Collection copiedArtifacts;
133
134 /**
135 * Performs this mojo's tasks.
136 *
137 * @throws MojoExecutionException If the artifacts could not be installed.
138 */
139 public void execute()
140 throws MojoExecutionException
141 {
142 if ( skipInstallation )
143 {
144 getLog().info( "Skipping artifact installation per configuration." );
145 return;
146 }
147
148 ArtifactRepository testRepository = createTestRepository();
149
150 installedArtifacts = new HashSet();
151 copiedArtifacts = new HashSet();
152
153 installProjectDependencies( project, reactorProjects, testRepository );
154 installProjectParents( project, testRepository );
155 installProjectArtifacts( project, testRepository );
156 }
157
158 /**
159 * Creates the local repository for the integration tests. If the user specified a custom repository location, the
160 * custom repository will have the same identifier, layout and policies as the real local repository. That means
161 * apart from the location, the custom repository will be indistinguishable from the real repository such that its
162 * usage is transparent to the integration tests.
163 *
164 * @return The local repository for the integration tests, never <code>null</code>.
165 * @throws MojoExecutionException If the repository could not be created.
166 */
167 private ArtifactRepository createTestRepository()
168 throws MojoExecutionException
169 {
170 ArtifactRepository testRepository = localRepository;
171
172 if ( localRepositoryPath != null )
173 {
174 try
175 {
176 if ( !localRepositoryPath.exists() && !localRepositoryPath.mkdirs() )
177 {
178 throw new IOException( "Failed to create directory: " + localRepositoryPath );
179 }
180
181 testRepository =
182 repositoryFactory.createArtifactRepository( localRepository.getId(),
183 localRepositoryPath.toURL().toExternalForm(),
184 localRepository.getLayout(),
185 localRepository.getSnapshots(),
186 localRepository.getReleases() );
187 }
188 catch ( Exception e )
189 {
190 throw new MojoExecutionException( "Failed to create local repository: " + localRepositoryPath, e );
191 }
192 }
193
194 return testRepository;
195 }
196
197 /**
198 * Installs the specified artifact to the local repository. Note: This method should only be used for artifacts that
199 * originate from the current (reactor) build. Artifacts that have been grabbed from the user's local repository
200 * should be installed to the test repository via {@link #copyArtifact(File, Artifact, ArtifactRepository)}.
201 *
202 * @param file The file associated with the artifact, must not be <code>null</code>. This is in most cases the value
203 * of <code>artifact.getFile()</code> with the exception of the main artifact from a project with
204 * packaging "pom". Projects with packaging "pom" have no main artifact file. They have however artifact
205 * metadata (e.g. site descriptors) which needs to be installed.
206 * @param artifact The artifact to install, must not be <code>null</code>.
207 * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
208 * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
209 */
210 private void installArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
211 throws MojoExecutionException
212 {
213 try
214 {
215 if ( file == null )
216 {
217 throw new IllegalStateException( "Artifact has no associated file: " + file );
218 }
219 if ( !file.isFile() )
220 {
221 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
222 }
223
224 if ( installedArtifacts.add( artifact.getId() ) )
225 {
226 installer.install( file, artifact, testRepository );
227 }
228 else
229 {
230 getLog().debug( "Not re-installing " + artifact + ", " + file );
231 }
232 }
233 catch ( Exception e )
234 {
235 throw new MojoExecutionException( "Failed to install artifact: " + artifact, e );
236 }
237 }
238
239 /**
240 * Installs the specified artifact to the local repository. This method serves basically the same purpose as
241 * {@link #installArtifact(File, Artifact, ArtifactRepository)} but is meant for artifacts that have been resolved
242 * from the user's local repository (and not the current build outputs). The subtle difference here is that
243 * artifacts from the repository have already undergone transformations and these manipulations should not be redone
244 * by the artifact installer. For this reason, this method performs plain copy operations to install the artifacts.
245 *
246 * @param file The file associated with the artifact, must not be <code>null</code>.
247 * @param artifact The artifact to install, must not be <code>null</code>.
248 * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
249 * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
250 */
251 private void copyArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
252 throws MojoExecutionException
253 {
254 try
255 {
256 if ( file == null )
257 {
258 throw new IllegalStateException( "Artifact has no associated file: " + file );
259 }
260 if ( !file.isFile() )
261 {
262 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
263 }
264
265 if ( copiedArtifacts.add( artifact.getId() ) )
266 {
267 File destination = new File( testRepository.getBasedir(), testRepository.pathOf( artifact ) );
268
269 getLog().debug( "Installing " + file + " to " + destination );
270
271 copyFileIfDifferent( file, destination );
272
273 MetadataUtils.createMetadata( destination, artifact );
274 }
275 else
276 {
277 getLog().debug( "Not re-installing " + artifact + ", " + file );
278 }
279 }
280 catch ( Exception e )
281 {
282 throw new MojoExecutionException( "Failed to stage artifact: " + artifact, e );
283 }
284 }
285
286 private void copyFileIfDifferent( File src, File dst )
287 throws IOException
288 {
289 if ( src.lastModified() != dst.lastModified() || src.length() != dst.length() )
290 {
291 FileUtils.copyFile( src, dst );
292 dst.setLastModified( src.lastModified() );
293 }
294 }
295
296 /**
297 * Installs the main artifact and any attached artifacts of the specified project to the local repository.
298 *
299 * @param mvnProject The project whose artifacts should be installed, must not be <code>null</code>.
300 * @param testRepository The local repository to install the artifacts to, must not be <code>null</code>.
301 * @throws MojoExecutionException If any artifact could not be installed.
302 */
303 private void installProjectArtifacts( MavenProject mvnProject, ArtifactRepository testRepository )
304 throws MojoExecutionException
305 {
306 try
307 {
308 // Install POM (usually attached as metadata but that happens only as a side effect of the Install Plugin)
309 installProjectPom( mvnProject, testRepository );
310
311 // Install the main project artifact (if the project has one, e.g. has no "pom" packaging)
312 Artifact mainArtifact = mvnProject.getArtifact();
313 if ( mainArtifact.getFile() != null )
314 {
315 installArtifact( mainArtifact.getFile(), mainArtifact, testRepository );
316 }
317
318 // Install any attached project artifacts
319 Collection attachedArtifacts = mvnProject.getAttachedArtifacts();
320 for ( Iterator artifactIter = attachedArtifacts.iterator(); artifactIter.hasNext(); )
321 {
322 Artifact attachedArtifact = (Artifact) artifactIter.next();
323 installArtifact( attachedArtifact.getFile(), attachedArtifact, testRepository );
324 }
325 }
326 catch ( Exception e )
327 {
328 throw new MojoExecutionException( "Failed to install project artifacts: " + mvnProject, e );
329 }
330 }
331
332 /**
333 * Installs the (locally reachable) parent POMs of the specified project to the local repository. The parent POMs
334 * from the reactor must be installed or the forked IT builds will fail when using a clean repository.
335 *
336 * @param mvnProject The project whose parent POMs should be installed, must not be <code>null</code>.
337 * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
338 * @throws MojoExecutionException If any POM could not be installed.
339 */
340 private void installProjectParents( MavenProject mvnProject, ArtifactRepository testRepository )
341 throws MojoExecutionException
342 {
343 try
344 {
345 for ( MavenProject parent = mvnProject.getParent(); parent != null; parent = parent.getParent() )
346 {
347 if ( parent.getFile() == null )
348 {
349 copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
350 break;
351 }
352 installProjectPom( parent, testRepository );
353 }
354 }
355 catch ( Exception e )
356 {
357 throw new MojoExecutionException( "Failed to install project parents: " + mvnProject, e );
358 }
359 }
360
361 /**
362 * Installs the POM of the specified project to the local repository.
363 *
364 * @param mvnProject The project whose POM should be installed, must not be <code>null</code>.
365 * @param testRepository The local repository to install the POM to, must not be <code>null</code>.
366 * @throws MojoExecutionException If the POM could not be installed.
367 */
368 private void installProjectPom( MavenProject mvnProject, ArtifactRepository testRepository )
369 throws MojoExecutionException
370 {
371 try
372 {
373 Artifact pomArtifact = null;
374 if ( "pom".equals( mvnProject.getPackaging() ) )
375 {
376 pomArtifact = mvnProject.getArtifact();
377 }
378 if ( pomArtifact == null )
379 {
380 pomArtifact =
381 artifactFactory.createProjectArtifact( mvnProject.getGroupId(), mvnProject.getArtifactId(),
382 mvnProject.getVersion() );
383 }
384 installArtifact( mvnProject.getFile(), pomArtifact, testRepository );
385 }
386 catch ( Exception e )
387 {
388 throw new MojoExecutionException( "Failed to install POM: " + mvnProject, e );
389 }
390 }
391
392 /**
393 * Installs the dependent projects from the reactor to the local repository. The dependencies on other modules from
394 * the reactor must be installed or the forked IT builds will fail when using a clean repository.
395 *
396 * @param mvnProject The project whose dependent projects should be installed, must not be <code>null</code>.
397 * @param reactorProjects The set of projects in the reactor build, must not be <code>null</code>.
398 * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
399 * @throws MojoExecutionException If any dependency could not be installed.
400 */
401 private void installProjectDependencies( MavenProject mvnProject, Collection reactorProjects,
402 ArtifactRepository testRepository )
403 throws MojoExecutionException
404 {
405 // index available reactor projects
406 Map projects = new HashMap();
407 for ( Iterator it = reactorProjects.iterator(); it.hasNext(); )
408 {
409 MavenProject reactorProject = (MavenProject) it.next();
410
411 String projectId =
412 reactorProject.getGroupId() + ':' + reactorProject.getArtifactId() + ':' + reactorProject.getVersion();
413
414 projects.put( projectId, reactorProject );
415 }
416
417 // group transitive dependencies (even those that don't contribute to the class path like POMs) ...
418 Collection artifacts = mvnProject.getArtifacts();
419 // ... into dependencies that were resolved from reactor projects ...
420 Collection dependencyProjects = new LinkedHashSet();
421 // ... and those that were resolved from the (local) repo
422 Collection dependencyArtifacts = new LinkedHashSet();
423 for ( Iterator it = artifacts.iterator(); it.hasNext(); )
424 {
425 Artifact artifact = (Artifact) it.next();
426
427 // workaround for MNG-2961 to ensure the base version does not contain a timestamp
428 artifact.isSnapshot();
429
430 String projectId = artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion();
431
432 if ( projects.containsKey( projectId ) )
433 {
434 dependencyProjects.add( projectId );
435 }
436 else
437 {
438 dependencyArtifacts.add( artifact );
439 }
440 }
441
442 // install dependencies
443 try
444 {
445 // copy dependencies that where resolved from the local repo
446 for ( Iterator it = dependencyArtifacts.iterator(); it.hasNext(); )
447 {
448 Artifact artifact = (Artifact) it.next();
449
450 Artifact depArtifact =
451 artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(),
452 artifact.getBaseVersion(), artifact.getType(),
453 artifact.getClassifier() );
454
455 File artifactFile = artifact.getFile();
456
457 Artifact pomArtifact =
458 artifactFactory.createProjectArtifact( depArtifact.getGroupId(), depArtifact.getArtifactId(),
459 depArtifact.getBaseVersion() );
460
461 File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
462
463 if ( pomFile.isFile() )
464 {
465 if ( !pomArtifact.getId().equals( depArtifact.getId() ) )
466 {
467 copyArtifact( pomFile, pomArtifact, testRepository );
468 }
469 copyParentPoms( pomFile, testRepository );
470 }
471
472 copyArtifact( artifactFile, depArtifact, testRepository );
473 }
474
475 // install dependencies that were resolved from the reactor
476 for ( Iterator it = dependencyProjects.iterator(); it.hasNext(); )
477 {
478 String projectId = (String) it.next();
479
480 MavenProject dependencyProject = (MavenProject) projects.get( projectId );
481
482 installProjectArtifacts( dependencyProject, testRepository );
483 installProjectParents( dependencyProject, testRepository );
484 }
485 }
486 catch ( Exception e )
487 {
488 throw new MojoExecutionException( "Failed to install project dependencies: " + mvnProject, e );
489 }
490 }
491
492 /**
493 * Installs all parent POMs of the specified POM file that are available in the local repository.
494 *
495 * @param pomFile The path to the POM file whose parents should be installed, must not be <code>null</code>.
496 * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
497 * @throws MojoExecutionException If any (existing) parent POM could not be installed.
498 */
499 private void copyParentPoms( File pomFile, ArtifactRepository testRepository )
500 throws MojoExecutionException
501 {
502 Model model = PomUtils.loadPom( pomFile );
503 Parent parent = model.getParent();
504 if ( parent != null )
505 {
506 copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
507 }
508 }
509
510 /**
511 * Installs the specified POM and all its parent POMs to the local repository.
512 *
513 * @param groupId The group id of the POM which should be installed, must not be <code>null</code>.
514 * @param artifactId The artifact id of the POM which should be installed, must not be <code>null</code>.
515 * @param version The version of the POM which should be installed, must not be <code>null</code>.
516 * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
517 * @throws MojoExecutionException If any (existing) parent POM could not be installed.
518 */
519 private void copyParentPoms( String groupId, String artifactId, String version, ArtifactRepository testRepository )
520 throws MojoExecutionException
521 {
522 Artifact pomArtifact = artifactFactory.createProjectArtifact( groupId, artifactId, version );
523
524 if ( installedArtifacts.contains( pomArtifact.getId() ) || copiedArtifacts.contains( pomArtifact.getId() ) )
525 {
526 getLog().debug( "Not re-installing " + pomArtifact );
527 return;
528 }
529
530 File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
531 if ( pomFile.isFile() )
532 {
533 copyArtifact( pomFile, pomArtifact, testRepository );
534 copyParentPoms( pomFile, testRepository );
535 }
536 }
537
538 }