From 3f5c18c61b687c8a4908987dffe95b14ffa672d2 Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Mon, 17 Nov 2014 15:20:01 +0100 Subject: [PATCH 1/2] MultiDex support: now secondary dex'es are located directly in apk file, not in assets (compatible with android.support.MultiDex) --- .../phase08preparepackage/DexMojo.java | 41 ---------- .../android/phase09package/ApkMojo.java | 77 +++++++++++++------ 2 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/jayway/maven/plugins/android/phase08preparepackage/DexMojo.java b/src/main/java/com/jayway/maven/plugins/android/phase08preparepackage/DexMojo.java index 8f434be23..9f8abd0be 100644 --- a/src/main/java/com/jayway/maven/plugins/android/phase08preparepackage/DexMojo.java +++ b/src/main/java/com/jayway/maven/plugins/android/phase08preparepackage/DexMojo.java @@ -60,8 +60,6 @@ public class DexMojo extends AbstractAndroidMojo { - private static final String DEX = ".dex"; - private static final String CLASSES = "classes"; /** * Configuration for the dex command execution. It can be configured in the plugin configuration like so * @@ -207,24 +205,6 @@ public void execute() throws MojoExecutionException, MojoFailureException if ( generateApk ) { runDex( executor, outputFile ); - - if ( parsedMultiDex ) - { - - File assets = new File( targetDirectory, - "generated-sources" + File.separator + "combined-assets" ); - - if ( !assets.exists() && !assets.mkdirs() ) - { - throw new IllegalStateException( "Unable to create combined-assets directory" ); - } - int i = 2; - while ( copyAdditionalDex( outputFile, i, assets ) ) - { - i++; - } - - } } if ( attachJar ) @@ -242,27 +222,6 @@ public void execute() throws MojoExecutionException, MojoFailureException } } - private boolean copyAdditionalDex( File outputFile, int dexIndex, File assets ) throws MojoExecutionException - { - File secondDexFile = new File( outputFile, CLASSES + dexIndex + DEX ); - if ( secondDexFile.exists() ) - { - File copiedSecondDexFile = new File( assets, CLASSES + dexIndex + DEX ); - - try - { - FileUtils.moveFile( secondDexFile, copiedSecondDexFile ); - } - catch ( IOException e ) - { - throw new MojoExecutionException( "IOException while moving classes" + dexIndex + ".dex to " - + "combined-assets directory", e ); - } - return true; - } - return false; - } - /** * Gets the input files for dex. This is a combination of directories and jar files. * diff --git a/src/main/java/com/jayway/maven/plugins/android/phase09package/ApkMojo.java b/src/main/java/com/jayway/maven/plugins/android/phase09package/ApkMojo.java index 07bc8ab8f..10fa08bbd 100644 --- a/src/main/java/com/jayway/maven/plugins/android/phase09package/ApkMojo.java +++ b/src/main/java/com/jayway/maven/plugins/android/phase09package/ApkMojo.java @@ -81,8 +81,8 @@ * * @author hugo.josefson@jayway.com */ -@Mojo( name = "apk", - defaultPhase = LifecyclePhase.PACKAGE, +@Mojo( name = "apk", + defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE ) public class ApkMojo extends AbstractAndroidMojo { @@ -146,10 +146,10 @@ public class ApkMojo extends AbstractAndroidMojo * The apk file produced by the apk goal. Per default the file is placed into the build directory (target * normally) using the build final name and apk as extension. */ - @Parameter( property = "android.outputApk", + @Parameter( property = "android.outputApk", defaultValue = "${project.build.directory}/${project.build.finalName}.apk" ) private String outputApk; - + /** *

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

*

These are not source directories, that contain java classes to be compiled. @@ -229,14 +229,14 @@ public class ApkMojo extends AbstractAndroidMojo * Specify a list of patterns that are matched against the names of jar file * dependencies. Matching jar files will not have their resources added to the * resulting APK. - * + * * The patterns are standard Java regexes. */ @Parameter private String[] excludeJarResources; private Pattern[] excludeJarResourcesPatterns; - + /** * Embedded configuration of this mojo. */ @@ -246,6 +246,10 @@ public class ApkMojo extends AbstractAndroidMojo private static final Pattern PATTERN_JAR_EXT = Pattern.compile( "^.+\\.jar$", Pattern.CASE_INSENSITIVE ); + private static final String DEX_SUFFIX = ".dex"; + + private static final String CLASSES = "classes"; + /** *

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

@@ -274,18 +278,18 @@ public void execute() throws MojoExecutionException, MojoFailureException generateIntermediateApk(); // Compile resource exclusion patterns, if any - if ( excludeJarResources != null && excludeJarResources.length > 0 ) + if ( excludeJarResources != null && excludeJarResources.length > 0 ) { getLog().debug( "Compiling " + excludeJarResources.length + " patterns" ); - + excludeJarResourcesPatterns = new Pattern[excludeJarResources.length]; - - for ( int index = 0; index < excludeJarResources.length; ++index ) + + for ( int index = 0; index < excludeJarResources.length; ++index ) { excludeJarResourcesPatterns[index] = Pattern.compile( excludeJarResources[index] ); } } - + // Initialize apk build configuration File outputFile = new File( outputApk ); final boolean signWithDebugKeyStore = getAndroidSigner().isSignWithDebugKeyStore(); @@ -423,7 +427,7 @@ private void updateWithMetaInf( ZipOutputStream zos, File jarFile, Set e { ne = new ZipEntry( zn ); } - + zos.putNextEntry( ne ); InputStream is = zin.getInputStream( ze ); @@ -530,7 +534,7 @@ private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive } } } - + try { final String debugKeyStore = signWithDebugKeyStore ? ApkBuilder.getDebugKeystore() : null; @@ -545,11 +549,11 @@ private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive getLog().debug( "Adding source folder : " + sourceFolder ); apkBuilder.addSourceFolder( sourceFolder ); } - + for ( File jarFile : jarFiles ) { boolean excluded = false; - + if ( excludeJarResourcesPatterns != null ) { final String name = jarFile.getName(); @@ -557,13 +561,13 @@ private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive for ( Pattern pattern : excludeJarResourcesPatterns ) { final Matcher matcher = pattern.matcher( name ); - if ( matcher.matches() ) + if ( matcher.matches() ) { getLog().debug( "Jar " + name + " excluded by pattern " + pattern ); excluded = true; break; - } - else + } + else { getLog().debug( "Jar " + name + " not excluded by pattern " + pattern ); } @@ -574,7 +578,7 @@ private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive { continue; } - + if ( jarFile.isDirectory() ) { getLog().debug( "Adding resources from jar folder : " + jarFile ); @@ -585,7 +589,7 @@ public boolean accept( File dir, String name ) return PATTERN_JAR_EXT.matcher( name ).matches(); } } ); - + for ( String filename : filenames ) { final File innerJar = new File( jarFile, filename ); @@ -600,29 +604,56 @@ public boolean accept( File dir, String name ) } } + addSecondaryDexes( dexFile, apkBuilder ); + for ( File nativeFolder : nativeFolders ) { getLog().debug( "Adding native library : " + nativeFolder ); apkBuilder.addNativeLibraries( nativeFolder ); } apkBuilder.sealApk(); - } + } catch ( ApkCreationException e ) { throw new MojoExecutionException( e.getMessage() ); - } + } catch ( DuplicateFileException e ) { final String msg = String.format( "Duplicated file: %s, found in archive %s and %s", e.getArchivePath(), e.getFile1(), e.getFile2() ); throw new MojoExecutionException( msg, e ); - } + } catch ( SealedApkException e ) { throw new MojoExecutionException( e.getMessage() ); } } + private void addSecondaryDexes( File dexFile, ApkBuilder apkBuilder ) throws ApkCreationException, + SealedApkException, DuplicateFileException + { + int dexNumber = 2; + String dexFileName = getNextDexFileName( dexNumber ); + File secondDexFile = createNextDexFile( dexFile, dexFileName ); + while ( secondDexFile.exists() ) + { + apkBuilder.addFile( secondDexFile, dexFileName ); + dexNumber++; + dexFileName = getNextDexFileName( dexNumber ); + secondDexFile = createNextDexFile( dexFile, dexFileName ); + } + } + + private File createNextDexFile( File dexFile, String dexFileName ) + { + return new File( dexFile.getParentFile(), dexFileName ); + } + + private String getNextDexFileName( int dexNumber ) + { + return CLASSES + dexNumber + DEX_SUFFIX; + } + private File removeDuplicatesFromJar( File in, List duplicates ) { String target = projectOutputDirectory.getAbsolutePath(); From 6bd180dd6d8877637101201f0c48ce3d8c91fbf5 Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Mon, 17 Nov 2014 15:25:14 +0100 Subject: [PATCH 2/2] generated Proguard parameters are passed to Proguard through temporary config file instead of full command line options --- .../phase04processclasses/ProguardMojo.java | 104 ++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/jayway/maven/plugins/android/phase04processclasses/ProguardMojo.java b/src/main/java/com/jayway/maven/plugins/android/phase04processclasses/ProguardMojo.java index 7cfaf1a23..565f163b0 100644 --- a/src/main/java/com/jayway/maven/plugins/android/phase04processclasses/ProguardMojo.java +++ b/src/main/java/com/jayway/maven/plugins/android/phase04processclasses/ProguardMojo.java @@ -8,7 +8,9 @@ import com.jayway.maven.plugins.android.config.ConfigPojo; import com.jayway.maven.plugins.android.config.PullParameter; import com.jayway.maven.plugins.android.configuration.Proguard; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.SystemUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -19,6 +21,8 @@ import org.codehaus.plexus.interpolation.os.Os; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -193,15 +197,15 @@ public class ProguardMojo extends AbstractAndroidMojo @PullParameter( defaultValue = "true" ) private Boolean parsedFilterManifest; - + /** * You can specify a custom filter which will be used to filter out unnecessary files from ProGuard input. - * + * * @see http://proguard.sourceforge.net/manual/usage.html#filefilters */ @Parameter( property = "android.proguard.customfilter" ) private String proguardCustomFilter; - + @PullParameter private String parsedCustomFilter; @@ -277,28 +281,26 @@ public ProGuardInput( String path, Collection< String > excludedFilter ) this.excludedFilter = excludedFilter; } - public String toCommandLine() + public String toPath() { if ( excludedFilter != null && !excludedFilter.isEmpty() ) { - String startQuotes, middleQuote, endQuote; + String middleQuote, endQuote; if ( !Os.isFamily( Os.FAMILY_WINDOWS ) ) { - startQuotes = "'\""; - middleQuote = "\"("; - endQuote = ")'"; + middleQuote = "("; + endQuote = ")"; } else { - startQuotes = "\"'"; - middleQuote = "'("; - endQuote = ")\""; + middleQuote = "("; + endQuote = ")"; } - StringBuilder sb = new StringBuilder( startQuotes ); - sb.append( path ); - sb.append( middleQuote ); + StringBuilder sb = new StringBuilder(); + sb.append( path ); + sb.append( middleQuote ); for ( Iterator< String > it = excludedFilter.iterator(); it.hasNext(); ) { sb.append( '!' ).append( it.next() ); @@ -312,20 +314,7 @@ public String toCommandLine() } else { - String startQuotes, endQuote; - - if ( !Os.isFamily( Os.FAMILY_WINDOWS ) ) - { - startQuotes = "'\""; - endQuote = "\"'"; - } - else - { - startQuotes = "\"'"; - endQuote = "'\""; - } - - return startQuotes + path + endQuote; + return path; } } @@ -388,50 +377,73 @@ private void executeProguard() throws MojoExecutionException commands.add( "-jar" ); commands.add( parsedProguardJarPath ); - commands.add( "@\"" + parsedConfig + "\"" ); + List proguardCommands = new ArrayList(); + + proguardCommands.add( "@" + parsedConfig + "" ); for ( String config : parsedConfigs ) { - commands.add( "@\"" + config + "\"" ); + proguardCommands.add( "@" + config ); } if ( proguardFile != null ) { - commands.add( "@\"" + proguardFile.getAbsolutePath() + "\"" ); + proguardCommands.add( "@" + proguardFile.getAbsolutePath() ); } - collectInputFiles( commands ); + collectInputFiles( proguardCommands ); - commands.add( "-outjars" ); - commands.add( "'\"" + obfuscatedJar + "\"'" ); + proguardCommands.add( "-outjars" ); + proguardCommands.add( obfuscatedJar ); - commands.add( "-dump" ); - commands.add( "'\"" + proguardDir + File.separator + "dump.txt\"'" ); - commands.add( "-printseeds" ); - commands.add( "'\"" + proguardDir + File.separator + "seeds.txt\"'" ); - commands.add( "-printusage" ); - commands.add( "'\"" + proguardDir + File.separator + "usage.txt\"'" ); + proguardCommands.add( "-dump" ); + proguardCommands.add( proguardDir + File.separator + "dump.txt" ); + proguardCommands.add( "-printseeds" ); + proguardCommands.add( proguardDir + File.separator + "seeds.txt" ); + proguardCommands.add( "-printusage" ); + proguardCommands.add( proguardDir + File.separator + "usage.txt" ); File mapFile = new File( proguardDir, "mapping.txt" ); - commands.add( "-printmapping" ); - commands.add( "'\"" + mapFile + "\"'" ); + proguardCommands.add( "-printmapping" ); + proguardCommands.add( mapFile.toString() ); - commands.addAll( Arrays.asList( parsedOptions ) ); + proguardCommands.addAll( Arrays.asList( parsedOptions ) ); final String javaExecutable = getJavaExecutable().getAbsolutePath(); - getLog().debug( javaExecutable + " " + commands.toString() ); + getLog().debug( javaExecutable + " " + commands.toString() + proguardCommands.toString() ); + FileOutputStream tempConfigFileOutputStream = null; try { + File tempConfigFile = new File ( proguardDir , "temp_config.cfg" ); + + StringBuilder commandStringBuilder = new StringBuilder(); + for ( String command : proguardCommands ) + { + commandStringBuilder.append( command ); + commandStringBuilder.append( SystemUtils.LINE_SEPARATOR ); + } + tempConfigFileOutputStream = new FileOutputStream( tempConfigFile ); + IOUtils.write( commandStringBuilder, tempConfigFileOutputStream ); + executor.setCaptureStdOut( true ); + commands.add( "@\"" + tempConfigFile.getAbsolutePath() + "\"" ); executor.executeCommand( javaExecutable, commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } + catch ( IOException e ) + { + throw new MojoExecutionException( "Error writing proguard commands to temporary file", e ); + } + finally + { + IOUtils.closeQuietly( tempConfigFileOutputStream ); + } if ( parsedAttachMap ) { @@ -472,7 +484,7 @@ private void collectInputFiles( List< String > commands ) throws MojoExecutionEx { getLog().debug( "Added injar : " + injar ); commands.add( "-injars" ); - commands.add( injar.toCommandLine() ); + commands.add( injar.toPath() ); } final List< ProGuardInput > libraryJars = getLibraryInputFiles(); @@ -480,7 +492,7 @@ private void collectInputFiles( List< String > commands ) throws MojoExecutionEx { getLog().debug( "Added libraryJar : " + libraryjar ); commands.add( "-libraryjars" ); - commands.add( libraryjar.toCommandLine() ); + commands.add( libraryjar.toPath() ); } }