You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2017/05/15 21:10:28 UTC
svn commit: r1795243 [3/4] - in
/maven/plugins/trunk/maven-invoker-plugin/src:
it/local-repo-url/src/it/project/ main/java/org/apache/maven/plugin/
main/java/org/apache/maven/plugins/
main/java/org/apache/maven/plugins/invoker/ main/mdo/ test/java/org/...
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:kenney@apache.org">Kenney Westerhof</a>
+ * @author <a href="mailto:jdcasey@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 );
+ }
+ }
+
+}