Version 2.1.66
Cherry pick: Allow D8 to take a proguard mapping file
CL: https://r8-review.googlesource.com/c/r8/+/53020
Bug: 150332381
Change-Id: I5c5880b797af635c233958755961220a11cf5f20
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 5781482..ceae49c 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -119,6 +119,12 @@
return super.addClasspathResourceProvider(provider);
}
+ /** Set input proguard map used for distribution of classes in multi-dex. */
+ public Builder setProguardInputMapFile(Path proguardInputMap) {
+ getAppBuilder().setProguardMapInputData(proguardInputMap);
+ return self();
+ }
+
/**
* Indicate if compilation is to intermediate results, i.e., intended for later merging.
*
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3c7fdd2..c81cbcf 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -29,6 +29,7 @@
"--output",
"--lib",
"--classpath",
+ "--pg-map",
MIN_API_FLAG,
"--main-dex-list",
"--main-dex-list-output",
@@ -128,6 +129,7 @@
+ "# Minimum Android API level compatibility, default: "
+ AndroidApiLevel.getDefault().getLevel()
+ ".",
+ " --pg-map <file> # Use <file> as a mapping file for distribution.",
" --intermediate # Compile an intermediate result intended for later",
" # merging.",
" --file-per-class # Produce a separate dex file per input class",
@@ -212,6 +214,8 @@
outputMode = OutputMode.DexFilePerClass;
} else if (arg.equals("--classfile")) {
outputMode = OutputMode.ClassFile;
+ } else if (arg.equals("--pg-map")) {
+ builder.setProguardInputMapFile(Paths.get(nextArg));
} else if (arg.equals("--output")) {
if (outputPath != null) {
builder.error(
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b9a633b..b83b334 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "2.1.65";
+ public static final String LABEL = "2.1.66";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index ca70ab0..5190adb 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -89,7 +89,9 @@
public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
return read(
- null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
+ inputApp.getProguardMapInputData(),
+ executorService,
+ ProgramClassCollection.defaultConflictResolver(options.reporter));
}
public final LazyLoadedDexApplication read(
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 7d2dbdc..a419f4a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -219,6 +219,10 @@
return transaction.getNumberOfFields();
}
+ public int getNumberOfClasses() {
+ return transaction.getNumberOfClasses();
+ }
+
void throwIfFull(boolean hasMainDexList, Reporter reporter) {
if (!isFull()) {
return;
@@ -458,7 +462,8 @@
fillStrategy,
0,
writer.initClassLens,
- writer.namingLens)
+ writer.namingLens,
+ options)
.call();
}
}
@@ -523,7 +528,8 @@
fillStrategy,
fileIndexOffset,
writer.initClassLens,
- writer.namingLens)
+ writer.namingLens,
+ options)
.call();
}
addFeatureSplitFiles(featureSplitClasses, fillStrategy);
@@ -750,6 +756,10 @@
return methods.size() + base.getNumberOfMethods();
}
+ int getNumberOfClasses() {
+ return classes.size() + base.classes.size();
+ }
+
int getNumberOfFields() {
return fields.size() + base.getNumberOfFields();
}
@@ -787,9 +797,6 @@
&& types.isEmpty() && strings.isEmpty();
}
- int getNumberOfClasses() {
- return classes.size() + base.classes.size();
- }
}
/**
@@ -919,6 +926,7 @@
private final Map<DexProgramClass, String> originalNames;
private final DexItemFactory dexItemFactory;
private final FillStrategy fillStrategy;
+ private final InternalOptions options;
private final VirtualFileCycler cycler;
PackageSplitPopulator(
@@ -929,11 +937,13 @@
FillStrategy fillStrategy,
int fileIndexOffset,
InitClassLens initClassLens,
- NamingLens namingLens) {
+ NamingLens namingLens,
+ InternalOptions options) {
this.classes = new ArrayList<>(classes);
this.originalNames = originalNames;
this.dexItemFactory = dexItemFactory;
this.fillStrategy = fillStrategy;
+ this.options = options;
this.cycler = new VirtualFileCycler(files, initClassLens, namingLens, fileIndexOffset);
}
@@ -1005,7 +1015,7 @@
nonPackageClasses.add(clazz);
continue;
}
- if (current.isFilledEnough(fillStrategy) || current.isFull()) {
+ if (isFullEnough(current, options)) {
current.abortTransaction();
// We allow for a final rollback that has at most 20% of classes in it.
// This is a somewhat random number that was empirically chosen.
@@ -1049,6 +1059,14 @@
return newPackageAssignments;
}
+ private boolean isFullEnough(VirtualFile current, InternalOptions options) {
+ if (options.testing.limitNumberOfClassesPerDex > 0
+ && current.getNumberOfClasses() > options.testing.limitNumberOfClassesPerDex) {
+ return true;
+ }
+ return current.isFilledEnough(fillStrategy) || current.isFull();
+ }
+
private void addNonPackageClasses(
VirtualFileCycler cycler, List<DexProgramClass> nonPackageClasses) {
cycler.restart();
@@ -1076,9 +1094,8 @@
private VirtualFile getVirtualFile(VirtualFileCycler cycler) {
VirtualFile current = null;
- while (cycler.hasNext()
- && (current = cycler.next()).isFilledEnough(fillStrategy)) {}
- if (current == null || current.isFilledEnough(fillStrategy)) {
+ while (cycler.hasNext() && isFullEnough(current = cycler.next(), options)) {}
+ if (current == null || isFullEnough(current, options)) {
current = cycler.addFile();
}
return current;
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 faebc96..7752da9 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -107,6 +107,7 @@
private final ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose;
private final StringResource proguardMapOutputData;
+ private final StringResource proguardMapInputData;
private final List<StringResource> mainDexListResources;
private final List<String> mainDexClasses;
@@ -176,6 +177,7 @@
ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
ImmutableList<InternalArchiveClassFileProvider> archiveProvidersToClose,
StringResource proguardMapOutputData,
+ StringResource proguardMapInputData,
List<StringResource> mainDexListResources,
List<String> mainDexClasses) {
this.programResourceProviders = programResourceProviders;
@@ -184,6 +186,7 @@
this.libraryResourceProviders = libraryResourceProviders;
this.archiveProvidersToClose = archiveProvidersToClose;
this.proguardMapOutputData = proguardMapOutputData;
+ this.proguardMapInputData = proguardMapInputData;
this.mainDexListResources = mainDexListResources;
this.mainDexClasses = mainDexClasses;
assert verifyInternalProvidersInCloseSet(classpathResourceProviders, archiveProvidersToClose);
@@ -344,6 +347,11 @@
return proguardMapOutputData;
}
+ /** Get the proguard-map associated with an input "app" if it exists. */
+ public StringResource getProguardMapInputData() {
+ return proguardMapInputData;
+ }
+
/**
* True if the main dex list resources exists.
*/
@@ -381,6 +389,7 @@
libraryResourceProviders,
archiveProvidersToClose,
proguardMapOutputData,
+ proguardMapInputData,
ImmutableList.of(),
ImmutableList.of());
}
@@ -712,8 +721,8 @@
private List<String> mainDexListClasses = new ArrayList<>();
private boolean ignoreDexInArchive = false;
- // Proguard map data is output only data. This should never be used as input to a compilation.
private StringResource proguardMapOutputData;
+ private StringResource proguardMapInputData;
private final Reporter reporter;
@@ -731,6 +740,7 @@
archiveProvidersToClose.addAll(app.archiveProvidersToClose);
mainDexListResources = app.mainDexListResources;
mainDexListClasses = app.mainDexClasses;
+ proguardMapInputData = app.proguardMapInputData;
}
public Reporter getReporter() {
@@ -1049,6 +1059,11 @@
return this;
}
+ public Builder setProguardMapInputData(Path mapPath) {
+ this.proguardMapInputData = StringResource.fromFile(mapPath);
+ return this;
+ }
+
/**
* Add a main-dex list file.
*/
@@ -1144,6 +1159,7 @@
ImmutableList.copyOf(libraryResourceProviders),
ImmutableList.copyOf(archiveProvidersToClose),
proguardMapOutputData,
+ proguardMapInputData,
mainDexListResources,
mainDexListClasses);
}
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 6264823..af62b23 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1206,6 +1206,8 @@
// Option for testing outlining with interface array arguments, see b/132420510.
public boolean allowOutlinerInterfaceArrayArguments = false;
+ public int limitNumberOfClassesPerDex = -1;
+
public MinifierTestingOptions minifier = new MinifierTestingOptions();
// Testing hooks to trigger effects in various compiler places.
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ce051a5..63d14a1 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -596,6 +596,17 @@
}
@Test
+ public void pgInputMap() throws CompilationFailedException, IOException, ResourceException {
+ Path mapFile = temp.newFile().toPath();
+ FileUtils.writeTextFile(
+ mapFile,
+ "com.android.tools.r8.ApiLevelException -> com.android.tools.r8.a:",
+ " boolean $assertionsDisabled -> c");
+ D8Command d8Command = parse("--pg-map", mapFile.toString());
+ assertFalse(d8Command.getInputApp().getProguardMapInputData().getString().isEmpty());
+ }
+
+ @Test
public void numThreadsOption() throws Exception {
assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
assertEquals(1, parse("--thread-count", "1").getThreadCount());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index aeaf585..fbe8b07 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -71,6 +71,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -419,6 +420,11 @@
.build();
}
+ protected List<String> classNamesFromDexFile(Path dexFile) throws IOException {
+ return new CodeInspector(dexFile)
+ .allClasses().stream().map(FoundClassSubject::toString).collect(Collectors.toList());
+ }
+
/**
* Build an AndroidApp with the specified test classes.
*/
diff --git a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
new file mode 100644
index 0000000..49e78f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, 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.d8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProguardMapSortByTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ProguardMapSortByTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMapSorting() throws Exception {
+ Path mappingFile = temp.newFile().toPath();
+ // Sort the classes, so that they are in 3 packages _not_ sorted by their original name.
+ FileUtils.writeTextFile(
+ mappingFile,
+ "com.A.a -> " + C.class.getTypeName() + ":",
+ "com.A.b -> " + B.class.getTypeName() + ":",
+ "com.B.a -> " + D.class.getTypeName() + ":",
+ "com.B.b -> " + E.class.getTypeName() + ":",
+ "com.C.a -> " + A.class.getTypeName() + ":");
+ D8TestBuilder d8TestBuilder =
+ testForD8()
+ .addOptionsModification(
+ internalOptions -> internalOptions.testing.limitNumberOfClassesPerDex = 2);
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
+ d8TestBuilder.addMainDexListClasses(A.class);
+ }
+ Path outputDir = temp.newFolder().toPath();
+ d8TestBuilder
+ .release()
+ .addProgramClasses(A.class, B.class, C.class, D.class, E.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().setProguardInputMapFile(mappingFile))
+ .run(parameters.getRuntime(), A.class)
+ .assertSuccessWithOutputLines("Hello world!")
+ .app()
+ .writeToDirectory(outputDir, OutputMode.DexIndexed);
+ Map<String, String> mapping = new HashMap<>();
+ for (String dexFile : ImmutableList.of("classes.dex", "classes2.dex", "classes3.dex")) {
+ Path resolvedDexFile = outputDir.resolve(dexFile);
+ assertTrue(Files.exists(resolvedDexFile));
+ classNamesFromDexFile(resolvedDexFile).forEach(name -> mapping.put(name, dexFile));
+ }
+ // Check that the classes are grouped by their original package name.
+ assertEquals(mapping.get(B.class.getTypeName()), mapping.get(C.class.getTypeName()));
+ assertEquals(mapping.get(D.class.getTypeName()), mapping.get(E.class.getTypeName()));
+ assertTrue(
+ mapping.values().stream().filter(s -> s.equals(mapping.get(A.class.getTypeName()))).count()
+ == 1);
+ }
+
+ static class A {
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ static class B {
+ public static void foo() {
+ System.out.println("foo");
+ }
+ }
+
+ static class C {
+ public static void bar() {
+ System.out.println("bar");
+ }
+ }
+
+ static class D {
+ public static void foobar() {
+ System.out.println("foobar");
+ }
+ }
+
+ static class E {
+ public static void barfoo() {
+ System.out.println("barfoo");
+ }
+ }
+}