Java tutorial
/* * 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.jayway.maven.plugins.android.phase09package; import com.android.SdkConstants; 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.AaptCommandBuilder; import com.jayway.maven.plugins.android.common.NativeHelper; import com.jayway.maven.plugins.android.config.PullParameter; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; 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 java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static com.jayway.maven.plugins.android.common.AndroidExtension.AAR; import static com.jayway.maven.plugins.android.common.AndroidExtension.APKLIB; /** * Creates an Android Archive (aar) file.<br/> */ @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"; /** * <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; /** * 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" }; @Parameter(property = "android.proguard.obfuscatedJar", defaultValue = "${project.build.directory}/${project.build.finalName}_obfuscated.jar") private String obfuscatedJar; 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); } } 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 <packaging>aar</packaging>) project.getArtifact().setFile(outputFile); } else { // If there is a classifier specified, attach the artifact using that projectHelper.attachArtifact(project, outputFile, classifier); } } /** * 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()) { 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(); 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); } } /** * @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"); addDirectory(zipArchiver, resourceDirectory, "res"); 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"); } } 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 { final File rFile = new File(targetDirectory + "/R.txt"); if (rFile.exists()) { zipArchiver.addFile(rFile, "R.txt"); getLog().debug("Packaging R.txt in AAR"); } else { getLog().debug("Not packaging R.txt in AAR - it does not exist (no resources??)"); } } private void addNativeLibraries(final ZipArchiver zipArchiver) throws MojoExecutionException { try { if (nativeLibrariesDirectory.exists()) { getLog().info(nativeLibrariesDirectory + " exists, adding libraries."); addDirectory(zipArchiver, 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(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 ); // get native libs from other aars and apklibs for (Artifact libraryArtifact : getTransitiveDependencyArtifacts(APKLIB, AAR)) { final File apklibLibsDirectory = new File(getUnpackedLibNativesFolder(libraryArtifact), architecture); if (apklibLibsDirectory.exists()) { addSharedLibraries(zipArchiver, apklibLibsDirectory, architecture); } } } } } 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. */ protected void addDirectory(ZipArchiver zipArchiver, File directory, String prefix) { if (directory != null && directory.exists()) { final DefaultFileSet fileSet = new DefaultFileSet(); fileSet.setPrefix(endWithSlash(prefix)); fileSet.setDirectory(directory); 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<File> dependenciesResDirectories = new ArrayList<File>(); 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<String> commands = commandBuilder.build(); executor.executeCommand(getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", e); } } }