Add an option for output of the main dex list Currently this is only in InternalOptions, and not wired to public options. Also added a utility for mapping and sorting a main dex list file. Change-Id: I97a2487997ec8e26e07b1a1ee323c5b4bc254235
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index d5cf1b5..8b57155 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -405,6 +405,13 @@ outputApp.writeProguardSeeds(closer, seedsOut); } } + if (options.printMainDexList && outputApp.hasMainDexList()) { + try (Closer closer = Closer.create()) { + OutputStream mainDexOut = + openPathWithDefault(closer, options.printMainDexListFile, true, System.out); + outputApp.writeMainDexList(closer, mainDexOut); + } + } } private static OutputStream openPathWithDefault(Closer closer,
diff --git a/src/main/java/com/android/tools/r8/ReadMainDexList.java b/src/main/java/com/android/tools/r8/ReadMainDexList.java new file mode 100644 index 0000000..3a76c1f --- /dev/null +++ b/src/main/java/com/android/tools/r8/ReadMainDexList.java
@@ -0,0 +1,58 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8; + +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.ProguardMapReader; +import com.android.tools.r8.utils.FileUtils; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +/** + * Utility for applying proguard map and sorting the main dex list. + */ +public class ReadMainDexList { + + private String DOT_CLASS = ".class"; + + private String stripDotClass(String name) { + return name.endsWith(DOT_CLASS) ? name.substring(0, name.length() - DOT_CLASS.length()) : name; + } + + private String addDotClass(String name) { + return name + DOT_CLASS; + } + + private String deobfuscateClassName(String name, ClassNameMapper mapper) { + if (mapper == null) { + return name; + } + return mapper.deobfuscateClassName(name); + } + + private void run(String[] args) throws Exception { + if (args.length != 1 && args.length != 2) { + System.out.println("Usage: command <main_dex_list> [<proguard_map>]"); + System.exit(0); + } + + final ClassNameMapper mapper = + args.length == 2 ? ProguardMapReader.mapperFromFile(Paths.get(args[1])) : null; + + FileUtils.readTextFile(Paths.get(args[0])) + .stream() + .map(this::stripDotClass) + .map(name -> name.replace('/', '.')) + .map(name -> deobfuscateClassName(name, mapper)) + .map(name -> name.replace('.', '/')) + .map(this::addDotClass) + .sorted() + .collect(Collectors.toList()) + .forEach(System.out::println); + } + + public static void main(String[] args) throws Exception { + new ReadMainDexList().run(args); + } +}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index db264ca..bba049c 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -12,11 +12,13 @@ import com.android.tools.r8.graph.DexDebugInfo; import com.android.tools.r8.graph.DexEncodedArray; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.DexTypeList; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.naming.MinifiedNameMapPrinter; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.PackageDistribution; import java.io.ByteArrayOutputStream; @@ -141,6 +143,10 @@ if (proguardSeedsData != null) { builder.setProguardSeedsData(proguardSeedsData); } + byte[] mainDexList = writeMainDexList(); + if (mainDexList != null) { + builder.setMainDexListData(mainDexList); + } return builder.build(); } finally { application.timing.end(); @@ -177,4 +183,22 @@ } return null; } + + private String mapMainDexListName(DexType type) { + return DescriptorUtils.descriptorToJavaType(namingLens.lookupDescriptor(type).toString()) + .replace('.', '/') + ".class"; + } + + private byte[] writeMainDexList() throws IOException { + if (application.mainDexList.isEmpty()) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(bytes); + application.mainDexList.forEach( + type -> writer.println(mapMainDexListName(type)) + ); + writer.flush(); + return bytes.toByteArray(); + } }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index db8adab..4415ea7 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -136,7 +136,7 @@ } else { System.out.println( "WARNING: Application does not contain `" - + type.toDescriptorString() + + type.toSourceString() + "` as referenced in main-dex-list."); } mainDexFile.commitTransaction();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java index 9e9a1ad..504cc4a 100644 --- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java +++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -59,10 +59,15 @@ return canonicalizeSignature(new FieldSignature(field.name.toString(), type)); } + /** + * Deobfuscate a class name. + * + * Returns the deobfuscated name if a mapping was found. Otherwise it returns the passed in name. + */ public String deobfuscateClassName(String name) { ClassNaming classNaming = classNameMappings.get(name); if (classNaming == null) { - return null; + return name; } return classNaming.originalName; }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java index 7ea2367..b4fdfe9 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -345,6 +345,12 @@ out.write(ByteStreams.toByteArray(input)); } + public void writeMainDexList(Closer closer, OutputStream out) throws IOException { + InputStream input = getMainDexList(closer); + assert input != null; + out.write(ByteStreams.toByteArray(input)); + } + private OpenOption[] openOptions(boolean overwrite) { return new OpenOption[]{ overwrite ? StandardOpenOption.CREATE : StandardOpenOption.CREATE_NEW, @@ -571,6 +577,14 @@ } /** + * Set the main-dex list data. + */ + public Builder setMainDexListData(byte[] content) { + mainDexList = content == null ? null : Resource.fromBytes(null, content); + return this; + } + + /** * Build final AndroidApp. */ public AndroidApp build() {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java index ec430fd..eae33d1 100644 --- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java +++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -88,11 +88,7 @@ String clazz = descriptor.substring(1, descriptor.length() - 1) .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); String originalName = - classNameMapper == null ? null : classNameMapper.deobfuscateClassName(clazz); - if (originalName == null) { - // Class was not renamed, reconstruct the java name. - return clazz; - } + classNameMapper == null ? clazz : classNameMapper.deobfuscateClassName(clazz); return originalName; case '[': return descriptorToJavaType(descriptor.substring(1, descriptor.length()), classNameMapper)
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 15fc89c..ad5485a 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -66,6 +66,8 @@ public Path seedsFile; public boolean printMapping; public Path printMappingFile; + public boolean printMainDexList; + public Path printMainDexListFile; public boolean ignoreMissingClasses = false; public boolean skipMinification = false; public String packagePrefix = "";
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index 28d4872..3f31686 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -96,6 +96,15 @@ /** * Compile an application with R8. */ + protected AndroidApp compileWithR8(List<Class> classes, Consumer<InternalOptions> optionsConsumer) + throws CompilationException, ProguardRuleParserException, ExecutionException, IOException { + R8Command command = ToolHelper.prepareR8CommandBuilder(readClasses(classes)).build(); + return ToolHelper.runR8(command, optionsConsumer); + } + + /** + * Compile an application with R8. + */ protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer) throws CompilationException, ProguardRuleParserException, ExecutionException, IOException { R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/HelloWorldMain.java b/src/test/java/com/android/tools/r8/maindexlist/HelloWorldMain.java new file mode 100644 index 0000000..b70e73f --- /dev/null +++ b/src/test/java/com/android/tools/r8/maindexlist/HelloWorldMain.java
@@ -0,0 +1,12 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.maindexlist; + +public class HelloWorldMain { + + public static void main(String[] args) { + System.out.println("Hello, world!"); + } +}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java new file mode 100644 index 0000000..f8ca2a8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -0,0 +1,48 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.maindexlist; + +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.R8Command; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.FileUtils; +import com.google.common.collect.ImmutableList; +import java.nio.file.Path; +import org.junit.Test; + +public class MainDexListOutputTest extends TestBase { + @Test + public void testNoMainDex() throws Exception { + Path mainDexList = temp.newFile().toPath(); + compileWithR8(ImmutableList.of(HelloWorldMain.class), + options -> { + options.printMainDexList = true; + options.printMainDexListFile = mainDexList; + }); + // Empty main dex list. + assertEquals(0, FileUtils.readTextFile(mainDexList).size()); + } + + @Test + public void testWithMainDex() throws Exception { + Path mainDexRules = writeTextToTempFile(keepMainProguardConfiguration(HelloWorldMain.class)); + R8Command command = + ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class)) + .addMainDexRules(mainDexRules) + .build(); + Path mainDexList = temp.newFile().toPath(); + ToolHelper.runR8(command, + options -> { + options.printMainDexList = true; + options.printMainDexListFile = mainDexList; + }); + // Main dex list with the single class. + assertEquals( + ImmutableList.of(HelloWorldMain.class.getTypeName().replace('.', '/') + ".class"), + FileUtils.readTextFile(mainDexList)); + } +}