/* * The MIT License * * Copyright (c) 2004, The Codehaus * * 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. */ package org.codehaus.mojo.natives.plugin; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.maven.artifact.Artifact; 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.MavenProjectHelper; import org.codehaus.mojo.natives.NativeBuildException; import org.codehaus.mojo.natives.javah.Javah; import org.codehaus.mojo.natives.javah.JavahConfiguration; import org.codehaus.mojo.natives.manager.JavahManager; import org.codehaus.mojo.natives.manager.NoSuchNativeProviderException; import org.codehaus.plexus.archiver.util.DefaultFileSet; import org.codehaus.plexus.archiver.zip.ZipArchiver; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.StringUtils; /** * Generate JNI include files based on a set of class names */ @Mojo(name = "javah", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE) public class NativeJavahMojo extends AbstractNativeMojo { /** * Javah Provider. * * @since 1.0-alpha-2 */ @Parameter(defaultValue = "default", required = true) private String javahProvider; /** * List of class names to generate native files. Additional JNI interface will automatically discovered from * project's dependencies of <i>jar</i> type, when <i>javahSearchJNIFromDependencies</i> is true * * @since 1.0-alpha-4 */ @Parameter private List<String> javahClassNames = new ArrayList<>( 0 ); /** * Enable the search from project dependencies for JNI interfaces, in addition to <i>javahClassNames</i> * * @since 1.0-alpha-4 */ @Parameter(defaultValue = "false") private boolean javahSearchJNIFromDependencies; /** * Path to javah executable, if present, it will override the default one which bases on architecture type. See * 'javahProvider' argument * * @since 1.0-alpha-2 */ @Parameter private File javahPath; /** * Where to place javah generated file * * @since 1.0-alpha-2 */ @Parameter(defaultValue = "${project.build.directory}/native/javah", required = true) protected File javahOutputDirectory; /** * if configured, this value will be combined with outputDirectory to pass into javah's -o option * * @since 1.0-alpha-4 */ @Parameter private String javahOutputFileName; /** * Additional javah classname and its corresponding header name. Use this option to create one class per header * <p/> * * <pre> * <javahIncludes> * <javahInclude> * <className>com.some.Class</className> * <headerName>Class.h</headerName> * <javahInclude> * </javahIncludes> * </pre> * * @since 1.0-alpha-8 */ @Parameter private List<JavahInclude> javahIncludes = new ArrayList<>(); /** * Enable javah verbose mode * * @since 1.0-alpha-2 */ @Parameter(defaultValue = "false") private boolean javahVerbose; /** * Archive all generated include files and deploy as an inczip */ @Parameter(defaultValue = "false") private boolean attach; /** * Classifier name when install/deploy generated includes file. See ${attach} for details */ @Parameter(defaultValue = "javah") private String classifier; /** * Archive file to bundle all generated include files if enable by ${attach} * * @since 1.0-alpha-8 */ @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.inczip", required = true) private File incZipFile; /** * Set CLASSPATH env variable instead of using -classpath command-line argument. Use this option to allow large * number of jars in classpath due to command line size limit under Windows * * @since 1.0-alpha-9 */ @Parameter(defaultValue = "false") private boolean useEnvClasspath; /** * Internal: To look up javah implementation * * @since 1.0-alpha-2 */ @Component private JavahManager manager; /** * Maven ProjectHelper. * * @since 1.0-alpha-8 */ @Component private MavenProjectHelper projectHelper; /** * For unit test only */ private JavahConfiguration config; @Override public void execute() throws MojoExecutionException { this.discoverAdditionalJNIClassName(); if ( this.javahClassNames.size() == 0 && this.javahIncludes.size() == 0 ) { return; } try { if ( this.javahClassNames.size() != 0 ) { this.config = this.createProviderConfiguration( javahClassNames.toArray( new String[javahClassNames.size()] ), this.javahOutputFileName ); this.getJavah().compile( config ); } for ( int i = 0; i < this.javahIncludes.size(); ++i ) { JavahInclude javahInclude = this.javahIncludes.get( i ); this.config = this.createProviderConfiguration( new String[] { javahInclude.getClassName() }, javahInclude.getHeaderName() ); this.getJavah().compile( config ); } if ( this.attach ) { attachGeneratedIncludeFilesAsIncZip(); } } catch ( NativeBuildException e ) { throw new MojoExecutionException( "Error running javah command", e ); } this.project.addCompileSourceRoot( this.javahOutputDirectory.getAbsolutePath() ); } private void attachGeneratedIncludeFilesAsIncZip() throws MojoExecutionException { try { ZipArchiver archiver = new ZipArchiver(); DefaultFileSet fileSet = new DefaultFileSet(); fileSet.setUsingDefaultExcludes( true ); fileSet.setDirectory( javahOutputDirectory ); archiver.addFileSet( fileSet ); archiver.setDestFile( this.incZipFile ); archiver.createArchive(); if ( StringUtils.isBlank( this.classifier ) ) { projectHelper.attachArtifact( this.project, INCZIP_TYPE, null, this.incZipFile ); } else { projectHelper.attachArtifact( this.project, INCZIP_TYPE, this.classifier, this.incZipFile ); } } catch ( Exception e ) { throw new MojoExecutionException( "Unable to archive/deploy generated include files", e ); } } private Javah getJavah() throws MojoExecutionException { Javah javah; try { javah = this.manager.getJavah( this.javahProvider ); } catch ( NoSuchNativeProviderException pe ) { throw new MojoExecutionException( pe.getMessage() ); } return javah; } /** * Get all jars in the pom excluding transitive, test, and provided scope dependencies. * * @return */ private List<Artifact> getJavahArtifacts() { List<Artifact> list = new ArrayList<>(); Set<Artifact> artifacts = this.project.getArtifacts(); if ( artifacts != null ) { for ( Artifact artifact : artifacts ) { // pick up only jar files from class path for compile (and system) scope if ( "jar".equals( artifact.getType() ) && artifact.getArtifactHandler().isAddedToClasspath() && ( Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) ) ) { list.add( artifact ); } } } return list; } /** * Build classpaths from dependent jars including project output directory (i.e. classes directory ) * * @return */ private String[] getJavahClassPath() { List<Artifact> artifacts = this.getJavahArtifacts(); String[] classPaths = new String[artifacts.size() + 1]; classPaths[0] = this.project.getBuild().getOutputDirectory(); Iterator<Artifact> iter = artifacts.iterator(); for ( int i = 1; i < classPaths.length; ++i ) { Artifact artifact = iter.next(); classPaths[i] = artifact.getFile().getPath(); } return classPaths; } /** * Get applicable class names to be "javahed" */ private void discoverAdditionalJNIClassName() throws MojoExecutionException { if ( !this.javahSearchJNIFromDependencies ) { return; } // scan the immediate dependency list for jni classes List<Artifact> artifacts = this.getJavahArtifacts(); for ( Iterator<Artifact> iter = artifacts.iterator(); iter.hasNext(); ) { Artifact artifact = iter.next(); this.getLog().info( "Parsing " + artifact.getFile() + " for native classes." ); try { ZipFile zipFile = new ZipFile( artifact.getFile() ); Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); while ( zipEntries.hasMoreElements() ) { ZipEntry zipEntry = zipEntries.nextElement(); if ( "class".equals( FileUtils.extension( zipEntry.getName() ) ) ) { ClassParser parser = new ClassParser( artifact.getFile().getPath(), zipEntry.getName() ); JavaClass clazz = parser.parse(); Method[] methods = clazz.getMethods(); for ( int j = 0; j < methods.length; ++j ) { if ( methods[j].isNative() ) { javahClassNames.add( clazz.getClassName() ); this.getLog().info( "Found native class: " + clazz.getClassName() ); break; } } } } // endwhile // not full proof zipFile.close(); } catch ( IOException ioe ) { throw new MojoExecutionException( "Error searching for native class in " + artifact.getFile(), ioe ); } } } private JavahConfiguration createProviderConfiguration( String[] classNames, String javahOutputFileName ) throws MojoExecutionException { JavahConfiguration config = new JavahConfiguration(); config.setWorkingDirectory( this.workingDirectory ); config.setVerbose( this.javahVerbose ); config.setOutputDirectory( this.javahOutputDirectory ); config.setFileName( javahOutputFileName ); config.setClassPaths( this.getJavahClassPath() ); config.setUseEnvClasspath( useEnvClasspath ); config.setClassNames( classNames ); config.setJavahPath( this.javahPath ); return config; } /** * Internal only for test harness purpose * * @return */ protected JavahConfiguration getJavahConfiguration() { return this.config; } /** * Internal for unit test only */ @Override protected MavenProject getProject() { return this.project; } }