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