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