org.codehaus.mojo.animal_sniffer.maven.BuildSignaturesMojo Maven / Gradle / Ivy
package org.codehaus.mojo.animal_sniffer.maven;
/*
* The MIT License
*
* Copyright (c) 2009, codehaus.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
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.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
import org.apache.maven.toolchain.MisconfiguredToolchainException;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.apache.maven.toolchain.ToolchainManagerPrivate;
import org.apache.maven.toolchain.ToolchainPrivate;
import org.codehaus.mojo.animal_sniffer.SignatureBuilder;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Generates an API Signature from at least one of: the java runtime, the
* module dependencies and the module classes.
*
* @author Stephen Connolly
*/
@Mojo( name = "build", configurator = "override", requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true )
public class BuildSignaturesMojo
extends AbstractMojo
{
/**
* Should the signatures from java home be included.
*
* @since 1.3
*/
@Parameter( property = "includeJavaHome", defaultValue = "true" )
private boolean includeJavaHome;
/**
* Should no signatures be generated if no java home is available.
*
* @since 1.3
*/
@Parameter( property = "skipIfNoJavaHome", defaultValue = "false" )
private boolean skipIfNoJavaHome;
/**
* Should the signatures from this module's classes be included..
*
* @since 1.3
*/
@Parameter( property = "includeJavaHome", defaultValue = "true" )
private boolean includeModuleClasses;
/**
* Classes to generate signatures of.
*
* @since 1.3
*/
@Parameter
private String[] includeClasses = null;
/**
* Classes to exclude from generating signatures of.
*
* @since 1.3
*/
@Parameter
private String[] excludeClasses = null;
/**
* A list of artifact patterns to include. Patterns can include *
as a wildcard match for any
* whole segment, valid patterns are:
*
* groupId:artifactId
* groupId:artifactId:type
* groupId:artifactId:type:version
* groupId:artifactId:type:classifier
* groupId:artifactId:type:classifier:version
*
*
* @since 1.3
*/
@Parameter
private String[] includeDependencies = null;
/**
* A list of artifact patterns to exclude. Patterns can include *
as a wildcard match for any
* whole segment, valid patterns are:
*
* groupId:artifactId
* groupId:artifactId:type
* groupId:artifactId:type:version
* groupId:artifactId:type:classifier
* groupId:artifactId:type:classifier:version
*
*
* @since 1.3
*/
@Parameter
private String[] excludeDependencies = null;
/**
* The java home to generate the signatures of, if not specified only the signatures of dependencies will be
* included. This parameter is overridden by {@link #javaHomeClassPath}. This parameter overrides {@link #jdk}
* and any java home specified by maven-toolchains-plugin.
*
* @since 1.3
*/
@Parameter( property = "javaHome" )
private String javaHome;
/**
* Use this configuration option only if the automatic boot classpath detection does not work for the specific
* {@link #javaHome} or {@link #jdk}. For example, the automatic boot classpath detection does not work with
* Sun Java 1.1. This parameter overrides {@link #javaHome}, {@link #jdk} and the maven-toolchains-plugin.
*
* @since 1.3
*/
@Parameter
private File[] javaHomeClassPath;
/**
* Where to put the generated signatures.
*
* @since 1.3
*/
@Parameter( defaultValue = "${project.build.directory}", required = true )
private File outputDirectory;
/**
* Where to find this modules classes.
*
* @since 1.3
*/
@Parameter( defaultValue = "${project.build.outputDirectory}", required = true )
private File classesDirectory;
/**
* The name of the generated signatures.
*
* @since 1.3
*/
@Parameter( defaultValue = "${project.build.finalName}", required = true )
private String signaturesName;
/**
* The classifier to add to the generated signatures.
*
* @since 1.3
*/
@Parameter
private String classifier;
/**
*/
@Component
private MavenProjectHelper projectHelper;
/**
* The maven project.
*/
@Parameter( defaultValue = "${project}", required = true, readonly = true )
private MavenProject project;
/**
*/
@Component
private ToolchainManager toolchainManager;
/**
*/
@Component
private ToolchainManagerPrivate toolchainManagerPrivate;
/**
* The current build session instance. This is used for toolchain manager API calls.
*/
@Parameter( defaultValue = "${session}", required = true, readonly = true )
private MavenSession session;
/**
* The JDK Toolchain to use. This parameter can be overridden by {@link #javaHome} or {@link #javaHomeClassPath}.
* This parameter overrides any toolchain specified with maven-toolchains-plugin.
*
* @since 1.3
*/
@Parameter
private JdkToolchain jdk;
/**
*/
@Parameter( defaultValue = "${plugin.artifacts}", required = true, readonly = true )
private List pluginArtifacts;
/**
* The groupId of the Java Boot Classpath Detector to use. The plugin's dependencies will be searched for a
* dependency of type jar
with this groupId and the artifactId specified in {@link #jbcpdArtifactId}.
* The dependency should be a standalone executable jar file which outputs the java boot classpath as a single
* line separated using {@link File#pathSeparatorChar} or else exits with a non-zero return code if it cannot determine
* the java boot classpath.
*
* @since 1.3
*/
@Parameter( defaultValue = "${plugin.groupId}" )
private String jbcpdGroupId;
/**
* The artifactId of the Java Boot Classpath Detector to use. The plugin's dependencies will be searched for a
* dependency of type jar
with this artifactId and the groupId specified in {@link #jbcpdGroupId}.
* The dependency should be a standalone executable jar file which outputs the java boot classpath as a single
* line separated using {@link File#pathSeparatorChar} or else exits with a non-zero return code if it cannot determine
* the java boot classpath.
*
* @since 1.3
*/
@Parameter( defaultValue = "java-boot-classpath-detector" )
private String jbcpdArtifactId;
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( includeJavaHome && ( javaHomeClassPath == null || javaHomeClassPath.length == 0 ) )
{
if ( javaHome != null )
{
getLog().warn( "Toolchains are ignored, 'javaHome' parameter is set to " + javaHome );
if ( !new File( javaHome ).isDirectory() )
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as java home (" + javaHome + ") does not exist" );
return;
}
throw new MojoFailureException( "Cannot include java home if specified java home does not exist" );
}
if ( !detectJavaBootClasspath( new File( new File( javaHome, "bin" ), "java" ).getAbsolutePath() ) )
{
return;
}
}
else
{
Toolchain tc = getJdkToolchain();
if ( tc == null && jdk == null )
{
String jvm = null;
// TODO: how could this give non null result here if tc was null when doing same???
tc = toolchainManager.getToolchainFromBuildContext( "jdk", //NOI18N
session );
getLog().info( "Toolchain from session: " + tc );
//assign the path to executable from toolchains
if ( tc != null )
{
jvm = tc.findTool( "java" ); //NOI18N
}
if ( jvm == null )
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not find java home" );
return;
}
throw new MojoFailureException(
"Cannot include java home if java home is not specified (either via javaClassPath, javaHome or jdk)" );
}
if ( !detectJavaBootClasspath( jvm ) )
{
return;
}
}
else if ( tc != null && StringUtils.equalsIgnoreCase( "jdk",tc.getType() ) ) //tc instanceof JavaToolChain )
{
getLog().info( "Toolchain in animal-sniffer-maven-plugin: " + tc );
//when the executable to use is explicitly set by user in mojo's parameter, ignore toolchains.
//assign the path to executable from toolchains
String jvm = tc.findTool( "java" ); //NOI18N
if ( jvm == null )
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not find java home" );
return;
}
throw new MojoFailureException(
"Cannot include java home if java home is not specified (either via javaClassPath, javaHome or jdk)" );
}
if ( !detectJavaBootClasspath( jvm ) )
{
return;
}
}
else if ( tc == null && jdk != null && jdk.getParameters() != null )
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not find jdk toolchain to match "
+ jdk.getParameters() );
return;
}
throw new MojoFailureException( "Could not find jdk toolchain to match " + jdk.getParameters() );
}
else
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not find java home" );
return;
}
throw new MojoFailureException(
"Cannot include java home if java home is not specified (either via javaClassPath, javaHome, "
+ "jdk or maven-toolchains-plugin)" );
}
}
}
displayJavaBootClasspath();
File sigFile = getTargetFile( outputDirectory, signaturesName, classifier, "signature" );
try
{
outputDirectory.mkdirs();
SignatureBuilder builder = new SignatureBuilder( getBaseSignatures(), new FileOutputStream( sigFile ),
new MavenLogger( getLog() ) );
if ( includeClasses != null )
{
getLog().info( "Restricting signatures to include only the following classes:" );
for ( String includeClass : includeClasses )
{
getLog().info( " " + includeClass );
builder.addInclude( includeClass );
}
}
if ( excludeClasses != null )
{
getLog().info( "Restricting signatures to exclude the following classes:" );
for ( String excludeClass : excludeClasses )
{
getLog().info( " " + excludeClass );
builder.addExclude( excludeClass );
}
}
processJavaBootClasspath( builder );
processModuleDependencies( builder );
processModuleClasses( builder );
builder.close();
projectHelper.attachArtifact( project, "signature", classifier, sigFile );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
}
private boolean detectJavaBootClasspath( String javaExecutable )
throws MojoFailureException, MojoExecutionException
{
getLog().info( "Attempting to auto-detect the boot classpath for " + javaExecutable );
Iterator i = pluginArtifacts.iterator();
Artifact javaBootClasspathDetector = null;
while ( i.hasNext() && javaBootClasspathDetector == null )
{
Artifact candidate = i.next();
if ( StringUtils.equals( jbcpdGroupId, candidate.getGroupId() )
&& StringUtils.equals( jbcpdArtifactId, candidate.getArtifactId() ) && candidate.getFile() != null
&& candidate.getFile().isFile() )
{
javaBootClasspathDetector = candidate;
}
}
if ( javaBootClasspathDetector == null )
{
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not find boot classpath detector ("
+ ArtifactUtils.versionlessKey( jbcpdGroupId, jbcpdArtifactId ) + ")." );
return false;
}
throw new MojoFailureException( "Could not find boot classpath detector ("
+ ArtifactUtils.versionlessKey( jbcpdGroupId, jbcpdArtifactId )
+ ")." );
}
try
{
if ( !detectJavaClasspath( javaBootClasspathDetector, javaExecutable ) )
{
return false;
}
}
catch ( CommandLineException e )
{
throw new MojoExecutionException( e.getLocalizedMessage(), e );
}
return true;
}
private boolean detectJavaClasspath( Artifact javaBootClasspathDetector, String javaExecutable )
throws CommandLineException, MojoFailureException
{
final Commandline cli = new Commandline();
cli.setWorkingDirectory( project.getBasedir().getAbsolutePath() );
cli.setExecutable( javaExecutable );
cli.addEnvironment( "CLASSPATH", "" );
cli.addEnvironment( "JAVA_HOME", "" );
cli.addArguments( new String[]{ "-jar", javaBootClasspathDetector.getFile().getAbsolutePath() } );
final CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
final CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
int exitCode = CommandLineUtils.executeCommandLine( cli, stdout, stderr );
if ( exitCode != 0 )
{
getLog().debug( "Stdout: " + stdout.getOutput() );
getLog().debug( "Stderr: " + stderr.getOutput() );
getLog().debug( "Exit code = " + exitCode );
if ( skipIfNoJavaHome )
{
getLog().warn( "Skipping signature generation as could not auto-detect java boot classpath for "
+ javaExecutable );
return false;
}
if ( SystemUtils.isJavaVersionAtLeast( JavaVersion.JAVA_9 ))
{
getLog().warn( "Skipping signature generation as this java version has no more java boot classpath "
+ javaExecutable );
return false;
}
throw new MojoFailureException( "Could not auto-detect java boot classpath for " + javaExecutable );
}
String[] classpath = StringUtils.split( stdout.getOutput(), File.pathSeparator );
javaHomeClassPath = new File[classpath.length];
for ( int j = 0; j < classpath.length; j++ )
{
javaHomeClassPath[j] = new File( classpath[j] );
}
return true;
}
private void displayJavaBootClasspath()
{
if ( includeJavaHome )
{
getLog().info( "Java Classpath:" );
if ( javaHomeClassPath == null )
{
getLog().info( " Empty" );
}
else
{
for ( int j = 0; j < javaHomeClassPath.length; j++ )
{
getLog().info( " [" + j + "] = " + javaHomeClassPath[j] );
}
}
}
}
private void processModuleDependencies( SignatureBuilder builder )
throws IOException
{
PatternIncludesArtifactFilter includesFilter = includeDependencies == null
? null
: new PatternIncludesArtifactFilter( Arrays.asList( includeDependencies ) );
PatternExcludesArtifactFilter excludesFilter = excludeDependencies == null
? null
: new PatternExcludesArtifactFilter( Arrays.asList( excludeDependencies ) );
for ( Artifact artifact : (Iterable) project.getArtifacts() )
{
if ( includesFilter != null && !includesFilter.include( artifact ) )
{
getLog().debug( "Artifact " + artifactId( artifact ) + " ignored as it does not match include rules." );
continue;
}
if ( excludesFilter != null && !excludesFilter.include( artifact ) )
{
getLog().debug( "Artifact " + artifactId( artifact ) + " ignored as it does match exclude rules." );
continue;
}
if ( artifact.getArtifactHandler().isAddedToClasspath() )
{
getLog().info( "Parsing signatures from " + artifactId( artifact ) );
builder.process( artifact.getFile() );
}
}
}
private void processModuleClasses( SignatureBuilder builder )
throws IOException
{
if ( includeModuleClasses && classesDirectory.isDirectory() )
{
getLog().info( "Parsing signatures from " + classesDirectory );
builder.process( classesDirectory );
}
}
private void processJavaBootClasspath( SignatureBuilder builder )
throws IOException
{
if ( includeJavaHome && javaHomeClassPath != null && javaHomeClassPath.length > 0 )
{
getLog().debug( "Parsing signatures java classpath:" );
for ( File file : javaHomeClassPath )
{
if ( file.isFile() || file.isDirectory() )
{
getLog().debug( "Processing " + file );
builder.process( file );
}
else
{
getLog().warn(
"Could not add signatures from boot classpath element: " + file + " as it does not exist." );
}
}
}
}
private InputStream[] getBaseSignatures()
throws FileNotFoundException
{
List baseSignatures = new ArrayList<>();
for ( Artifact artifact : (Iterable) project.getArtifacts() )
{
if ( StringUtils.equals( "signature", artifact.getType() ) )
{
getLog().info( "Importing sigantures from " + artifact.getFile() );
baseSignatures.add( new FileInputStream( artifact.getFile() ) );
}
}
return baseSignatures.toArray( new InputStream[0] );
}
/**
* Gets the jdk
toolchain to use.
*
* @return the jdk
toolchain to use or null
if no toolchain is configured or if no toolchain can be found.
* @throws MojoExecutionException if toolchains are misconfigured.
*/
private Toolchain getJdkToolchain()
throws MojoExecutionException
{
Toolchain tc = getJdkToolchainFromConfiguration();
if ( tc == null )
{
tc = getJdkToolchainFromContext();
}
return tc;
}
/**
* Gets the toolchain specified for the current context, e.g. specified via the maven-toolchain-plugin
*
* @return the toolchain from the context or null
if there is no such toolchain.
*/
private Toolchain getJdkToolchainFromContext()
{
if ( toolchainManager != null )
{
return toolchainManager.getToolchainFromBuildContext( "jdk", //NOI18N
session );
}
return null;
}
/**
* Gets the jdk
toolchain from this plugin's configuration.
*
* @return the toolchain from this plugin's configuration, or null
if no matching toolchain can be
* found.
* @throws MojoExecutionException if the toolchains are configured incorrectly.
*/
private Toolchain getJdkToolchainFromConfiguration()
throws MojoExecutionException
{
if ( toolchainManager != null && jdk != null && jdk.getParameters() != null )
{
try
{
final ToolchainPrivate[] tcp = getToolchains( "jdk" );
for ( ToolchainPrivate toolchainPrivate : tcp )
{
if ( toolchainPrivate.getType().equals( "jdk" ) /* MNG-5716 */
&& toolchainPrivate.matchesRequirements( jdk.getParameters() ) )
{
return toolchainPrivate;
}
}
}
catch ( MisconfiguredToolchainException e )
{
throw new MojoExecutionException( e.getLocalizedMessage(), e );
}
}
return null;
}
/*package*/ static String artifactId( Artifact artifact )
{
return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType()
+ ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" ) + ":"
+ artifact.getBaseVersion();
}
private ToolchainPrivate[] getToolchains( String type )
throws MojoExecutionException, MisconfiguredToolchainException
{
// The toolchain API has moved about quite a bit... this method tries to navigate through to a
// successful enumeration of all the toolchains of the required type.
// This method is only ever called in versions of Maven that have toolchain support.
Class> managerClass = toolchainManagerPrivate.getClass();
try
{
try
{
// try 3.x style API
Method newMethod =
managerClass.getMethod( "getToolchainsForType", new Class[]{ String.class, MavenSession.class } );
return (ToolchainPrivate[]) newMethod.invoke( toolchainManagerPrivate, new Object[]{ type, session } );
}
catch ( NoSuchMethodException e1 )
{
try
{
// try 2.2.1 style API
Method oldMethod = managerClass.getMethod( "getToolchainsForType", new Class[]{ String.class } );
return (ToolchainPrivate[]) oldMethod.invoke( toolchainManagerPrivate, new Object[]{ type } );
}
catch ( NoSuchMethodException e2 )
{
e2.initCause( e1 );
throw e2;
}
}
}
catch ( NoSuchMethodException e )
{
StringBuilder buf = new StringBuilder( "Incompatible toolchain API." );
buf.append( "\n\nCannot find a suitable 'getToolchainsForType' method. Available methods are:\n" );
Method[] methods = managerClass.getMethods();
for ( Method method : methods )
{
buf.append( " " ).append( method ).append( '\n' );
}
throw new MojoExecutionException( buf.toString(), e );
}
catch ( IllegalAccessException e )
{
throw new MojoExecutionException( "Incompatible toolchain API", e );
}
catch ( InvocationTargetException e )
{
Throwable cause = e.getCause();
if ( cause instanceof RuntimeException )
{
throw (RuntimeException) cause;
}
if ( cause instanceof MisconfiguredToolchainException )
{
throw (MisconfiguredToolchainException) cause;
}
throw new MojoExecutionException( "Incompatible toolchain API", e );
}
}
private static File getTargetFile( File basedir, String finalName, String classifier, String type )
{
if ( classifier == null )
{
classifier = "";
}
else if ( classifier.trim().length() > 0 && !classifier.startsWith( "-" ) )
{
classifier = "-" + classifier;
}
return new File( basedir, finalName + classifier + "." + type );
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy