com.jayway.maven.plugins.android.phase05compile.NdkBuildMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-maven-plugin Show documentation
Show all versions of android-maven-plugin Show documentation
Maven Plugin for Android Development
/*
* 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.Const;
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.handler.ArtifactHandler;
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.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 AbstractAndroidMojo
{
/**
* Allows for overriding the default ndk-build executable.
*/
@Parameter( property = "android.ndk.ndk-build-executable" )
@PullParameter
private String ndkBuildExecutable;
/**
* Folder in which the Ndk makefiles are constructed and the build is executed.
*/
@Parameter(
property = "android.ndk.ndk-build-directory",
defaultValue = "${project.build.directory}/ndk-build",
readonly = true
)
@PullParameter
private File ndkBuildDirectory;
/**
* Specifies the classifier with which the artifact should be stored in the repository
*/
@Parameter( property = "android.ndk.build.native-classifier" )
@PullParameter
private String ndkClassifier;
/**
* Specifies additional command line parameters to pass to ndk-build
*/
@Parameter( property = "android.ndk.build.command-line" )
@PullParameter
protected String ndkBuildAdditionalCommandline;
/**
* 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( property = "android.ndk.build.attach-native-artifact", defaultValue = "false" )
@PullParameter( defaultValue = "false" )
private Boolean attachNativeArtifacts;
/**
* Folder containing native, static libraries compiled and linked by the NDK.
*
* The NDK build executable seems determined to create the native libs in the root folder.
* TODO work out how to create them in /target.
*/
@Parameter(
property = "android.nativeLibrariesOutputDirectory",
defaultValue = "${project.basedir}/obj/local",
readonly = true
)
private File nativeLibrariesOutputDirectory;
/**
* Target to invoke on the native makefile.
*/
@Parameter( property = "android.nativeTarget" )
@PullParameter
private String target;
/**
* Defines the architecture for the NDK build
*
* @deprecated Use {@link NdkBuildMojo#ndkArchitectures} instead
*/
@Parameter( property = "android.ndk.build.architecture" )
@PullParameter
private String ndkArchitecture;
/**
* Defines the architectures for the NDK build - this is a space separated list (i.e x86 armeabi)
*/
@Parameter( property = "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;
/**
* 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.build.attach-header-files", defaultValue = "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( property = "android.ndk.build.use-local-src-include-paths", defaultValue = "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.class, hint = "jar" )
private JarArchiver jarArchiver;
@Component( role = org.apache.maven.artifact.handler.ArtifactHandler.class, hint = "har" )
private ArtifactHandler harArtifactHandler;
/**
* 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" )
@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( property = "android.ndk.build.ignore-build-warnings", defaultValue = "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(
property = "android.ndk.build.build-warnings-regular-expression",
defaultValue = ".*[warning|note]: .*"
)
@PullParameter( defaultValue = ".*[warning|note]: .*" )
private String buildWarningsRegularExpression;
@Parameter( property = "android.ndk.build.skip-native-library-stripping", defaultValue = "false" )
@PullParameter( defaultValue = "false" )
private Boolean skipStripping;
@Parameter( property = "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( property = "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( property = "android.ndk.build.maxJobs", defaultValue = "false" )
@PullParameter( defaultValue = "false" )
private Boolean maxJobs;
/**
* @throws MojoExecutionException
* @throws MojoFailureException
*/
public void execute() throws MojoExecutionException, MojoFailureException
{
// Validate the NDK
final File ndkBuildFile = new File( getAndroidNdk().getNdkBuildPath() );
NativeHelper.validateNDKVersion( ndkBuildFile.getParentFile() );
validateMakefile( project, makefile );
final String[] resolvedNDKArchitectures = NativeHelper.getNdkArchitectures(
ndkArchitecture != null ? ndkArchitecture : ndkArchitectures, applicationMakefile,
project.getBasedir() );
for ( String architecture : resolvedNDKArchitectures )
{
try
{
compileForArchitecture( architecture );
}
catch ( IOException e )
{
getLog().error( "Error while executing: " + e.getMessage() );
throw new MojoExecutionException( e.getMessage(), e );
}
catch ( ExecutionException e )
{
getLog().error( "Error while executing: " + e.getMessage() );
throw new MojoExecutionException( e.getMessage(), e );
}
}
}
private void compileForArchitecture( String architecture )
throws MojoExecutionException, IOException, ExecutionException
{
getLog().debug( "Resolving for NDK architecture : " + architecture );
// 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 =
getArtifactResolverHelper().resolveArtifacts( nativeLibraryArtifacts );
getLog().debug( "resolveArtifacts found " + resolveNativeLibraryArtifacts.size()
+ ": " + resolveNativeLibraryArtifacts.toString() );
final File buildFolder = new File( ndkBuildDirectory, architecture );
buildFolder.mkdirs();
final File androidMavenMakefile = new File( buildFolder, "android_maven_plugin_makefile.mk" );
final MakefileHelper makefileHelper = new MakefileHelper( getLog(),
getArtifactResolverHelper(),
harArtifactHandler, getUnpackedLibsDirectory()
);
final MakefileHelper.MakefileHolder makefileHolder = makefileHelper
.createMakefileFromArtifacts( resolveNativeLibraryArtifacts, architecture, "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,
architecture );
// 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( project.getBasedir().getAbsolutePath() );
// 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( architecture, 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 ( Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( project.getPackaging() ) )*/
{
commands.add( project.getArtifactId() );
}
final String ndkBuildPath = resolveNdkBuildExecutable();
getLog().debug( ndkBuildPath + " " + commands.toString() );
getLog().info( "Executing NDK " + architecture + " make at : " + ndkBuildDirectory );
executor.setCaptureStdOut( true );
executor.executeCommand( ndkBuildPath, commands, ndkBuildDirectory, true );
getLog().debug( "Executed NDK " + architecture + " make at : " + ndkBuildDirectory );
// Where the NDK build creates the libs.
final File nativeLibOutputDirectory = new File( nativeLibrariesOutputDirectory, architecture );
nativeLibOutputDirectory.mkdirs();
// Move the built native libs into the packaging folder.
// We don't create them there to start with because the NDK build seems determined to create them in the root.
final File destinationDirectory = new File( ndkOutputDirectory, architecture );
FileUtils.moveDirectory( nativeLibOutputDirectory, destinationDirectory );
// 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 ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( project.getPackaging() )
|| Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( project.getPackaging() )
|| attachNativeArtifacts )
{
attachNativeLib( destinationDirectory, architecture );
}
// Process conditionally any of the headers to include into the header archive file
if ( attachHeaderFiles )
{
attachHeaderFiles( makefileCaptureFile, architecture );
}
// If we created a makefile for the build we should be polite and remove any extracted include
// directories after we're done
getLog().info( "Cleaning up extracted include directories used for build" );
MakefileHelper.cleanupAfterBuild( makefileHolder );
}
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 )
{
final String[] additionalCommands = ndkBuildAdditionalCommandline.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
commands.add( "NDK_TOOLCHAIN=" + ndkToolchain );
}
else
{
// Resolve the toolchain from the architecture
//
// x86-4.6
// x86-4.6
//
final String toolchainFromArchitecture = getAndroidNdk().getToolchainFromArchitecture(
architecture, ndkArchitectureToolchainMappings );
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 attachNativeLib( File destinationDirectory, String architecture )
throws IOException, MojoExecutionException
{
final File nativeArtifactFile;
if ( ndkFinalLibraryName == null )
{
nativeArtifactFile = findNativeLibrary( destinationDirectory );
}
else
{
nativeArtifactFile = nativeLibraryFromName( destinationDirectory );
}
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 );
final String classifier = ( ndkClassifier == null )
? architecture
: architecture + "-" + ndkClassifier;
projectHelper.attachArtifact( this.project, artifactType, classifier, nativeArtifactFile );
}
/**
* 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 ( 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 )
{
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 ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( project.getPackaging() )
|| Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.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;
}
};
}
/**
* 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 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.setCaptureStdOut( true );
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 attachHeaderFiles( File localCIncludesFile, String architecture )
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" } );
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, architecture );
}
private void createHeaderArchive( List finalHeaderFilesDirectives, String architecture )
throws MojoExecutionException
{
try
{
MavenArchiver mavenArchiver = new MavenArchiver();
mavenArchiver.setArchiver( jarArchiver );
final File jarFile = new File( targetDirectory, finalName + ".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 = architecture;
if ( ndkClassifier != null )
{
classifier += "-" + ndkClassifier;
}
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( MakefileHelper makefileHelper, CommandExecutor executor,
Set resolveNativeLibraryArtifacts, String architecture )
{
// Only add the LOCAL_STATIC_LIBRARIES
if ( NativeHelper.hasStaticNativeLibraryArtifact( resolveNativeLibraryArtifacts, getUnpackedLibsDirectory(),
architecture ) )
{
String staticlibs = makefileHelper.createLibraryList( resolveNativeLibraryArtifacts,
architecture,
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, getUnpackedLibsDirectory(),
architecture ) )
{
String sharedlibs = makefileHelper.createLibraryList( resolveNativeLibraryArtifacts,
architecture,
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
{
final NativeHelper nativeHelper = getNativeHelper();
final Set staticLibraryArtifacts = nativeHelper
.getNativeDependenciesArtifacts( this, getUnpackedLibsDirectory(), false );
final Set sharedLibraryArtifacts = nativeHelper
.getNativeDependenciesArtifacts( this, getUnpackedLibsDirectory(), 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;
}
}
}