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