package org.apache.maven.plugins.invoker; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.shared.artifact.install.ArtifactInstaller; import org.apache.maven.shared.dependencies.DefaultDependableCoordinate; import org.apache.maven.shared.dependencies.resolve.DependencyResolver; import org.apache.maven.shared.dependencies.resolve.DependencyResolverException; import org.apache.maven.shared.repository.RepositoryManager; import org.codehaus.plexus.util.FileUtils; /** * Installs the project artifacts of the main build into the local repository as a preparation to run the sub projects. * More precisely, all artifacts of the project itself, all its locally reachable parent POMs and all its dependencies * from the reactor will be installed to the local repository. * * @since 1.2 * @author Paul Gier * @author Benjamin Bentmann * @version $Id: InstallMojo.java 1748000 2016-06-12 13:20:59Z rfscholte $ */ // CHECKSTYLE_OFF: LineLength @Mojo( name = "install", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true ) // CHECKSTYLE_ON: LineLength public class InstallMojo extends AbstractMojo { /** * Maven artifact install component to copy artifacts to the local repository. */ @Component private ArtifactInstaller installer; @Component private RepositoryManager repositoryManager; /** * The component used to create artifacts. */ @Component private ArtifactFactory artifactFactory; /** */ @Parameter( property = "localRepository", required = true, readonly = true ) private ArtifactRepository localRepository; /** * The path to the local repository into which the project artifacts should be installed for the integration tests. * If not set, the regular local repository will be used. To prevent soiling of your regular local repository with * possibly broken artifacts, it is strongly recommended to use an isolated repository for the integration tests * (e.g. <code>${project.build.directory}/it-repo</code>). */ @Parameter( property = "invoker.localRepositoryPath", defaultValue = "${session.localRepository.basedir}", required = true ) private File localRepositoryPath; /** * The current Maven project. */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) private MavenProject project; @Parameter( defaultValue = "${session}", readonly = true, required = true ) private MavenSession session; /** * The set of Maven projects in the reactor build. */ @Parameter( defaultValue = "${reactorProjects}", readonly = true ) private Collection<MavenProject> reactorProjects; /** * A flag used to disable the installation procedure. This is primarily intended for usage from the command line to * occasionally adjust the build. * * @since 1.4 */ @Parameter( property = "invoker.skip", defaultValue = "false" ) private boolean skipInstallation; /** * The identifiers of already installed artifacts, used to avoid multiple installation of the same artifact. */ private Collection<String> installedArtifacts; /** * The identifiers of already copied artifacts, used to avoid multiple installation of the same artifact. */ private Collection<String> copiedArtifacts; /** * Extra dependencies that need to be installed on the local repository.<BR> * Format: * * <pre> * groupId:artifactId:version:type:classifier * </pre> * * Examples: * * <pre> * org.apache.maven.plugins:maven-clean-plugin:2.4:maven-plugin * org.apache.maven.plugins:maven-clean-plugin:2.4:jar:javadoc * </pre> * * If the type is 'maven-plugin' the plugin will try to resolve the artifact using plugin remote repositories, * instead of using artifact remote repositories. * * @since 1.6 */ @Parameter private String[] extraArtifacts; /** */ @Component private DependencyResolver resolver; private ProjectBuildingRequest projectBuildingRequest; /** * Performs this mojo's tasks. * * @throws MojoExecutionException If the artifacts could not be installed. */ public void execute() throws MojoExecutionException { if ( skipInstallation ) { getLog().info( "Skipping artifact installation per configuration." ); return; } createTestRepository(); installedArtifacts = new HashSet<String>(); copiedArtifacts = new HashSet<String>(); installProjectDependencies( project, reactorProjects ); installProjectParents( project ); installProjectArtifacts( project ); installExtraArtifacts( extraArtifacts ); } /** * Creates the local repository for the integration tests. If the user specified a custom repository location, the * custom repository will have the same identifier, layout and policies as the real local repository. That means * apart from the location, the custom repository will be indistinguishable from the real repository such that its * usage is transparent to the integration tests. * * @return The local repository for the integration tests, never <code>null</code>. * @throws MojoExecutionException If the repository could not be created. */ private void createTestRepository() throws MojoExecutionException { if ( !localRepositoryPath.exists() && !localRepositoryPath.mkdirs() ) { throw new MojoExecutionException( "Failed to create directory: " + localRepositoryPath ); } projectBuildingRequest = repositoryManager.setLocalRepositoryBasedir( session.getProjectBuildingRequest(), localRepositoryPath ); } /** * Installs the specified artifact to the local repository. Note: This method should only be used for artifacts that * originate from the current (reactor) build. Artifacts that have been grabbed from the user's local repository * should be installed to the test repository via {@link #copyArtifact(File, Artifact)}. * * @param file The file associated with the artifact, must not be <code>null</code>. This is in most cases the value * of <code>artifact.getFile()</code> with the exception of the main artifact from a project with * packaging "pom". Projects with packaging "pom" have no main artifact file. They have however artifact * metadata (e.g. site descriptors) which needs to be installed. * @param artifact The artifact to install, must not be <code>null</code>. * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file). */ private void installArtifact( File file, Artifact artifact ) throws MojoExecutionException { try { if ( file == null ) { throw new IllegalStateException( "Artifact has no associated file: " + artifact.getId() ); } if ( !file.isFile() ) { throw new IllegalStateException( "Artifact is not fully assembled: " + file ); } if ( installedArtifacts.add( artifact.getId() ) ) { artifact.setFile( file ); installer.install( projectBuildingRequest, localRepositoryPath, Collections.singletonList( artifact ) ); } else { getLog().debug( "Not re-installing " + artifact + ", " + file ); } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to install artifact: " + artifact, e ); } } /** * Installs the specified artifact to the local repository. This method serves basically the same purpose as * {@link #installArtifact(File, Artifact)} but is meant for artifacts that have been resolved * from the user's local repository (and not the current build outputs). The subtle difference here is that * artifacts from the repository have already undergone transformations and these manipulations should not be redone * by the artifact installer. For this reason, this method performs plain copy operations to install the artifacts. * * @param file The file associated with the artifact, must not be <code>null</code>. * @param artifact The artifact to install, must not be <code>null</code>. * @throws MojoExecutionException If the artifact could not be installed (e.g. has no associated file). */ private void copyArtifact( File file, Artifact artifact ) throws MojoExecutionException { try { if ( file == null ) { throw new IllegalStateException( "Artifact has no associated file: " + artifact.getId() ); } if ( !file.isFile() ) { throw new IllegalStateException( "Artifact is not fully assembled: " + file ); } if ( copiedArtifacts.add( artifact.getId() ) ) { File destination = new File( localRepositoryPath, repositoryManager.getPathForLocalArtifact( projectBuildingRequest, artifact ) ); getLog().debug( "Installing " + file + " to " + destination ); copyFileIfDifferent( file, destination ); MetadataUtils.createMetadata( destination, artifact ); } else { getLog().debug( "Not re-installing " + artifact + ", " + file ); } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to stage artifact: " + artifact, e ); } } private void copyFileIfDifferent( File src, File dst ) throws IOException { if ( src.lastModified() != dst.lastModified() || src.length() != dst.length() ) { FileUtils.copyFile( src, dst ); dst.setLastModified( src.lastModified() ); } } /** * Installs the main artifact and any attached artifacts of the specified project to the local repository. * * @param mvnProject The project whose artifacts should be installed, must not be <code>null</code>. * @throws MojoExecutionException If any artifact could not be installed. */ private void installProjectArtifacts( MavenProject mvnProject ) throws MojoExecutionException { try { // Install POM (usually attached as metadata but that happens only as a side effect of the Install Plugin) installProjectPom( mvnProject ); // Install the main project artifact (if the project has one, e.g. has no "pom" packaging) Artifact mainArtifact = mvnProject.getArtifact(); if ( mainArtifact.getFile() != null ) { installArtifact( mainArtifact.getFile(), mainArtifact ); } // Install any attached project artifacts Collection<Artifact> attachedArtifacts = (Collection<Artifact>) mvnProject.getAttachedArtifacts(); for ( Artifact attachedArtifact : attachedArtifacts ) { installArtifact( attachedArtifact.getFile(), attachedArtifact ); } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to install project artifacts: " + mvnProject, e ); } } /** * Installs the (locally reachable) parent POMs of the specified project to the local repository. The parent POMs * from the reactor must be installed or the forked IT builds will fail when using a clean repository. * * @param mvnProject The project whose parent POMs should be installed, must not be <code>null</code>. * @throws MojoExecutionException If any POM could not be installed. */ private void installProjectParents( MavenProject mvnProject ) throws MojoExecutionException { try { for ( MavenProject parent = mvnProject.getParent(); parent != null; parent = parent.getParent() ) { if ( parent.getFile() == null ) { copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion() ); break; } installProjectPom( parent ); } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to install project parents: " + mvnProject, e ); } } /** * Installs the POM of the specified project to the local repository. * * @param mvnProject The project whose POM should be installed, must not be <code>null</code>. * @throws MojoExecutionException If the POM could not be installed. */ private void installProjectPom( MavenProject mvnProject ) throws MojoExecutionException { try { Artifact pomArtifact = null; if ( "pom".equals( mvnProject.getPackaging() ) ) { pomArtifact = mvnProject.getArtifact(); } if ( pomArtifact == null ) { pomArtifact = artifactFactory.createProjectArtifact( mvnProject.getGroupId(), mvnProject.getArtifactId(), mvnProject.getVersion() ); } installArtifact( mvnProject.getFile(), pomArtifact ); } catch ( Exception e ) { throw new MojoExecutionException( "Failed to install POM: " + mvnProject, e ); } } /** * Installs the dependent projects from the reactor to the local repository. The dependencies on other modules from * the reactor must be installed or the forked IT builds will fail when using a clean repository. * * @param mvnProject The project whose dependent projects should be installed, must not be <code>null</code>. * @param reactorProjects The set of projects in the reactor build, must not be <code>null</code>. * @throws MojoExecutionException If any dependency could not be installed. */ private void installProjectDependencies( MavenProject mvnProject, Collection<MavenProject> reactorProjects ) throws MojoExecutionException { // keep track if we have passed mvnProject in reactorProjects boolean foundCurrent = false; // ... into dependencies that were resolved from reactor projects ... Collection<String> dependencyProjects = new LinkedHashSet<String>(); // index available reactor projects Map<String, MavenProject> projects = new HashMap<String, MavenProject>( reactorProjects.size() ); for ( MavenProject reactorProject : reactorProjects ) { String projectId = reactorProject.getGroupId() + ':' + reactorProject.getArtifactId() + ':' + reactorProject.getVersion(); projects.put( projectId, reactorProject ); // only add projects of reactor build previous to this mvnProject foundCurrent |= ( mvnProject.equals( reactorProject ) ); if ( !foundCurrent ) { dependencyProjects.add( projectId ); } } // group transitive dependencies (even those that don't contribute to the class path like POMs) ... Collection<Artifact> artifacts = (Collection<Artifact>) mvnProject.getArtifacts(); // ... and those that were resolved from the (local) repo Collection<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>(); for ( Artifact artifact : artifacts ) { // workaround for MNG-2961 to ensure the base version does not contain a timestamp artifact.isSnapshot(); String projectId = artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getBaseVersion(); if ( !projects.containsKey( projectId ) ) { dependencyArtifacts.add( artifact ); } } // install dependencies try { // copy dependencies that where resolved from the local repo for ( Artifact artifact : dependencyArtifacts ) { copyArtifact( artifact ); } // install dependencies that were resolved from the reactor for ( String projectId : dependencyProjects ) { MavenProject dependencyProject = projects.get( projectId ); installProjectArtifacts( dependencyProject ); installProjectParents( dependencyProject ); } } catch ( Exception e ) { throw new MojoExecutionException( "Failed to install project dependencies: " + mvnProject, e ); } } private void copyArtifact( Artifact artifact ) throws MojoExecutionException { copyPoms( artifact ); Artifact depArtifact = artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(), artifact.getBaseVersion(), artifact.getType(), artifact.getClassifier() ); File artifactFile = artifact.getFile(); copyArtifact( artifactFile, depArtifact ); } private void copyPoms( Artifact artifact ) throws MojoExecutionException { Artifact pomArtifact = artifactFactory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getBaseVersion() ); File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) ); if ( pomFile.isFile() ) { copyArtifact( pomFile, pomArtifact ); copyParentPoms( pomFile ); } } /** * Installs all parent POMs of the specified POM file that are available in the local repository. * * @param pomFile The path to the POM file whose parents should be installed, must not be <code>null</code>. * @throws MojoExecutionException If any (existing) parent POM could not be installed. */ private void copyParentPoms( File pomFile ) throws MojoExecutionException { Model model = PomUtils.loadPom( pomFile ); Parent parent = model.getParent(); if ( parent != null ) { copyParentPoms( parent.getGroupId(), parent.getArtifactId(), parent.getVersion() ); } } /** * Installs the specified POM and all its parent POMs to the local repository. * * @param groupId The group id of the POM which should be installed, must not be <code>null</code>. * @param artifactId The artifact id of the POM which should be installed, must not be <code>null</code>. * @param version The version of the POM which should be installed, must not be <code>null</code>. * @throws MojoExecutionException If any (existing) parent POM could not be installed. */ private void copyParentPoms( String groupId, String artifactId, String version ) throws MojoExecutionException { Artifact pomArtifact = artifactFactory.createProjectArtifact( groupId, artifactId, version ); if ( installedArtifacts.contains( pomArtifact.getId() ) || copiedArtifacts.contains( pomArtifact.getId() ) ) { getLog().debug( "Not re-installing " + pomArtifact ); return; } File pomFile = new File( localRepository.getBasedir(), localRepository.pathOf( pomArtifact ) ); if ( pomFile.isFile() ) { copyArtifact( pomFile, pomArtifact ); copyParentPoms( pomFile ); } } private void installExtraArtifacts( String[] extraArtifacts ) throws MojoExecutionException { if ( extraArtifacts == null ) { return; } for ( String extraArtifact : extraArtifacts ) { String[] gav = extraArtifact.split( ":" ); if ( gav.length < 3 || gav.length > 5 ) { throw new MojoExecutionException( "Invalid artifact " + extraArtifact ); } String groupId = gav[0]; String artifactId = gav[1]; String version = gav[2]; String type = "jar"; if ( gav.length > 3 ) { type = gav[3]; } String classifier = null; if ( gav.length == 5 ) { classifier = gav[4]; } DefaultDependableCoordinate coordinate = new DefaultDependableCoordinate(); try { coordinate.setGroupId( groupId ); coordinate.setArtifactId( artifactId ); coordinate.setVersion( version ); coordinate.setType( type ); coordinate.setClassifier( classifier ); resolver.resolveDependencies( projectBuildingRequest, coordinate, null ); } catch ( DependencyResolverException e ) { throw new MojoExecutionException( "Unable to resolve dependencies for: " + coordinate, e ); } } } }