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