Merge "Decrease peak memory usage for the common case."
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index b1b9166..48e2309 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -43,7 +43,7 @@
* @return an immutable list of compiled DEX resources.
*/
public List<Resource> getDexResources() {
- return ImmutableList.copyOf(app.getDexProgramResources());
+ return ImmutableList.copyOf(app.getDexProgramResourcesForOutput());
}
/**
diff --git a/src/main/java/com/android/tools/r8/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index 3208e52..15b5dd1 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -18,7 +18,7 @@
DEX, CLASSFILE
}
- private Resource(Kind kind) {
+ protected Resource(Kind kind) {
this.kind = kind;
}
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 19d5153..b328895 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -15,7 +15,6 @@
import com.google.common.io.Closer;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -25,19 +24,15 @@
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
@@ -52,6 +47,7 @@
private final ImmutableList<Resource> programResources;
private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
+ private final ImmutableList<ProgramFileArchiveReader> programFileArchiveReaders;
private final Resource deadCode;
private final Resource proguardMap;
private final Resource proguardSeeds;
@@ -61,6 +57,7 @@
// See factory methods and AndroidApp.Builder below.
private AndroidApp(
ImmutableList<Resource> programResources,
+ ImmutableList<ProgramFileArchiveReader> programFileArchiveReaders,
ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
Resource deadCode,
@@ -69,6 +66,7 @@
Resource packageDistribution,
Resource mainDexList) {
this.programResources = programResources;
+ this.programFileArchiveReaders = programFileArchiveReaders;
this.classpathResourceProviders = classpathResourceProviders;
this.libraryResourceProviders = libraryResourceProviders;
this.deadCode = deadCode;
@@ -142,13 +140,26 @@
}
/** Get input streams for all dex program resources. */
- public List<Resource> getDexProgramResources() {
+ public List<Resource> getDexProgramResources() throws IOException {
+ List<Resource> dexResources = filter(programResources, Resource.Kind.DEX);
+ for (ProgramFileArchiveReader reader : programFileArchiveReaders) {
+ dexResources.addAll(reader.getDexProgramResources());
+ }
+ return dexResources;
+ }
+
+ public List<Resource> getDexProgramResourcesForOutput() {
+ assert programFileArchiveReaders.isEmpty();
return filter(programResources, Resource.Kind.DEX);
}
/** Get input streams for all Java-bytecode program resources. */
- public List<Resource> getClassProgramResources() {
- return filter(programResources, Resource.Kind.CLASSFILE);
+ public List<Resource> getClassProgramResources() throws IOException {
+ List<Resource> classResources = filter(programResources, Resource.Kind.CLASSFILE);
+ for (ProgramFileArchiveReader reader : programFileArchiveReaders) {
+ classResources.addAll(reader.getClassProgramResources());
+ }
+ return classResources;
}
/** Get classpath resource providers. */
@@ -367,6 +378,7 @@
public static class Builder {
private final List<Resource> programResources = new ArrayList<>();
+ private final List<ProgramFileArchiveReader> programFileArchiveReaders = new ArrayList<>();
private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
private Resource deadCode;
@@ -382,6 +394,7 @@
// See AndroidApp::builder(AndroidApp).
private Builder(AndroidApp app) {
programResources.addAll(app.programResources);
+ programFileArchiveReaders.addAll(app.programFileArchiveReaders);
classpathResourceProviders.addAll(app.classpathResourceProviders);
libraryResourceProviders.addAll(app.libraryResourceProviders);
deadCode = app.deadCode;
@@ -597,6 +610,7 @@
public AndroidApp build() {
return new AndroidApp(
ImmutableList.copyOf(programResources),
+ ImmutableList.copyOf(programFileArchiveReaders),
ImmutableList.copyOf(classpathResourceProviders),
ImmutableList.copyOf(libraryResourceProviders),
deadCode,
@@ -615,7 +629,7 @@
} else if (isClassFile(file)) {
programResources.add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
} else if (isArchive(file)) {
- addProgramArchive(file);
+ programFileArchiveReaders.add(new ProgramFileArchiveReader(file));
} else {
throw new CompilationError("Unsupported source file type for file: " + file);
}
@@ -634,35 +648,5 @@
throw new CompilationError("Unsupported source file type for file: " + file);
}
}
-
- private void addProgramArchive(Path archive) throws IOException {
- assert isArchive(archive);
- boolean containsDexData = false;
- boolean containsClassData = false;
- try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
- ZipEntry entry;
- while ((entry = stream.getNextEntry()) != null) {
- Path name = Paths.get(entry.getName());
- if (isDexFile(name)) {
- containsDexData = true;
- programResources.add(Resource.fromBytes(
- Resource.Kind.DEX, ByteStreams.toByteArray(stream)));
- } else if (isClassFile(name)) {
- containsClassData = true;
- String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
- programResources.add(Resource.fromBytes(Resource.Kind.CLASSFILE,
- ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
- }
- }
- } catch (ZipException e) {
- throw new CompilationError(
- "Zip error while reading '" + archive + "': " + e.getMessage(), e);
- }
- if (containsDexData && containsClassData) {
- throw new CompilationError(
- "Cannot create android app from an archive '" + archive
- + "' containing both DEX and Java-bytecode content");
- }
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
new file mode 100644
index 0000000..51aa312
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
@@ -0,0 +1,36 @@
+// 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.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+class OneShotByteResource extends Resource {
+
+ private byte[] bytes;
+ private final Set<String> classDescriptors;
+
+ OneShotByteResource(Kind kind, byte[] bytes, Set<String> classDescriptors) {
+ super(kind);
+ assert bytes != null;
+ this.bytes = bytes;
+ this.classDescriptors = classDescriptors;
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return classDescriptors;
+ }
+
+ @Override
+ public InputStream getStream() throws IOException {
+ assert bytes != null;
+ InputStream result = new ByteArrayInputStream(bytes);
+ bytes = null;
+ return result;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
new file mode 100644
index 0000000..814ffd1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -0,0 +1,82 @@
+// 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 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;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.errors.CompilationError;
+import com.google.common.io.ByteStreams;
+import java.io.FileInputStream;
+import java.io.IOException;
+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.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+
+class ProgramFileArchiveReader {
+
+ private final Path archive;
+ private List<Resource> dexResources = null;
+ private List<Resource> classResources = null;
+
+ ProgramFileArchiveReader(Path archive) {
+ this.archive = archive;
+ }
+
+ private void readArchive() throws IOException {
+ assert isArchive(archive);
+ dexResources = new ArrayList<>();
+ classResources = new ArrayList<>();
+ try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
+ ZipEntry entry;
+ while ((entry = stream.getNextEntry()) != null) {
+ Path name = Paths.get(entry.getName());
+ if (isDexFile(name)) {
+ Resource resource =
+ new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+ dexResources.add(resource);
+ } else if (isClassFile(name)) {
+ String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
+ Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
+ ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
+ classResources.add(resource);
+ }
+ }
+ } catch (ZipException e) {
+ throw new CompilationError(
+ "Zip error while reading '" + archive + "': " + e.getMessage(), e);
+ }
+ if (!dexResources.isEmpty() && !classResources.isEmpty()) {
+ throw new CompilationError(
+ "Cannot create android app from an archive '" + archive
+ + "' containing both DEX and Java-bytecode content");
+ }
+ }
+
+ public Collection<Resource> getDexProgramResources() throws IOException {
+ if (dexResources == null) {
+ readArchive();
+ }
+ List<Resource> result = dexResources;
+ dexResources = null;
+ return result;
+ }
+
+ public Collection<Resource> getClassProgramResources() throws IOException {
+ if (classResources == null) {
+ readArchive();
+ }
+ List<Resource> result = classResources;
+ classResources = null;
+ return result;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index ed146b72..443e257 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -46,7 +46,7 @@
verifyEmptyCommand(parse("\t", "\t"));
}
- private void verifyEmptyCommand(D8Command command) {
+ private void verifyEmptyCommand(D8Command command) throws IOException {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
assertFalse(ToolHelper.getApp(command).hasMainDexList());
diff --git a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
index 27f0367..a1e57ac 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -46,7 +46,7 @@
verifyEmptyCommand(parse("\t", "\t"));
}
- private void verifyEmptyCommand(R8Command command) {
+ private void verifyEmptyCommand(R8Command command) throws IOException {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResources().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResources().size());
assertFalse(ToolHelper.getApp(command).hasMainDexList());