/* * 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 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; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Resource; 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 java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR; import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB; /** * Creates the apklib file.<br> * apklib files do not generate deployable artifacts. * * @author nmaiorana@gmail.com * @deprecated Use Aar instead see {@link com.simpligility.maven.plugins.android.phase09package.AarMojo} */ @Deprecated @Mojo( name = "apklib", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE ) public class ApklibMojo extends AbstractAndroidMojo { /** * The name of the top level folder in the APKLIB where native libraries are found. * NOTE: This is inconsistent with APK where the folder is called "lib" */ public static final String NATIVE_LIBRARIES_FOLDER = "libs"; /** * <p>Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.</p> */ @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; private List<String> sourceFolders = new ArrayList<String>(); /** * @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 ); } } generateIntermediateApk(); File outputFile = createApkLibraryFile(); if ( classifier == null ) { // Set the generated file as the main artifact (because the pom states <packaging>apklib</packaging>) project.getArtifact().setFile( outputFile ); } else { // If there is a classifier specified, attach the artifact using that projectHelper.attachArtifact( project, AndroidExtension.APKLIB, classifier, outputFile ); } if ( attachJar ) { final File jarFile = new File( targetDirectory, finalName + ".jar" ); projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile ); } } private File createApkLibraryFile() throws MojoExecutionException { final File apklibrary = new File( targetDirectory, finalName + "." + APKLIB ); FileUtils.deleteQuietly( apklibrary ); try { JarArchiver jarArchiver = new JarArchiver(); jarArchiver.setDestFile( apklibrary ); jarArchiver.addFile( destinationManifestFile, "AndroidManifest.xml" ); addDirectory( jarArchiver, assetsDirectory, "assets" ); addDirectory( jarArchiver, resourceDirectory, "res" ); for ( String src : sourceFolders ) { addDirectory( jarArchiver, new File( src ), "src" ); } File[] overlayDirectories = getResourceOverlayDirectories(); for ( File resOverlayDir : overlayDirectories ) { if ( resOverlayDir != null && resOverlayDir.exists() ) { addDirectory( jarArchiver, resOverlayDir, "res" ); } } addJavaResources( jarArchiver, resources, "src" ); // Lastly, add any native libraries addNativeLibraries( jarArchiver ); jarArchiver.createArchive(); } catch ( ArchiverException e ) { throw new MojoExecutionException( "ArchiverException while creating ." + APKLIB + " file.", e ); } catch ( IOException e ) { throw new MojoExecutionException( "IOException while creating ." + APKLIB + " file.", e ); } return apklibrary; } private void addNativeLibraries( final JarArchiver jarArchiver ) throws MojoExecutionException { try { if ( nativeLibrariesDirectory.exists() ) { getLog().info( nativeLibrariesDirectory + " exists, adding libraries." ); addDirectory( jarArchiver, nativeLibrariesDirectory, NATIVE_LIBRARIES_FOLDER ); } 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( jarArchiver, 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 APKLIB //final File dependentLibs = new File( ndkOutputDirectory.getAbsolutePath(), ndkArchitecture ); //addSharedLibraries( jarArchiver, dependentLibs, prefix ); } } // Removing this as the APK is now able to pull the native libs from the chained apklibs // get native libs from other apklibs // for ( Artifact apkLibraryArtifact : getTransitiveDependencyArtifacts( APKLIB ) ) // { // final File apklibLibsDirectory = getUnpackedLibNativesFolder( apkLibraryArtifact ); // if ( apklibLibsDirectory.exists() ) // { // addDirectory( jarArchiver, apklibLibsDirectory, NATIVE_LIBRARIES_FOLDER ); // } // } } catch ( ArchiverException e ) { throw new MojoExecutionException( "IOException while creating ." + APKLIB + " 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? } protected void addJavaResources( JarArchiver jarArchiver, List<Resource> javaResources, String prefix ) throws IOException { for ( Resource javaResource : javaResources ) { addJavaResource( jarArchiver, javaResource, prefix ); } } /** * Adds a Java Resources directory (typically "src/main/resources") to a {@link JarArchiver}. * * @param jarArchiver * @param javaResource The Java resource to add. * @param prefix An optional prefix for where in the Jar file the directory's contents should go. * @throws IOException in case the resource path can not be resolved */ protected void addJavaResource( JarArchiver jarArchiver, Resource javaResource, String prefix ) throws IOException { if ( javaResource != null ) { final File javaResourceDirectory = new File( javaResource.getDirectory() ); if ( javaResourceDirectory.exists() ) { final String resourcePath = javaResourceDirectory.getCanonicalPath(); final String apkLibUnpackBasePath = getUnpackedLibsDirectory().getCanonicalPath(); // Don't include our dependencies' resource dirs. if ( ! resourcePath.startsWith( apkLibUnpackBasePath ) ) { final DefaultFileSet javaResourceFileSet = new DefaultFileSet(); javaResourceFileSet.setDirectory( javaResourceDirectory ); javaResourceFileSet.setPrefix( endWithSlash( prefix ) ); jarArchiver.addFileSet( javaResourceFileSet ); } } } } /** * 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 jarArchiver * @param directory The directory to add. * @param prefix An optional prefix for where in the Jar file the directory's contents should go. */ protected void addDirectory( JarArchiver jarArchiver, File directory, String prefix ) { if ( directory != null && directory.exists() ) { final DefaultFileSet fileSet = new DefaultFileSet(); fileSet.setPrefix( endWithSlash( prefix ) ); fileSet.setDirectory( directory ); // XXX: trying to avoid duplicated sources fileSet.setExcludes( new String[] { "**/R.java", "**/BuildConfig.java" } ); jarArchiver.addFileSet( fileSet ); getLog().debug( "Added files from " + directory ); } } /** * Adds all shared libraries (.so) to a {@link JarArchiver} under 'libs'. * * @param jarArchiver 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( JarArchiver jarArchiver, 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 ); jarArchiver.addFile( libFile, dest ); } } } /** * Generates an intermediate apk file (actually .ap_) containing the resources and assets. * * @throws MojoExecutionException */ private void generateIntermediateApk() throws MojoExecutionException { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( this.getLog() ); File[] overlayDirectories = getResourceOverlayDirectories(); File androidJar = getAndroidSdk().getAndroidJar(); File outputFile = new File( targetDirectory, finalName + ".ap_" ); List<File> dependencyArtifactResDirectoryList = new ArrayList<File>(); for ( Artifact libraryArtifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) ) { final File apklibResDirectory = getUnpackedLibResourceFolder( libraryArtifact ); if ( apklibResDirectory.exists() ) { dependencyArtifactResDirectoryList.add( apklibResDirectory ); } } AaptCommandBuilder commandBuilder = AaptCommandBuilder .packageResources( getLog() ) .forceOverwriteExistingFiles() .setPathToAndroidManifest( destinationManifestFile ) .addResourceDirectoriesIfExists( overlayDirectories ) .addResourceDirectoryIfExists( resourceDirectory ) .addResourceDirectoriesIfExists( dependencyArtifactResDirectoryList ) .autoAddOverlay() // NB aapt only accepts a single assets parameter - combinedAssets is a merge of all assets .addRawAssetsDirectoryIfExists( combinedAssets ) .addExistingPackageToBaseIncludeSet( androidJar ) .setOutputApkFile( outputFile ) .addConfigurations( configurations ) .setVerbose( aaptVerbose ); getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() ); getLog().info( "Generating apklib" ); try { executor.setCaptureStdOut( true ); List<String> commands = commandBuilder.build(); executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } } }