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.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
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.installer.ArtifactInstaller;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
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.project.MavenProject;
41  import org.codehaus.plexus.util.FileUtils;
42  
43  /**
44   * Installs the project artifacts of the main build into the local repository as a preparation to run the sub projects.
45   * More precisely, all artifacts of the project itself, all its locally reachable parent POMs and all its dependencies
46   * from the reactor will be installed to the local repository.
47   * 
48   * @goal install
49   * @phase pre-integration-test
50   * @requiresDependencyResolution runtime
51   * @since 1.2
52   * @author Paul Gier
53   * @author Benjamin Bentmann
54   * @version $Id: InstallMojo.java 814226 2009-09-12 20:01:59Z bentmann $
55   */
56  public class InstallMojo
57      extends AbstractMojo
58  {
59  
60      /**
61       * Maven artifact install component to copy artifacts to the local repository.
62       * 
63       * @component
64       */
65      private ArtifactInstaller installer;
66  
67      /**
68       * The component used to create artifacts.
69       * 
70       * @component
71       */
72      private ArtifactFactory artifactFactory;
73  
74      /**
75       * The component used to create artifacts.
76       * 
77       * @component
78       */
79      private ArtifactRepositoryFactory repositoryFactory;
80  
81      /**
82       * @parameter expression="${localRepository}"
83       * @required
84       * @readonly
85       */
86      private ArtifactRepository localRepository;
87  
88      /**
89       * The path to the local repository into which the project artifacts should be installed for the integration tests.
90       * If not set, the regular local repository will be used. To prevent soiling of your regular local repository with
91       * possibly broken artifacts, it is strongly recommended to use an isolated repository for the integration tests
92       * (e.g. <code>${project.build.directory}/it-repo</code>).
93       * 
94       * @parameter expression="${invoker.localRepositoryPath}"
95       */
96      private File localRepositoryPath;
97  
98      /**
99       * The current Maven project.
100      * 
101      * @parameter expression="${project}"
102      * @required
103      * @readonly
104      */
105     private MavenProject project;
106 
107     /**
108      * The set of Maven projects in the reactor build.
109      * 
110      * @parameter default-value="${reactorProjects}"
111      * @readonly
112      */
113     private Collection 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      * @parameter expression="${invoker.skip}" default-value="false"
120      * @since 1.4
121      */
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 installedArtifacts;
128 
129     /**
130      * The identifiers of already copied artifacts, used to avoid multiple installation of the same artifact.
131      */
132     private Collection copiedArtifacts;
133 
134     /**
135      * Performs this mojo's tasks.
136      * 
137      * @throws MojoExecutionException If the artifacts could not be installed.
138      */
139     public void execute()
140         throws MojoExecutionException
141     {
142         if ( skipInstallation )
143         {
144             getLog().info( "Skipping artifact installation per configuration." );
145             return;
146         }
147 
148         ArtifactRepository testRepository = createTestRepository();
149 
150         installedArtifacts = new HashSet();
151         copiedArtifacts = new HashSet();
152 
153         installProjectDependencies( project, reactorProjects, testRepository );
154         installProjectParents( project, testRepository );
155         installProjectArtifacts( project, testRepository );
156     }
157 
158     /**
159      * Creates the local repository for the integration tests. If the user specified a custom repository location, the
160      * custom repository will have the same identifier, layout and policies as the real local repository. That means
161      * apart from the location, the custom repository will be indistinguishable from the real repository such that its
162      * usage is transparent to the integration tests.
163      * 
164      * @return The local repository for the integration tests, never <code>null</code>.
165      * @throws MojoExecutionException If the repository could not be created.
166      */
167     private ArtifactRepository createTestRepository()
168         throws MojoExecutionException
169     {
170         ArtifactRepository testRepository = localRepository;
171 
172         if ( localRepositoryPath != null )
173         {
174             try
175             {
176                 if ( !localRepositoryPath.exists() && !localRepositoryPath.mkdirs() )
177                 {
178                     throw new IOException( "Failed to create directory: " + localRepositoryPath );
179                 }
180 
181                 testRepository =
182                     repositoryFactory.createArtifactRepository( localRepository.getId(),
183                                                                 localRepositoryPath.toURL().toExternalForm(),
184                                                                 localRepository.getLayout(),
185                                                                 localRepository.getSnapshots(),
186                                                                 localRepository.getReleases() );
187             }
188             catch ( Exception e )
189             {
190                 throw new MojoExecutionException( "Failed to create local repository: " + localRepositoryPath, e );
191             }
192         }
193 
194         return testRepository;
195     }
196 
197     /**
198      * Installs the specified artifact to the local repository. Note: This method should only be used for artifacts that
199      * originate from the current (reactor) build. Artifacts that have been grabbed from the user's local repository
200      * should be installed to the test repository via {@link #copyArtifact(File, Artifact, ArtifactRepository)}.
201      * 
202      * @param file The file associated with the artifact, must not be <code>null</code>. This is in most cases the value
203      *            of <code>artifact.getFile()</code> with the exception of the main artifact from a project with
204      *            packaging "pom". Projects with packaging "pom" have no main artifact file. They have however artifact
205      *            metadata (e.g. site descriptors) which needs to be installed.
206      * @param artifact The artifact to install, must not be <code>null</code>.
207      * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
208      * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
209      */
210     private void installArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
211         throws MojoExecutionException
212     {
213         try
214         {
215             if ( file == null )
216             {
217                 throw new IllegalStateException( "Artifact has no associated file: " + file );
218             }
219             if ( !file.isFile() )
220             {
221                 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
222             }
223 
224             if ( installedArtifacts.add( artifact.getId() ) )
225             {
226                 installer.install( file, artifact, testRepository );
227             }
228             else
229             {
230                 getLog().debug( "Not re-installing " + artifact + ", " + file );
231             }
232         }
233         catch ( Exception e )
234         {
235             throw new MojoExecutionException( "Failed to install artifact: " + artifact, e );
236         }
237     }
238 
239     /**
240      * Installs the specified artifact to the local repository. This method serves basically the same purpose as
241      * {@link #installArtifact(File, Artifact, ArtifactRepository)} but is meant for artifacts that have been resolved
242      * from the user's local repository (and not the current build outputs). The subtle difference here is that
243      * artifacts from the repository have already undergone transformations and these manipulations should not be redone
244      * by the artifact installer. For this reason, this method performs plain copy operations to install the artifacts.
245      * 
246      * @param file The file associated with the artifact, must not be <code>null</code>.
247      * @param artifact The artifact to install, must not be <code>null</code>.
248      * @param testRepository The local repository to install the artifact to, must not be <code>null</code>.
249      * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file).
250      */
251     private void copyArtifact( File file, Artifact artifact, ArtifactRepository testRepository )
252         throws MojoExecutionException
253     {
254         try
255         {
256             if ( file == null )
257             {
258                 throw new IllegalStateException( "Artifact has no associated file: " + file );
259             }
260             if ( !file.isFile() )
261             {
262                 throw new IllegalStateException( "Artifact is not fully assembled: " + file );
263             }
264 
265             if ( copiedArtifacts.add( artifact.getId() ) )
266             {
267                 File destination = new File( testRepository.getBasedir(), testRepository.pathOf( artifact ) );
268 
269                 getLog().debug( "Installing " + file + " to " + destination );
270 
271                 copyFileIfDifferent( file, destination );
272 
273                 MetadataUtils.createMetadata( destination, artifact );
274             }
275             else
276             {
277                 getLog().debug( "Not re-installing " + artifact + ", " + file );
278             }
279         }
280         catch ( Exception e )
281         {
282             throw new MojoExecutionException( "Failed to stage artifact: " + artifact, e );
283         }
284     }
285 
286     private void copyFileIfDifferent( File src, File dst )
287         throws IOException
288     {
289         if ( src.lastModified() != dst.lastModified() || src.length() != dst.length() )
290         {
291             FileUtils.copyFile( src, dst );
292             dst.setLastModified( src.lastModified() );
293         }
294     }
295 
296     /**
297      * Installs the main artifact and any attached artifacts of the specified project to the local repository.
298      * 
299      * @param mvnProject The project whose artifacts should be installed, must not be <code>null</code>.
300      * @param testRepository The local repository to install the artifacts to, must not be <code>null</code>.
301      * @throws MojoExecutionException If any artifact could not be installed.
302      */
303     private void installProjectArtifacts( MavenProject mvnProject, ArtifactRepository testRepository )
304         throws MojoExecutionException
305     {
306         try
307         {
308             // Install POM (usually attached as metadata but that happens only as a side effect of the Install Plugin)
309             installProjectPom( mvnProject, testRepository );
310 
311             // Install the main project artifact (if the project has one, e.g. has no "pom" packaging)
312             Artifact mainArtifact = mvnProject.getArtifact();
313             if ( mainArtifact.getFile() != null )
314             {
315                 installArtifact( mainArtifact.getFile(), mainArtifact, testRepository );
316             }
317 
318             // Install any attached project artifacts
319             Collection attachedArtifacts = mvnProject.getAttachedArtifacts();
320             for ( Iterator artifactIter = attachedArtifacts.iterator(); artifactIter.hasNext(); )
321             {
322                 Artifact attachedArtifact = (Artifact) artifactIter.next();
323                 installArtifact( attachedArtifact.getFile(), attachedArtifact, testRepository );
324             }
325         }
326         catch ( Exception e )
327         {
328             throw new MojoExecutionException( "Failed to install project artifacts: " + mvnProject, e );
329         }
330     }
331 
332     /**
333      * Installs the (locally reachable) parent POMs of the specified project to the local repository. The parent POMs
334      * from the reactor must be installed or the forked IT builds will fail when using a clean repository.
335      * 
336      * @param mvnProject The project whose parent POMs should be installed, must not be <code>null</code>.
337      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
338      * @throws MojoExecutionException If any POM could not be installed.
339      */
340     private void installProjectParents( MavenProject mvnProject, ArtifactRepository testRepository )
341         throws MojoExecutionException
342     {
343         try
344         {
345             for ( MavenProject parent = mvnProject.getParent(); parent != null; parent = parent.getParent() )
346             {
347                 if ( parent.getFile() == null )
348                 {
349                     copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
350                     break;
351                 }
352                 installProjectPom( parent, testRepository );
353             }
354         }
355         catch ( Exception e )
356         {
357             throw new MojoExecutionException( "Failed to install project parents: " + mvnProject, e );
358         }
359     }
360 
361     /**
362      * Installs the POM of the specified project to the local repository.
363      * 
364      * @param mvnProject The project whose POM should be installed, must not be <code>null</code>.
365      * @param testRepository The local repository to install the POM to, must not be <code>null</code>.
366      * @throws MojoExecutionException If the POM could not be installed.
367      */
368     private void installProjectPom( MavenProject mvnProject, ArtifactRepository testRepository )
369         throws MojoExecutionException
370     {
371         try
372         {
373             Artifact pomArtifact = null;
374             if ( "pom".equals( mvnProject.getPackaging() ) )
375             {
376                 pomArtifact = mvnProject.getArtifact();
377             }
378             if ( pomArtifact == null )
379             {
380                 pomArtifact =
381                     artifactFactory.createProjectArtifact( mvnProject.getGroupId(), mvnProject.getArtifactId(),
382                                                            mvnProject.getVersion() );
383             }
384             installArtifact( mvnProject.getFile(), pomArtifact, testRepository );
385         }
386         catch ( Exception e )
387         {
388             throw new MojoExecutionException( "Failed to install POM: " + mvnProject, e );
389         }
390     }
391 
392     /**
393      * Installs the dependent projects from the reactor to the local repository. The dependencies on other modules from
394      * the reactor must be installed or the forked IT builds will fail when using a clean repository.
395      * 
396      * @param mvnProject The project whose dependent projects should be installed, must not be <code>null</code>.
397      * @param reactorProjects The set of projects in the reactor build, must not be <code>null</code>.
398      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
399      * @throws MojoExecutionException If any dependency could not be installed.
400      */
401     private void installProjectDependencies( MavenProject mvnProject, Collection reactorProjects,
402                                              ArtifactRepository testRepository )
403         throws MojoExecutionException
404     {
405         // index available reactor projects
406         Map projects = new HashMap();
407         for ( Iterator it = reactorProjects.iterator(); it.hasNext(); )
408         {
409             MavenProject reactorProject = (MavenProject) it.next();
410 
411             String projectId =
412                 reactorProject.getGroupId() + ':' + reactorProject.getArtifactId() + ':' + reactorProject.getVersion();
413 
414             projects.put( projectId, reactorProject );
415         }
416 
417         // group transitive dependencies (even those that don't contribute to the class path like POMs) ...
418         Collection artifacts = mvnProject.getArtifacts();
419         // ... into dependencies that were resolved from reactor projects ...
420         Collection dependencyProjects = new LinkedHashSet();
421         // ... and those that were resolved from the (local) repo
422         Collection dependencyArtifacts = new LinkedHashSet();
423         for ( Iterator it = artifacts.iterator(); it.hasNext(); )
424         {
425             Artifact artifact = (Artifact) it.next();
426 
427             // workaround for MNG-2961 to ensure the base version does not contain a timestamp
428             artifact.isSnapshot();
429 
430             String projectId = artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion();
431 
432             if ( projects.containsKey( projectId ) )
433             {
434                 dependencyProjects.add( projectId );
435             }
436             else
437             {
438                 dependencyArtifacts.add( artifact );
439             }
440         }
441 
442         // install dependencies
443         try
444         {
445             // copy dependencies that where resolved from the local repo
446             for ( Iterator it = dependencyArtifacts.iterator(); it.hasNext(); )
447             {
448                 Artifact artifact = (Artifact) it.next();
449 
450                 Artifact depArtifact =
451                     artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(),
452                                                                   artifact.getBaseVersion(), artifact.getType(),
453                                                                   artifact.getClassifier() );
454 
455                 File artifactFile = artifact.getFile();
456 
457                 Artifact pomArtifact =
458                     artifactFactory.createProjectArtifact( depArtifact.getGroupId(), depArtifact.getArtifactId(),
459                                                            depArtifact.getBaseVersion() );
460 
461                 File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
462 
463                 if ( pomFile.isFile() )
464                 {
465                     if ( !pomArtifact.getId().equals( depArtifact.getId() ) )
466                     {
467                         copyArtifact( pomFile, pomArtifact, testRepository );
468                     }
469                     copyParentPoms( pomFile, testRepository );
470                 }
471 
472                 copyArtifact( artifactFile, depArtifact, testRepository );
473             }
474 
475             // install dependencies that were resolved from the reactor
476             for ( Iterator it = dependencyProjects.iterator(); it.hasNext(); )
477             {
478                 String projectId = (String) it.next();
479 
480                 MavenProject dependencyProject = (MavenProject) projects.get( projectId );
481 
482                 installProjectArtifacts( dependencyProject, testRepository );
483                 installProjectParents( dependencyProject, testRepository );
484             }
485         }
486         catch ( Exception e )
487         {
488             throw new MojoExecutionException( "Failed to install project dependencies: " + mvnProject, e );
489         }
490     }
491 
492     /**
493      * Installs all parent POMs of the specified POM file that are available in the local repository.
494      * 
495      * @param pomFile The path to the POM file whose parents should be installed, must not be <code>null</code>.
496      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
497      * @throws MojoExecutionException If any (existing) parent POM could not be installed.
498      */
499     private void copyParentPoms( File pomFile, ArtifactRepository testRepository )
500         throws MojoExecutionException
501     {
502         Model model = PomUtils.loadPom( pomFile );
503         Parent parent = model.getParent();
504         if ( parent != null )
505         {
506             copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), testRepository );
507         }
508     }
509 
510     /**
511      * Installs the specified POM and all its parent POMs to the local repository.
512      * 
513      * @param groupId The group id of the POM which should be installed, must not be <code>null</code>.
514      * @param artifactId The artifact id of the POM which should be installed, must not be <code>null</code>.
515      * @param version The version of the POM which should be installed, must not be <code>null</code>.
516      * @param testRepository The local repository to install the POMs to, must not be <code>null</code>.
517      * @throws MojoExecutionException If any (existing) parent POM could not be installed.
518      */
519     private void copyParentPoms( String groupId, String artifactId, String version, ArtifactRepository testRepository )
520         throws MojoExecutionException
521     {
522         Artifact pomArtifact = artifactFactory.createProjectArtifact( groupId, artifactId, version );
523 
524         if ( installedArtifacts.contains( pomArtifact.getId() ) || copiedArtifacts.contains( pomArtifact.getId() ) )
525         {
526             getLog().debug( "Not re-installing " + pomArtifact );
527             return;
528         }
529 
530         File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) );
531         if ( pomFile.isFile() )
532         {
533             copyArtifact( pomFile, pomArtifact, testRepository );
534             copyParentPoms( pomFile, testRepository );
535         }
536     }
537 
538 }