Allow legacy multidex compilations for API 21
Bug: b/320893283
Change-Id: Idc3634a64842dcc6bce057dda22aaf07508cd857
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 33b687b..766371d 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -455,11 +455,11 @@
reporter.error(
"Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
}
- if (getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
+ if (getMinApiLevel() >= AndroidApiLevel.L_MR1.getLevel()) {
if (getMainDexListConsumer() != null || getAppBuilder().hasMainDexList()) {
reporter.error(
"D8 does not support main-dex inputs and outputs when compiling to API level "
- + AndroidApiLevel.L.getLevel()
+ + AndroidApiLevel.L_MR1.getLevel()
+ " and above");
}
}
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 47f7ed6..e3d2f78 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -229,7 +229,9 @@
if (!options.isGeneratingDex()) {
return true;
}
- AndroidApiLevel nativeMultiDex = AndroidApiLevel.L;
+ // Native multidex is supported from L, but the compiler supports compiling to L/21 using
+ // legacy multidex as there are some devices that have issues with it still.
+ AndroidApiLevel nativeMultiDex = AndroidApiLevel.L_MR1;
if (options.getMinApiLevel().isLessThan(nativeMultiDex)) {
return true;
}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 842b6a8..aaee1b0 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -329,15 +329,25 @@
assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
+ @Test
+ public void mainDexListNonLegacyMinApiL() throws Throwable {
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command =
+ parse(
+ "--min-api", Integer.toString(AndroidApiLevel.L.getLevel()),
+ "--main-dex-list", mainDexList.toString());
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+ }
+
@Test(expected = CompilationFailedException.class)
- public void mainDexListWithNonLegacyMinApi() throws Throwable {
+ public void mainDexListWithNonLegacyMinApiAboveL() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
DiagnosticsChecker.checkErrorsContains(
"does not support main-dex",
(handler) ->
D8Command.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setMinApiLevel(AndroidApiLevel.L.getLevel())
+ .setMinApiLevel(AndroidApiLevel.L_MR1.getLevel())
.addMainDexListFiles(mainDexList)
.build());
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
new file mode 100644
index 0000000..b9c3c87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2024, 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 static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+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.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test for allowing main-dex support for API 21 / L (See b//320893283).
+@RunWith(Parameterized.class)
+public class MainDexApi21Test extends TestBase {
+
+ static class TestClassA {}
+
+ static class TestClassB {}
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public MainDexApi21Test(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ static class TestConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+ Map<Integer, byte[]> data = new HashMap<>();
+ Map<Integer, Set<String>> descriptors = new HashMap<>();
+
+ public TestConsumer() {
+ super(null);
+ }
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ assertNull(this.data.put(fileIndex, data.copyByteData()));
+ assertNull(this.descriptors.put(fileIndex, descriptors));
+ }
+ }
+
+ @Test
+ public void testWithoutMainDexInputs() throws Exception {
+ // Test to ensure that running at API 21 / L without any main-dex content works.
+ // Since L does support native multdex the compiler should not require any main-dex inputs.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(1, programConsumer.data.size());
+ assertEquals(1, programConsumer.descriptors.size());
+ assertEquals(
+ ImmutableSet.of(descriptor(TestClassA.class), descriptor(TestClassB.class)),
+ programConsumer.descriptors.get(0));
+ }
+
+ @Test
+ public void testWithMainDexRules() throws Exception {
+ // Test to ensure that running at API 21 / L with main-dex rules will actually produce a
+ // main-dex file. Debug mode will compile to a minimal main-dex, so we will observe two files.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .addMainDexKeepClassAndMemberRules(TestClassB.class)
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(2, programConsumer.data.size());
+ assertEquals(2, programConsumer.descriptors.size());
+ assertEquals(ImmutableSet.of(descriptor(TestClassB.class)), programConsumer.descriptors.get(0));
+ assertEquals(ImmutableSet.of(descriptor(TestClassA.class)), programConsumer.descriptors.get(1));
+ }
+
+ @Test
+ public void testWithMainDexList() throws Exception {
+ // Test to ensure that running at API 21 / L with main-dex list will actually produce a
+ // main-dex file. Debug mode will compile to a minimal main-dex, so we will observe two files.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .addMainDexListClasses(TestClassB.class)
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(2, programConsumer.data.size());
+ assertEquals(2, programConsumer.descriptors.size());
+ assertEquals(ImmutableSet.of(descriptor(TestClassB.class)), programConsumer.descriptors.get(0));
+ assertEquals(ImmutableSet.of(descriptor(TestClassA.class)), programConsumer.descriptors.get(1));
+ }
+}