View Javadoc

1   package org.apache.maven.plugin.invoker;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.factory.ArtifactFactory;
35  import org.apache.maven.artifact.installer.ArtifactInstaller;
36  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
37  import org.apache.maven.artifact.repository.ArtifactRepository;
38  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
39  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
40  import org.apache.maven.artifact.resolver.ArtifactResolver;
41  import org.apache.maven.model.Model;
42  import org.apache.maven.model.Parent;
43  import org.apache.maven.plugin.AbstractMojo;
44  import org.apache.maven.plugin.MojoExecutionException;
45  import org.apache.maven.project.MavenProject;
46  import org.codehaus.plexus.util.FileUtils;
47  
48  /**
49   * Installs the project artifacts of the main build into the local repository as a preparation to run the sub projects.
50   * More precisely, all artifacts of the project itself, all its locally reachable parent POMs and all its dependencies
51   * from the reactor will be installed to the local repository.
52   * 
53   * @goal install
54   * @phase pre-integration-test
55   * @requiresDependencyResolution runtime
56   * @threadSafe
57   * @since 1.2
58   * @author Paul Gier
59   * @author Benjamin Bentmann
60   * @version $Id: InstallMojo.java 1326204 2012-04-14 20:55:16Z olamy $
61   */
62  public class InstallMojo
63      extends AbstractMojo
64  {
65  
66      /**
67       * Maven artifact install component to copy artifacts to the local repository.
68       * 
69       * @component
70       */
71      private ArtifactInstaller installer;
72  
73      /**
74       * The component used to create artifacts.
75       * 
76       * @component
77       */
78      private ArtifactFactory artifactFactory;
79  
80      /**
81       * The component used to create artifacts.
82       * 
83       * @component
84       */
85      private ArtifactRepositoryFactory repositoryFactory;
86  
87      /**
88       * @parameter expression="${localRepository}"
89       * @required
90       * @readonly
91       */
92      private ArtifactRepository localRepository;
93  
94      /**
95       * The path to the local repository into which the project artifacts should be installed for the integration tests.
96       * If not set, the regular local repository will be used. To prevent soiling of your regular local repository with
97       * possibly broken artifacts, it is strongly recommended to use an isolated repository for the integration tests
98       * (e.g. <code>${project.build.directory}/it-repo</code>).
99       * 
100      * @parameter expression="${invoker.localRepositoryPath}"
101      */
102     private File localRepositoryPath;
103 
104     /**
105      * The current Maven project.
106      * 
107      * @parameter expression="${project}"
108      * @required
109      * @readonly
110      */
111     private MavenProject project;
112 
113     /**
114      * The set of Maven projects in the reactor build.
115      * 
116      * @parameter default-value="${reactorProjects}"
117      * @readonly
118      */
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      * @parameter expression="${invoker.skip}" default-value="false"
126      * @since 1.4
127      */
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      * @parameter
159      * @since 1.6
160      */
161     private String[] extraArtifacts;
162 
163     /**
164      * @component
165      */
166     private ArtifactResolver resolver;
167 
168     /**
169      * @parameter default-value="${project.remoteArtifactRepositories}"
170      * @readonly
171      */
172     private List<ArtifactRepository> remoteArtifactRepositories;
173 
174     /**
175      * @parameter default-value="${project.pluginArtifactRepositories}"
176      * @readonly
177      */
178     private List<ArtifactRepository> remotePluginRepositories;
179 
180     /**
181      * @component
182      */
183     private ArtifactMetadataSource artifactMetadataSource;
184 
185     /**
186      * Performs this mojo's tasks.
187      * 
188      * @throws MojoExecutionException If the artifacts could not be installed.
189      */
190     public void execute()
191         throws MojoExecutionException
192     {
193         if ( skipInstallation )
194         {
195             getLog().info( "Skipping artifact installation per configuration." );
196             return;
197         }
198 
199         ArtifactRepository testRepository = createTestRepository();
200 
201         installedArtifacts = new HashSet<String>();
202         copiedArtifacts = new HashSet<String>();
203 
204         installProjectDependencies( project, reactorProjects, testRepository );
205         installProjectParents( project, testRepository );
206         installProjectArtifacts( project, testRepository );
207 
208         installExtraArtifacts( testRepository, extraArtifacts );
209     }
210 
211     /**
212      * Creates the local repository for the integration tests. If the user specified a custom repository location, the
213      * custom repository will have the same identifier, layout and policies as the real local repository. That means
214      * apart from the location, the custom repository will be indistinguishable from the real repository such that its
215      * usage is transparent to the integration tests.
216      * 
217      * @return The local repository for the integration tests, never <code>null</code>.
218      * @throws MojoExecutionException If the repository could not be created.
219      */
220     private ArtifactRepository createTestRepository()
221         throws MojoExecutionException
222     {
223         ArtifactRepository testRepository = localRepository;
224 
225         if ( localRepositoryPath != null )
226         {
227             try
228             {
229                 if ( !localRepositoryPath.exists() && !localRepositoryPath.mkdirs() )
230                 {
231                     throw new IOException( "Failed to create directory: " + localRepositoryPath );
232                 }
233 
234                 testRepository =
235                     repositoryFactory.createArtifactRepository( localRepository.getId(),
236                                                                 localRepositoryPath.toURL().toExternalForm(),
237                                                                 localRepository.getLayout(),
238                                                                 localRepository.getSnapshots(),
239                                                                 localRepository.getReleases() );
240             }
241             catch ( Exception e )
242             {
243                 throw new MojoExecutionException( "Failed to create local repository: " + localRepositoryPath, e );
244             }
245         }
246 
247         return testRepository;
248     }
249 
250     /**
251      * Installs the specified artifact to the local repository. Note: This method should only be used for artifacts that
252      * originate from the current (reactor) build. Artifacts that have been grabbed from the user's local repository
253      * should be installed to the test repository via {@link #copyArtifact(File, Artifact, ArtifactRepository)}.
254      * 
255      * @param file The file associated with the artifact, must not be <code>null</code>. This is in most cases the value
256      *            of <code>artifact.getFile()</code> with the exception of the main artifact from a project with
257      *            packaging "pom". Projects with packaging "pom" have no main artifact file. They have however artifact
258      *            metadata (e.g. site descriptors) which needs to be installed.
259      * @param artifact The artifact to install, must not be <code>null</code>.
260      * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
261      * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
262      */
263     private void installArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
264         throws MojoExecutionException
265     {
266         try
267         {
268             if ( file == null )
269             {
270                 throw new IllegalStateException( "Artifact has no associated file: " + file );
271             }
272             if ( !file.isFile() )
273             {
274                 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
275             }
276 
277             if ( installedArtifacts.add( artifact.getId() ) )
278             {
279                 installer.install( file, artifact, testRepository );
280             }
281             else
282             {
283                 getLog().debug( "Not re-installing " + artifact + ", " + file );
284             }
285         }
286         catch ( Exception e )
287         {
288             throw new MojoExecutionException( "Failed to install artifact: " + artifact, e );
289         }
290     }
291 
292     /**
293      * Installs the specified artifact to the local repository. This method serves basically the same purpose as
294      * {@link #installArtifact(File, Artifact, ArtifactRepository)} but is meant for artifacts that have been resolved
295      * from the user's local repository (and not the current build outputs). The subtle difference here is that
296      * artifacts from the repository have already undergone transformations and these manipulations should not be redone
297      * by the artifact installer. For this reason, this method performs plain copy operations to install the artifacts.
298      * 
299      * @param file The file associated with the artifact, must not be <code>null</code>.
300      * @param artifact The artifact to install, must not be <code>null</code>.
301      * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
302      * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
303      */
304     private void copyArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
305         throws MojoExecutionException
306     {
307         try
308         {
309             if ( file == null )
310             {
311                 throw new IllegalStateException( "Artifact has no associated file: " + file );
312             }
313             if ( !file.isFile() )
314             {
315                 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
316             }
317 
318             if ( copiedArtifacts.add( artifact.getId() ) )
319             {
320                 File destination = new File( testRepository.getBasedir(), testRepository.pathOf( artifact ) );
321 
322                 getLog().debug( "Installing " + file + " to " + destination );
323 
324                 copyFileIfDifferent( file, destination );
325 
326                 MetadataUtils.createMetadata( destination, artifact );
327             }
328             else
329             {
330                 getLog().debug( "Not re-installing " + artifact + ", " + file );
331             }
332         }
333         catch ( Exception e )
334         {
335             throw new MojoExecutionException( "Failed to stage artifact: " + artifact, e );
336         }
337     }
338 
339     private void copyFileIfDifferent( File src, File dst )
340         throws IOException
341     {
342         if ( src.lastModified() != dst.lastModified() || src.length() != dst.length() )
343         {
344             FileUtils.copyFile( src, dst );
345             dst.setLastModified( src.lastModified() );
346         }
347     }
348 
349     /**
350      * Installs the main artifact and any attached artifacts of the specified project to the local repository.
351      * 
352      * @param mvnProject The project whose artifacts should be installed, must not be <code>null</code>.
353      * @param testRepository The local repository to install the artifacts to, must not be <code>null</code>.
354      * @throws MojoExecutionException If any artifact could not be installed.
355      */
356     private void installProjectArtifacts( MavenProject mvnProject, ArtifactRepository testRepository )
357         throws MojoExecutionException
358     {
359         try
360         {
361             // Install POM (usually attached as metadata but that happens only as a side effect of the Install Plugin)
362             installProjectPom( mvnProject, testRepository );
363 
364             // Install the main project artifact (if the project has one, e.g. has no "pom" packaging)
365             Artifact mainArtifact = mvnProject.getArtifact();
366             if ( mainArtifact.getFile() != null )
367             {
368                 installArtifact( mainArtifact.getFile(), mainArtifact, testRepository );
369             }
370 
371             // Install any attached project artifacts
372             Collection<Artifact> attachedArtifacts = (Collection<Artifact>) mvnProject.getAttachedArtifacts();
373             for ( Artifact attachedArtifact : attachedArtifacts )
374             {
375                 installArtifact( attachedArtifact.getFile(), attachedArtifact, testRepository );
376             }
377         }
378         catch ( Exception e )
379         {
380             throw new MojoExecutionException( "Failed to install project artifacts: " + mvnProject, e );
381         }
382     }
383 
384     /**
385      * Installs the (locally reachable) parent POMs of the specified project to the local repository. The parent POMs
386      * from the reactor must be installed or the forked IT builds will fail when using a clean repository.
387      * 
388      * @param mvnProject The project whose parent POMs should be installed, must not be <code>null</code>.
389      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
390      * @throws MojoExecutionException If any POM could not be installed.
391      */
392     private void installProjectParents( MavenProject mvnProject, ArtifactRepository testRepository )
393         throws MojoExecutionException
394     {
395         try
396         {
397             for ( MavenProject parent = mvnProject.getParent(); parent != null; parent = parent.getParent() )
398             {
399                 if ( parent.getFile() == null )
400                 {
401                     copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
402                     break;
403                 }
404                 installProjectPom( parent, testRepository );
405             }
406         }
407         catch ( Exception e )
408         {
409             throw new MojoExecutionException( "Failed to install project parents: " + mvnProject, e );
410         }
411     }
412 
413     /**
414      * Installs the POM of the specified project to the local repository.
415      * 
416      * @param mvnProject The project whose POM should be installed, must not be <code>null</code>.
417      * @param testRepository The local repository to install the POM to, must not be <code>null</code>.
418      * @throws MojoExecutionException If the POM could not be installed.
419      */
420     private void installProjectPom( MavenProject mvnProject, ArtifactRepository testRepository )
421         throws MojoExecutionException
422     {
423         try
424         {
425             Artifact pomArtifact = null;
426             if ( "pom".equals( mvnProject.getPackaging() ) )
427             {
428                 pomArtifact = mvnProject.getArtifact();
429             }
430             if ( pomArtifact == null )
431             {
432                 pomArtifact =
433                     artifactFactory.createProjectArtifact( mvnProject.getGroupId(), mvnProject.getArtifactId(),
434                                                            mvnProject.getVersion() );
435             }
436             installArtifact( mvnProject.getFile(), pomArtifact, testRepository );
437         }
438         catch ( Exception e )
439         {
440             throw new MojoExecutionException( "Failed to install POM: " + mvnProject, e );
441         }
442     }
443 
444     /**
445      * Installs the dependent projects from the reactor to the local repository. The dependencies on other modules from
446      * the reactor must be installed or the forked IT builds will fail when using a clean repository.
447      * 
448      * @param mvnProject The project whose dependent projects should be installed, must not be <code>null</code>.
449      * @param reactorProjects The set of projects in the reactor build, must not be <code>null</code>.
450      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
451      * @throws MojoExecutionException If any dependency could not be installed.
452      */
453     private void installProjectDependencies( MavenProject mvnProject, Collection<MavenProject> reactorProjects,
454                                              ArtifactRepository testRepository )
455         throws MojoExecutionException
456     {
457         // index available reactor projects
458         Map<String, MavenProject> projects = new HashMap<String, MavenProject>();
459         for ( MavenProject reactorProject : reactorProjects )
460         {
461             String projectId =
462                 reactorProject.getGroupId() + ':' + reactorProject.getArtifactId() + ':' + reactorProject.getVersion();
463 
464             projects.put( projectId, reactorProject );
465         }
466 
467         // group transitive dependencies (even those that don't contribute to the class path like POMs) ...
468         Collection<Artifact> artifacts = (Collection<Artifact>) mvnProject.getArtifacts();
469         // ... into dependencies that were resolved from reactor projects ...
470         Collection<String> dependencyProjects = new LinkedHashSet<String>();
471         // ... and those that were resolved from the (local) repo
472         Collection<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>();
473         for ( Artifact artifact : artifacts )
474         {
475             // workaround for MNG-2961 to ensure the base version does not contain a timestamp
476             artifact.isSnapshot();
477 
478             String projectId = artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion();
479 
480             if ( projects.containsKey( projectId ) )
481             {
482                 dependencyProjects.add( projectId );
483             }
484             else
485             {
486                 dependencyArtifacts.add( artifact );
487             }
488         }
489 
490         // install dependencies
491         try
492         {
493             // copy dependencies that where resolved from the local repo
494             for ( Artifact artifact : dependencyArtifacts )
495             {
496                 copyArtifact( artifact, testRepository );
497             }
498 
499             // install dependencies that were resolved from the reactor
500             for ( String projectId : dependencyProjects )
501             {
502                 MavenProject dependencyProject = projects.get( projectId );
503 
504                 installProjectArtifacts( dependencyProject, testRepository );
505                 installProjectParents( dependencyProject, testRepository );
506             }
507         }
508         catch ( Exception e )
509         {
510             throw new MojoExecutionException( "Failed to install project dependencies: " + mvnProject, e );
511         }
512     }
513 
514     private void copyArtifact( Artifact artifact, ArtifactRepository testRepository )
515         throws MojoExecutionException
516     {
517         copyPoms( artifact, testRepository );
518 
519         Artifact depArtifact =
520             artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(),
521                                                           artifact.getBaseVersion(), artifact.getType(),
522                                                           artifact.getClassifier() );
523 
524         File artifactFile = artifact.getFile();
525 
526         copyArtifact( artifactFile, depArtifact, testRepository );
527     }
528 
529     private void copyPoms( Artifact artifact, ArtifactRepository testRepository )
530         throws MojoExecutionException
531     {
532         Artifact pomArtifact =
533             artifactFactory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(),
534                                                    artifact.getBaseVersion() );
535 
536         File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
537 
538         if ( pomFile.isFile() )
539         {
540             copyArtifact( pomFile, pomArtifact, testRepository );
541             copyParentPoms( pomFile, testRepository );
542         }
543     }
544 
545     /**
546      * Installs all parent POMs of the specified POM file that are available in the local repository.
547      * 
548      * @param pomFile The path to the POM file whose parents should be installed, must not be <code>null</code>.
549      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
550      * @throws MojoExecutionException If any (existing) parent POM could not be installed.
551      */
552     private void copyParentPoms( File pomFile, ArtifactRepository testRepository )
553         throws MojoExecutionException
554     {
555         Model model = PomUtils.loadPom( pomFile );
556         Parent parent = model.getParent();
557         if ( parent != null )
558         {
559             copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
560         }
561     }
562 
563     /**
564      * Installs the specified POM and all its parent POMs to the local repository.
565      * 
566      * @param groupId The group id of the POM which should be installed, must not be <code>null</code>.
567      * @param artifactId The artifact id of the POM which should be installed, must not be <code>null</code>.
568      * @param version The version of the POM which should be installed, must not be <code>null</code>.
569      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
570      * @throws MojoExecutionException If any (existing) parent POM could not be installed.
571      */
572     private void copyParentPoms( String groupId, String artifactId, String version, ArtifactRepository testRepository )
573         throws MojoExecutionException
574     {
575         Artifact pomArtifact = artifactFactory.createProjectArtifact( groupId, artifactId, version );
576 
577         if ( installedArtifacts.contains( pomArtifact.getId() ) || copiedArtifacts.contains( pomArtifact.getId() ) )
578         {
579             getLog().debug( "Not re-installing " + pomArtifact );
580             return;
581         }
582 
583         File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
584         if ( pomFile.isFile() )
585         {
586             copyArtifact( pomFile, pomArtifact, testRepository );
587             copyParentPoms( pomFile, testRepository );
588         }
589     }
590 
591     private void installExtraArtifacts( ArtifactRepository testRepository, String[] extraArtifacts )
592         throws MojoExecutionException
593     {
594         if ( extraArtifacts == null )
595         {
596             return;
597         }
598 
599         Artifact originatingArtifact = project.getArtifact();
600 
601         for ( int i = 0; i < extraArtifacts.length; i++ )
602         {
603             String[] gav = extraArtifacts[i].split( ":" );
604             if ( gav.length < 3 || gav.length > 5 )
605             {
606                 throw new MojoExecutionException( "Invalid artifact " + extraArtifacts[i] );
607             }
608 
609             String groupId = gav[0];
610             String artifactId = gav[1];
611             String version = gav[2];
612 
613             String type = "jar";
614             if ( gav.length > 3 )
615             {
616                 type = gav[3];
617             }
618 
619             String classifier = null;
620             if ( gav.length == 5 )
621             {
622                 classifier = gav[4];
623             }
624 
625             List<ArtifactRepository> remoteRepositories;
626             if ( "maven-plugin".equals( type ) )
627             {
628                 remoteRepositories = this.remotePluginRepositories;
629             }
630             else
631             {
632                 remoteRepositories = this.remoteArtifactRepositories;
633             }
634 
635             Artifact artifact = null;
636             try
637             {
638                 artifact = artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, type, classifier );
639 
640                 ArtifactResolutionResult arr =
641                     resolver.resolveTransitively( Collections.singleton( artifact ), originatingArtifact,
642                                                   remoteRepositories, localRepository, artifactMetadataSource );
643 
644                 if ( !groupId.equals( artifact.getGroupId() ) || !artifactId.equals( artifact.getArtifactId() )
645                     || !version.equals( artifact.getVersion() ) )
646                 {
647                     artifact =
648                         artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, type, classifier );
649                     copyPoms( artifact, testRepository );
650                 }
651 
652                 for ( Artifact arrArtifact : (Set<Artifact>) arr.getArtifacts() )
653                 {
654                     copyArtifact( arrArtifact, testRepository );
655                 }
656             }
657             catch ( Exception e )
658             {
659                 throw new MojoExecutionException( "Unable to resolve dependencies for: " + artifact, e );
660             }
661         }
662     }
663 
664 }