Merge "Add a public ArchiveProgramResourceProvider utility to the API."
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
new file mode 100644
index 0000000..b3a4bfb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -0,0 +1,132 @@
+// 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 static com.android.tools.r8.utils.FileUtils.isClassFile;
+import static com.android.tools.r8.utils.FileUtils.isDexFile;
+
+import com.android.tools.r8.ProgramResource.Kind;
+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.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/** Provider for archives of program resources. */
+public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
+
+ public interface ZipFileSupplier {
+ ZipFile open() throws IOException;
+ }
+
+ public static boolean includeClassFileEntries(String entry) {
+ return isClassFile(Paths.get(entry));
+ }
+
+ public static boolean includeDexEntries(String entry) {
+ return isDexFile(Paths.get(entry));
+ }
+
+ public static boolean includeClassFileOrDexEntries(String entry) {
+ Path path = Paths.get(entry);
+ return isClassFile(path) || isDexFile(path);
+ }
+
+ private final Origin origin;
+ private final ZipFileSupplier supplier;
+ private final Predicate<String> include;
+
+ public static ArchiveProgramResourceProvider fromArchive(Path archive) {
+ return fromArchive(archive, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
+ }
+
+ public static ArchiveProgramResourceProvider fromArchive(
+ Path archive, Predicate<String> include) {
+ return fromSupplier(new PathOrigin(archive), () -> new ZipFile(archive.toFile()), include);
+ }
+
+ public static ArchiveProgramResourceProvider fromSupplier(
+ Origin origin, ZipFileSupplier supplier) {
+ return fromSupplier(
+ origin, supplier, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
+ }
+
+ public static ArchiveProgramResourceProvider fromSupplier(
+ Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
+ return new ArchiveProgramResourceProvider(origin, supplier, include);
+ }
+
+ private ArchiveProgramResourceProvider(
+ Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
+ assert origin != null;
+ assert supplier != null;
+ assert include != null;
+ this.origin = origin;
+ this.supplier = supplier;
+ this.include = include;
+ }
+
+ private List<ProgramResource> readArchive() throws IOException {
+ List<ProgramResource> dexResources = new ArrayList<>();
+ List<ProgramResource> classResources = new ArrayList<>();
+ try (ZipFile zipFile = supplier.open()) {
+ final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ try (InputStream stream = zipFile.getInputStream(entry)) {
+ String name = entry.getName();
+ Path path = Paths.get(name);
+ Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin);
+ if (include.test(name)) {
+ if (FileUtils.isDexFile(path)) {
+ dexResources.add(
+ ProgramResource.fromBytes(
+ entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
+ } else if (isClassFile(path)) {
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ classResources.add(
+ ProgramResource.fromBytes(
+ entryOrigin,
+ Kind.CF,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor)));
+ }
+ }
+ }
+ }
+ } catch (ZipException e) {
+ throw new CompilationError("Zip error while reading archive" + e.getMessage(), e, origin);
+ }
+ if (!dexResources.isEmpty() && !classResources.isEmpty()) {
+ throw new CompilationError(
+ "Cannot create android app from an archive containing both DEX and Java-bytecode content",
+ origin);
+ }
+ return !dexResources.isEmpty() ? dexResources : classResources;
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ try {
+ return readArchive();
+ } catch (IOException e) {
+ throw new ResourceException(origin, e);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 43c68af..10c92dc 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -136,6 +136,11 @@
return self();
}
+ public B addProgramResourceProvider(ProgramResourceProvider programProvider) {
+ app.addProgramResourceProvider(programProvider);
+ return self();
+ }
+
/** Add library file resource provider. */
public B addLibraryResourceProvider(ClassFileResourceProvider provider) {
guard(() -> getAppBuilder().addLibraryResourceProvider(provider));
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 e18133b..9e6d79c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -511,7 +511,9 @@
} else if (isClassFile(file)) {
addProgramResources(ProgramResource.fromFile(Kind.CF, file));
} else if (isArchive(file)) {
- addProgramResourceProvider(new ArchiveProgramResourceProvider(file, ignoreDexInArchive));
+ addProgramResourceProvider(
+ new FilteredArchiveProgramResourceProvider(
+ FilteredClassPath.unfiltered(file), ignoreDexInArchive));
} else {
throw new CompilationError("Unsupported source file type", new PathOrigin(file));
}
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java
deleted file mode 100644
index 4485688..0000000
--- a/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.utils;
-
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.shaking.FilteredClassPath;
-import java.nio.file.Path;
-
-/** Provider for archives of program resources. */
-public class ArchiveProgramResourceProvider extends FilteredArchiveProgramResourceProvider
- implements ProgramResourceProvider {
-
- ArchiveProgramResourceProvider(Path archive, boolean ignoreDexInArchive) {
- super(FilteredClassPath.unfiltered(archive), ignoreDexInArchive);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index c2f0966..7eb73df 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -13,6 +13,7 @@
import com.android.sdklib.AndroidVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DirectoryClassFileProvider;
import com.android.tools.r8.utils.OutputMode;
@@ -22,6 +23,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.zip.ZipFile;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -258,6 +260,40 @@
D8Command.builder().addProgramFiles(vdexFile).build();
}
+ @Test
+ public void addProgramResources() throws ResourceException, CompilationFailedException {
+
+ // Stub out a custom origin to identify our resources.
+ class MyOrigin extends Origin {
+
+ public MyOrigin() {
+ super(Origin.root());
+ }
+
+ @Override
+ public String part() {
+ return "MyOrigin";
+ }
+ }
+
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
+ ProgramResourceProvider myProvider =
+ ArchiveProgramResourceProvider.fromSupplier(
+ new MyOrigin(), () -> new ZipFile(input.toFile()));
+ D8Command command = D8Command.builder().addProgramResourceProvider(myProvider).build();
+
+ // Check that each resource was provided by our provider.
+ ProgramResourceProvider inAppProvider =
+ command.getInputApp().getProgramResourceProviders().get(0);
+ for (ProgramResource resource : inAppProvider.getProgramResources()) {
+ Origin outermost = resource.getOrigin();
+ while (outermost.parent() != null && outermost.parent() != Origin.root()) {
+ outermost = outermost.parent();
+ }
+ assertTrue(outermost instanceof MyOrigin);
+ }
+ }
+
private D8Command parse(String... args) throws CompilationFailedException {
return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
}