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