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

com.simpligility.maven.plugins.androidndk.phase05compile.NdkBuildMojo Maven / Gradle / Ivy

The 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.simpligility.maven.plugins.androidndk.phase05compile;

import com.simpligility.maven.plugins.androidndk.AndroidNdk;
import com.simpligility.maven.plugins.androidndk.CommandExecutor;
import com.simpligility.maven.plugins.androidndk.common.ArtifactResolverHelper;
import com.simpligility.maven.plugins.androidndk.common.Const;
import com.simpligility.maven.plugins.androidndk.common.MavenToPlexusLogAdapter;
import com.simpligility.maven.plugins.androidndk.common.NativeHelper;
import com.simpligility.maven.plugins.androidndk.configuration.AdditionallyBuiltModule;
import com.simpligility.maven.plugins.androidndk.configuration.HeaderFilesDirective;
import com.simpligility.maven.plugins.androidndk.configuration.ArchitectureToolchainMappings;
import com.simpligility.maven.plugins.androidndk.configuration.IgnoreHeaderFilesArchive;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.resolver.ArtifactResolver;
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.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
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 
 */
@Mojo( name = "ndk-build", defaultPhase = LifecyclePhase.COMPILE )
public class NdkBuildMojo extends AbstractMojo
{
    /**
     * The ANDROID_NDK_HOME environment variable name.
     */
    public static final String ENV_ANDROID_NDK_HOME = "ANDROID_NDK_HOME";

    /**
     * 

Parameter designed to pick up -Dandroid.ndk.ndkPath in case there is no pom with an * <ndk> configuration tag.

*/ @Parameter( property = "android.ndk.ndkPath", readonly = true ) private File ndkPath; /** * Allows for overriding the default ndk-build executable. */ @Parameter( property = "android.ndk.ndkBuildExecutable" ) private String ndkBuildExecutable; /** * Folder in which the NDK makefiles are constructed. */ @Parameter( property = "android.ndk.buildDirectory", defaultValue = "${project.build.directory}/android-ndk-maven-plugin", readonly = true ) private File buildDirectory; /** Folder in which the ndk-build command is executed. This is using the -C command line flag. */ @Parameter( property = "android.ndk.workingDirectory", defaultValue = "${project.basedir}", readonly = true ) private File workingDirectory; /** Specifies the classifier with which the artifact should be stored in the repository */ @Parameter( property = "android.ndk.classifier" ) private String classifier; /** Specifies additional command line parameters to pass to ndk-build */ @Parameter( property = "android.ndk.additionalCommandline" ) protected String additionalCommandline; /** *

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

*

*/ @Parameter( property = "android.ndk.objectsOutputDirectory", defaultValue = "${project.build.directory}/obj" ) private File objectsOutputDirectory; /** *

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

*

*/ @Parameter( property = "android.ndk.librariesOutputDirectory", defaultValue = "${project.build.directory}/ndk-libs" ) private File librariesOutputDirectory; /** * Folder in which AAR/APKLIB library dependencies will be unpacked. */ @Parameter( property = "unpackedLibsFolder", defaultValue = "${project.build.directory}/unpacked-libs" ) private File unpackedLibsFolder; /** *

Target to invoke on the native makefile.

*/ @Parameter( property = "android.ndk.target" ) private String target; /** * Defines the architectures for the NDK build - this is a space separated list (i.e x86 armeabi) */ @Parameter( property = "android.ndk.architectures" ) private String architectures; /** * Defines the architecture to toolchain mappings for the NDK build * <architectureToolchainMappings> * <x86>x86-4.7</x86> * <armeabi>arm-linux-androideabi-4.7</armeabi> * </architectureToolchainMappings> */ @Parameter private ArchitectureToolchainMappings architectureToolchainMappings; /** * Flag indicating whether the header files used in the build should be included and attached to the build as * an additional artifact. */ @Parameter( property = "android.ndk.attachHeaderFiles", defaultValue = "true" ) private Boolean attachHeaderFiles; /** * Flag indicating whether the final artifacts should be included and attached to the build as an artifact. */ @Parameter( property = "android.ndk.attachLibrariesArtifacts", defaultValue = "true" ) private Boolean attachLibrariesArtifacts; /** * Flag indicating whether temporary build artifacts are removed after a build */ @Parameter( property = "android.ndk.leaveTemporaryBuildArtifacts", defaultValue = "false" ) private Boolean leaveTemporaryBuildArtifacts; /** * Flag indicating whether the make files last LOCAL_SRC_INCLUDES should be used for determining 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( property = "android.ndk.useLocalSrcIncludePaths", 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 private List headerFilesDirectives; /** * 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( property = "android.ndk.build.use-header-archive", defaultValue = "true" ) private Boolean useHeaderArchives; /** Specifies a set of group/artifact identifiers for which header archives should not be attempted to be resolved. * This is useful when a static library dependcy on other static libraries but the headers of those libraries are not necessarily * available. This allows the plugin to exclude the retrieval of those header archives *

*

     *   <ignoreHeaderFilesArchives>
     *     <ignoreHeaderFilesArchive>
     *       <groupId>com.insidesecure.drm.agent.android</groupId>
     *       <artifactId>expat-static-lib</artifactId>
     *     </ignoreHeaderFilesArchive>
     *     <ignoreHeaderFilesArchive>
     *       <groupId>com.insidesecure.drm.agent.android</groupId>
     *       <artifactId>pcre-static-</artifactId>
     *     </ignoreHeaderFilesArchive>
     *   </ignoreHeaderFilesArchives>
     * 
* */ @Parameter private List ignoreHeaderFilesArchives; /** * 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 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( property = "android.ndk.ignoreBuildWarnings", 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( property = "android.ndk.buildWarningsRegularExpression", defaultValue = ".*[warning|note]: .*" ) private String buildWarningsRegularExpression; /** Specifies the NDK toolchain to use for the build. This will be using the NDK_TOOLCHAIN define on the ndk-build commandline. */ @Parameter( property = "android.ndk.build.ndk-toolchain" ) 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( property = "android.ndk.finalLibraryName" ) private String finalLibraryName; /** * Specifies the makefile to use for the build (if other than the default Android.mk). */ @Parameter( property = "android.ndk.makefile" ) private String makefile; /** * Specifies the application makefile to use for the build (if other than the default Application.mk). */ @Parameter( property = "android.ndk.applicationMakefile" ) private String applicationMakefile; /** * Flag indicating whether to use the max available jobs for the host machine */ @Parameter( property = "android.ndk.maxJobs", defaultValue = "false" ) private Boolean maxJobs; /** * */ @Parameter() private List additionallyBuiltModules; /** * Flag indicating whether or not the build should be skipped entirely */ @Parameter( defaultValue = "false" ) private boolean skip; /** * Flag indicating whether or not multiple number of native libraries should be included */ @Parameter( defaultValue = "false" ) private boolean allowMultiArtifacts; /** * The Jar archiver. */ @Component( role = org.codehaus.plexus.archiver.Archiver.class, hint = "jar" ) private JarArchiver jarArchiver; @Component( role = org.apache.maven.artifact.handler.ArtifactHandler.class, hint = "har" ) private ArtifactHandler harArtifactHandler; /** * The maven project. */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) protected MavenProject project; /** * Maven ProjectHelper. */ @Component protected MavenProjectHelper projectHelper; @Component private ArtifactResolver artifactResolver; /** * Dependency graph builder component. */ @Component( hint = "default" ) protected DependencyGraphBuilder dependencyGraphBuilder; private ArtifactResolverHelper artifactResolverHelper; private NativeHelper nativeHelper; /** * @parameter expression="${mojoExecution}" */ @Component private org.apache.maven.plugin.MojoExecution execution; /** * @throws MojoExecutionException * @throws MojoFailureException */ public void execute() throws MojoExecutionException, MojoFailureException { if ( skip ) { getLog().info( "Skipping execution as per configuration" ); return; } if ( !attachLibrariesArtifacts && NativeHelper.isNativeArtifactProject( project ) ) { getLog().warn( "Configured to not attach artifacts, this may cause an error at install/deploy time" ); } // Validate the NDK final File ndkBuildFile = new File( getAndroidNdk().getNdkBuildPath() ); NativeHelper.validateNDKVersion( ndkBuildFile.getParentFile() ); validateMakefile( project, makefile ); final String[] resolvedNDKArchitectures = NativeHelper.getNdkArchitectures( architectures, applicationMakefile, project.getBasedir() ); // Resolve all dependencies final Set nativeLibraryArtifacts = findNativeLibraryDependencies(); // If there are any static libraries the code needs to link to, include those in the make file final Set resolvedNativeLibraryArtifacts = getArtifactResolverHelper().resolveArtifacts( nativeLibraryArtifacts ); getLog().debug( "resolveArtifacts found " + resolvedNativeLibraryArtifacts.size() + ": " + resolvedNativeLibraryArtifacts.toString() ); CompileCommand compileCommand = new CompileCommand (); compileCommand.nativeLibraryDepedencies = resolvedNativeLibraryArtifacts; compileCommand.resolvedArchitectures = resolvedNDKArchitectures; setupOutputDirectories( compileCommand ); compile ( compileCommand ); } private void setupOutputDirectories ( final CompileCommand compileCommand ) { compileCommand.librariesOutputDirectory = librariesOutputDirectory; compileCommand.objectsOutputDirectory = objectsOutputDirectory; // This indicates that the build is either the default build (by extension) // or an execution within an (for example) APKLIB build. // The execution can currently not be named since it would differ // for APKLIB once that is executed. // // TODO: Once the AAR/APKLIB can pull the attached artifacts from the // TODO: Maven session, this can be sorted out better. // if ( ! "default-ndk-build".equals( execution.getExecutionId () ) && ! "default".equals ( execution.getExecutionId () ) ) { String libsOut = librariesOutputDirectory.getAbsolutePath(); String out = objectsOutputDirectory.getAbsolutePath(); libsOut = libsOut + "/" + execution.getExecutionId (); out = out + "/" + execution.getExecutionId (); // FIXME: Will this actually work - what happens if the execution is the single one & it has an ID? // compileCommand.librariesOutputDirectory = new File( libsOut ); // compileCommand.objectsOutputDirectory = new File( out ); } getLog ().debug ( "Setting library out to " + compileCommand.librariesOutputDirectory.getAbsolutePath () ); getLog ().debug ( "Setting out to " + compileCommand.objectsOutputDirectory.getAbsolutePath () ); } private class CompileCommand { private File objectsOutputDirectory; private File librariesOutputDirectory; private Set nativeLibraryDepedencies; private String[] resolvedArchitectures; public Set getNativeLibraryDepedencies () { return nativeLibraryDepedencies; } public String[] getResolvedArchitectures () { return resolvedArchitectures; } } private void compile ( CompileCommand compileCommand ) throws MojoExecutionException { MakefileHelper.MakefileResponse makefileResponse = null; try { // 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 = compileCommand.getNativeLibraryDepedencies (); // If there are any static libraries the code needs to link to, include those in the make file final Set resolvedNativeLibraryArtifacts = getArtifactResolverHelper ().resolveArtifacts ( nativeLibraryArtifacts ); getLog ().debug ( "resolveArtifacts found " + resolvedNativeLibraryArtifacts.size () + ": " + resolvedNativeLibraryArtifacts.toString () ); final File buildFolder = new File ( buildDirectory, "makefile" ); buildFolder.mkdirs (); final File androidMavenMakefile = new File ( buildFolder, "android_maven_plugin_makefile.mk" ); final MakefileHelper makefileHelper = new MakefileHelper ( project, getLog (), getArtifactResolverHelper (), harArtifactHandler, unpackedLibsFolder, buildDirectory ); MakefileHelper.MakefileRequest makefileRequest = new MakefileHelper.MakefileRequest (); makefileRequest.artifacts = resolvedNativeLibraryArtifacts; makefileRequest.defaultNDKArchitecture = "armeabi"; makefileRequest.useHeaderArchives = useHeaderArchives; makefileRequest.ignoreHeaderFilesArchives = ignoreHeaderFilesArchives; makefileRequest.leaveTemporaryBuildArtifacts = leaveTemporaryBuildArtifacts; makefileRequest.architectures = compileCommand.getResolvedArchitectures (); makefileResponse = makefileHelper.createMakefileFromArtifacts ( makefileRequest ); final FileOutputStream output = new FileOutputStream ( androidMavenMakefile ); try { IOUtil.copy ( makefileResponse.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 ( executor, makefileResponse ); // 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", buildDirectory ); if ( !leaveTemporaryBuildArtifacts ) { 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(); configureArchitectures( commands, compileCommand.getResolvedArchitectures () ); configureBuildDirectory( compileCommand, commands ); configureMakefile( commands ); configureApplicationMakefile( commands ); configureMaxJobs( commands ); // Only allow configuration of the toolchain if the architecture being built is a single one! if ( compileCommand.getResolvedArchitectures ().length == 1 ) { configureNdkToolchain ( compileCommand.getResolvedArchitectures ()[0], commands ); } configureAdditionalCommands( commands ); // If a build target is specified, tag that onto the command line as the very last of the parameters commands.add ( target != null ? target : "all" ); final String ndkBuildPath = resolveNdkBuildExecutable (); getLog ().debug ( ndkBuildPath + " " + commands.toString () ); getLog ().info ( "Executing NDK make at : " + buildDirectory ); executor.setCaptureStdOut ( true ); executor.executeCommand ( ndkBuildPath, commands, buildDirectory, true ); getLog ().debug ( "Executed NDK make at : " + buildDirectory ); if ( attachLibrariesArtifacts ) { // Attempt to attach the native libraries (shared only) for ( int i = 0; i < compileCommand.getResolvedArchitectures ().length; i++ ) { String architecture = compileCommand.getResolvedArchitectures ()[ i ]; processCompiledArtifacts ( compileCommand, architecture, makefileCaptureFile ); } } else { getLog ().info ( "Will skip attaching compiled libraries as per configuration" ); } } catch ( Exception e ) { throw new MojoExecutionException ( "Failure during build: " + e.getMessage (), e ); } finally { cleanupAfterBuild( makefileResponse ); } } private void configureArchitectures ( final List commands, final String[] resolvedArchitectures ) { StringBuilder sb = new StringBuilder ( ); for ( int i = 0; i < resolvedArchitectures.length; i++ ) { sb.append ( resolvedArchitectures[i] ); if ( ( i + 1 ) < resolvedArchitectures.length ) { sb.append ( " " ); } } // We always for the APP_ABI onto the command line commands.add ( "APP_ABI=" + sb.toString () ); } private void configureBuildDirectory( final CompileCommand compileCommand, final List commands ) { // Setup the build directory (defaults to the current directory) but may be different depending // on user configuration commands.add( "-C" ); commands.add( workingDirectory.getAbsolutePath() ); // Next, configure the output directories commands.add( "NDK_LIBS_OUT=" + compileCommand.librariesOutputDirectory.getAbsolutePath () ); commands.add( "NDK_OUT=" + compileCommand.objectsOutputDirectory.getAbsolutePath () ); } private void configureMakefile( final List commands ) throws MojoExecutionException { // 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( "APP_BUILD_SCRIPT=" + makefile ); } } private void cleanupAfterBuild ( final MakefileHelper.MakefileResponse makefileResponse ) { // directories after we're done if ( makefileResponse != null ) { getLog().info( "Cleaning up extracted include directories used for build" ); MakefileHelper.cleanupAfterBuild( makefileResponse ); } } 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 ( additionalCommandline != null ) { final String[] additionalCommands = additionalCommandline.split( " " ); commands.addAll( Arrays.asList( additionalCommands ) ); } } 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 architecture, List commands ) throws MojoExecutionException { if ( ndkToolchain != null ) { // Setup the correct toolchain to use // FIXME: perform a validation that this toolchain exists in the NDK and is valid for the specified // FIXME: architecture! commands.add( "NDK_TOOLCHAIN=" + ndkToolchain ); commands.add( "APP_ABI=" + architecture ); } else { // Resolve the toolchain from the architecture // // x86-4.6 // x86-4.6 // final String toolchainFromArchitecture = getAndroidNdk().getToolchainFromArchitecture( architecture, architectureToolchainMappings ); getLog().debug( "Resolved toolchain for " + architecture + " to " + toolchainFromArchitecture ); commands.add( "NDK_TOOLCHAIN=" + toolchainFromArchitecture ); commands.add( "APP_ABI=" + architecture ); } } /** * Attaches native libs to project. */ private void processCompiledArtifacts ( final CompileCommand compileCommand, String architecture, final File makefileCaptureFile ) throws IOException, MojoExecutionException { // Where the NDK build creates the libs. final File nativeLibraryDirectory = new File( compileCommand.librariesOutputDirectory, architecture ); // Where the NDK build creates the object files - static files end up here final File nativeObjDirectory = new File( new File( compileCommand.objectsOutputDirectory, "local" ), architecture ); final List classifiers = new ArrayList(); if ( allowMultiArtifacts ) { attachManyArtifacts( nativeLibraryDirectory, architecture, nativeObjDirectory, classifiers ); } else { attachOneArtifact( nativeLibraryDirectory, architecture, nativeObjDirectory, classifiers ); } if ( additionallyBuiltModules != null && !additionallyBuiltModules.isEmpty() ) { for ( AdditionallyBuiltModule additionallyBuiltModule : additionallyBuiltModules ) { File additionalBuiltModuleFile = nativeLibraryFromName( true, nativeLibraryDirectory, additionallyBuiltModule.getName() ); // If it doesnt exist, check the object directory if ( !additionalBuiltModuleFile.exists() ) { additionalBuiltModuleFile = nativeLibraryFromName( true, nativeObjDirectory, additionallyBuiltModule.getName() ); } // FIMXE: This should be validated final String additionallyBuiltArtifactType = resolveArtifactType( additionalBuiltModuleFile ); String additionallyBuiltClassifier = architecture + "-" + additionallyBuiltModule.getClassifier(); projectHelper.attachArtifact( this.project, additionallyBuiltArtifactType, additionallyBuiltClassifier, additionalBuiltModuleFile ); classifiers.add( additionallyBuiltClassifier ); } } // Process conditionally any of the headers to include into the header archive file if ( attachHeaderFiles ) { attachHeaderFiles( compileCommand, makefileCaptureFile, classifiers ); } } private void attachManyArtifacts( File nativeLibraryDirectory, String architecture, File nativeObjDirectory, List classifiers ) throws MojoExecutionException { List artifacts = Arrays.asList( findNativeLibrary( nativeLibraryDirectory, nativeObjDirectory ) ); for ( File file : artifacts ) { attachArtifactFile( architecture, classifiers, file ); } } private void attachOneArtifact( File nativeLibraryDirectory, String architecture, File nativeObjDirectory, List classifiers ) throws MojoExecutionException { final File nativeArtifactFile; if ( finalLibraryName == null ) { nativeArtifactFile = findNativeLibrary( nativeLibraryDirectory, nativeObjDirectory )[0]; } else { nativeArtifactFile = nativeLibraryFromName( nativeLibraryDirectory, nativeObjDirectory, finalLibraryName ); } attachArtifactFile( architecture, classifiers, nativeArtifactFile ); } private void attachArtifactFile( String architecture, List classifiers, File nativeArtifactFile ) { final String artifactType = resolveArtifactType( nativeArtifactFile ); getLog().debug( "Adding native compiled artifact: " + nativeArtifactFile ); final String actualClassifier = ( classifier == null ) ? architecture : architecture + "-" + classifier; projectHelper.attachArtifact( this.project, artifactType, actualClassifier, nativeArtifactFile ); classifiers.add( actualClassifier ); } /** * Search the specified directory for native artifacts that match the artifact Id */ private File[] findNativeLibrary( File nativeLibDirectory, final File nativeObjDirectory ) throws MojoExecutionException { getLog().info( "Searching " + nativeLibDirectory + " for built shared library" ); // FIXME: Should really just look for shared libraries in here really .... File[] files = nativeLibDirectory.listFiles( new FilenameFilter() { public boolean accept( final File dir, final String name ) { String libraryName = finalLibraryName; if ( libraryName == null || libraryName.isEmpty() ) { libraryName = project.getArtifactId(); } // FIXME: The following logic won't work for an APKLIB building a static library final String extension = Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( project.getPackaging() ) ? ".a" : ".so"; boolean found = name.startsWith( "lib" + libraryName ) && name.endsWith( extension ); if ( !found ) { // Issue #14 : Work-around issue where the project is actually called "lib" something if ( libraryName.startsWith( "lib" ) ) { found = name.startsWith( libraryName ) && name.endsWith( extension ); } } return found; } } ); // Check the object output directory as well // FIXME: Should really just look for static libraries in here really .... if ( files == null || files.length == 0 ) { getLog().info( "Searching " + nativeObjDirectory + " for built static library" ); files = nativeObjDirectory.listFiles( new FilenameFilter() { public boolean accept( final File dir, final String name ) { String libraryName = finalLibraryName; if ( libraryName == null || libraryName.isEmpty() ) { libraryName = project.getArtifactId(); } // FIXME: The following logic won't work for an APKLIB building a static library if ( Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.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 ) && !allowMultiArtifacts ) { 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 'finalLibraryName' 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; } private File nativeLibraryFromName( File nativeLibDirectory, final File nativeObjDirectory, final String libraryName ) throws MojoExecutionException { try { return nativeLibraryFromName( false, nativeLibDirectory, libraryName ); } catch ( MojoExecutionException e ) { // Try the obj directory return nativeLibraryFromName( true, nativeObjDirectory, libraryName ); } } private File nativeLibraryFromName( boolean logErrors, File directory, final String libraryName ) throws MojoExecutionException { final File libraryFile; // Find the nativeArtifactFile in the nativeLibDirectory/finalLibraryName if ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( project.getPackaging() ) || Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( project.getPackaging() ) ) { libraryFile = new File( directory, "lib" + libraryName + "." + project.getPackaging() ); } else { final File staticLib = new File( directory, "lib" + libraryName + ".a" ); if ( staticLib.exists() ) { libraryFile = staticLib; } else { libraryFile = new File( directory, "lib" + libraryName + ".so" ); } } if ( !libraryFile.exists() ) { if ( logErrors ) { getLog().error( "Could not locate final native library using the provided finalLibraryName " + libraryName + " (tried " + libraryFile.getAbsolutePath() + ")" ); } throw new MojoExecutionException( "Could not locate final native library using the provided finalLibraryName " + libraryName + " (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; } }; } /** * 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. */ private void validateMakefile( MavenProject project, String file ) { // TODO: actually perform validation } private String resolveNdkBuildExecutable() throws MojoExecutionException { if ( ndkBuildExecutable != null ) { getLog().debug( "ndk-build overriden, using " + ndkBuildExecutable ); return ndkBuildExecutable; } return getAndroidNdk().getNdkBuildPath(); } private void attachHeaderFiles ( final CompileCommand compileCommand, final File localCIncludesFile, final List classifiers ) throws MojoExecutionException, IOException { 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", "**/*.hpp" } ); 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(); final File folder = new File( project.getBasedir() + "/jni" ); if ( folder.exists() ) { e.setDirectory( folder.getAbsolutePath() ); e.setIncludes( new String[] { "**/*.h", "**/*.hpp" } ); finalHeaderFilesDirectives.add( e ); } } createHeaderArchive( compileCommand, finalHeaderFilesDirectives, classifiers ); } private void createHeaderArchive ( final CompileCommand compileCommand, final List finalHeaderFilesDirectives, final List classifiers ) throws MojoExecutionException { try { MavenArchiver mavenArchiver = new MavenArchiver(); mavenArchiver.setArchiver( jarArchiver ); final File jarFile = File.createTempFile( "tmp", ".har", buildDirectory ); 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 ); for ( String classifier : classifiers ) { getLog().debug( "Attaching 'har' classifier=" + classifier + " file=" + jarFile ); projectHelper.attachArtifact( project, Const.ArtifactType.NATIVE_HEADER_ARCHIVE, classifier, jarFile ); } } catch ( Exception e ) { throw new MojoExecutionException( e.getMessage() ); } } private void setupNativeLibraryEnvironment ( final CommandExecutor executor, final MakefileHelper.MakefileResponse makefileResponse ) { if ( makefileResponse.hasStaticLibraryDepdendencies() ) { String staticlibs = makefileResponse.getStaticLibraryList(); executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_STATIC_LIBRARIES", staticlibs ); getLog().debug( "Set ANDROID_MAVEN_PLUGIN_LOCAL_STATIC_LIBRARIES = " + staticlibs ); } if ( makefileResponse.hasSharedLibraryDepdendencies() ) { String staticlibs = makefileResponse.getSharedLibraryList(); executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_SHARED_LIBRARIES", staticlibs ); getLog().debug( "Set ANDROID_MAVEN_PLUGIN_LOCAL_SHARED_LIBRARIES = " + staticlibs ); } } private Set findNativeLibraryDependencies() throws MojoExecutionException { final NativeHelper nativeHelper = getNativeHelper(); final Set staticLibraryArtifacts = nativeHelper.getNativeDependenciesArtifacts( false ); final Set sharedLibraryArtifacts = nativeHelper.getNativeDependenciesArtifacts( true ); final Set mergedArtifacts = new LinkedHashSet(); filterNativeDependencies( mergedArtifacts, staticLibraryArtifacts ); filterNativeDependencies( mergedArtifacts, sharedLibraryArtifacts ); 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 targetSet artifact Set to copy in to * @param source artifact Set to filter */ private void filterNativeDependencies( Set targetSet, 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 { targetSet.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 ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( project.getPackaging() ) || Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.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( Const.ArtifactType.NATIVE_SYMBOL_OBJECT ) ? Const.ArtifactType.NATIVE_SYMBOL_OBJECT : Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE; } } /** *

Returns the Android NDK to use.

*

*

Current implementation looks for <ndk><path> configuration in pom, then System * property android.ndk.path, then environment variable ANDROID_NDK_HOME. *

*

This is where we collect all logic for how to lookup where it is, and which one to choose. The lookup is * based on available parameters. This method should be the only one you should need to look at to understand how * the Android NDK is chosen, and from where on disk.

* * @return the Android NDK to use. * @throws org.apache.maven.plugin.MojoExecutionException if no Android NDK path configuration is available at all. */ protected AndroidNdk getAndroidNdk() throws MojoExecutionException { final File chosenNdkPath; if ( ndkPath != null ) { // -Dandroid.ndk.path is set on command line, or via ... chosenNdkPath = ndkPath; } else { // No -Dandroid.ndk.path is set on command line, or via ... chosenNdkPath = new File( getAndroidNdkHomeOrThrow() ); } return new AndroidNdk( chosenNdkPath ); } /** * @return * @throws MojoExecutionException */ private String getAndroidNdkHomeOrThrow() throws MojoExecutionException { final String androidHome = System.getenv( ENV_ANDROID_NDK_HOME ); if ( StringUtils.isBlank( androidHome ) ) { throw new MojoExecutionException( "No Android NDK path could be found. You may configure it in the pom using ... or " + "... or on command-line using -Dandroid.ndk.path=... " + "or by setting environment variable " + ENV_ANDROID_NDK_HOME ); } return androidHome; } protected final ArtifactResolverHelper getArtifactResolverHelper() { if ( artifactResolverHelper == null ) { artifactResolverHelper = new ArtifactResolverHelper( artifactResolver, new MavenToPlexusLogAdapter( getLog() ), project.getRemoteArtifactRepositories() ); } return artifactResolverHelper; } protected final NativeHelper getNativeHelper() { if ( nativeHelper == null ) { nativeHelper = new NativeHelper( project, dependencyGraphBuilder, getLog() ); } return nativeHelper; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy