All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.animal_sniffer.maven.BuildSignaturesMojo Maven / Gradle / Ivy

There is a newer version: 1.24
Show newest version
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