com.simpligility.maven.plugins.android.phase09package.AarMojo 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
The newest version!
/*
* Copyright (C) 2009 Jayway AB
* Copyright (C) 2007-2008 JVending Masa
*
* 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.android.phase09package;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
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.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.util.DefaultFileSet;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import com.android.SdkConstants;
import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
import com.simpligility.maven.plugins.android.CommandExecutor;
import com.simpligility.maven.plugins.android.ExecutionException;
import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
import com.simpligility.maven.plugins.android.common.AndroidExtension;
import com.simpligility.maven.plugins.android.common.NativeHelper;
import com.simpligility.maven.plugins.android.config.PullParameter;
/**
* Creates an Android Archive (aar) file.
*/
@Mojo(
name = "aar",
defaultPhase = LifecyclePhase.VERIFY,
requiresDependencyResolution = ResolutionScope.COMPILE
)
public class AarMojo extends AbstractAndroidMojo
{
/**
* The name of the top level folder in the AAR where native libraries are found.
* NOTE: This is inconsistent with APK where the folder is called "lib", and does not match APKLIB
* layout either, where the folder is called "libs".
*/
public static final String NATIVE_LIBRARIES_FOLDER = "jni";
/**
* Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.
*/
@Parameter
private String classifier;
/**
* Specifies the application makefile to use for the build (if other than the default Application.mk).
*/
@Parameter
@PullParameter
private String applicationMakefile;
/**
* Defines the architecture for the NDK build
*/
@Parameter( property = "android.ndk.build.architecture" )
@PullParameter
private String ndkArchitecture;
/**
* 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 the files that should be included in the classes.jar within the aar
*/
@Parameter
@PullParameter
private String[] classesJarIncludes = new String[]{"**/*"};
/**
* Specifies the files that should be excluded from the classes.jar within the aar
*/
@Parameter
@PullParameter
private String[] classesJarExcludes = new String[]{"**/R.class", "**/R$*.class"};
/**
* Specifies the proguard rule files to be included in the final package. All specified files will be merged into
* one proguard.txt file.
*/
@Parameter
private File[] consumerProguardFiles;
@Parameter(
property = "android.proguard.obfuscatedJar",
defaultValue = "${project.build.directory}/${project.build.finalName}_obfuscated.jar"
)
private String obfuscatedJar;
private List sourceFolders = new ArrayList();
/**
* @throws MojoExecutionException
* @throws MojoFailureException
*/
public void execute() throws MojoExecutionException, MojoFailureException
{
String out = targetDirectory.getPath();
for ( String src : project.getCompileSourceRoots() )
{
if ( !src.startsWith( out ) )
{
sourceFolders.add( src );
}
}
getLog().info( "Generating AAR file : " + project.getArtifactId() );
generateIntermediateApk();
final File outputFile = createAarLibraryFile( createAarClassesJar() );
if ( classifier == null )
{
// Set the generated file as the main artifact (because the pom states aar )
project.getArtifact().setFile( outputFile );
}
else
{
// If there is a classifier specified, attach the artifact using that
projectHelper.attachArtifact( project, AndroidExtension.AAR, classifier, outputFile );
}
}
/**
* Creates an appropriate aar/classes.jar that does not include R
*
* @return File which is the AAR classes jar.
* @throws MojoExecutionException
*/
protected File createAarClassesJar() throws MojoExecutionException
{
final File obfuscatedJarFile = new File( obfuscatedJar );
if ( obfuscatedJarFile.exists() )
{
attachJar( obfuscatedJarFile );
return obfuscatedJarFile;
}
final File classesJar = new File( targetDirectory, finalName + ".aar.classes.jar" );
try
{
JarArchiver jarArchiver = new JarArchiver();
jarArchiver.setDestFile( classesJar );
jarArchiver.addDirectory( projectOutputDirectory,
classesJarIncludes,
classesJarExcludes );
jarArchiver.createArchive();
attachJar( classesJar );
return classesJar;
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "ArchiverException while creating ." + classesJar + " file.", e );
}
catch ( IOException e )
{
throw new MojoExecutionException( "IOException while creating ." + classesJar + " file.", e );
}
}
private void attachJar( File jarFile )
{
if ( attachJar )
{
projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile );
}
}
/**
* @return AAR file.
* @throws MojoExecutionException
*/
protected File createAarLibraryFile( File classesJar ) throws MojoExecutionException
{
final File aarLibrary = new File( targetDirectory,
finalName + "." + AAR );
FileUtils.deleteQuietly( aarLibrary );
try
{
final ZipArchiver zipArchiver = new ZipArchiver();
zipArchiver.setDestFile( aarLibrary );
zipArchiver.addFile( destinationManifestFile, "AndroidManifest.xml" );
addDirectory( zipArchiver, assetsDirectory, "assets", false );
// res folder must be included in the archive even if empty or non-existent.
if ( !resourceDirectory.exists() )
{
resourceDirectory.mkdir();
}
addDirectory( zipArchiver, resourceDirectory, "res", true );
zipArchiver.addFile( classesJar, SdkConstants.FN_CLASSES_JAR );
final File[] overlayDirectories = getResourceOverlayDirectories();
for ( final File resOverlayDir : overlayDirectories )
{
if ( resOverlayDir != null && resOverlayDir.exists() )
{
addDirectory( zipArchiver, resOverlayDir, "res", false );
}
}
if ( consumerProguardFiles != null )
{
final File mergedConsumerProguardFile = new File( targetDirectory, "consumer-proguard.txt" );
if ( mergedConsumerProguardFile.exists() )
{
FileUtils.forceDelete( mergedConsumerProguardFile );
}
mergedConsumerProguardFile.createNewFile();
StringBuilder mergedConsumerProguardFileBuilder = new StringBuilder();
for ( File consumerProguardFile : consumerProguardFiles )
{
if ( consumerProguardFile.exists() )
{
getLog().info( "Adding consumer proguard file " + consumerProguardFile );
FileInputStream consumerProguardFileInputStream = null;
try
{
consumerProguardFileInputStream = new FileInputStream( consumerProguardFile );
mergedConsumerProguardFileBuilder.append(
IOUtils.toString( consumerProguardFileInputStream ) );
mergedConsumerProguardFileBuilder.append( SystemUtils.LINE_SEPARATOR );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error writing consumer proguard file ", e );
}
finally
{
IOUtils.closeQuietly( consumerProguardFileInputStream );
}
}
}
FileOutputStream mergedConsumerProguardFileOutputStream = null;
try
{
mergedConsumerProguardFileOutputStream = new FileOutputStream( mergedConsumerProguardFile );
IOUtils.write( mergedConsumerProguardFileBuilder, mergedConsumerProguardFileOutputStream );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error writing consumer proguard file ", e );
}
finally
{
IOUtils.closeQuietly( mergedConsumerProguardFileOutputStream );
}
zipArchiver.addFile( mergedConsumerProguardFile, "proguard.txt" );
}
addR( zipArchiver );
// Lastly, add any native libraries
addNativeLibraries( zipArchiver );
zipArchiver.createArchive();
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "ArchiverException while creating ." + AAR + " file.", e );
}
catch ( IOException e )
{
throw new MojoExecutionException( "IOException while creating ." + AAR + " file.", e );
}
return aarLibrary;
}
private void addR( ZipArchiver zipArchiver ) throws MojoExecutionException, IOException
{
final File rFile = new File( targetDirectory, "R.txt" );
if ( !rFile.exists() )
{
getLog().debug( "No resources - creating empty R.txt" );
if ( !rFile.createNewFile() )
{
getLog().warn( "Unable to create R.txt in AAR" );
}
}
zipArchiver.addFile( rFile, "R.txt" );
getLog().debug( "Packaging R.txt in AAR" );
}
private void addNativeLibraries( final ZipArchiver zipArchiver ) throws MojoExecutionException
{
try
{
if ( nativeLibrariesDirectory.exists() )
{
getLog().info( nativeLibrariesDirectory + " exists, adding libraries." );
addDirectory( zipArchiver, nativeLibrariesDirectory, NATIVE_LIBRARIES_FOLDER, false );
}
else
{
getLog().info( nativeLibrariesDirectory
+ " does not exist, looking for libraries in target directory." );
// Add native libraries built and attached in this build
String[] ndkArchitectures = NativeHelper.getNdkArchitectures( ndkArchitecture,
applicationMakefile,
project.getBasedir() );
for ( String architecture : ndkArchitectures )
{
final File ndkLibsDirectory = new File( ndkOutputDirectory, architecture );
addSharedLibraries( zipArchiver, ndkLibsDirectory, architecture );
// Add native library dependencies
// FIXME: Remove as causes duplicate libraries when building final APK if this set includes
// libraries from dependencies of the AAR
//final File dependentLibs = new File( ndkOutputDirectory.getAbsolutePath(), ndkArchitecture );
//addSharedLibraries( jarArchiver, dependentLibs, prefix );
}
}
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "IOException while creating ." + AAR + " file.", e );
}
// TODO: Next is to check for any:
// TODO: - compiled in (as part of this build) libs
// TODO: - That is of course easy if the artifact is indeed attached
// TODO: - If not attached, it gets a little trickier - check the target dir for any compiled .so files (generated by NDK mojo)
// TODO: - But where is that directory configured?
}
/**
* Makes sure the string ends with "/"
*
* @param prefix any string, or null.
* @return the prefix with a "/" at the end, never null.
*/
protected String endWithSlash( String prefix )
{
prefix = StringUtils.defaultIfEmpty( prefix, "/" );
if ( ! prefix.endsWith( "/" ) )
{
prefix = prefix + "/";
}
return prefix;
}
/**
* Adds a directory to a {@link JarArchiver} with a directory prefix.
*
* @param zipArchiver ZipArchiver to use to archive the file.
* @param directory The directory to add.
* @param prefix An optional prefix for where in the Jar file the directory's contents should go.
* @param includeEmptyFolders Whether to include an entry for empty folder in the archive.
*/
protected void addDirectory( ZipArchiver zipArchiver, File directory, String prefix, boolean includeEmptyFolders )
{
if ( directory != null && directory.exists() )
{
final DefaultFileSet fileSet = new DefaultFileSet();
fileSet.setPrefix( endWithSlash( prefix ) );
fileSet.setDirectory( directory );
fileSet.setIncludingEmptyDirectories( includeEmptyFolders );
zipArchiver.addFileSet( fileSet );
getLog().debug( "Added files from " + directory );
}
}
/**
* Adds all shared libraries (.so) to a {@link JarArchiver} under 'jni'.
*
* @param zipArchiver The jarArchiver to add files to
* @param directory The directory to scan for .so files
* @param architecture The prefix for where in the jar the .so files will go.
*/
protected void addSharedLibraries( ZipArchiver zipArchiver, File directory, String architecture )
{
getLog().debug( "Searching for shared libraries in " + directory );
File[] libFiles = directory.listFiles( new FilenameFilter()
{
public boolean accept( final File dir, final String name )
{
return name.startsWith( "lib" ) && name.endsWith( ".so" );
}
} );
if ( libFiles != null )
{
for ( File libFile : libFiles )
{
String dest = NATIVE_LIBRARIES_FOLDER + "/" + architecture + "/" + libFile.getName();
getLog().debug( "Adding " + libFile + " as " + dest );
zipArchiver.addFile( libFile, dest );
}
}
}
/**
* Generates an intermediate apk file (actually .ap_) containing the resources and assets.
*
* @throws MojoExecutionException
*/
private void generateIntermediateApk() throws MojoExecutionException
{
// Have to generate the AAR against the dependent resources or build will fail if any local resources
// directly reference any of the dependent resources. NB this does NOT include the dep resources in the AAR.
List dependenciesResDirectories = new ArrayList();
for ( Artifact libraryArtifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) )
{
final File apkLibResDir = getUnpackedLibResourceFolder( libraryArtifact );
if ( apkLibResDir.exists() )
{
dependenciesResDirectories.add( apkLibResDir );
}
}
final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
executor.setLogger( this.getLog() );
File outputFile = new File( targetDirectory, finalName + ".ap_" );
final AaptCommandBuilder commandBuilder = AaptCommandBuilder
.packageResources( getLog() )
.makePackageDirectories()
.forceOverwriteExistingFiles()
.setPathToAndroidManifest( destinationManifestFile )
.addResourceDirectoriesIfExists( getResourceOverlayDirectories() )
.addResourceDirectoryIfExists( resourceDirectory )
.addResourceDirectoriesIfExists( dependenciesResDirectories )
.autoAddOverlay()
.addRawAssetsDirectoryIfExists( combinedAssets )
.addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
.setOutputApkFile( outputFile )
.addConfigurations( configurations )
.setResourceConstantsFolder( genDirectory )
.makeResourcesNonConstant()
.generateRTextFile( targetDirectory )
.setVerbose( aaptVerbose );
getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
getLog().info( "Generating aar" );
try
{
executor.setCaptureStdOut( true );
final List commands = commandBuilder.build();
executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
}
catch ( ExecutionException e )
{
throw new MojoExecutionException( "", e );
}
}
}