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 java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jayway.maven.plugins.android.*;
import com.jayway.maven.plugins.android.common.AetherHelper;
import com.jayway.maven.plugins.android.common.NativeHelper;
import com.jayway.maven.plugins.android.configuration.HeaderFilesDirective;
import com.jayway.maven.plugins.android.configuration.Ndk;
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.*;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.util.IOUtil;

import static org.apache.commons.lang.StringUtils.isBlank;

/**
 * @author Johan Lindquist 
 * @goal ndk-build
 * @phase compile
 * @requiresProject true
 */
public class NdkBuildMojo extends AbstractAndroidMojo {
    /**
     * 

The Android NDK to use.

*

Looks like this:

*
     * <ndk>
     *     <path>/opt/android-ndk-r4</path>
     * </ndk>
     * 
*

The <path> parameter is optional. The default is the setting of the ANDROID_NDK_HOME environment * variable. The parameter can be used to override this setting with a different environment variable like this:

*
     * <ndk>
     *     <path>${env.ANDROID_NDK_HOME}</path>
     * </ndk>
     * 
*

or just with a hardcoded absolute path. The parameters can also be configured from command-line with parameter * -Dandroid.ndk.path.

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

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

*

Corresponds to {@link com.jayway.maven.plugins.android.configuration.Ndk#path}.

* * @parameter expression="${android.ndk.path}" * @readonly */ private File ndkPath; /** * Specifies the classifier with which the artifact should be stored in the repository * * @parameter expression="${android.ndk.build.native-classifier}" */ protected String ndkClassifier; /** * Specifies additional command line parameters to pass to ndk-build * * @parameter expression="${android.ndk.build.command-line}" */ 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="false" */ protected boolean clearNativeArtifacts = false; /** * 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="false" */ protected boolean attachNativeArtifacts; /** * The ANDROID_NDK_HOME environment variable name. */ public static final String ENV_ANDROID_NDK_HOME = "ANDROID_NDK_HOME"; /** * Build folder to place built native libraries into * * @parameter expression="${android.ndk.build.ndk-output-directory}" default-value="${project.build.directory}/ndk-libs" */ protected File ndkOutputDirectory; /**

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

* * @parameter expression="${android.nativeLibrariesDirectory}" default-value="${project.basedir}/libs" */ private File nativeSharedLibrariesDirectory; /**

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

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

Target to invoke on the native makefile.

* * @parameter expression="${android.nativeTarget}" */ private String target; /** * Defines the architecture for the NDK build * * @parameter expression="${android.ndk.build.architecture}" default="armeabi" */ protected String ndkArchitecture = "armeabi"; /** * @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="true" */ private boolean attachHeaderFiles = true; /** 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="false" */ private boolean useLocalSrcIncludePaths = false; /** 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; /** 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="true" */ private boolean useHeaderArchives = true; /** 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 expression="${android.ndk.build.ignore-build-warnings}" default="true" */ private boolean ignoreBuildWarnings = true; /** * 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=".*[warning|note]: .*" */ private String buildWarningsRegularExpression = ".*[warning|note]: .*"; public void execute() throws MojoExecutionException, MojoFailureException { // This points File nativeLibDirectory = new File((project.getPackaging().equals("a") ? nativeStaticLibrariesDirectory : nativeSharedLibrariesDirectory), ndkArchitecture ); final boolean libsDirectoryExists = nativeLibDirectory.exists(); File directoryToRemove = nativeLibDirectory; if ( !libsDirectoryExists ) { getLog().info( "Creating native output directory " + nativeLibDirectory ); if ( nativeLibDirectory.getParentFile().exists() ) { nativeLibDirectory.mkdir(); } else { nativeLibDirectory.mkdirs(); directoryToRemove = nativeLibDirectory.getParentFile(); } } final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setErrorListener(new CommandExecutor.ErrorListener() { @Override public boolean isError(String error) { Pattern pattern = Pattern.compile(buildWarningsRegularExpression); Matcher matcher = pattern.matcher(error); if ( ignoreBuildWarnings && matcher.matches() ) { return false; } return true; } }); 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 ); try { File f = File.createTempFile( "android_maven_plugin_makefile", ".mk" ); f.deleteOnExit(); String makeFile = MakefileHelper.createMakefileFromArtifacts( f.getParentFile(), resolveNativeLibraryArtifacts, useHeaderArchives, repoSession, projectRepos, repoSystem); IOUtil.copy( makeFile, new FileOutputStream( f )); // Add the path to the generated makefile executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_MAKEFILE", f.getAbsolutePath() ); // Only add the LOCAL_STATIC_LIBRARIES if ( NativeHelper.hasStaticNativeLibraryArtifact(resolveNativeLibraryArtifacts) ) { executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_STATIC_LIBRARIES", MakefileHelper.createStaticLibraryList(resolveNativeLibraryArtifacts, true )); } if ( NativeHelper.hasSharedNativeLibraryArtifact(resolveNativeLibraryArtifacts) ) { executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_SHARED_LIBRARIES", MakefileHelper.createStaticLibraryList(resolveNativeLibraryArtifacts, false )); } } catch ( IOException e ) { throw new MojoExecutionException(e.getMessage()); } File localCIncludesFile = null; // try { localCIncludesFile = File.createTempFile("android_maven_plugin_makefile_captures", ".tmp"); localCIncludesFile.deleteOnExit(); executor.addEnvironment( "ANDROID_MAVEN_PLUGIN_LOCAL_C_INCLUDES_FILE", localCIncludesFile.getAbsolutePath()); } catch (IOException e) { throw new MojoExecutionException(e.getMessage()); } // 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() ); final List commands = new ArrayList(); commands.add( "-C" ); if (ndkBuildDirectory == null) { ndkBuildDirectory = project.getBasedir().getAbsolutePath(); } commands.add( ndkBuildDirectory ); if ( ndkBuildAdditionalCommandline != null ) { String[] additionalCommands = ndkBuildAdditionalCommandline.split( " " ); for ( final String command : additionalCommands ) { commands.add( command ); } } // 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() ); try { executor.executeCommand( ndkBuildPath, commands, project.getBasedir(), true ); } catch ( ExecutionException e ) { throw new MojoExecutionException( e.getMessage(), e ); } // Cleanup libs/armeabi directory if needed - this implies moving any native artifacts into target/libs if ( clearNativeArtifacts ) { final File destinationDirectory = new File( ndkOutputDirectory.getAbsolutePath(), "/" + ndkArchitecture ); try { if ( !libsDirectoryExists ) { FileUtils.moveDirectory( nativeLibDirectory, destinationDirectory ); } else { FileUtils.copyDirectory( nativeLibDirectory, destinationDirectory ); FileUtils.cleanDirectory( nativeLibDirectory ); } nativeLibDirectory = destinationDirectory; } catch ( IOException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } if ( !libsDirectoryExists ) { getLog().info( "Cleaning up native library output directory after build" ); getLog().debug( "Removing directory: " + directoryToRemove ); if ( !directoryToRemove.delete() ) { getLog().warn( "Could not remove directory, marking as delete on exit" ); directoryToRemove.deleteOnExit(); } } // 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 ) { File[] files = nativeLibDirectory.listFiles( new FilenameFilter() { public boolean accept( final File dir, final String name ) { if ( "a".equals( project.getPackaging() ) ) { return name.startsWith("lib" + (target != null ? target : project.getArtifactId())) && name.endsWith(".a"); } else { return name.startsWith("lib" + (target != null ? target : project.getArtifactId())) && 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 ( files != null ) { getLog().warn( "Currently, only a single, final native library is supported by the build" ); } } else { getLog().debug( "Adding native compile artifact: " + files[ 0 ] ); final String artifactType = resolveArtifactType(files[0]); projectHelper.attachArtifact( this.project, artifactType, ( ndkClassifier != null ? ndkClassifier : ndkArchitecture ), files[ 0 ] ); } } // Process conditionally any of the headers to include into the header archive file processHeaderFileIncludes(localCIncludesFile); } private String resolveNdkBuildExecutable() throws MojoExecutionException { if (ndkBuildExecutable != null) { getLog().debug("ndk-build overriden, using " + ndkBuildExecutable); return ndkBuildExecutable; } return getAndroidNdk().getNdkBuildPath(); } private void processHeaderFileIncludes(File localCIncludesFile) 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(); headerFilesDirective.setDirectory(include); 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); } } catch ( Exception e ) { throw new MojoExecutionException("Error while processing headers to include: " + e.getMessage(), e); } } private void createHeaderArchive(List finalHeaderFilesDirectives) 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 ); projectHelper.attachArtifact( project, "har", jarFile ); } catch ( Exception e ) { throw new MojoExecutionException( e.getMessage() ); } } 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(staticLibraryArtifacts); mergedArtifacts.addAll(sharedLibraryArtifacts); return mergedArtifacts; } /** 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"; } } /** *

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 { File chosenNdkPath; if ( ndk != null ) { // An tag exists in the pom. if ( ndk.getPath() != null ) { // An tag is set in the pom. chosenNdkPath = ndk.getPath(); } else { // There is no tag in the pom. 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() ); } } } else { // There is no tag in the pom. 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 ); } private String getAndroidNdkHomeOrThrow() throws MojoExecutionException { final String androidHome = System.getenv( ENV_ANDROID_NDK_HOME ); if ( 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy