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

com.jayway.maven.plugins.android.phase05compile.NdkBuildMojo Maven / Gradle / Ivy

There is a newer version: 4.0.0-rc.2
Show newest version
/*
 * Licensed 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.
 */
package com.jayway.maven.plugins.android.phase05compile;

import com.jayway.maven.plugins.android.AbstractAndroidMojo;
import com.jayway.maven.plugins.android.CommandExecutor;
import com.jayway.maven.plugins.android.ExecutionException;
import com.jayway.maven.plugins.android.common.AetherHelper;
import com.jayway.maven.plugins.android.common.AndroidExtension;
import com.jayway.maven.plugins.android.common.NativeHelper;
import com.jayway.maven.plugins.android.config.PullParameter;
import com.jayway.maven.plugins.android.configuration.HeaderFilesDirective;
import com.jayway.maven.plugins.android.configuration.NDKArchitectureToolchainMappings;
import org.apache.commons.io.FileUtils;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.util.IOUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Johan Lindquist 
 * @goal ndk-build
 * @phase compile
 * @requiresProject true
 */
public class NdkBuildMojo extends AbstractAndroidMojo
{
    /**
     * Name of the subdirectory of 'target' where we put the generated makefile
     */
    public static final String NDK_MAKFILE_DIRECTORY = "ndk-build";

    /**
     * Allows for overriding the default ndk-build executable.
     *
     * @parameter expression="${android.ndk.ndk-build-executable}"
     */
    @PullParameter
    private String ndkBuildExecutable;

    /**
     * @parameter expression="${android.ndk.ndk-build-directory}"
     */
    @PullParameter
    private String ndkBuildDirectory;

    /**
     * Specifies the classifier with which the artifact should be stored in the repository
     *
     * @parameter expression="${android.ndk.build.native-classifier}"
     */
    @PullParameter
    private String ndkClassifier;

    /**
     * Specifies additional command line parameters to pass to ndk-build
     *
     * @parameter expression="${android.ndk.build.command-line}"
     */
    @PullParameter
    protected String ndkBuildAdditionalCommandline;

    /**
     * Flag indicating whether the NDK output directory (libs/<architecture>) should be cleared after build. This
     * will essentially 'move' all the native artifacts (.so) to
     * the ${project.build.directory}/libs/<architecture>.
     * If an APK is built as part of the invocation, the libraries will be included from here.
     *
     * @parameter expression="${android.ndk.build.clear-native-artifacts}" default-value="false"
     */
    @PullParameter( defaultValue = "false" )
    private Boolean clearNativeArtifacts;

    /**
     * Flag indicating whether the resulting native library should be attached as an artifact to the build.  This
     * means the resulting .so is installed into the repository as well as being included in the final APK.
     *
     * @parameter expression="${android.ndk.build.attach-native-artifact}" default-value="false"
     */
    @PullParameter( defaultValue = "false" )
    private Boolean attachNativeArtifacts;

    /**
     * Build folder to place built native libraries into
     *
     * @parameter expression="${android.ndk.build.ndk-output-directory}"
     * default-value="${project.build.directory}/ndk-libs"
     */
    private File ndkOutputDirectory;

    /**
     * 

Folder containing native, static libraries compiled and linked by the NDK.

* * @parameter expression="${android.nativeLibrariesOutputDirectory}" default-value="${project.basedir}/obj/local" */ private File nativeLibrariesOutputDirectory; /** *

Target to invoke on the native makefile.

* * @parameter expression="${android.nativeTarget}" */ @PullParameter private String target; /** * Defines the architecture for the NDK build * * @parameter expression="${android.ndk.build.architecture}" * @deprecated Use {@link NdkBuildMojo#ndkArchitectures} instead */ @PullParameter private String ndkArchitecture; /** * Defines the architectures for the NDK build - this is a space separated list (i.e x86 armeabi) * * @parameter expression="${android.ndk.build.architectures}" */ @PullParameter private String ndkArchitectures; /** * Defines the architecture to toolchain mappings for the NDK build * <ndkArchitectureToolchainMappings> * <x86>x86-4.7</x86> * <armeabi>arm-linux-androideabi-4.7</armeabi> * </ndkArchitectureToolchainMappings> * * @parameter */ @PullParameter private NDKArchitectureToolchainMappings ndkArchitectureToolchainMappings; /** * @component * @readonly * @required */ protected ArtifactFactory artifactFactory; /** * Flag indicating whether the header files used in the build should be included and attached to the build as * an additional artifact. * * @parameter expression="${android.ndk.build.attach-header-files}" default-value="true" */ @PullParameter( defaultValue = "true" ) private Boolean attachHeaderFiles; /** * Flag indicating whether the make files last LOCAL_SRC_INCLUDES should be used for determing what header * files to include. Setting this flag to true, overrides any defined header files directives. * Note: By setting this flag to true, all header files used in the project will be * added to the resulting header archive. This may be undesirable in most cases and is therefore turned off by * default. * * @parameter expression="${android.ndk.build.use-local-src-include-paths}" default-value="false" */ @PullParameter( defaultValue = "false" ) private Boolean useLocalSrcIncludePaths; /** * Specifies the set of header files includes/excludes which should be used for bundling the exported header * files. The below shows an example of how this can be used. *

*

     * <headerFilesDirectives>
     *   <headerFilesDirective>
     *     <directory>${basedir}/jni/include</directory>
     *     <includes>
     *       <includes>**\/*.h</include>
     *     </includes>
     *   <headerFilesDirective>
     * </headerFilesDirectives>
     * 
*
* If no headerFilesDirectives is specified, the default includes will be defined as shown below: *
*
     * <headerFilesDirectives>
     *   <headerFilesDirective>
     *     <directory>${basedir}/jni</directory>
     *     <includes>
     *       <includes>**\/*.h</include>
     *     </includes>
     *     <excludes>
     *       <exclude>**\/*.c</exclude>
     *     </excludes>
     *   <headerFilesDirective>
     *   [..]
     * </headerFilesDirectives>
     * 
* * @parameter */ @PullParameter private List headerFilesDirectives; /** * The Jar archiver. * * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="jar" */ private JarArchiver jarArchiver; /** * Flag indicating whether the header files for native, static library dependencies should be used. If true, * the header archive for each statically linked dependency will be resolved. * * @parameter expression="${android.ndk.build.use-header-archives}" default-value="true" */ @PullParameter( defaultValue = "true" ) private Boolean useHeaderArchives; /** * Defines additional system properties which should be exported to the ndk-build script. This *
*
     * <systemProperties>
     *   <propertyName>propertyValue</propertyName>
     *   <build-target>android</build-target>
     *   [..]
     * </systemProperties>
     * 
* * * @parameter */ @PullParameter private Map systemProperties; /** * Flag indicating whether warnings should be ignored while compiling. If true, * the build will not fail if warning are found during compile. * * @parameter expression="${android.ndk.build.ignore-build-warnings}" default-value="true" */ @PullParameter( defaultValue = "true" ) private Boolean ignoreBuildWarnings; /** * Defines the regular expression used to detect whether error/warning output from ndk-build is a minor compile * warning or is actually an error which should cause the build to fail. *

* If the pattern matches, the output from the compiler will not be considered an error and compile * will be successful. * * @parameter expression="${android.ndk.build.build-warnings-regular-expression}" * default-value=".*[warning|note]: .*" */ @PullParameter( defaultValue = ".*[warning|note]: .*" ) private String buildWarningsRegularExpression; /** * @parameter expression="${android.ndk.build.skip-native-library-stripping}" default-value="false" */ @PullParameter( defaultValue = "false" ) private Boolean skipStripping; /** * @parameter expression="${android.ndk.build.ndk-toolchain}" */ @PullParameter private String ndkToolchain; /** * Specifies the final name of the library output by the build (this allows * the pom to override the default artifact name). The value should not * include the 'lib' prefix or filename extension (e.g. '.so'). * * @parameter expression="${android.ndk.build.build.final-library.name}" */ @PullParameter private String ndkFinalLibraryName; /** * Specifies the makefile to use for the build (if other than the default Android.mk). * * @parameter */ @PullParameter private String makefile; /** * Specifies the application makefile to use for the build (if other than the default Application.mk). * * @parameter */ @PullParameter private String applicationMakefile; /** * Flag indicating whether to use the max available jobs for the host machine * * @parameter expression="${android.ndk.build.maxJobs}" default-value="false" */ @PullParameter( defaultValue = "false" ) private Boolean maxJobs; /** * * @throws MojoExecutionException * @throws MojoFailureException */ public void execute() throws MojoExecutionException, MojoFailureException { try { // Validate the NDK final File ndkBuildFile = new File( getAndroidNdk().getNdkBuildPath() ); NativeHelper.validateNDKVersion( ndkBuildFile.getParentFile() ); // Validate the makefile - if our packaging type is so (for example) and there are // dependencies on .a files (or shared files for that matter) the makefile should include // the include of our Android Maven plugin generated makefile. validateMakefile( project, makefile ); String[] resolvedNDKArchitectures = NativeHelper.getNdkArchitectures( ndkArchitecture != null ? ndkArchitecture : ndkArchitectures, applicationMakefile, project.getBasedir() ); for ( String ndkArchitecture : resolvedNDKArchitectures ) { Preparation preparation = new Preparation().invoke( ndkArchitecture ); boolean libsDirectoryExists = preparation.isLibsDirectoryExists(); File directoryToRemove = preparation.getDirectoryToRemove(); // Start setting up the command line to be executed final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); // Add an error listener to the build - this allows the build to conditionally fail // depending on a) the output of the build b) whether or not build errors (output on stderr) should be // ignored and c) whether the pattern matches or not executor.setErrorListener( getNdkErrorListener() ); final Set nativeLibraryArtifacts = findNativeLibraryDependencies(); // If there are any static libraries the code needs to link to, include those in the make file final Set resolveNativeLibraryArtifacts = AetherHelper .resolveArtifacts( nativeLibraryArtifacts, repoSystem, repoSession, projectRepos ); if ( getLog().isDebugEnabled() ) { getLog().debug( "resolveArtifacts found " + resolveNativeLibraryArtifacts.size() + ": " + resolveNativeLibraryArtifacts.toString() ); } final File makefileDir = new File( project.getBuild().getDirectory(), NDK_MAKFILE_DIRECTORY ); makefileDir.mkdirs(); final File androidMavenMakefile = new File( makefileDir, "android_maven_plugin_makefile.mk" ); // set the ndk build directory if ( ndkBuildDirectory == null ) { ndkBuildDirectory = project.getBasedir().getAbsolutePath(); } final MakefileHelper makefileHelper = new MakefileHelper( getLog(), repoSystem, repoSession, projectRepos, unpackedApkLibsDirectory ); final MakefileHelper.MakefileHolder makefileHolder = makefileHelper .createMakefileFromArtifacts( new File( ndkBuildDirectory ), resolveNativeLibraryArtifacts, ndkArchitecture, "armeabi", useHeaderArchives ); final FileOutputStream output = new FileOutputStream( androidMavenMakefile ); try { IOUtil.copy( makefileHolder.getMakeFile(), output ); } finally { output.close(); } // Add the path to the generated makefile - this is picked up by the build (by an include from the user) executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_MAKEFILE", androidMavenMakefile.getAbsolutePath() ); setupNativeLibraryEnvironment( makefileHelper, executor, resolveNativeLibraryArtifacts, ndkArchitecture ); // Adds the location of the Makefile capturer file - this file will after the build include // things like header files, flags etc. It is processed after the build to retrieve the headers // and also capture flags etc ... final File makefileCaptureFile = File.createTempFile( "android_maven_plugin_makefile_captures", ".tmp" ); makefileCaptureFile.deleteOnExit(); executor.addEnvironment( MakefileHelper.MAKEFILE_CAPTURE_FILE, makefileCaptureFile.getAbsolutePath() ); // Add any defined system properties if ( systemProperties != null && ! systemProperties.isEmpty() ) { for ( Map.Entry entry : systemProperties.entrySet() ) { executor.addEnvironment( entry.getKey(), entry.getValue() ); } } executor.setLogger( this.getLog() ); // Setup the command line for the make final List commands = new ArrayList(); // Setup the build directory (defaults to the current directory) but may be different depending // on user configuration commands.add( "-C" ); commands.add( ndkBuildDirectory ); // If the build should use a custom makefile or not - some validation is done to ensure // this exists and all if ( makefile != null ) { File makeFile = new File( project.getBasedir(), makefile ); if ( ! makeFile.exists() ) { getLog().error( "Specified makefile " + makeFile + " does not exist" ); throw new MojoExecutionException( "Specified makefile " + makeFile + " does not exist" ); } commands.add( "-f" ); commands.add( makefile ); } configureApplicationMakefile( commands ); configureMaxJobs( commands ); configureNdkToolchain( ndkArchitecture, commands ); configureAdditionalCommands( commands ); // If a build target is specified, tag that onto the command line as the // very last of the parameters if ( target != null ) { commands.add( target ); } else /*if ( "a".equals( project.getPackaging() ) )*/ { commands.add( project.getArtifactId() ); } final String ndkBuildPath = resolveNdkBuildExecutable(); getLog().info( ndkBuildPath + " " + commands.toString() ); executor.executeCommand( ndkBuildPath, commands, project.getBasedir(), true ); cleanUp( preparation.getNativeLibDirectory(), ndkArchitecture, libsDirectoryExists, directoryToRemove, makefileHolder, makefileCaptureFile ); } } catch ( MojoExecutionException e ) { getLog().error( "Error during build: " + e.getMessage(), e ); throw e; } catch ( Exception e ) { getLog().error( "Error while executing: " + e.getMessage() ); throw new MojoExecutionException( e.getMessage(), e ); } } private void configureAdditionalCommands( final List commands ) { // Anything else on the command line the user wants to add - simply splice it up and // add it one by one to the command line if ( ndkBuildAdditionalCommandline != null ) { String[] additionalCommands = ndkBuildAdditionalCommandline.split( " " ); for ( final String command : additionalCommands ) { commands.add( command ); } } } private void configureApplicationMakefile( List commands ) throws MojoExecutionException { if ( applicationMakefile != null ) { File appMK = new File( project.getBasedir(), applicationMakefile ); if ( ! appMK.exists() ) { getLog().error( "Specified application makefile " + appMK + " does not exist" ); throw new MojoExecutionException( "Specified application makefile " + appMK + " does not exist" ); } commands.add( "NDK_APPLICATION_MK=" + applicationMakefile ); } } private void configureMaxJobs( List commands ) { if ( maxJobs ) { String jobs = String.valueOf( Runtime.getRuntime().availableProcessors() ); getLog().info( "executing " + jobs + " parallel jobs" ); commands.add( "-j" ); commands.add( jobs ); } } private void configureNdkToolchain( String ndkArchitecture, List commands ) throws MojoExecutionException { if ( ndkToolchain != null ) { // Setup the correct toolchain to use // FIXME: perform a validation that this toolchain exists in the NDK commands.add( "NDK_TOOLCHAIN=" + ndkToolchain ); } else { // Resolve the toolchain from the architecture // // x86-4.6 // x86-4.6 // final String toolchainFromArchitecture = getAndroidNdk().getToolchainFromArchitecture( ndkArchitecture, ndkArchitectureToolchainMappings ); getLog().debug( "Resolved toolchain for " + ndkArchitecture + " to " + toolchainFromArchitecture ); commands.add( "NDK_TOOLCHAIN=" + toolchainFromArchitecture ); commands.add( "APP_ABI=" + ndkArchitecture ); } } private void cleanUp( File nativeLibDirectory, String ndkArchitecture, boolean libsDirectoryExists, File directoryToRemove, MakefileHelper.MakefileHolder makefileHolder, File makefileCaptureFile ) throws IOException, MojoExecutionException { try { // Cleanup libs/armeabi directory if needed - this implies moving any native artifacts into target/libs if ( clearNativeArtifacts ) { nativeLibDirectory = cleanUpNativeArtifacts( nativeLibDirectory, ndkArchitecture, libsDirectoryExists ); } // Attempt to attach the native library if the project is defined as a "pure" native Android library // (packaging is 'so' or 'a') or if the plugin has been configured to attach the native library to the // build if ( "so".equals( project.getPackaging() ) || "a".equals( project.getPackaging() ) || attachNativeArtifacts ) { final File nativeArtifactFile; if ( ndkFinalLibraryName == null ) { nativeArtifactFile = findNativeLibrary( nativeLibDirectory ); } else { nativeArtifactFile = nativeLibraryFromName( nativeLibDirectory ); } final String artifactType = resolveArtifactType( nativeArtifactFile ); if ( nativeArtifactFile.getName().endsWith( ".so" ) && ! skipStripping ) { getLog().debug( "Post processing (stripping) native compiled artifact: " + nativeArtifactFile ); invokeNDKStripper( nativeArtifactFile ); } getLog().debug( "Adding native compiled artifact: " + nativeArtifactFile ); File fileToAttach = nativeArtifactFile; if ( ! libsDirectoryExists && !clearNativeArtifacts ) { final String destFileName = ndkArchitecture + File.separator + nativeArtifactFile.getName(); final File destFile = new File( ndkOutputDirectory, destFileName ); if ( !destFile.equals( nativeArtifactFile ) ) { getLog().debug( "Moving native compiled artifact to target directory for preservation" ); // This indicates the output directory was created by the build (us) and that we should really // move it to the target (needed to preserve the attached artifact once install is invoked) if ( destFile.exists() ) { destFile.delete(); } getLog().debug( nativeArtifactFile + " -> " + destFile ); FileUtils.moveFile( nativeArtifactFile, destFile ); fileToAttach = destFile; } else { getLog().debug( "Not moving native compiled artifact " + nativeArtifactFile + " to target as they point to the same file" ); fileToAttach = nativeArtifactFile; } } String classifier = ndkArchitecture; if ( ndkClassifier != null ) { classifier += "-" + ndkClassifier; } projectHelper.attachArtifact( this.project, artifactType, classifier, fileToAttach ); } // Process conditionally any of the headers to include into the header archive file processMakefileCapture( makefileCaptureFile, ndkArchitecture ); } finally { // If we created any directories as part of the build, blow those away after we're done if ( ! libsDirectoryExists ) { getLog().info( "Cleaning up native library output directory after build" ); getLog().debug( "Removing directory: " + directoryToRemove ); // AJE - removes 'obj' directory FileUtils.deleteDirectory( directoryToRemove ); } // If we created a makefile for the build we should be polite and remove any extracted include // directories after we're done if ( makefileHolder != null ) { getLog().info( "Cleaning up extracted include directories used for build" ); MakefileHelper.cleanupAfterBuild( makefileHolder ); } } } /** * Search the specified directory for native artifacts that match the artifact Id */ private File findNativeLibrary( File nativeLibDirectory ) throws MojoExecutionException { getLog().info( "Searching " + nativeLibDirectory + " for built library" ); File[] files = nativeLibDirectory.listFiles( new FilenameFilter() { public boolean accept( final File dir, final String name ) { String libraryName = ndkFinalLibraryName; if ( libraryName == null || libraryName.isEmpty() ) { libraryName = project.getArtifactId(); } // FIXME: The following logic won't work for an APKLIB building a static library if ( "a".equals( project.getPackaging() ) ) { return name.startsWith( "lib" + libraryName ) && name.endsWith( ".a" ); } else { return name.startsWith( "lib" + libraryName ) && name.endsWith( ".so" ); } } } ); // slight limitation at this stage - we only handle a single .so artifact if ( files == null || files.length != 1 ) { getLog().warn( "Error while detecting native compile artifacts: " + ( files == null || files.length == 0 ? "None found" : "Found more than 1 artifact" ) ); if ( target != null ) { getLog().warn( "Using the 'target' configuration option to specify the output file name " + "is no longer supported, use 'ndkFinalLibraryName' instead." ); } if ( files != null && files.length > 1 ) { getLog().debug( "List of files found: " + Arrays.asList( files ) ); getLog().error( "Currently, only a single, final native library is supported by the build" ); throw new MojoExecutionException( "Currently, only a single, final native library is supported by the build" ); } else { getLog().error( "No native compiled library found, did the native compile complete " + "successfully?" ); throw new MojoExecutionException( "No native compiled library found, did the native " + "compile complete successfully?" ); } } return files[ 0 ]; } private File nativeLibraryFromName( File nativeLibDirectory ) throws MojoExecutionException { final File libraryFile; // Find the nativeArtifactFile in the nativeLibDirectory/ndkFinalLibraryName if ( "so".equals( project.getPackaging() ) || "a".equals( project.getPackaging() ) ) { libraryFile = new File( nativeLibDirectory, "lib" + ndkFinalLibraryName + "." + project.getPackaging() ); } else { final File staticLib = new File( nativeLibDirectory, "lib" + ndkFinalLibraryName + ".a" ); if ( staticLib.exists() ) { libraryFile = staticLib; } else { libraryFile = new File( nativeLibDirectory, "lib" + ndkFinalLibraryName + ".so" ); } } if ( ! libraryFile.exists() ) { getLog().error( "Could not locate final native library using the provided ndkFinalLibraryName " + ndkFinalLibraryName + " (tried " + libraryFile.getAbsolutePath() + ")" ); throw new MojoExecutionException( "Could not locate final native library using the provided ndkFinalLibraryName " + ndkFinalLibraryName + " (tried " + libraryFile.getAbsolutePath() + ")" ); } return libraryFile; } private CommandExecutor.ErrorListener getNdkErrorListener() { return new CommandExecutor.ErrorListener() { @Override public boolean isError( String error ) { // Unconditionally ignore *All* build warning if configured to if ( ignoreBuildWarnings ) { return false; } final Pattern pattern = Pattern.compile( buildWarningsRegularExpression ); final Matcher matcher = pattern.matcher( error ); // If the the reg.exp actually matches, we can safely say this is not an error // since in theory the user told us so if ( matcher.matches() ) { return false; } // Otherwise, it is just another error return true; } }; } private File cleanUpNativeArtifacts( File nativeLibDirectory, String ndkArchitecture, boolean libsDirectoryExists ) throws IOException { final File destinationDirectory = new File( ndkOutputDirectory.getAbsolutePath(), ndkArchitecture ); if ( ! libsDirectoryExists && ! destinationDirectory.exists() ) { FileUtils.moveDirectory( nativeLibDirectory, destinationDirectory ); } else { FileUtils.copyDirectory( nativeLibDirectory, destinationDirectory ); FileUtils.cleanDirectory( nativeLibDirectory ); } nativeLibDirectory = destinationDirectory; return nativeLibDirectory; } private void validateMakefile( MavenProject project, String makefile ) { // TODO: actually perform validation } private void invokeNDKStripper( File file ) throws MojoExecutionException { try { getLog().debug( "Detected shared library artifact, will now strip it" ); // Execute the strip command final CommandExecutor stripCommandExecutor = CommandExecutor.Factory.createDefaultCommmandExecutor(); stripCommandExecutor.setErrorListener( new CommandExecutor.ErrorListener() { public boolean isError( String error ) { getLog().error( "Error while stripping binary: " + error ); return true; } } ); stripCommandExecutor.setLogger( getLog() ); stripCommandExecutor.executeCommand( resolveNdkStripper( file ).getAbsolutePath(), Arrays.asList( file.getAbsolutePath() ) ); } catch ( ExecutionException e ) { getLog().error( "Error while attempting to strip shared library", e ); throw new MojoExecutionException( "Error while attempting to strip shared library" ); } } private String resolveNdkBuildExecutable() throws MojoExecutionException { if ( ndkBuildExecutable != null ) { getLog().debug( "ndk-build overriden, using " + ndkBuildExecutable ); return ndkBuildExecutable; } return getAndroidNdk().getNdkBuildPath(); } private File resolveNdkStripper( File nativeLibrary ) throws MojoExecutionException { if ( ndkToolchain != null ) { return getAndroidNdk().getStripper( ndkToolchain ); } else { return getAndroidNdk().getStripper( getAndroidNdk().getToolchain( nativeLibrary ) ); } } private void processMakefileCapture( File localCIncludesFile, String ndkArchitecture ) throws MojoExecutionException { try { if ( attachHeaderFiles ) { final List finalHeaderFilesDirectives = new ArrayList(); if ( useLocalSrcIncludePaths ) { Properties props = new Properties(); props.load( new FileInputStream( localCIncludesFile ) ); String localCIncludes = props.getProperty( "LOCAL_C_INCLUDES" ); if ( localCIncludes != null && ! localCIncludes.trim().isEmpty() ) { String[] includes = localCIncludes.split( " " ); for ( String include : includes ) { final HeaderFilesDirective headerFilesDirective = new HeaderFilesDirective(); File includeDir = new File( project.getBasedir(), include ); headerFilesDirective.setDirectory( includeDir.getAbsolutePath() ); headerFilesDirective.setIncludes( new String[]{ "**/*.h" } ); finalHeaderFilesDirectives.add( headerFilesDirective ); } } } else { if ( headerFilesDirectives != null ) { finalHeaderFilesDirectives.addAll( headerFilesDirectives ); } } if ( finalHeaderFilesDirectives.isEmpty() ) { getLog().debug( "No header files included, will add default set" ); final HeaderFilesDirective e = new HeaderFilesDirective(); e.setDirectory( new File( project.getBasedir() + "/jni" ).getAbsolutePath() ); e.setIncludes( new String[]{ "**/*.h" } ); finalHeaderFilesDirectives.add( e ); } createHeaderArchive( finalHeaderFilesDirectives, ndkArchitecture ); } } catch ( Exception e ) { throw new MojoExecutionException( "Error while processing headers to include: " + e.getMessage(), e ); } } private void createHeaderArchive( List finalHeaderFilesDirectives, String ndkArchitecture ) throws MojoExecutionException { try { MavenArchiver mavenArchiver = new MavenArchiver(); mavenArchiver.setArchiver( jarArchiver ); final File jarFile = new File( new File( project.getBuild().getDirectory() ), project.getBuild().getFinalName() + ".har" ); mavenArchiver.setOutputFile( jarFile ); for ( HeaderFilesDirective headerFilesDirective : finalHeaderFilesDirectives ) { mavenArchiver.getArchiver().addDirectory( new File( headerFilesDirective.getDirectory() ), headerFilesDirective.getIncludes(), headerFilesDirective.getExcludes() ); } final MavenArchiveConfiguration mavenArchiveConfiguration = new MavenArchiveConfiguration(); mavenArchiveConfiguration.setAddMavenDescriptor( false ); mavenArchiver.createArchive( project, mavenArchiveConfiguration ); String classifier = ndkArchitecture; if ( ndkClassifier != null ) { classifier += "-" + ndkClassifier; } if ( AndroidExtension.APKLIB.equals( project.getPackaging() ) ) { projectHelper.attachArtifact( project, "har", classifier, jarFile ); } else { projectHelper.attachArtifact( project, "har", classifier, jarFile ); } } catch ( Exception e ) { throw new MojoExecutionException( e.getMessage() ); } } private void setupNativeLibraryEnvironment( MakefileHelper makefileHelper, CommandExecutor executor, Set resolveNativeLibraryArtifacts, String ndkArchitecture ) { // Only add the LOCAL_STATIC_LIBRARIES if ( NativeHelper.hasStaticNativeLibraryArtifact( resolveNativeLibraryArtifacts, unpackedApkLibsDirectory, ndkArchitecture ) ) { String staticlibs = makefileHelper.createLibraryList( resolveNativeLibraryArtifacts, ndkArchitecture, true ); executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_STATIC_LIBRARIES", staticlibs ); getLog().debug( "Set ANDROID_MAVEN_PLUGIN_LOCAL_STATIC_LIBRARIES = " + staticlibs ); } // Only add the LOCAL_SHARED_LIBRARIES if ( NativeHelper.hasSharedNativeLibraryArtifact( resolveNativeLibraryArtifacts, unpackedApkLibsDirectory, ndkArchitecture ) ) { String sharedlibs = makefileHelper.createLibraryList( resolveNativeLibraryArtifacts, ndkArchitecture, false ); executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_SHARED_LIBRARIES", sharedlibs ); getLog().debug( "Set ANDROID_MAVEN_PLUGIN_LOCAL_SHARED_LIBRARIES = " + sharedlibs ); } } private Set findNativeLibraryDependencies() throws MojoExecutionException { NativeHelper nativeHelper = new NativeHelper( project, projectRepos, repoSession, repoSystem, artifactFactory, getLog() ); final Set staticLibraryArtifacts = nativeHelper .getNativeDependenciesArtifacts( unpackedApkLibsDirectory, false ); final Set sharedLibraryArtifacts = nativeHelper .getNativeDependenciesArtifacts( unpackedApkLibsDirectory, true ); final Set mergedArtifacts = new LinkedHashSet(); filterNativeDependencies( mergedArtifacts, staticLibraryArtifacts ); filterNativeDependencies( mergedArtifacts, sharedLibraryArtifacts ); if ( getLog().isDebugEnabled() ) { getLog().debug( "findNativeLibraryDependencies found " + mergedArtifacts.size() + ": " + mergedArtifacts.toString() ); } return mergedArtifacts; } /** * Selectively add artifacts from source to target excluding any whose groupId and artifactId match * the current build. * Introduced to work around an issue when the ndk-build is executed twice by maven for example when * invoking maven 'install site'. In this case the artifacts attached by the first invocation are * found but are not valid dependencies and must be excluded. * @param target artifact Set to copy in to * @param source artifact Set to filter */ private void filterNativeDependencies( Set target, Set source ) { for ( Artifact a : source ) { if ( project.getGroupId().equals( a.getGroupId() ) && project.getArtifactId().equals( a.getArtifactId() ) ) { getLog().warn( "Excluding native dependency attached by this build" ); } else { target.add( a ); } } } /** * Resolve the artifact type from the current project and the specified file. If the project packaging is * either 'a' or 'so' it will use the packaging, otherwise it checks the file for the extension * * @param file The file being added as an artifact * @return The artifact type (so or a) */ private String resolveArtifactType( File file ) { if ( "so".equals( project.getPackaging() ) || "a".equals( project.getPackaging() ) ) { return project.getPackaging(); } else { // At this point, the file (as found by our filtering previously will end with either 'so' or 'a' return file.getName().endsWith( "so" ) ? "so" : "a"; } } private class Preparation { private File nativeLibDirectory; private boolean libsDirectoryExists; private File directoryToRemove; public File getNativeLibDirectory() { return nativeLibDirectory; } public boolean isLibsDirectoryExists() { return libsDirectoryExists; } public File getDirectoryToRemove() { return directoryToRemove; } public Preparation invoke( String ndkArchitecture ) { // This usually points to ${basedir}/obj/local nativeLibDirectory = new File( nativeLibrariesOutputDirectory, ndkArchitecture ); libsDirectoryExists = nativeLibDirectory.exists(); // Determine how much of the output directory structure (most likely obj/...) does not exist // and based on what we find, determine how much of it we delete after the build directoryToRemove = nativeLibDirectory; if ( ! libsDirectoryExists ) { getLog().info( "Creating native output directory " + nativeLibDirectory ); // This simply checks how much of the structure already exists - nothing (e.g. we make all the dirs) // or just a partial part (the architecture part)? if ( ! nativeLibrariesOutputDirectory.exists() ) { if ( nativeLibrariesOutputDirectory.getParentFile().exists() ) { nativeLibDirectory.mkdir(); } else { nativeLibDirectory.mkdirs(); directoryToRemove = nativeLibrariesOutputDirectory.getParentFile(); } } else { if ( nativeLibDirectory.getParentFile().exists() ) { nativeLibDirectory.mkdir(); } else { nativeLibDirectory.mkdirs(); directoryToRemove = nativeLibDirectory.getParentFile(); } } } return this; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy