Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/CompositeMap.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/CompositeMap.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/CompositeMap.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/CompositeMap.java Mon May 15 21:10:27 2017 @@ -0,0 +1,259 @@ +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.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.introspection.ReflectionValueExtractor; + +/** + * A map-like source to interpolate expressions. + * + * @author Olivier Lamy + * @since 1.1 + * @version $Id: CompositeMap.java 1784076 2017-02-23 00:35:39Z schulte $ + */ +class CompositeMap + implements Map<String, Object> +{ + + /** + * The Maven project from which to extract interpolated values, never <code>null</code>. + */ + private MavenProject mavenProject; + + /** + * The set of additional properties from which to extract interpolated values, never <code>null</code>. + */ + private Map<String, Object> properties; + + /** + * Flag indicating to escape XML special characters. + */ + private final boolean escapeXml; + + /** + * Creates a new interpolation source backed by the specified Maven project and some user-specified properties. + * + * @param mavenProject The Maven project from which to extract interpolated values, must not be <code>null</code>. + * @param properties The set of additional properties from which to extract interpolated values, may be + * <code>null</code>. + * @param escapeXml {@code true}, to escape any XML special characters; {@code false}, to not perform any escaping. + */ + protected CompositeMap( MavenProject mavenProject, Map<String, Object> properties, boolean escapeXml ) + { + if ( mavenProject == null ) + { + throw new IllegalArgumentException( "no project specified" ); + } + this.mavenProject = mavenProject; + this.properties = properties == null ? new HashMap<String, Object>() : properties; + this.escapeXml = escapeXml; + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#clear() + */ + public void clear() + { + // nothing here + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#containsKey(java.lang.Object) + */ + public boolean containsKey( Object key ) + { + if ( !( key instanceof String ) ) + { + return false; + } + + String expression = (String) key; + if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) ) + { + try + { + Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject ); + if ( evaluated != null ) + { + return true; + } + } + catch ( Exception e ) + { + // uhm do we have to throw a RuntimeException here ? + } + } + + return properties.containsKey( key ) || mavenProject.getProperties().containsKey( key ); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#containsValue(java.lang.Object) + */ + public boolean containsValue( Object value ) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#entrySet() + */ + public Set<Entry<String, Object>> entrySet() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#get(java.lang.Object) + */ + public Object get( Object key ) + { + if ( !( key instanceof String ) ) + { + return null; + } + + Object value = null; + String expression = (String) key; + if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) ) + { + try + { + Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject ); + if ( evaluated != null ) + { + value = evaluated; + } + } + catch ( Exception e ) + { + // uhm do we have to throw a RuntimeException here ? + } + } + + if ( value == null ) + { + value = properties.get( key ); + } + + if ( value == null ) + { + value = this.mavenProject.getProperties().get( key ); + } + + if ( value != null && this.escapeXml ) + { + value = value.toString(). + replaceAll( "\"", """ ). + replaceAll( "<", "<" ). + replaceAll( ">", ">" ). + replaceAll( "&", "&" ); + + } + + return value; + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#isEmpty() + */ + public boolean isEmpty() + { + return this.mavenProject.getProperties().isEmpty() && this.properties.isEmpty(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#keySet() + */ + public Set<String> keySet() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#put(java.lang.Object, java.lang.Object) + */ + public Object put( String key, Object value ) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#putAll(java.util.Map) + */ + public void putAll( Map<? extends String, ? extends Object> t ) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#remove(java.lang.Object) + */ + public Object remove( Object key ) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#size() + */ + public int size() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Map#values() + */ + public Collection<Object> values() + { + throw new UnsupportedOperationException(); + } +}
Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/FileLogger.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/FileLogger.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/FileLogger.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/FileLogger.java Mon May 15 21:10:27 2017 @@ -0,0 +1,62 @@ +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 org.apache.maven.plugin.logging.Log; +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.scriptinterpreter.ExecutionLogger; + +/** + * @version $Id: FileLogger.java 1671476 2015-04-06 03:53:29Z dantran $ + */ +class FileLogger + extends org.apache.maven.shared.scriptinterpreter.FileLogger + implements InvocationOutputHandler, ExecutionLogger +{ + + /** + * Creates a new logger that writes to the specified file. + * + * @param outputFile The path to the output file, must not be <code>null</code>. + * @throws IOException If the output file could not be created. + */ + public FileLogger( File outputFile ) + throws IOException + { + super( outputFile, null ); + } + + /** + * Creates a new logger that writes to the specified file and optionally mirrors messages to the given mojo logger. + * + * @param outputFile The path to the output file, must not be <code>null</code>. + * @param log The mojo logger to additionally output messages to, may be <code>null</code> if not used. + * @throws IOException If the output file could not be created. + */ + public FileLogger( File outputFile, Log log ) + throws IOException + { + super( outputFile, log ); + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InstallMojo.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InstallMojo.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InstallMojo.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InstallMojo.java Mon May 15 21:10:27 2017 @@ -0,0 +1,605 @@ +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 ); + } + } + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/IntegrationTestMojo.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/IntegrationTestMojo.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/IntegrationTestMojo.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/IntegrationTestMojo.java Mon May 15 21:10:27 2017 @@ -0,0 +1,48 @@ +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 org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; + +/** + * Searches for integration test Maven projects, and executes each, collecting a log in the project directory, will + * never fail the build, designed to be used in conjunction with the verify mojo. + * + * @since 1.4 + * @author <a href="mailto:stephenconnolly at codehaus">Stephen Connolly</a> + * @version $Id: IntegrationTestMojo.java 1637968 2014-11-10 20:02:25Z khmarbaise $ + */ +// CHECKSTYLE_OFF: LineLength +@Mojo( name = "integration-test", defaultPhase = LifecyclePhase.INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true ) +public class IntegrationTestMojo + extends AbstractInvokerMojo +{ + + void processResults( InvokerSession invokerSession ) + throws MojoFailureException + { + // do nothing + } + +} +// CHECKSTYLE_ON: LineLength Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerMojo.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerMojo.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerMojo.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerMojo.java Mon May 15 21:10:27 2017 @@ -0,0 +1,82 @@ +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 org.apache.maven.plugin.MojoFailureException; +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; + +/** + * Searches for integration test Maven projects, and executes each, collecting a log in the project directory, and + * outputting the results to the command line. + * + * @since 1.0 + * @author <a href="mailto:ken...@apache.org">Kenney Westerhof</a> + * @author <a href="mailto:jdca...@apache.org">John Casey</a> + * @version $Id: InvokerMojo.java 1637968 2014-11-10 20:02:25Z khmarbaise $ + */ +// CHECKSTYLE_OFF: LineLength +@Mojo( name = "run", defaultPhase = LifecyclePhase.INTEGRATION_TEST, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true ) +// CHECKSTYLE_ON: LineLength +public class InvokerMojo + extends AbstractInvokerMojo +{ + + /** + * A flag controlling whether failures of the sub builds should fail the main build, too. If set to + * <code>true</code>, the main build will proceed even if one or more sub builds failed. + * + * @since 1.3 + */ + @Parameter( property = "maven.test.failure.ignore", defaultValue = "false" ) + private boolean ignoreFailures; + + /** + * Set this to <code>true</code> to cause a failure if there are no projects to invoke. + * + * @since 1.9 + */ + @Parameter( property = "invoker.failIfNoProjects" ) + private Boolean failIfNoProjects; + + void processResults( InvokerSession invokerSession ) + throws MojoFailureException + { + if ( !suppressSummaries ) + { + invokerSession.logSummary( getLog(), ignoreFailures ); + } + + invokerSession.handleFailures( getLog(), ignoreFailures ); + } + + @Override + protected void doFailIfNoProjects() + throws MojoFailureException + { + if ( Boolean.TRUE.equals( failIfNoProjects ) ) + { + throw new MojoFailureException( "No projects to invoke!" ); + } + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerProperties.java Mon May 15 21:10:27 2017 @@ -0,0 +1,365 @@ +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.util.ArrayList; +import java.util.Arrays; +import java.util.Properties; + +import org.apache.maven.shared.invoker.InvocationRequest; +import org.apache.maven.shared.invoker.InvocationRequest.ReactorFailureBehavior; +import org.codehaus.plexus.util.StringUtils; + +/** + * Provides a convenient facade around the <code>invoker.properties</code>. + * + * @author Benjamin Bentmann + * @version $Id: InvokerProperties.java 1779250 2017-01-17 20:20:02Z rfscholte $ + */ +class InvokerProperties +{ + private static final String SELECTOR_PREFIX = "selector."; + + private enum InvocationProperty + { + PROJECT( "invoker.project" ), + GOALS( "invoker.goals" ), + PROFILES( "invoker.profiles" ), + MAVEN_OPTS( "invoker.mavenOpts" ), + FAILURE_BEHAVIOR( "invoker.failureBehavior" ), + NON_RECURSIVE( "invoker.nonRecursive" ), + OFFLINE( "invoker.offline" ), + SYSTEM_PROPERTIES_FILE( "invoker.systemPropertiesFile" ), + DEBUG( "invoker.debug" ); + + private final String key; + + private InvocationProperty( final String s ) + { + this.key = s; + } + + @Override + public String toString() + { + return key; + } + } + + private enum SelectorProperty + { + JAVA_VERSION( ".java.version" ), + MAVEN_VERSION( ".maven.version" ), + OS_FAMLY( ".os.family" ); + + private final String suffix; + + private SelectorProperty( String suffix ) + { + this.suffix = suffix; + } + + @Override + public String toString() + { + return suffix; + } + } + + /** + * The invoker properties being wrapped. + */ + private final Properties properties; + + /** + * Creates a new facade for the specified invoker properties. The properties will not be copied, so any changes to + * them will be reflected by the facade. + * + * @param properties The invoker properties to wrap, may be <code>null</code> if none. + */ + public InvokerProperties( Properties properties ) + { + this.properties = ( properties != null ) ? properties : new Properties(); + } + + /** + * Gets the invoker properties being wrapped. + * + * @return The invoker properties being wrapped, never <code>null</code>. + */ + public Properties getProperties() + { + return this.properties; + } + + /** + * Gets the name of the corresponding build job. + * + * @return The name of the build job or an empty string if not set. + */ + public String getJobName() + { + return this.properties.getProperty( "invoker.name", "" ); + } + + /** + * Gets the description of the corresponding build job. + * + * @return The description of the build job or an empty string if not set. + */ + public String getJobDescription() + { + return this.properties.getProperty( "invoker.description", "" ); + } + + /** + * Gets the specification of JRE versions on which this build job should be run. + * + * @return The specification of JRE versions or an empty string if not set. + */ + public String getJreVersion() + { + return this.properties.getProperty( "invoker.java.version", "" ); + } + + /** + * Gets the specification of JRE versions on which this build job should be run. + * + * @return The specification of JRE versions or an empty string if not set. + */ + public String getJreVersion( int index ) + { + return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.JAVA_VERSION.suffix, + getJreVersion() ); + } + + /** + * Gets the specification of Maven versions on which this build job should be run. + * + * @return The specification of Maven versions on which this build job should be run. + * @since 1.5 + */ + public String getMavenVersion() + { + return this.properties.getProperty( "invoker.maven.version", "" ); + } + + /** + * + * @param index the selector index + * @return The specification of Maven versions on which this build job should be run. + * @since 3.0.0 + */ + public String getMavenVersion( int index ) + { + return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.MAVEN_VERSION.suffix, + getMavenVersion() ); + } + + /** + * Gets the specification of OS families on which this build job should be run. + * + * @return The specification of OS families or an empty string if not set. + */ + public String getOsFamily() + { + return this.properties.getProperty( "invoker.os.family", "" ); + } + + /** + * Gets the specification of OS families on which this build job should be run. + * + * @param index the selector index + * @return The specification of OS families or an empty string if not set. + * @since 3.0.0 + */ + public String getOsFamily( int index ) + { + return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.OS_FAMLY.suffix, + getOsFamily() ); + } + + + /** + * Determines whether these invoker properties contain a build definition for the specified invocation index. + * + * @param index The one-based index of the invocation to check for, must not be negative. + * @return <code>true</code> if the invocation with the specified index is defined, <code>false</code> otherwise. + */ + public boolean isInvocationDefined( int index ) + { + for ( InvocationProperty prop : InvocationProperty.values() ) + { + if ( properties.getProperty( prop.toString() + '.' + index ) != null ) + { + return true; + } + } + return false; + } + + /** + * Determines whether these invoker properties contain a build definition for the specified selector index. + * + * @param index the index + * @return <code>true</code> if the selector with the specified index is defined, <code>false</code> otherwise. + * @since 3.0.0 + */ + public boolean isSelectorDefined( int index ) + { + for ( SelectorProperty prop : SelectorProperty.values() ) + { + if ( properties.getProperty( SELECTOR_PREFIX + index + prop.suffix ) != null ) + { + return true; + } + } + return false; + } + + /** + * Configures the specified invocation request from these invoker properties. Settings not present in the invoker + * properties will be left unchanged in the invocation request. + * + * @param request The invocation request to configure, must not be <code>null</code>. + * @param index The one-based index of the invocation to configure, must not be negative. + */ + public void configureInvocation( InvocationRequest request, int index ) + { + String project = get( InvocationProperty.PROJECT, index ); + if ( project != null ) + { + File file = new File( request.getBaseDirectory(), project ); + if ( file.isFile() ) + { + request.setBaseDirectory( file.getParentFile() ); + request.setPomFile( file ); + } + else + { + request.setBaseDirectory( file ); + request.setPomFile( null ); + } + } + + String goals = get( InvocationProperty.GOALS, index ); + if ( goals != null ) + { + request.setGoals( new ArrayList<String>( Arrays.asList( StringUtils.split( goals, ", \t\n\r\f" ) ) ) ); + } + + String profiles = get( InvocationProperty.PROFILES, index ); + if ( profiles != null ) + { + // CHECKSTYLE_OFF: LineLength + request.setProfiles( new ArrayList<String>( Arrays.asList( StringUtils.split( profiles, + ", \t\n\r\f" ) ) ) ); + // CHECKSTYLE_ON: LineLength + } + + String mvnOpts = get( InvocationProperty.MAVEN_OPTS, index ); + if ( mvnOpts != null ) + { + request.setMavenOpts( mvnOpts ); + } + + String failureBehavior = get( InvocationProperty.FAILURE_BEHAVIOR, index ); + if ( failureBehavior != null ) + { + ReactorFailureBehavior valueOf = + InvocationRequest.ReactorFailureBehavior.valueOfByLongOption( failureBehavior ); + request.setReactorFailureBehavior( valueOf ); + } + + String nonRecursive = get( InvocationProperty.NON_RECURSIVE, index ); + if ( nonRecursive != null ) + { + request.setRecursive( !Boolean.valueOf( nonRecursive ) ); + } + + String offline = get( InvocationProperty.OFFLINE, index ); + if ( offline != null ) + { + request.setOffline( Boolean.valueOf( offline ) ); + } + + String debug = get( InvocationProperty.DEBUG, index ); + if ( debug != null ) + { + request.setDebug( Boolean.valueOf( debug ) ); + } + } + + /** + * Checks whether the specified exit code matches the one expected for the given invocation. + * + * @param exitCode The exit code of the Maven invocation to check. + * @param index The index of the invocation for which to check the exit code, must not be negative. + * @return <code>true</code> if the exit code is zero and a success was expected or if the exit code is non-zero and + * a failue was expected, <code>false</code> otherwise. + */ + public boolean isExpectedResult( int exitCode, int index ) + { + boolean nonZeroExit = "failure".equalsIgnoreCase( get( "invoker.buildResult", index ) ); + return ( exitCode != 0 ) == nonZeroExit; + } + + /** + * Gets the path to the properties file used to set the system properties for the specified invocation. + * + * @param index The index of the invocation for which to check the exit code, must not be negative. + * @return The path to the properties file or <code>null</code> if not set. + */ + public String getSystemPropertiesFile( int index ) + { + return get( InvocationProperty.SYSTEM_PROPERTIES_FILE, index ); + } + + /** + * Gets a value from the invoker properties. The invoker properties are intended to describe the invocation settings + * for multiple builds of the same project. For this reason, the properties are indexed. First, a property named + * <code>key.index</code> will be queried. If this property does not exist, the value of the property named + * <code>key</code> will finally be returned. + * + * @param key The (base) key for the invoker property to lookup, must not be <code>null</code>. + * @param index The index of the invocation for which to retrieve the value, must not be negative. + * @return The value for the requested invoker property or <code>null</code> if not defined. + */ + String get( String key, int index ) + { + if ( index < 0 ) + { + throw new IllegalArgumentException( "invalid invocation index: " + index ); + } + + String value = properties.getProperty( key + '.' + index ); + if ( value == null ) + { + value = properties.getProperty( key ); + } + return value; + } + + private String get( InvocationProperty prop, int index ) + { + return get( prop.toString(), index ); + } +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerReport.java Mon May 15 21:10:27 2017 @@ -0,0 +1,357 @@ +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.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.doxia.sink.Sink; +import org.apache.maven.doxia.siterenderer.Renderer; +import org.apache.maven.plugins.invoker.model.BuildJob; +import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Reader; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.AbstractMavenReport; +import org.apache.maven.reporting.MavenReportException; +import org.codehaus.plexus.i18n.I18N; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Generate a report based on the results of the Maven invocations. <strong>Note:</strong> This mojo doesn't fork any + * lifecycle, if you have a clean working copy, you have to use a command like + * <code>mvn clean integration-test site</code> to ensure the build results are present when this goal is invoked. + * + * @author Olivier Lamy + * @since 1.4 + */ +@Mojo( name = "report", threadSafe = true ) +public class InvokerReport + extends AbstractMavenReport +{ + + /** + * The Maven Project. + */ + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + protected MavenProject project; + + /** + * Doxia Site Renderer component. + */ + @Component + protected Renderer siteRenderer; + + /** + * Internationalization component. + */ + @Component + protected I18N i18n; + + /** + * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from + * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in + * the Maven Site Plugin is used instead. + */ + @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true ) + protected File outputDirectory; + + /** + * Base directory where all build reports have been written to. + */ + @Parameter( defaultValue = "${project.build.directory}/invoker-reports", property = "invoker.reportsDirectory" ) + private File reportsDirectory; + + /** + * The number format used to print percent values in the report locale. + */ + private NumberFormat percentFormat; + + /** + * The number format used to print time values in the report locale. + */ + private NumberFormat secondsFormat; + + protected void executeReport( Locale locale ) + throws MavenReportException + { + DecimalFormatSymbols symbols = new DecimalFormatSymbols( locale ); + percentFormat = new DecimalFormat( getText( locale, "report.invoker.format.percent" ), symbols ); + secondsFormat = new DecimalFormat( getText( locale, "report.invoker.format.seconds" ), symbols ); + + Sink sink = getSink(); + + sink.head(); + + sink.title(); + sink.text( getText( locale, "report.invoker.result.title" ) ); + sink.title_(); + + sink.head_(); + + sink.body(); + + sink.section1(); + sink.sectionTitle1(); + sink.text( getText( locale, "report.invoker.result.title" ) ); + sink.sectionTitle1_(); + sink.paragraph(); + sink.text( getText( locale, "report.invoker.result.description" ) ); + sink.paragraph_(); + sink.section1_(); + + // ---------------------------------- + // build buildJob beans + // ---------------------------------- + File[] reportFiles = ReportUtils.getReportFiles( reportsDirectory ); + if ( reportFiles.length <= 0 ) + { + getLog().info( "no invoker report files found, skip report generation" ); + return; + } + + List<BuildJob> buildJobs = new ArrayList<BuildJob>( reportFiles.length ); + for ( File reportFile : reportFiles ) + { + try + { + BuildJobXpp3Reader reader = new BuildJobXpp3Reader(); + buildJobs.add( reader.read( ReaderFactory.newXmlReader( reportFile ) ) ); + } + catch ( XmlPullParserException e ) + { + throw new MavenReportException( "Failed to parse report file: " + reportFile, e ); + } + catch ( IOException e ) + { + throw new MavenReportException( "Failed to read report file: " + reportFile, e ); + } + } + + // ---------------------------------- + // summary + // ---------------------------------- + + constructSummarySection( buildJobs, locale ); + + // ---------------------------------- + // per file/it detail + // ---------------------------------- + + sink.section2(); + sink.sectionTitle2(); + + sink.text( getText( locale, "report.invoker.detail.title" ) ); + + sink.sectionTitle2_(); + + sink.section2_(); + + // detail tests table header + sink.table(); + + sink.tableRow(); + // ------------------------------------------- + // name | Result | time | message + // ------------------------------------------- + sinkTableHeader( sink, getText( locale, "report.invoker.detail.name" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.detail.result" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.detail.time" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.detail.message" ) ); + + sink.tableRow_(); + + for ( BuildJob buildJob : buildJobs ) + { + renderBuildJob( buildJob, locale ); + } + + sink.table_(); + + sink.body_(); + + sink.flush(); + sink.close(); + } + + private void constructSummarySection( List<? extends BuildJob> buildJobs, Locale locale ) + { + Sink sink = getSink(); + + sink.section2(); + sink.sectionTitle2(); + + sink.text( getText( locale, "report.invoker.summary.title" ) ); + + sink.sectionTitle2_(); + sink.section2_(); + + // ------------------------------------------------------------------------ + // Building a table with + // it number | succes nb | failed nb | Success rate | total time | avg time + // ------------------------------------------------------------------------ + + sink.table(); + sink.tableRow(); + + sinkTableHeader( sink, getText( locale, "report.invoker.summary.number" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.success" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.failed" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.skipped" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.success.rate" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.total" ) ); + sinkTableHeader( sink, getText( locale, "report.invoker.summary.time.avg" ) ); + + int number = buildJobs.size(); + int success = 0; + int failed = 0; + int skipped = 0; + double totalTime = 0; + + for ( BuildJob buildJob : buildJobs ) + { + if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) ) + { + success++; + } + else if ( BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) ) + { + skipped++; + } + else + { + failed++; + } + totalTime += buildJob.getTime(); + } + + sink.tableRow_(); + sink.tableRow(); + + sinkCell( sink, Integer.toString( number ) ); + sinkCell( sink, Integer.toString( success ) ); + sinkCell( sink, Integer.toString( failed ) ); + sinkCell( sink, Integer.toString( skipped ) ); + + if ( success + failed > 0 ) + { + sinkCell( sink, percentFormat.format( (double) success / ( success + failed ) ) ); + } + else + { + sinkCell( sink, "" ); + } + + sinkCell( sink, secondsFormat.format( totalTime ) ); + + sinkCell( sink, secondsFormat.format( totalTime / number ) ); + + sink.tableRow_(); + sink.table_(); + + } + + private void renderBuildJob( BuildJob buildJob, Locale locale ) + { + Sink sink = getSink(); + sink.tableRow(); + StringBuilder buffer = new StringBuilder(); + if ( !StringUtils.isEmpty( buildJob.getName() ) && !StringUtils.isEmpty( buildJob.getDescription() ) ) + { + buffer.append( buildJob.getName() ); + buffer.append( " : " ); + buffer.append( buildJob.getDescription() ); + } + else + { + buffer.append( buildJob.getProject() ); + } + sinkCell( sink, buffer.toString() ); + // FIXME image + sinkCell( sink, buildJob.getResult() ); + sinkCell( sink, secondsFormat.format( buildJob.getTime() ) ); + sinkCell( sink, buildJob.getFailureMessage() ); + sink.tableRow_(); + } + + protected String getOutputDirectory() + { + return outputDirectory.getAbsolutePath(); + } + + protected MavenProject getProject() + { + return project; + } + + protected Renderer getSiteRenderer() + { + return siteRenderer; + } + + public String getDescription( Locale locale ) + { + return getText( locale, "report.invoker.result.description" ); + } + + public String getName( Locale locale ) + { + return getText( locale, "report.invoker.result.name" ); + } + + public String getOutputName() + { + return "invoker-report"; + } + + public boolean canGenerateReport() + { + return ReportUtils.getReportFiles( reportsDirectory ).length > 0; + } + + private String getText( Locale locale, String key ) + { + return i18n.getString( "invoker-report", locale, key ); + } + + private void sinkTableHeader( Sink sink, String header ) + { + sink.tableHeaderCell(); + sink.text( header ); + sink.tableHeaderCell_(); + } + + private void sinkCell( Sink sink, String text ) + { + sink.tableCell(); + sink.text( text ); + sink.tableCell_(); + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/InvokerSession.java Mon May 15 21:10:27 2017 @@ -0,0 +1,279 @@ +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 static org.apache.maven.shared.utils.logging.MessageUtils.buffer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.invoker.model.BuildJob; +import org.apache.maven.plugin.logging.Log; + +/** + * Tracks a set of build jobs and their results. + * + * @author Benjamin Bentmann + */ +class InvokerSession +{ + + private List<BuildJob> buildJobs; + + private List<BuildJob> failedJobs; + + private List<BuildJob> errorJobs; + + private List<BuildJob> successfulJobs; + + private List<BuildJob> skippedJobs; + + /** + * Creates a new empty session. + */ + public InvokerSession() + { + buildJobs = new ArrayList<BuildJob>(); + } + + /** + * Creates a session that initially contains the specified build jobs. + * + * @param buildJobs The build jobs to set, must not be <code>null</code>. + */ + public InvokerSession( BuildJob[] buildJobs ) + { + this.buildJobs = new ArrayList<BuildJob>( Arrays.asList( buildJobs ) ); + } + + /** + * Adds the specified build job to this session. + * + * @param buildJob The build job to add, must not be <code>null</code>. + */ + public void addJob( BuildJob buildJob ) + { + buildJobs.add( buildJob ); + + resetStats(); + } + + /** + * Sets the build jobs of this session. + * + * @param buildJobs The build jobs to set, must not be <code>null</code>. + */ + public void setJobs( List<? extends BuildJob> buildJobs ) + { + this.buildJobs = new ArrayList<BuildJob>( buildJobs ); + + resetStats(); + } + + /** + * Gets the build jobs in this session. + * + * @return The build jobs in this session, can be empty but never <code>null</code>. + */ + public List<BuildJob> getJobs() + { + return buildJobs; + } + + /** + * Gets the successful build jobs in this session. + * + * @return The successful build jobs in this session, can be empty but never <code>null</code>. + */ + public List<BuildJob> getSuccessfulJobs() + { + updateStats(); + + return successfulJobs; + } + + /** + * Gets the failed build jobs in this session. + * + * @return The failed build jobs in this session, can be empty but never <code>null</code>. + */ + public List<BuildJob> getFailedJobs() + { + updateStats(); + + return failedJobs; + } + + /** + * Gets the build jobs which had errors for this session. + * + * @return The build jobs in error for this session, can be empty but never <code>null</code>. + */ + public List<BuildJob> getErrorJobs() + { + updateStats(); + + return errorJobs; + } + + /** + * Gets the skipped build jobs in this session. + * + * @return The skipped build jobs in this session, can be empty but never <code>null</code>. + */ + public List<BuildJob> getSkippedJobs() + { + updateStats(); + + return skippedJobs; + } + + private void resetStats() + { + successfulJobs = null; + failedJobs = null; + skippedJobs = null; + errorJobs = null; + } + + private void updateStats() + { + if ( successfulJobs != null && skippedJobs != null && failedJobs != null && errorJobs != null ) + { + return; + } + + successfulJobs = new ArrayList<BuildJob>(); + failedJobs = new ArrayList<BuildJob>(); + skippedJobs = new ArrayList<BuildJob>(); + errorJobs = new ArrayList<BuildJob>(); + + for ( BuildJob buildJob : buildJobs ) + { + if ( BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) ) + { + successfulJobs.add( buildJob ); + } + else if ( BuildJob.Result.SKIPPED.equals( buildJob.getResult() ) ) + { + skippedJobs.add( buildJob ); + } + else if ( BuildJob.Result.ERROR.equals( buildJob.getResult() ) ) + { + errorJobs.add( buildJob ); + } + else if ( buildJob.getResult() != null ) + { + failedJobs.add( buildJob ); + } + } + } + + /** + * Prints a summary of this session to the specified logger. + * + * @param logger The mojo logger to output messages to, must not be <code>null</code>. + * @param ignoreFailures A flag whether failures should be ignored or whether a build failure should be signaled. + */ + public void logSummary( Log logger, boolean ignoreFailures ) + { + updateStats(); + + String separator = buffer().strong( "-------------------------------------------------" ).toString(); + + logger.info( separator ); + logger.info( "Build Summary:" ); + logger.info( " Passed: " + successfulJobs.size() + ", Failed: " + failedJobs.size() + ", Errors: " + + errorJobs.size() + ", Skipped: " + skippedJobs.size() ); + logger.info( separator ); + + if ( !failedJobs.isEmpty() ) + { + String heading = "The following builds failed:"; + if ( ignoreFailures ) + { + logger.warn( heading ); + } + else + { + logger.error( heading ); + } + + for ( BuildJob buildJob : failedJobs ) + { + String item = "* " + buildJob.getProject(); + if ( ignoreFailures ) + { + logger.warn( item ); + } + else + { + logger.error( item ); + } + } + + logger.info( separator ); + } + } + + /** + * Handles the build failures in this session. + * + * @param logger The mojo logger to output messages to, must not be <code>null</code>. + * @param ignoreFailures A flag whether failures should be ignored or whether a build failure should be signaled. + * @throws MojoFailureException If failures are present and not ignored. + */ + public void handleFailures( Log logger, boolean ignoreFailures ) + throws MojoFailureException + { + updateStats(); + + if ( !failedJobs.isEmpty() ) + { + String message = failedJobs.size() + " build" + ( failedJobs.size() == 1 ? "" : "s" ) + " failed."; + + if ( ignoreFailures ) + { + logger.warn( "Ignoring that " + message ); + } + else + { + throw new MojoFailureException( message + " See console output above for details." ); + } + } + + if ( !errorJobs.isEmpty() ) + { + String message = errorJobs.size() + " build" + ( errorJobs.size() == 1 ? "" : "s" ) + " in error."; + + if ( ignoreFailures ) + { + logger.warn( "Ignoring that " + message ); + } + else + { + throw new MojoFailureException( message + " See console output above for details." ); + } + } + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/MetadataUtils.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/MetadataUtils.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/MetadataUtils.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/MetadataUtils.java Mon May 15 21:10:27 2017 @@ -0,0 +1,186 @@ +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.io.Reader; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.maven.artifact.Artifact; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.WriterFactory; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.codehaus.plexus.util.xml.Xpp3DomUtils; +import org.codehaus.plexus.util.xml.Xpp3DomWriter; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Provides utility methods for artifact metadata processing. + * + * @author Benjamin Bentmann + */ +class MetadataUtils +{ + + /** + * Creates local metadata files for the specified artifact. The goal is to simulate the installation of the artifact + * by a local build, thereby decoupling the forked builds from the inderministic collection of remote repositories + * that are available to the main build and from which the artifact was originally resolved. + * + * @param file The artifact's file in the local test repository, must not be <code>null</code>. + * @param artifact The artifact to create metadata for, must not be <code>null</code>. + * @throws IOException If the metadata could not be created. + */ + public static void createMetadata( File file, Artifact artifact ) + throws IOException + { + TimeZone tz = java.util.TimeZone.getTimeZone( "UTC" ); + SimpleDateFormat fmt = new SimpleDateFormat( "yyyyMMddHHmmss" ); + fmt.setTimeZone( tz ); + String timestamp = fmt.format( new Date() ); + + if ( artifact.isSnapshot() ) + { + File metadataFile = new File( file.getParentFile(), "maven-metadata-local.xml" ); + + Xpp3Dom metadata = new Xpp3Dom( "metadata" ); + addChild( metadata, "groupId", artifact.getGroupId() ); + addChild( metadata, "artifactId", artifact.getArtifactId() ); + addChild( metadata, "version", artifact.getBaseVersion() ); + Xpp3Dom versioning = new Xpp3Dom( "versioning" ); + versioning.addChild( addChild( new Xpp3Dom( "snapshot" ), "localCopy", "true" ) ); + addChild( versioning, "lastUpdated", timestamp ); + metadata.addChild( versioning ); + + writeMetadata( metadataFile, metadata ); + } + + File metadataFile = new File( file.getParentFile().getParentFile(), "maven-metadata-local.xml" ); + + Set<String> allVersions = new LinkedHashSet<String>(); + + Xpp3Dom metadata = readMetadata( metadataFile ); + + if ( metadata != null ) + { + Xpp3Dom versioning = metadata.getChild( "versioning" ); + if ( versioning != null ) + { + Xpp3Dom versions = versioning.getChild( "versions" ); + if ( versions != null ) + { + + Xpp3Dom[] children = versions.getChildren( "version" ); + for ( Xpp3Dom aChildren : children ) + { + allVersions.add( aChildren.getValue() ); + } + } + } + } + + allVersions.add( artifact.getBaseVersion() ); + + metadata = new Xpp3Dom( "metadata" ); + addChild( metadata, "groupId", artifact.getGroupId() ); + addChild( metadata, "artifactId", artifact.getArtifactId() ); + Xpp3Dom versioning = new Xpp3Dom( "versioning" ); + versioning.addChild( addChildren( new Xpp3Dom( "versions" ), "version", allVersions ) ); + addChild( versioning, "lastUpdated", timestamp ); + metadata.addChild( versioning ); + + metadata = Xpp3DomUtils.mergeXpp3Dom( metadata, readMetadata( metadataFile ) ); + + writeMetadata( metadataFile, metadata ); + } + + private static Xpp3Dom addChild( Xpp3Dom parent, String childName, String childValue ) + { + Xpp3Dom child = new Xpp3Dom( childName ); + child.setValue( childValue ); + parent.addChild( child ); + return parent; + } + + private static Xpp3Dom addChildren( Xpp3Dom parent, String childName, Collection<String> childValues ) + { + for ( String childValue : childValues ) + { + addChild( parent, childName, childValue ); + } + return parent; + } + + private static Xpp3Dom readMetadata( File metadataFile ) + throws IOException + { + if ( !metadataFile.isFile() ) + { + return null; + } + + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( metadataFile ); + final Xpp3Dom xpp3Dom = Xpp3DomBuilder.build( reader ); + reader.close(); + reader = null; + return xpp3Dom; + } + catch ( XmlPullParserException e ) + { + throw (IOException) new IOException( e.getMessage() ).initCause( e ); + } + finally + { + IOUtil.close( reader ); + } + } + + private static void writeMetadata( File metadataFile, Xpp3Dom metadata ) + throws IOException + { + metadataFile.getParentFile().mkdirs(); + + Writer writer = null; + try + { + writer = WriterFactory.newXmlWriter( metadataFile ); + Xpp3DomWriter.write( writer, metadata ); + writer.close(); + writer = null; + } + finally + { + IOUtil.close( writer ); + } + } + +} Added: maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/PomUtils.java URL: http://svn.apache.org/viewvc/maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/PomUtils.java?rev=1795243&view=auto ============================================================================== --- maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/PomUtils.java (added) +++ maven/plugins/trunk/maven-invoker-plugin/src/main/java/org/apache/maven/plugins/invoker/PomUtils.java Mon May 15 21:10:27 2017 @@ -0,0 +1,74 @@ +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.io.Reader; + +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.plugin.MojoExecutionException; +import org.codehaus.plexus.util.IOUtil; +import org.codehaus.plexus.util.ReaderFactory; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +/** + * Provides utility methods for POM processing. + * + * @author Benjamin Bentmann + */ +class PomUtils +{ + + /** + * Loads the (raw) model from the specified POM file. + * + * @param pomFile The path to the POM file to load, must not be <code>null</code>. + * @return The raw model, never <code>null</code>. + * @throws MojoExecutionException If the POM file could not be loaded. + */ + public static Model loadPom( File pomFile ) + throws MojoExecutionException + { + Reader reader = null; + try + { + reader = ReaderFactory.newXmlReader( pomFile ); + final Model model = new MavenXpp3Reader().read( reader, false ); + reader.close(); + reader = null; + return model; + } + catch ( XmlPullParserException e ) + { + throw new MojoExecutionException( "Failed to parse POM: " + pomFile, e ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( "Failed to read POM: " + pomFile, e ); + } + finally + { + IOUtil.close( reader ); + } + } + +}