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