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