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