Allow passing AAR archives to D8.
Bug: 132131664
Bug: 132422247
Change-Id: Ifa9cdc3f51e0924c3494a287b0e1936d7c5a600c
diff --git a/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java
new file mode 100644
index 0000000..7de79dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AarArchiveResourceProvider.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, 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.utils;
+
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+public class AarArchiveResourceProvider implements ProgramResourceProvider {
+
+ private final Origin origin;
+ private final Path archive;
+
+ public static AarArchiveResourceProvider fromArchive(Path archive) {
+ return new AarArchiveResourceProvider(archive);
+ }
+
+ AarArchiveResourceProvider(Path archive) {
+ assert isArchive(archive);
+ origin = new ArchiveEntryOrigin("classes.jar", new PathOrigin(archive));
+ this.archive = archive;
+ }
+
+ private List<ProgramResource> readClassesJar(ZipInputStream stream) throws IOException {
+ ZipEntry entry;
+ List<ProgramResource> resources = new ArrayList<>();
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor));
+ resources.add(resource);
+ }
+ }
+ return resources;
+ }
+
+ private List<ProgramResource> readArchive() throws IOException {
+ List<ProgramResource> classResources = null;
+ try (ZipFile zipFile = new ZipFile(archive.toFile(), StandardCharsets.UTF_8)) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ String name = entry.getName();
+ if (name.equals("classes.jar")) {
+ try (ZipInputStream classesStream = new ZipInputStream(stream)) {
+ classResources = readClassesJar(classesStream);
+ }
+ break;
+ }
+ }
+ }
+ } catch (ZipException e) {
+ throw new CompilationError("Zip error while reading '" + archive + "': " + e.getMessage(), e);
+ }
+ return classResources == null ? Collections.emptyList() : classResources;
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ try {
+ return readArchive();
+ } catch (IOException e) {
+ throw new ResourceException(origin, e);
+ }
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return null;
+ }
+}
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 4c15524..f420261 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.FileUtils.isAarFile;
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.android.tools.r8.utils.FileUtils.isClassFile;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
@@ -711,6 +712,8 @@
addProgramResources(ProgramResource.fromFile(Kind.DEX, file));
} else if (isClassFile(file)) {
addProgramResources(ProgramResource.fromFile(Kind.CF, file));
+ } else if (isAarFile(file)) {
+ addProgramResourceProvider(AarArchiveResourceProvider.fromArchive(file));
} else if (isArchive(file)) {
addProgramResourceProvider(ArchiveResourceProvider.fromArchive(file, ignoreDexInArchive));
} else {
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 3f1f26e..87b8506 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -18,6 +18,7 @@
public class FileUtils {
+ public static final String AAR_EXTENSION = ".aar";
public static final String APK_EXTENSION = ".apk";
public static final String CLASS_EXTENSION = ".class";
public static final String DEX_EXTENSION = ".dex";
@@ -61,11 +62,17 @@
return name.endsWith(APK_EXTENSION);
}
+ public static boolean isAarFile(Path path) {
+ String name = path.getFileName().toString().toLowerCase();
+ return name.endsWith(AAR_EXTENSION);
+ }
+
public static boolean isArchive(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(APK_EXTENSION)
|| name.endsWith(JAR_EXTENSION)
- || name.endsWith(ZIP_EXTENSION);
+ || name.endsWith(ZIP_EXTENSION)
+ || name.endsWith(AAR_EXTENSION);
}
public static String readTextFile(Path file, Charset charset) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/files/AarInputTest.java b/src/test/java/com/android/tools/r8/files/AarInputTest.java
new file mode 100644
index 0000000..17c86eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/files/AarInputTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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.files;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.ZipUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AarInputTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public AarInputTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private Path buildAar() throws Exception {
+ Path aar = temp.newFolder().toPath().resolve("out.aar");
+ Path classesJar = temp.newFolder().toPath().resolve("classes.jar");
+ writeClassesToJar(classesJar, Collections.singletonList(TestClass.class));
+ try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(aar))) {
+ ZipUtils.writeToZipStream(
+ stream, "classes.jar", Files.readAllBytes(classesJar), ZipEntry.DEFLATED);
+ }
+ return aar;
+ }
+
+ @Test
+ public void allowAarProgramInputD8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello!");
+ } else {
+ testForD8()
+ .addProgramFiles(buildAar())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertThat(inspector.clazz(TestClass.class), isPresent()))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello!");
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello!");
+ }
+ }
+}