All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jayway.maven.plugins.android.phase09package.ApkMojo Maven / Gradle / Ivy

There is a newer version: 4.0.0-rc.2
Show 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.jayway.maven.plugins.android.phase09package;

import static com.jayway.maven.plugins.android.common.AndroidExtension.APK;
import static com.jayway.maven.plugins.android.common.AndroidExtension.APKLIB;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import com.jayway.maven.plugins.android.common.AetherHelper;
import com.jayway.maven.plugins.android.configuration.Sign;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.AbstractScanner;

import com.jayway.maven.plugins.android.AbstractAndroidMojo;
import com.jayway.maven.plugins.android.AndroidSigner;
import com.jayway.maven.plugins.android.CommandExecutor;
import com.jayway.maven.plugins.android.ExecutionException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.sonatype.aether.collection.CollectRequest;
import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.graph.DependencyNode;
import org.sonatype.aether.resolution.DependencyRequest;
import org.sonatype.aether.util.filter.ScopeDependencyFilter;
import org.sonatype.aether.util.graph.PreorderNodeListGenerator;

/**
 * Creates the apk file. By default signs it with debug keystore.
* Change that by setting configuration parameter <sign><debug>false</debug></sign>. * * @author [email protected] * @goal apk * @phase package * @requiresDependencyResolution compile */ public class ApkMojo extends AbstractAndroidMojo { /** *

How to sign the apk.

*

Looks like this:

*
     * <sign>
     *     <debug>auto</debug>
     * </sign>
     * 
*

Valid values for <debug> are: *

    *
  • true = sign with the debug keystore. *
  • false = don't sign with the debug keystore. *
  • both = create a signed as well as an unsigned apk. *
  • auto (default) = sign with debug keystore, unless another keystore is defined. (Signing with * other keystores is not yet implemented. See * Issue 2.) *

*

Can also be configured from command-line with parameter -Dandroid.sign.debug.

* * @parameter */ private Sign sign; /** *

Parameter designed to pick up -Dandroid.sign.debug in case there is no pom with a * <sign> configuration tag.

*

Corresponds to {@link com.jayway.maven.plugins.android.configuration.Sign#debug}.

* * @parameter expression="${android.sign.debug}" default-value="auto" * @readonly */ private String signDebug; /** *

A possibly new package name for the application. This value will be passed on to the aapt * parameter --rename-manifest-package. Look to aapt for more help on this.

* * @parameter expression="${android.renameManifestPackage}" */ private String renameManifestPackage; /** *

Rewrite the manifest so that all of its instrumentation components target the given package. * This value will be passed on to the aapt parameter --rename-instrumentation-target-package. * Look to aapt for more help on this.

* * @parameter expression="${android.renameInstrumentationTargetPackage}" */ private String renameInstrumentationTargetPackage; /** *

Allows to detect and extract the duplicate files from embedded jars. In that case, the plugin analyzes * the content of all embedded dependencies and checks they are no duplicates inside those dependencies. Indeed, * Android does not support duplicates, and all dependencies are inlined in the APK. If duplicates files are found, * the resource is kept in the first dependency and removes from others. * * @parameter expression="${android.extractDuplicates}" default-value="false" */ private boolean extractDuplicates; /** *

Temporary folder for collecting native libraries.

* * @parameter default-value="${project.build.directory}/libs" * @readonly */ private File nativeLibrariesOutputDirectory; /** *

Default hardware architecture for native library dependencies (with {@code <type>so</type>}).

*

This value is used for dependencies without classifier, if {@code nativeLibrariesDependenciesHardwareArchitectureOverride} is not set.

*

Valid values currently include {@code armeabi} and {@code armeabi-v7a}.

* * @parameter expression="${android.nativeLibrariesDependenciesHardwareArchitectureDefault}" default-value="armeabi" */ private String nativeLibrariesDependenciesHardwareArchitectureDefault; /** *

Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.

* * @parameter */ private String classifier; /** *

Override hardware architecture for native library dependencies (with {@code <type>so</type>}).

*

This overrides any classifier on native library dependencies, and any {@code nativeLibrariesDependenciesHardwareArchitectureDefault}.

*

Valid values currently include {@code armeabi} and {@code armeabi-v7a}.

* * @parameter expression="${android.nativeLibrariesDependenciesHardwareArchitectureOverride}" */ private String nativeLibrariesDependenciesHardwareArchitectureOverride; /** *

Additional source directories that contain resources to be packaged into the apk.

*

These are not source directories, that contain java classes to be compiled. * It corresponds to the -df option of the apkbuilder program. It allows you to specify directories, * that contain additional resources to be packaged into the apk.

* So an example inside the plugin configuration could be: *
     * <configuration>
     * 	  ...
     *    <sourceDirectories>
     *      <sourceDirectory>${project.basedir}/additionals</sourceDirectory>
     *	  </sourceDirectories>
     *	  ...
     * </configuration>
     * 
* * @parameter expression="${android.sourceDirectories}" default-value="" */ private File[] sourceDirectories; /** * @component * @readonly * @required */ protected ArtifactFactory artifactFactory; private static final Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$", 2); public void execute() throws MojoExecutionException, MojoFailureException { // Make an early exit if we're not supposed to generate the APK if (!generateApk) { return; } generateIntermediateAp_(); // Initialize apk build configuration File outputFile = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + "." + APK); final boolean signWithDebugKeyStore = getAndroidSigner().isSignWithDebugKeyStore(); if (getAndroidSigner().shouldCreateBothSignedAndUnsignedApk()) { getLog().info("Creating debug key signed apk file " + outputFile); createApkFile(outputFile, true); final File unsignedOutputFile = new File(project.getBuild().getDirectory(), project.getBuild() .getFinalName() + "-unsigned." + APK); getLog().info("Creating additional unsigned apk file " + unsignedOutputFile); createApkFile(unsignedOutputFile, false); projectHelper.attachArtifact(project, unsignedOutputFile, classifier == null ? "unsigned" : classifier + "_unsigned"); } else { createApkFile(outputFile, signWithDebugKeyStore); } if (classifier == null) { // Set the generated .apk file as the main artifact (because the pom states apk) project.getArtifact().setFile(outputFile); } else { // If there is a classifier specified, attach the artifact using that projectHelper.attachArtifact(project, outputFile, classifier); } } void createApkFile(File outputFile, boolean signWithDebugKeyStore) throws MojoExecutionException { File dexFile = new File(project.getBuild().getDirectory(), "classes.dex"); File zipArchive = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_"); ArrayList sourceFolders = new ArrayList(); if (sourceDirectories != null) { for(File f:sourceDirectories) { sourceFolders.add(f); } } ArrayList jarFiles = new ArrayList(); ArrayList nativeFolders = new ArrayList(); boolean useInternalAPKBuilder = true; try { initializeAPKBuilder(); // Ok... // So we can try to use the internal ApkBuilder } catch (Throwable e) { // Not supported platform try to old way. useInternalAPKBuilder = false; } // Process the native libraries, looking both in the current build directory as well as // at the dependencies declared in the pom. Currently, all .so files are automatically included processNativeLibraries(nativeFolders); if (useInternalAPKBuilder) { doAPKWithAPKBuilder(outputFile, dexFile, zipArchive, sourceFolders, jarFiles, nativeFolders, false, signWithDebugKeyStore, false); } else { doAPKWithCommand(outputFile, dexFile, zipArchive, sourceFolders, jarFiles, nativeFolders, signWithDebugKeyStore); } } private Map> m_jars = new HashMap>(); private void computeDuplicateFiles(File jar) throws ZipException, IOException { ZipFile file = new ZipFile(jar); Enumeration list = file.entries(); while(list.hasMoreElements()) { ZipEntry ze = list.nextElement(); if (! (ze.getName().contains("META-INF/") || ze.isDirectory())) { // Exclude META-INF and Directories List l = m_jars.get(ze.getName()); if (l == null) { l = new ArrayList(); m_jars.put(ze.getName(), l); } l.add(jar); } } } /** * Creates the APK file using the internal APKBuilder. * @param outputFile the output file * @param dexFile the dex file * @param zipArchive the classes folder * @param sourceFolders the resources * @param jarFiles the embedded java files * @param nativeFolders the native folders * @param verbose enables the verbose mode * @param signWithDebugKeyStore enables the signature of the APK using the debug key * @param debug enables the debug mode * @throws MojoExecutionException if the APK cannot be created. */ private void doAPKWithAPKBuilder(File outputFile, File dexFile, File zipArchive, ArrayList sourceFolders, ArrayList jarFiles, ArrayList nativeFolders, boolean verbose, boolean signWithDebugKeyStore, boolean debug) throws MojoExecutionException { sourceFolders.add(new File(project.getBuild().getDirectory(), "classes")); for (Artifact artifact : getRelevantCompileArtifacts()) { if (extractDuplicates) { try { computeDuplicateFiles(artifact.getFile()); } catch (Exception e) { getLog().warn("Cannot compute duplicates files from " + artifact.getFile().getAbsolutePath(), e); } } jarFiles.add(artifact.getFile()); } // Check duplicates. if (extractDuplicates) { List duplicates = new ArrayList(); List jarToModify = new ArrayList(); for (String s : m_jars.keySet()) { List l = m_jars.get(s); if (l.size() > 1) { getLog().warn("Duplicate file " + s + " : " + l); duplicates.add(s); for (int i = 1; i < l.size(); i++) { if (! jarToModify.contains(l.get(i))) { jarToModify.add(l.get(i)); } } } } // Rebuild jars. for (File file : jarToModify) { File newJar; newJar = removeDuplicatesFromJar(file, duplicates); int index = jarFiles.indexOf(file); if (newJar != null) { jarFiles.set(index, newJar); } } } ApkBuilder builder = new ApkBuilder(outputFile, zipArchive, dexFile, signWithDebugKeyStore, (verbose) ? System.out : null); if (debug) { builder.setDebugMode(debug); } for (File sourceFolder : sourceFolders) { builder.addSourceFolder(sourceFolder); } for (File jarFile : jarFiles) { if (jarFile.isDirectory()) { String[] filenames = jarFile.list(new FilenameFilter() { public boolean accept(File dir, String name) { return PATTERN_JAR_EXT.matcher(name).matches(); } }); for (String filename : filenames) { builder.addResourcesFromJar(new File(jarFile, filename)); } } else { builder.addResourcesFromJar(jarFile); } } for (File nativeFolder : nativeFolders) { builder.addNativeLibraries(nativeFolder, null); } builder.sealApk(); } private File removeDuplicatesFromJar(File in, List duplicates) { File target = new File(project.getBasedir(), "target"); File tmp = new File(target, "unpacked-embedded-jars"); tmp.mkdirs(); File out = new File(tmp, in.getName()); if (out.exists()) { return out; } else { try { out.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } // Create a new Jar file FileOutputStream fos = null; ZipOutputStream jos = null; try { fos = new FileOutputStream(out); jos = new ZipOutputStream(fos); } catch (FileNotFoundException e1) { getLog().error("Cannot remove duplicates : the output file " + out.getAbsolutePath() + " does not found"); return null; } ZipFile inZip = null; try { inZip = new ZipFile(in); Enumeration< ? extends ZipEntry> entries = inZip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); // If the entry is not a duplicate, copy. if (! duplicates.contains(entry.getName())) { // copy the entry header to jos jos.putNextEntry(entry); InputStream currIn = inZip.getInputStream(entry); copyStreamWithoutClosing(currIn, jos); currIn.close(); jos.closeEntry(); } } } catch (IOException e) { getLog().error("Cannot removing duplicates : " + e.getMessage()); return null; } try { if (inZip != null) { inZip.close(); } jos.close(); fos.close(); jos = null; fos = null; } catch (IOException e) { // ignore it. } getLog().info(in.getName() + " rewritten without duplicates : " + out.getAbsolutePath()); return out; } /** * Copies an input stream into an output stream but does not close the streams. * @param in the input stream * @param out the output stream * @throws IOException if the stream cannot be copied */ private static void copyStreamWithoutClosing(InputStream in, OutputStream out) throws IOException { byte[] b = new byte[4096]; for (int n; (n = in.read(b)) != -1;) { out.write(b, 0, n); } } /** * Creates the APK file using the command line. * @param outputFile the output file * @param dexFile the dex file * @param zipArchive the classes folder * @param sourceFolders the resources * @param jarFiles the embedded java files * @param nativeFolders the native folders * @param signWithDebugKeyStore enables the signature of the APK using the debug key * @throws MojoExecutionException if the APK cannot be created. */ private void doAPKWithCommand(File outputFile, File dexFile, File zipArchive, ArrayList sourceFolders, ArrayList jarFiles, ArrayList nativeFolders, boolean signWithDebugKeyStore) throws MojoExecutionException { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger(this.getLog()); List commands = new ArrayList(); commands.add(outputFile.getAbsolutePath()); if (! signWithDebugKeyStore) { commands.add("-u"); } commands.add("-z"); commands.add(new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_").getAbsolutePath()); commands.add("-f"); commands.add(new File(project.getBuild().getDirectory(), "classes.dex").getAbsolutePath()); commands.add("-rf"); commands.add(new File(project.getBuild().getDirectory(), "classes").getAbsolutePath()); if (nativeFolders != null && ! nativeFolders.isEmpty()) { for (File lib : nativeFolders) { commands.add("-nf"); commands.add(lib.getAbsolutePath()); } } for (Artifact artifact : getRelevantCompileArtifacts()) { commands.add("-rj"); commands.add(artifact.getFile().getAbsolutePath()); } getLog().info(getAndroidSdk().getPathForTool("apkbuilder") + " " + commands.toString()); try { executor.executeCommand(getAndroidSdk().getPathForTool("apkbuilder"), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", e); } } private void initializeAPKBuilder() throws MojoExecutionException { File file = getAndroidSdk().getSDKLibJar(); ApkBuilder.initialize(getLog(), file); } private void processNativeLibraries(final List natives) throws MojoExecutionException { // Examine the native libraries directory for content. This will only be true if: // a) the directory exists // b) it contains at least 1 file final boolean hasValidNativeLibrariesDirectory = nativeLibrariesDirectory != null && nativeLibrariesDirectory.exists() && ( nativeLibrariesDirectory.listFiles() != null && nativeLibrariesDirectory.listFiles().length > 0 ); // Retrieve any native dependencies or attached artifacts. This may include artifacts from the ndk-build MOJO final Set artifacts = getNativeDependenciesArtifacts(); final boolean hasValidBuildNativeLibrariesDirectory = nativeLibrariesOutputDirectory.exists() && ( nativeLibrariesOutputDirectory.listFiles() != null && nativeLibrariesOutputDirectory.listFiles().length > 0 ); if ( artifacts.isEmpty() && hasValidNativeLibrariesDirectory && !hasValidBuildNativeLibrariesDirectory ) { getLog().debug("No native library dependencies detected, will point directly to " + nativeLibrariesDirectory); // Point directly to the directory in this case - no need to copy files around natives.add(nativeLibrariesDirectory); } else if (!artifacts.isEmpty() || hasValidNativeLibrariesDirectory) { // In this case, we may have both .so files in it's normal location // as well as .so dependencies // Create the ${project.build.outputDirectory}/libs final File destinationDirectory = new File(nativeLibrariesOutputDirectory.getAbsolutePath()); destinationDirectory.mkdirs(); // Point directly to the directory natives.add(destinationDirectory); // If we have a valid native libs, copy those files - these already come in the structure required if ( hasValidNativeLibrariesDirectory ) { copyLocalNativeLibraries( nativeLibrariesDirectory, destinationDirectory ); } if ( hasValidBuildNativeLibrariesDirectory ) { copyLocalNativeLibraries( nativeLibrariesOutputDirectory, destinationDirectory ); } if (!artifacts.isEmpty()) { final Set resolvedArtifacts = AetherHelper.resolveArtifacts( artifacts, repoSystem, repoSession, projectRepos ); for (Artifact resolvedArtifact : resolvedArtifacts) { if ("so".equals(resolvedArtifact.getType())) { final File artifactFile = resolvedArtifact.getFile(); try { final String artifactId = resolvedArtifact.getArtifactId(); final String filename = artifactId.startsWith("lib") ? artifactId + ".so" : "lib" + artifactId + ".so"; final File finalDestinationDirectory = getFinalDestinationDirectoryFor(resolvedArtifact, destinationDirectory); final File file = new File(finalDestinationDirectory, filename); getLog().debug("Copying native dependency " + artifactId + " (" + resolvedArtifact.getGroupId() + ") to " + file ); org.apache.commons.io.FileUtils.copyFile(artifactFile, file); } catch (Exception e) { throw new MojoExecutionException("Could not copy native dependency.", e); } } else if (APKLIB.equals(resolvedArtifact.getType())) { natives.add(new File(getLibraryUnpackDirectory(resolvedArtifact)+"/libs")); } } } } } private File getFinalDestinationDirectoryFor(Artifact resolvedArtifact, File destinationDirectory) { final String hardwareArchitecture = getHardwareArchitectureFor(resolvedArtifact); File finalDestinationDirectory = new File(destinationDirectory, hardwareArchitecture + "/"); finalDestinationDirectory.mkdirs(); return finalDestinationDirectory; } private String getHardwareArchitectureFor(Artifact resolvedArtifact) { if (StringUtils.isNotBlank(nativeLibrariesDependenciesHardwareArchitectureOverride)){ return nativeLibrariesDependenciesHardwareArchitectureOverride; } final String classifier = resolvedArtifact.getClassifier(); if (StringUtils.isNotBlank(classifier)) { return classifier; } return nativeLibrariesDependenciesHardwareArchitectureDefault; } private Set getNativeDependenciesArtifacts() throws MojoExecutionException { final Set filteredArtifacts = new HashSet(); // Add all dependent artifacts declared in the pom file @SuppressWarnings("unchecked") final Set allArtifacts = project.getDependencyArtifacts(); // Add all attached artifacts as well - this could come from the NDK mojo for example boolean result = allArtifacts.addAll( project.getAttachedArtifacts() ); for (Artifact artifact : allArtifacts) { // A null value in the scope indicates that the artifact has been attached // as part of a previous build step (NDK mojo) if ( "so".equals( artifact.getType() ) && artifact.getScope() == null ) { // Including attached artifact getLog().debug( "Including attached artifact: " + artifact.getArtifactId() + "(" + artifact.getGroupId() + ")" ); filteredArtifacts.add( artifact ); } else if ("so".equals(artifact.getType()) && (Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME.equals( artifact.getScope() ))) { filteredArtifacts.add(artifact); } else if (APKLIB.equals(artifact.getType())) { // Check if the artifact contains a libs folder - if so, include it in the list File libsFolder = new File(getLibraryUnpackDirectory(artifact) + "/libs"); if (libsFolder.exists()) { filteredArtifacts.add(artifact); } } } Set transientArtifacts = processTransientDependencies(allArtifacts); filteredArtifacts.addAll( transientArtifacts ); return filteredArtifacts; } private Set processTransientDependencies( Set artifacts ) throws MojoExecutionException { Set transientArtifacts = new TreeSet( ); for ( Artifact artifact : artifacts ) { if (!"provided".equals( artifact.getScope() )) { org.sonatype.aether.artifact.Artifact aetherArtifact = AetherHelper.createAetherArtifact( artifact ); transientArtifacts.addAll( processTransientDependencies( aetherArtifact, artifact.getArtifactHandler() ) ); } } return transientArtifacts; } private Set processTransientDependencies( org.sonatype.aether.artifact.Artifact aetherArtifact, ArtifactHandler artifactHandler) throws MojoExecutionException { try { final Set artifacts = new TreeSet( ); Dependency dependency = new Dependency( aetherArtifact, "runtime" ); final CollectRequest collectRequest=new CollectRequest(); collectRequest.setRoot( dependency ); collectRequest.setRepositories( projectRepos ); final DependencyNode node = repoSystem.collectDependencies( repoSession, collectRequest ).getRoot(); final DependencyRequest dependencyRequest = new DependencyRequest( node, new ScopeDependencyFilter( Arrays.asList( "compile", "runtime"), Arrays.asList("test") ) ); repoSystem.resolveDependencies( repoSession, dependencyRequest ); PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); node.accept( nlg ); final List dependencies = nlg.getDependencies( false ); for ( Dependency dep : dependencies ) { final org.sonatype.aether.artifact.Artifact artifact = dep.getArtifact(); if ("so".equals( artifact.getExtension())) { final Artifact mavenArtifact = artifactFactory.createDependencyArtifact( artifact.getGroupId(), artifact.getArtifactId(), VersionRange.createFromVersion( artifact.getVersion() ), artifact.getExtension(), artifact.getClassifier(),dep.getScope()); artifacts.add( mavenArtifact ); } } return artifacts; } catch ( Exception e ) { throw new MojoExecutionException( "Error while processing transient dependencies", e ); } } private void copyLocalNativeLibraries( final File localNativeLibrariesDirectory, final File destinationDirectory ) throws MojoExecutionException { getLog().debug( "Copying existing native libraries from " + localNativeLibrariesDirectory ); try { org.apache.commons.io.FileUtils.copyDirectory( localNativeLibrariesDirectory, destinationDirectory, new FileFilter() { public boolean accept( final File pathname ) { return pathname.getName().endsWith( ".so" ); } } ); } catch ( IOException e ) { getLog().error( "Could not copy native libraries: " + e.getMessage(), e ); throw new MojoExecutionException( "Could not copy native dependency.", e ); } } /** * Generates an intermediate apk file (actually .ap_) containing the resources and assets. * * @throws MojoExecutionException */ private void generateIntermediateAp_() throws MojoExecutionException { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger(this.getLog()); File[] overlayDirectories; if (resourceOverlayDirectories == null || resourceOverlayDirectories.length == 0) { overlayDirectories = new File[]{resourceOverlayDirectory}; } else { overlayDirectories = resourceOverlayDirectories; } if (extractedDependenciesRes.exists()) { try { getLog().info("Copying dependency resource files to combined resource directory."); if (!combinedRes.exists()) { if (!combinedRes.mkdirs()) { throw new MojoExecutionException("Could not create directory for combined resources at " + combinedRes.getAbsolutePath()); } } org.apache.commons.io.FileUtils.copyDirectory(extractedDependenciesRes, combinedRes); } catch (IOException e) { throw new MojoExecutionException("", e); } } if (resourceDirectory.exists() && combinedRes.exists()) { try { getLog().info("Copying local resource files to combined resource directory."); org.apache.commons.io.FileUtils.copyDirectory(resourceDirectory, combinedRes, new FileFilter() { /** * Excludes files matching one of the common file to exclude. * The default excludes pattern are the ones from * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES} * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File file) { for (String pattern : DirectoryScanner.DEFAULTEXCLUDES) { if (DirectoryScanner.match(pattern, file.getAbsolutePath())) { getLog().debug("Excluding " + file.getName() + " from resource copy : matching " + pattern); return false; } } return true; } }); } catch (IOException e) { throw new MojoExecutionException("", e); } } // Must combine assets. // The aapt tools does not support several -A arguments. // We copy the assets from extracted dependencies first, and then the local assets. // This allows redefining the assets in the current project if (extractedDependenciesAssets.exists()) { try { getLog().info("Copying dependency assets files to combined assets directory."); org.apache.commons.io.FileUtils.copyDirectory(extractedDependenciesAssets, combinedAssets, new FileFilter() { /** * Excludes files matching one of the common file to exclude. * The default excludes pattern are the ones from * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES} * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File file) { for (String pattern : AbstractScanner.DEFAULTEXCLUDES) { if (AbstractScanner.match(pattern, file.getAbsolutePath())) { getLog().debug("Excluding " + file.getName() + " from asset copy : matching " + pattern); return false; } } return true; } }); } catch (IOException e) { throw new MojoExecutionException("", e); } } // Next pull APK Lib assets, reverse the order to give precedence to libs higher up the chain List artifactList = new ArrayList(getAllRelevantDependencyArtifacts()); for (Artifact artifact: artifactList) { if (artifact.getType().equals(APKLIB)) { File apklibAsssetsDirectory = new File(getLibraryUnpackDirectory(artifact) + "/assets"); if (apklibAsssetsDirectory.exists()) { try { getLog().info("Copying dependency assets files to combined assets directory."); org.apache.commons.io.FileUtils.copyDirectory(apklibAsssetsDirectory, combinedAssets, new FileFilter() { /** * Excludes files matching one of the common file to exclude. * The default excludes pattern are the ones from * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES} * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File file) { for (String pattern : AbstractScanner.DEFAULTEXCLUDES) { if (AbstractScanner.match(pattern, file.getAbsolutePath())) { getLog().debug("Excluding " + file.getName() + " from asset copy : matching " + pattern); return false; } } return true; } }); } catch (IOException e) { throw new MojoExecutionException("", e); } } } } if (assetsDirectory.exists()) { try { getLog().info("Copying local assets files to combined assets directory."); org.apache.commons.io.FileUtils.copyDirectory(assetsDirectory, combinedAssets, new FileFilter() { /** * Excludes files matching one of the common file to exclude. * The default excludes pattern are the ones from * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES} * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File file) { for (String pattern : AbstractScanner.DEFAULTEXCLUDES) { if (AbstractScanner.match(pattern, file.getAbsolutePath())) { getLog().debug("Excluding " + file.getName() + " from asset copy : matching " + pattern); return false; } } return true; } }); } catch (IOException e) { throw new MojoExecutionException("", e); } } File androidJar = getAndroidSdk().getAndroidJar(); File outputFile = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_"); List commands = new ArrayList(); commands.add("package"); commands.add("-f"); commands.add("-M"); commands.add(androidManifestFile.getAbsolutePath()); for (File resOverlayDir : overlayDirectories) { if (resOverlayDir != null && resOverlayDir.exists()) { commands.add("-S"); commands.add(resOverlayDir.getAbsolutePath()); } } if (combinedRes.exists()) { commands.add("-S"); commands.add(combinedRes.getAbsolutePath()); } else { if (resourceDirectory.exists()) { commands.add("-S"); commands.add(resourceDirectory.getAbsolutePath()); } } for (Artifact artifact: getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { final String apkLibResDir = getLibraryUnpackDirectory(artifact) + "/res"; if (new File(apkLibResDir).exists()){ commands.add("-S"); commands.add(apkLibResDir); } } } commands.add("--auto-add-overlay"); // Use the combined assets. // Indeed, aapt does not support several -A arguments. if (combinedAssets.exists()) { commands.add("-A"); commands.add(combinedAssets.getAbsolutePath()); } if (StringUtils.isNotBlank(renameManifestPackage)) { commands.add("--rename-manifest-package"); commands.add(renameManifestPackage); } if (StringUtils.isNotBlank(renameInstrumentationTargetPackage)) { commands.add("--rename-instrumentation-target-package"); commands.add(renameInstrumentationTargetPackage); } commands.add("-I"); commands.add(androidJar.getAbsolutePath()); commands.add("-F"); commands.add(outputFile.getAbsolutePath()); if (StringUtils.isNotBlank(configurations)) { commands.add("-c"); commands.add(configurations); } for (String aaptExtraArg : aaptExtraArgs) { commands.add(aaptExtraArg); } getLog().info(getAndroidSdk().getPathForTool("aapt") + " " + commands.toString()); try { executor.executeCommand(getAndroidSdk().getPathForTool("aapt"), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", e); } } protected AndroidSigner getAndroidSigner() { if (sign == null) { return new AndroidSigner(signDebug); } else { return new AndroidSigner(sign.getDebug()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy