aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Vnukov <vnukov@google.com>2017-06-06 07:54:08 -0700
committerDenis Vnukov <vnukov@google.com>2017-06-06 07:54:08 -0700
commitf19dfabc704b3975d66019c0817d65fc48921284 (patch)
tree28591b6a9e555cfb094fc840966fe3e15f0168f4
parent86d295dfdfb58854cb0076989886c99f2758897d (diff)
downloadr8-f19dfabc704b3975d66019c0817d65fc48921284.tar.gz
DRAFT: introducing --dex-per-class option in D8.
Proposal to better support incremental compilation: for recompiling N changed/affected files instead of running D8 on each of them separately, we run D8 on all of them and split the result into separate DEX files. After transformation the mapping from resulting resource into the class is available via Resource.getClassDescriptor(). When written into directly, all dex files are named based on the original class names, such that class com.blah.Foo will be written into DEX file named com.blah.Foo.dex. BUG= Change-Id: I359a6c6399ea786662f0c2a1f9c8d4cae416bcbd
-rw-r--r--src/main/java/com/android/tools/r8/BaseCommand.java23
-rw-r--r--src/main/java/com/android/tools/r8/BaseOutput.java10
-rw-r--r--src/main/java/com/android/tools/r8/D8.java11
-rw-r--r--src/main/java/com/android/tools/r8/D8Command.java14
-rw-r--r--src/main/java/com/android/tools/r8/D8Output.java7
-rw-r--r--src/main/java/com/android/tools/r8/R8.java6
-rw-r--r--src/main/java/com/android/tools/r8/R8Command.java7
-rw-r--r--src/main/java/com/android/tools/r8/Resource.java7
-rw-r--r--src/main/java/com/android/tools/r8/bisect/Bisect.java3
-rw-r--r--src/main/java/com/android/tools/r8/dex/ApplicationReader.java6
-rw-r--r--src/main/java/com/android/tools/r8/dex/ApplicationWriter.java17
-rw-r--r--src/main/java/com/android/tools/r8/dex/VirtualFile.java22
-rw-r--r--src/main/java/com/android/tools/r8/graph/LazyClassFileLoader.java4
-rw-r--r--src/main/java/com/android/tools/r8/utils/AndroidApp.java57
-rw-r--r--src/main/java/com/android/tools/r8/utils/InternalOptions.java3
-rw-r--r--src/main/java/com/android/tools/r8/utils/InternalResource.java33
-rw-r--r--src/main/java/com/android/tools/r8/utils/OutputMode.java28
-rw-r--r--src/test/examplesAndroidO/incremental/IncrementallyCompiled.java20
-rw-r--r--src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java209
-rw-r--r--src/test/java/com/android/tools/r8/TestBase.java3
-rw-r--r--src/test/java/com/android/tools/r8/ToolHelper.java5
-rw-r--r--src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java3
-rw-r--r--src/test/java/com/android/tools/r8/internal/CompilationTestBase.java3
-rw-r--r--src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java3
-rw-r--r--src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java3
-rw-r--r--src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java5
-rw-r--r--src/test/java/com/android/tools/r8/smali/SmaliTestBase.java5
27 files changed, 422 insertions, 95 deletions
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index bf58365d9..349777885 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -5,6 +5,7 @@ package com.android.tools.r8;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
@@ -19,6 +20,7 @@ abstract class BaseCommand {
private final AndroidApp app;
private final Path outputPath;
+ private final OutputMode outputMode;
private final CompilationMode mode;
private final int minApiLevel;
@@ -28,16 +30,19 @@ abstract class BaseCommand {
// All other fields are initialized with stub/invalid values.
this.app = null;
this.outputPath = null;
+ this.outputMode = OutputMode.Indexed;
this.mode = null;
this.minApiLevel = 0;
}
- BaseCommand(AndroidApp app, Path outputPath, CompilationMode mode, int minApiLevel) {
+ BaseCommand(AndroidApp app, Path outputPath,
+ OutputMode outputMode, CompilationMode mode, int minApiLevel) {
assert app != null;
assert mode != null;
assert minApiLevel > 0;
this.app = app;
this.outputPath = outputPath;
+ this.outputMode = outputMode;
this.mode = mode;
this.minApiLevel = minApiLevel;
// Print options are not set.
@@ -73,12 +78,17 @@ abstract class BaseCommand {
return minApiLevel;
}
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> {
private boolean printHelp = false;
private boolean printVersion = false;
private final AndroidApp.Builder app;
private Path outputPath = null;
+ private OutputMode outputMode = OutputMode.Indexed;
private CompilationMode mode;
private int minApiLevel = Constants.DEFAULT_ANDROID_API;
@@ -183,12 +193,23 @@ abstract class BaseCommand {
return outputPath;
}
+ /** Get the output mode. */
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
/** Set an output path. Must be an existing directory or a non-existent zip file. */
public B setOutputPath(Path outputPath) throws CompilationException {
this.outputPath = FileUtils.validateOutputFile(outputPath);
return self();
}
+ /** Set an output mode. */
+ public B setOutputMode(OutputMode outputMode) {
+ this.outputMode = outputMode;
+ return self();
+ }
+
/** Get the minimum API level (aka SDK version). */
public int getMinApiLevel() {
return minApiLevel;
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index 0f082c9e4..cb4d47302 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -11,9 +12,11 @@ import java.nio.file.Path;
abstract class BaseOutput {
private final AndroidApp app;
+ private final OutputMode outputMode;
- BaseOutput(AndroidApp app) {
+ BaseOutput(AndroidApp app, OutputMode outputMode) {
this.app = app;
+ this.outputMode = outputMode;
}
// Internal access to the underlying app.
@@ -21,6 +24,11 @@ abstract class BaseOutput {
return app;
}
+ // Internal access to the options.
+ public OutputMode getOutputMode() {
+ return outputMode;
+ }
+
/**
* Get the list of compiled DEX resources.
*
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 82831f317..0a0c97606 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -59,8 +59,9 @@ public final class D8 {
* @return the compilation result.
*/
public static D8Output run(D8Command command) throws IOException {
- D8Output output = new D8Output(
- runForTesting(command.getInputApp(), command.getInternalOptions()).androidApp);
+ CompilationResult result = runForTesting(command.getInputApp(), command.getInternalOptions());
+ assert result != null;
+ D8Output output = new D8Output(result.androidApp, command.getOutputMode());
if (command.getOutputPath() != null) {
output.write(command.getOutputPath());
}
@@ -78,8 +79,10 @@ public final class D8 {
* @return the compilation result.
*/
public static D8Output run(D8Command command, ExecutorService executor) throws IOException {
- D8Output output = new D8Output(
- runForTesting(command.getInputApp(), command.getInternalOptions(), executor).androidApp);
+ CompilationResult result = runForTesting(
+ command.getInputApp(), command.getInternalOptions(), executor);
+ assert result != null;
+ D8Output output = new D8Output(result.androidApp, command.getOutputMode());
if (command.getOutputPath() != null) {
output.write(command.getOutputPath());
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b4a83a468..a395530cc 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -30,7 +31,6 @@ public class D8Command extends BaseCommand {
* Builder for constructing a D8Command.
*/
public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
-
private Builder() {
super(CompilationMode.DEBUG);
}
@@ -52,7 +52,8 @@ public class D8Command extends BaseCommand {
if (isPrintHelp() || isPrintVersion()) {
return new D8Command(isPrintHelp(), isPrintVersion());
}
- return new D8Command(getAppBuilder().build(), getOutputPath(), getMode(), getMinApiLevel());
+ return new D8Command(getAppBuilder().build(),
+ getOutputPath(), getOutputMode(), getMode(), getMinApiLevel());
}
}
@@ -67,6 +68,7 @@ public class D8Command extends BaseCommand {
" --lib <file> # Add <file> as a library resource.",
" --classpath <file> # Add <file> as a classpath resource.",
" --min-sdk-version # minimum Android API level compatibility",
+ " --file-per-class # produce a separate dex file per class",
" --version # print the version of d8.",
" --help # print this message."));
@@ -103,6 +105,8 @@ public class D8Command extends BaseCommand {
}
builder.setMode(CompilationMode.RELEASE);
modeSet = CompilationMode.RELEASE;
+ } else if (arg.equals("--file-per-class")) {
+ builder.setOutputMode(OutputMode.FilePerClass);
} else if (arg.equals("--output")) {
String output = args[++i];
if (outputPath != null) {
@@ -126,8 +130,9 @@ public class D8Command extends BaseCommand {
return builder.setOutputPath(outputPath);
}
- private D8Command(AndroidApp inputApp, Path outputPath, CompilationMode mode, int minApiLevel) {
- super(inputApp, outputPath, mode, minApiLevel);
+ private D8Command(AndroidApp inputApp, Path outputPath,
+ OutputMode outputMode, CompilationMode mode, int minApiLevel) {
+ super(inputApp, outputPath, outputMode, mode, minApiLevel);
}
private D8Command(boolean printHelp, boolean printVersion) {
@@ -158,6 +163,7 @@ public class D8Command extends BaseCommand {
internal.outline.enabled = false;
internal.lazyClasspathLoading = true;
internal.lazyLibraryLoading = true;
+ internal.outputMode = getOutputMode();
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/D8Output.java b/src/main/java/com/android/tools/r8/D8Output.java
index 84f880352..1910e9b74 100644
--- a/src/main/java/com/android/tools/r8/D8Output.java
+++ b/src/main/java/com/android/tools/r8/D8Output.java
@@ -4,18 +4,19 @@
package com.android.tools.r8;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OutputMode;
import java.io.IOException;
import java.nio.file.Path;
/** Represents the output of a D8 compilation. */
public class D8Output extends BaseOutput {
- D8Output(AndroidApp app) {
- super(app);
+ D8Output(AndroidApp app, OutputMode outputMode) {
+ super(app, outputMode);
}
@Override
public void write(Path output) throws IOException {
- getAndroidApp().write(output);
+ getAndroidApp().write(output, getOutputMode());
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3b895671e..306f37b93 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -26,7 +26,6 @@ import com.android.tools.r8.shaking.AbstractMethodRemover;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexListBuilder;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.shaking.ProguardTypeMatcher;
@@ -40,6 +39,7 @@ import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.AttributeRemovalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.PackageDistribution;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -384,7 +384,7 @@ public class R8 {
AndroidApp outputApp =
runForTesting(command.getInputApp(), command.getInternalOptions()).androidApp;
if (command.getOutputPath() != null) {
- outputApp.write(command.getOutputPath());
+ outputApp.write(command.getOutputPath(), OutputMode.Indexed);
}
return outputApp;
}
@@ -404,7 +404,7 @@ public class R8 {
AndroidApp outputApp =
runForTesting(command.getInputApp(), command.getInternalOptions(), executor).androidApp;
if (command.getOutputPath() != null) {
- outputApp.write(command.getOutputPath());
+ outputApp.write(command.getOutputPath(), OutputMode.Indexed);
}
return outputApp;
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index f70a295d9..9b4454618 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -158,6 +159,7 @@ public class R8Command extends BaseCommand {
return new R8Command(
getAppBuilder().build(),
getOutputPath(),
+ getOutputMode(),
mainDexKeepRules,
configuration,
getMode(),
@@ -296,6 +298,7 @@ public class R8Command extends BaseCommand {
private R8Command(
AndroidApp inputApp,
Path outputPath,
+ OutputMode outputMode,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
ProguardConfiguration proguardConfiguration,
CompilationMode mode,
@@ -303,9 +306,10 @@ public class R8Command extends BaseCommand {
boolean useTreeShaking,
boolean useMinification,
boolean ignoreMissingClasses) {
- super(inputApp, outputPath, mode, minApiLevel);
+ super(inputApp, outputPath, outputMode, mode, minApiLevel);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
+ assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
this.mainDexKeepRules = mainDexKeepRules;
this.proguardConfiguration = proguardConfiguration;
this.useTreeShaking = useTreeShaking;
@@ -373,6 +377,7 @@ public class R8Command extends BaseCommand {
internal.mainDexKeepRules = mainDexKeepRules;
internal.keepRules = proguardConfiguration.getRules();
internal.dontWarnPatterns = proguardConfiguration.getDontWarnPatterns();
+ internal.outputMode = getOutputMode();
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index 74236a0ab..c7760bb3a 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -6,6 +6,7 @@ package com.android.tools.r8;
import com.google.common.io.Closer;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Set;
/** Represents application resources. */
public interface Resource {
@@ -18,6 +19,12 @@ public interface Resource {
/** Get the kind of the resource. */
Kind getKind();
+ /**
+ * Returns the set of class descriptors for classes represented
+ * by the resource if known, or `null' otherwise.
+ */
+ Set<String> getClassDescriptors();
+
/** Get the resource as a stream. */
InputStream getStream(Closer closer) throws IOException;
}
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index b48f268a1..1c76f4679 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
import com.google.common.io.CharStreams;
import java.io.File;
@@ -180,7 +181,7 @@ public class Bisect {
AppInfo appInfo = new AppInfo(app);
ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null);
AndroidApp outApp = writer.write(null, executor);
- outApp.writeToDirectory(output);
+ outApp.writeToDirectory(output, OutputMode.Indexed);
}
public static void main(String[] args) throws Exception {
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 8ab8921e5..4bf5cd956 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -91,14 +91,14 @@ public class ApplicationReader {
reader.read(DEFAULT_DEX_FILENAME, Resource.Kind.PROGRAM, input.getStream(closer));
}
for (InternalResource input : inputApp.getClassClasspathResources()) {
- if (options.lazyClasspathLoading && input.getClassDescriptor() != null) {
+ if (options.lazyClasspathLoading && input.getSingleClassDescriptorOrNull() != null) {
addLazyLoader(application, builder, input);
} else {
reader.read(DEFAULT_DEX_FILENAME, Resource.Kind.CLASSPATH, input.getStream(closer));
}
}
for (InternalResource input : inputApp.getClassLibraryResources()) {
- if (options.lazyLibraryLoading && input.getClassDescriptor() != null) {
+ if (options.lazyLibraryLoading && input.getSingleClassDescriptorOrNull() != null) {
addLazyLoader(application, builder, input);
} else {
reader.read(DEFAULT_DEX_FILENAME, Resource.Kind.LIBRARY, input.getStream(closer));
@@ -109,7 +109,7 @@ public class ApplicationReader {
private void addLazyLoader(JarApplicationReader application,
DexApplication.Builder builder, InternalResource resource) {
// Generate expected DEX type.
- String classDescriptor = resource.getClassDescriptor();
+ String classDescriptor = resource.getSingleClassDescriptorOrNull();
assert classDescriptor != null;
DexType type = options.itemFactory.createType(classDescriptor);
LazyClassFileLoader newLoader = new LazyClassFileLoader(type, resource, application);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index d86f9296c..db264ca76 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -19,14 +19,12 @@ import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PackageDistribution;
-import com.android.tools.r8.utils.ThreadUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -117,18 +115,23 @@ public class ApplicationWriter {
VirtualFile.fileSetFrom(this, packageDistribution, executorService);
// Write the dex files and the Proguard mapping file in parallel.
- List<Future<byte[]>> dexDataFutures = new ArrayList<>();
+ LinkedHashMap<VirtualFile, Future<byte[]>> dexDataFutures = new LinkedHashMap<>();
for (Integer index : newFiles.keySet()) {
VirtualFile newFile = newFiles.get(index);
if (!newFile.isEmpty()) {
- dexDataFutures.add(executorService.submit(() -> writeDexFile(newFile)));
+ dexDataFutures.put(newFile, executorService.submit(() -> writeDexFile(newFile)));
}
}
// Wait for all the spawned futures to terminate.
- List<byte[]> dexData = ThreadUtils.awaitFutures(dexDataFutures);
AndroidApp.Builder builder = AndroidApp.builder();
- dexData.forEach(builder::addDexProgramData);
+ try {
+ for (Map.Entry<VirtualFile, Future<byte[]>> entry : dexDataFutures.entrySet()) {
+ builder.addDexProgramData(entry.getValue().get(), entry.getKey().getClassDescriptors());
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while waiting for future.", e);
+ }
// Write the proguard map file after writing the dex files, as the map writer traverses
// the DexProgramClass structures, which are destructively updated during dex file writing.
byte[] proguardMapResult = writeProguardMapFile();
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 e575cee50..ad5420d55 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.PackageDistribution;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Iterators;
@@ -37,6 +38,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -61,6 +63,7 @@ public class VirtualFile {
private static final int MAX_PREFILL_ENTRIES = MAX_ENTRIES - 5000;
private final int id;
+ private final Set<String> classDescriptors = new HashSet<>();
private final VirtualFileIndexedItemCollection indexedItems;
private final IndexedItemTransaction transaction;
@@ -70,6 +73,10 @@ public class VirtualFile {
this.transaction = new IndexedItemTransaction(indexedItems, namingLens);
}
+ public Set<String> getClassDescriptors() {
+ return classDescriptors;
+ }
+
public static Map<Integer, VirtualFile> fileSetFrom(
ApplicationWriter writer,
PackageDistribution packageDistribution,
@@ -81,6 +88,20 @@ public class VirtualFile {
DexApplication application = writer.application;
InternalOptions options = writer.options;
Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
+
+ if (options.outputMode == OutputMode.FilePerClass) {
+ assert packageDistribution == null :
+ "Cannot combine package distribution definition with file-per-class option.";
+ // Assign dedicated virtual files for all program classes.
+ for (DexProgramClass clazz : application.classes()) {
+ VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
+ nameToFileMap.put(nameToFileMap.size(), file);
+ file.addClass(clazz);
+ file.commitTransaction();
+ }
+ return nameToFileMap;
+ }
+
if (packageDistribution != null) {
int maxReferencedIndex = packageDistribution.maxReferencedIndex();
for (int index = 0; index <= maxReferencedIndex; index++) {
@@ -268,6 +289,7 @@ public class VirtualFile {
private void addClass(DexProgramClass clazz) {
transaction.addClassAndDependencies(clazz);
+ classDescriptors.add(clazz.type.descriptor.toString());
}
private static boolean isFull(int numberOfMethods, int numberOfFields, int maximum) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyClassFileLoader.java b/src/main/java/com/android/tools/r8/graph/LazyClassFileLoader.java
index d5fa09ac3..ce40d19de 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyClassFileLoader.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyClassFileLoader.java
@@ -83,11 +83,11 @@ public final class LazyClassFileLoader implements DexClassPromise {
JarClassFileReader reader = new JarClassFileReader(this.reader, this::addClass);
reader.read(DEFAULT_DEX_FILENAME, resource.getKind(), resource.getStream(closer));
} catch (IOException e) {
- throw new CompilationError("Failed to load class: " + resource.getClassDescriptor(), e);
+ throw new CompilationError("Failed to load class: " + type.toSourceString(), e);
}
if (loadedClass == null) {
- throw new Unreachable("Class is supposed to be loaded: " + resource.getClassDescriptor());
+ throw new Unreachable("Class is supposed to be loaded: " + type.toSourceString());
}
if (loadedClass.type != type) {
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 85d5c572e..5c151094b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -31,7 +31,9 @@ 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.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
@@ -234,51 +236,45 @@ public class AndroidApp {
/**
* Write the dex program resources and proguard resource to @code{output}.
*/
- public void write(Path output) throws IOException {
- write(output, false);
+ public void write(Path output, OutputMode outputMode) throws IOException {
+ write(output, outputMode, false);
}
/**
* Write the dex program resources and proguard resource to @code{output}.
*/
- public void write(Path output, boolean overwrite) throws IOException {
+ public void write(Path output, OutputMode outputMode, boolean overwrite) throws IOException {
if (isArchive(output)) {
- writeToZip(output);
+ writeToZip(output, outputMode, overwrite);
} else {
- writeToDirectory(output);
+ writeToDirectory(output, outputMode, overwrite);
}
}
/**
* Write the dex program resources and proguard resource to @code{directory}.
*/
- public void writeToDirectory(Path directory) throws IOException {
- writeToDirectory(directory, false);
+ public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
+ writeToDirectory(directory, outputMode, false);
}
/**
* Write the dex program resources and proguard resource to @code{directory}.
*/
- public void writeToDirectory(Path directory, boolean overwrite) throws IOException {
+ public void writeToDirectory(
+ Path directory, OutputMode outputMode, boolean overwrite) throws IOException {
CopyOption[] options = copyOptions(overwrite);
try (Closer closer = Closer.create()) {
List<InternalResource> dexProgramSources = getDexProgramResources();
for (int i = 0; i < dexProgramSources.size(); i++) {
- Files.copy(dexProgramSources.get(i).getStream(closer),
- directory.resolve(getDexFileName(i)), options);
+ Path fileName = directory.resolve(outputMode.getFileName(dexProgramSources.get(i), i));
+ Files.copy(dexProgramSources.get(i).getStream(closer), fileName, options);
}
writeProguardMap(closer, directory, overwrite);
writeProguardSeeds(closer, directory, overwrite);
}
}
- /**
- * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
- */
- public void writeToZip(Path archive) throws IOException {
- writeToZip(archive, false);
- }
-
public List<byte[]> writeToMemory() throws IOException {
List<byte[]> dex = new ArrayList<>();
try (Closer closer = Closer.create()) {
@@ -296,13 +292,21 @@ public class AndroidApp {
/**
* Write the dex program resources to @code{archive} and the proguard resource as its sibling.
*/
- public void writeToZip(Path archive, boolean overwrite) throws IOException {
+ public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
+ writeToZip(archive, outputMode, false);
+ }
+
+ /**
+ * Write the dex program resources to @code{archive} and the proguard resource as its sibling.
+ */
+ public void writeToZip(
+ Path archive, OutputMode outputMode, boolean overwrite) throws IOException {
OpenOption[] options = openOptions(overwrite);
try (Closer closer = Closer.create()) {
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
List<InternalResource> dexProgramSources = getDexProgramResources();
for (int i = 0; i < dexProgramSources.size(); i++) {
- ZipEntry zipEntry = new ZipEntry(getDexFileName(i));
+ ZipEntry zipEntry = new ZipEntry(outputMode.getFileName(dexProgramSources.get(i), i));
byte[] bytes = ByteStreams.toByteArray(dexProgramSources.get(i).getStream(closer));
zipEntry.setSize(bytes.length);
out.putNextEntry(zipEntry);
@@ -338,10 +342,6 @@ public class AndroidApp {
}
}
- private String getDexFileName(int index) {
- return index == 0 ? "classes.dex" : ("classes" + (index + 1) + ".dex");
- }
-
private OpenOption[] openOptions(boolean overwrite) {
return new OpenOption[]{
overwrite ? StandardOpenOption.CREATE : StandardOpenOption.CREATE_NEW,
@@ -456,6 +456,14 @@ public class AndroidApp {
}
/**
+ * Add dex program-data with class descriptor.
+ */
+ public Builder addDexProgramData(byte[] data, Set<String> classDescriptors) {
+ dexSources.add(InternalResource.fromBytes(Resource.Kind.PROGRAM, data, classDescriptors));
+ return this;
+ }
+
+ /**
* Add dex program-data.
*/
public Builder addDexProgramData(byte[]... data) {
@@ -577,8 +585,9 @@ public class AndroidApp {
dexSources.add(InternalResource.fromBytes(kind, ByteStreams.toByteArray(stream)));
} else if (isClassFile(name)) {
containsClassData = true;
+ String descriptor = guessTypeDescriptor(name);
classSources.add(InternalResource.fromBytes(
- kind, ByteStreams.toByteArray(stream), guessTypeDescriptor(name)));
+ kind, ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
}
}
} catch (ZipException e) {
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 7f13315f2..3f8d99266 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -57,6 +57,9 @@ public class InternalOptions {
// Defines try-with-resources rewriter behavior.
public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Off;
+ // Application writing mode.
+ public OutputMode outputMode = OutputMode.Indexed;
+
public boolean useTreeShaking = true;
public boolean printCfg = false;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalResource.java b/src/main/java/com/android/tools/r8/utils/InternalResource.java
index 2d9bdb945..918109259 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalResource.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalResource.java
@@ -11,8 +11,10 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
+import java.util.Set;
-/** Internal resource class that is not intended for use from the outside.
+/**
+ * Internal resource class that is not intended for use from the outside.
*
* <p>This is only here to hide the creation and class descriptor methods
* from the javadoc for the D8 API. If we decide to expose those methods
@@ -26,9 +28,6 @@ public abstract class InternalResource implements Resource {
this.kind = kind;
}
- /** Returns class descriptor if known, otherwise returns null. */
- public abstract String getClassDescriptor();
-
/** Get the kind of the resource. */
public Kind getKind() {
return kind;
@@ -45,8 +44,18 @@ public abstract class InternalResource implements Resource {
}
/** Create an application resource for a given content, kind and type descriptor. */
- public static InternalResource fromBytes(Kind kind, byte[] bytes, String typeDescriptor) {
- return new ByteResource(kind, bytes, typeDescriptor);
+ public static InternalResource fromBytes(Kind kind, byte[] bytes, Set<String> typeDescriptors) {
+ return new ByteResource(kind, bytes, typeDescriptors);
+ }
+
+ /**
+ * If the resource represents a single class returns
+ * its descriptor, returns `null` otherwise.
+ */
+ public String getSingleClassDescriptorOrNull() {
+ Set<String> descriptors = getClassDescriptors();
+ return (descriptors == null) || (descriptors.size() != 1)
+ ? null : descriptors.iterator().next();
}
/** File based application resource. */
@@ -60,7 +69,7 @@ public abstract class InternalResource implements Resource {
}
@Override
- public String getClassDescriptor() {
+ public Set<String> getClassDescriptors() {
return null;
}
@@ -72,19 +81,19 @@ public abstract class InternalResource implements Resource {
/** Byte content based application resource. */
private static class ByteResource extends InternalResource {
- final String classDescriptor;
+ final Set<String> classDescriptors;
final byte[] bytes;
- ByteResource(Kind kind, byte[] bytes, String classDescriptor) {
+ ByteResource(Kind kind, byte[] bytes, Set<String> classDescriptors) {
super(kind);
assert bytes != null;
- this.classDescriptor = classDescriptor;
+ this.classDescriptors = classDescriptors;
this.bytes = bytes;
}
@Override
- public String getClassDescriptor() {
- return classDescriptor;
+ public Set<String> getClassDescriptors() {
+ return classDescriptors;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/OutputMode.java b/src/main/java/com/android/tools/r8/utils/OutputMode.java
new file mode 100644
index 000000000..c27429a9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OutputMode.java
@@ -0,0 +1,28 @@
+// 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;
+
+/** Defines way the output is formed. */
+public enum OutputMode {
+ Indexed {
+ @Override
+ String getFileName(Resource resource, int index) {
+ return index == 0 ? "classes.dex" : ("classes" + (index + 1) + ".dex");
+ }
+ },
+ FilePerClass {
+ @Override
+ String getFileName(Resource resource, int index) {
+ assert resource instanceof InternalResource;
+ String classDescriptor = ((InternalResource) resource).getSingleClassDescriptorOrNull();
+ assert classDescriptor != null;
+ assert !classDescriptor.contains(".");
+ return DescriptorUtils.descriptorToJavaType(classDescriptor) + ".dex";
+ }
+ };
+
+ abstract String getFileName(Resource resource, int index);
+}
diff --git a/src/test/examplesAndroidO/incremental/IncrementallyCompiled.java b/src/test/examplesAndroidO/incremental/IncrementallyCompiled.java
new file mode 100644
index 000000000..848528ed7
--- /dev/null
+++ b/src/test/examplesAndroidO/incremental/IncrementallyCompiled.java
@@ -0,0 +1,20 @@
+// 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 incremental;
+
+public class IncrementallyCompiled {
+ class A implements B {
+ class AB implements B.BA {
+ }
+ }
+
+ interface B {
+ interface BA {
+ }
+ }
+
+ static class C {
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index a5e944a91..4fd3276b6 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -4,16 +4,33 @@
package com.android.tools.r8;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalResource;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.OutputMode;
+import com.beust.jcommander.internal.Lists;
+import com.google.common.io.Closer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+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.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import java.util.function.UnaryOperator;
+import org.junit.Assert;
+import org.junit.Test;
public class D8IncrementalRunExamplesAndroidOTest
extends RunExamplesAndroidOTest<D8Command.Builder> {
@@ -31,18 +48,53 @@ public class D8IncrementalRunExamplesAndroidOTest
@Override
void build(Path testJarFile, Path out) throws Throwable {
- // Collect classes and compile separately.
+ Map<String, Resource> files = compileClassesTogether(testJarFile, null);
+ mergeClassFiles(Lists.newArrayList(files.values()), out);
+ }
+
+ // Dex classes separately.
+ SortedMap<String, Resource> compileClassesSeparately(Path testJarFile) throws Throwable {
+ TreeMap<String, Resource> fileToResource = new TreeMap<>();
List<String> classFiles = collectClassFiles(testJarFile);
- List<String> dexFiles = new ArrayList<>();
- for (int i = 0; i < classFiles.size(); i++) {
- Path indexedOut = Paths.get(
- out.getParent().toString(), out.getFileName() + "." + i + ".zip");
- compile(testJarFile.toString(), Collections.singletonList(classFiles.get(i)), indexedOut);
- dexFiles.add(indexedOut.toString());
+ for (String classFile : classFiles) {
+ AndroidApp app = compileClassFiles(
+ testJarFile.toString(), Collections.singletonList(classFile), null, OutputMode.Indexed);
+ assert app.getDexProgramResources().size() == 1;
+ fileToResource.put(
+ makeRelative(testJarFile, Paths.get(classFile)).toString(),
+ app.getDexProgramResources().get(0));
}
+ return fileToResource;
+ }
- // When compiled add files separately, merge them.
- compile(null, dexFiles, out);
+ // Dex classes in one D8 invocation.
+ SortedMap<String, Resource> compileClassesTogether(
+ Path testJarFile, Path output) throws Throwable {
+ TreeMap<String, Resource> fileToResource = new TreeMap<>();
+ List<String> classFiles = collectClassFiles(testJarFile);
+ AndroidApp app = compileClassFiles(
+ testJarFile.toString(), classFiles, output, OutputMode.FilePerClass);
+ for (InternalResource resource : app.getDexProgramResources()) {
+ String classDescriptor = resource.getSingleClassDescriptorOrNull();
+ Assert.assertNotNull("Add resources are expected to have a descriptor", classDescriptor);
+ classDescriptor = classDescriptor.substring(1, classDescriptor.length() - 1);
+ fileToResource.put(classDescriptor + ".class", resource);
+ }
+ return fileToResource;
+ }
+
+ private Path makeRelative(Path testJarFile, Path classFile) {
+ classFile = classFile.toAbsolutePath();
+ Path regularParent =
+ testJarFile.getParent().resolve(Paths.get("classes")).toAbsolutePath();
+ Path legacyParent = regularParent.resolve(Paths.get("..",
+ regularParent.getFileName().toString() + "Legacy", "classes")).toAbsolutePath();
+
+ if (classFile.startsWith(regularParent)) {
+ return regularParent.relativize(classFile);
+ }
+ Assert.assertTrue(classFile.startsWith(legacyParent));
+ return legacyParent.relativize(classFile);
}
private List<String> collectClassFiles(Path testJarFile) {
@@ -73,21 +125,48 @@ public class D8IncrementalRunExamplesAndroidOTest
}
}
- private void compile(String classpath, List<String> inputFiles, Path out) throws Throwable {
+ AndroidApp compileClassFiles(String classpath,
+ List<String> inputFiles, Path output, OutputMode outputMode) throws Throwable {
D8Command.Builder builder = D8Command.builder();
- if (classpath != null) {
- builder.addClasspathFiles(Paths.get(classpath));
- }
+ builder = builder.addClasspathFiles(Paths.get(classpath));
for (String inputFile : inputFiles) {
- builder.addProgramFiles(Paths.get(inputFile));
+ builder = builder.addProgramFiles(Paths.get(inputFile));
+ }
+ for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ builder = builder.setOutputMode(outputMode);
+ builder = builder.addLibraryFiles(
+ Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
+ if (output != null) {
+ builder = builder.setOutputPath(output);
+ }
+ D8Command command = builder.build();
+ try {
+ return ToolHelper.runD8(command, this::combinedOptionConsumer);
+ } catch (Unimplemented | CompilationError | InternalCompilerError re) {
+ throw re;
+ } catch (RuntimeException re) {
+ throw re.getCause() == null ? re : re.getCause();
+ }
+ }
+
+ Resource mergeClassFiles(List<Resource> dexFiles, Path out) throws Throwable {
+ D8Command.Builder builder = D8Command.builder();
+ for (Resource dexFile : dexFiles) {
+ builder.addDexProgramData(readFromResource(dexFile));
}
for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
- builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
- D8Command command = builder.setOutputPath(out).build();
+ if (out != null) {
+ builder = builder.setOutputPath(out);
+ }
+ D8Command command = builder.build();
try {
- ToolHelper.runD8(command, this::combinedOptionConsumer);
+ AndroidApp app = ToolHelper.runD8(command, this::combinedOptionConsumer);
+ assert app.getDexProgramResources().size() == 1;
+ return app.getDexProgramResources().get(0);
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
throw re;
} catch (RuntimeException re) {
@@ -96,8 +175,102 @@ public class D8IncrementalRunExamplesAndroidOTest
}
}
+ @Test
+ public void dexPerClassFileNoDesugaring() throws Throwable {
+ String testName = "dexPerClassFileNoDesugaring";
+ String testPackage = "incremental";
+ String mainClass = "IncrementallyCompiled";
+
+ Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
+
+ D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
+
+ Map<String, Resource> compiledSeparately = test.compileClassesSeparately(inputJarFile);
+ Map<String, Resource> compiledTogether = test.compileClassesTogether(inputJarFile, null);
+ Assert.assertEquals(compiledSeparately.size(), compiledTogether.size());
+
+ for (Map.Entry<String, Resource> entry : compiledSeparately.entrySet()) {
+ Resource otherResource = compiledTogether.get(entry.getKey());
+ Assert.assertNotNull(otherResource);
+ Assert.assertArrayEquals(readFromResource(entry.getValue()), readFromResource(otherResource));
+ }
+
+ Resource mergedFromCompiledSeparately =
+ test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
+ Resource mergedFromCompiledTogether =
+ test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
+ Assert.assertArrayEquals(
+ readFromResource(mergedFromCompiledSeparately),
+ readFromResource(mergedFromCompiledTogether));
+ }
+
+ @Test
+ public void dexPerClassFileWithDesugaring() throws Throwable {
+ String testName = "dexPerClassFileWithDesugaring";
+ String testPackage = "lambdadesugaringnplus";
+ String mainClass = "LambdasWithStaticAndDefaultMethods";
+
+ Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
+
+ D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
+ test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
+
+ Resource mergedFromCompiledSeparately =
+ test.mergeClassFiles(Lists.newArrayList(
+ test.compileClassesSeparately(inputJarFile).values()), null);
+ Resource mergedFromCompiledTogether =
+ test.mergeClassFiles(Lists.newArrayList(
+ test.compileClassesTogether(inputJarFile, null).values()), null);
+
+ Assert.assertArrayEquals(
+ readFromResource(mergedFromCompiledSeparately),
+ readFromResource(mergedFromCompiledTogether));
+ }
+
+ @Test
+ public void dexPerClassFileOutputFiles() throws Throwable {
+ String testName = "dexPerClassFileNoDesugaring";
+ String testPackage = "incremental";
+ String mainClass = "IncrementallyCompiled";
+
+ Path out = temp.getRoot().toPath();
+
+ Path inputJarFile = Paths.get(EXAMPLE_DIR, testPackage + JAR_EXTENSION);
+
+ D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
+ test.compileClassesTogether(inputJarFile, out);
+
+ String[] dexFiles = out.toFile().list();
+ assert dexFiles != null;
+ Arrays.sort(dexFiles);
+
+ String[] expectedFileNames = {
+ "incremental.IncrementallyCompiled$A$AB.dex",
+ "incremental.IncrementallyCompiled$A.dex",
+ "incremental.IncrementallyCompiled$B$BA.dex",
+ "incremental.IncrementallyCompiled$B.dex",
+ "incremental.IncrementallyCompiled$C.dex",
+ "incremental.IncrementallyCompiled.dex"
+ };
+
+ Assert.assertArrayEquals(expectedFileNames, dexFiles);
+ }
+
@Override
- TestRunner test(String testName, String packageName, String mainClass) {
+ D8IncrementalTestRunner test(String testName, String packageName, String mainClass) {
return new D8IncrementalTestRunner(testName, packageName, mainClass);
}
+
+ static byte[] readFromResource(Resource resource) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ byte[] buffer = new byte[16384];
+ try (Closer closer = Closer.create()) {
+ InputStream stream = resource.getStream(closer);
+ int read;
+ while ((read = stream.read(buffer, 0, buffer.length)) != -1) {
+ output.write(buffer, 0, read);
+ }
+ }
+ return output.toByteArray();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bcb9f7d9a..333e77b98 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import java.io.File;
@@ -121,7 +122,7 @@ public class TestBase {
*/
protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
- app.writeToZip(out, true);
+ app.writeToZip(out, OutputMode.Indexed, true);
return ToolHelper.runArtNoVerificationErrors(
ImmutableList.of(out.toString()), mainClass.getCanonicalName(), null);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 739d90c01..425dab797 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -454,7 +455,7 @@ public class ToolHelper {
}
CompilationResult result = R8.runForTesting(app, options);
if (command.getOutputPath() != null) {
- result.androidApp.write(command.getOutputPath());
+ result.androidApp.write(command.getOutputPath(), OutputMode.Indexed);
}
return result;
}
@@ -495,7 +496,7 @@ public class ToolHelper {
}
AndroidApp result = D8.runForTesting(command.getInputApp(), options).androidApp;
if (command.getOutputPath() != null) {
- result.write(command.getOutputPath());
+ result.write(command.getOutputPath(), command.getOutputMode());
}
return result;
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
index c16291aa5..98684376d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -49,7 +50,7 @@ public class DebugInfoTestBase {
protected String runOnArt(AndroidApp app, String main) throws IOException {
Path out = temp.getRoot().toPath().resolve("out.zip");
- app.writeToZip(out, true);
+ app.writeToZip(out, OutputMode.Indexed, true);
return ToolHelper.runArtNoVerificationErrors(ImmutableList.of(out.toString()), main, null);
}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 4b2ece003..80a1324a8 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OutputMode;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
@@ -78,7 +79,7 @@ public abstract class CompilationTestBase {
}
Path out = temp.getRoot().toPath().resolve("all.zip");
Path oatFile = temp.getRoot().toPath().resolve("all.oat");
- outputApp.writeToZip(out);
+ outputApp.writeToZip(out, OutputMode.Indexed);
try {
ToolHelper.runDex2Oat(out, oatFile);
return outputApp;
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 1481bc891..d9314d8f4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalResource;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.IOException;
@@ -57,7 +58,7 @@ public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
// Check that the generated bytecode runs through the dex2oat verifier with no errors.
Path combinedInput = temp.getRoot().toPath().resolve("all.jar");
Path oatFile = temp.getRoot().toPath().resolve("all.oat");
- app1.writeToZip(combinedInput);
+ app1.writeToZip(combinedInput, OutputMode.Indexed);
ToolHelper.runDex2Oat(combinedInput, oatFile);
}
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 7db5451a0..615e289c9 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import jasmin.ClassFile;
@@ -121,7 +122,7 @@ public class JasminTestBase {
protected String runOnArt(AndroidApp app, String main) throws IOException {
Path out = temp.getRoot().toPath().resolve("out.zip");
- app.writeToZip(out);
+ app.writeToZip(out, OutputMode.Indexed);
return ToolHelper.runArtNoVerificationErrors(ImmutableList.of(out.toString()), main, null);
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 115aabd37..29cef1945 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -49,6 +49,7 @@ import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MainDexList;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
@@ -108,7 +109,7 @@ public class MainDexListTests {
}
AndroidApp generated = generateApplication(TWO_LARGE_CLASSES, MAX_METHOD_COUNT);
if (regenerateApplications) {
- generated.write(getTwoLargeClassesAppPath(), true);
+ generated.write(getTwoLargeClassesAppPath(), OutputMode.Indexed, true);
} else {
AndroidApp cached = AndroidApp.fromProgramFiles(getTwoLargeClassesAppPath());
compareToCachedVersion(cached, generated, TWO_LARGE_CLASSES_APP);
@@ -123,7 +124,7 @@ public class MainDexListTests {
}
AndroidApp generated = generateApplication(MANY_CLASSES, 1);
if (regenerateApplications) {
- generated.write(getManyClassesAppPath(), true);
+ generated.write(getManyClassesAppPath(), OutputMode.Indexed, true);
} else {
AndroidApp cached = AndroidApp.fromProgramFiles(getManyClassesAppPath());
compareToCachedVersion(cached, generated, MANY_CLASSES_APP);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 314adb179..3665a8645 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -28,6 +28,7 @@ import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Smali;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Iterables;
@@ -452,7 +453,7 @@ public class SmaliTestBase {
AndroidApp app = writeDex(application, options);
Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
// TODO(sgjesse): Pass in a unique temp directory for each run.
- app.writeToZip(out, true);
+ app.writeToZip(out, OutputMode.Indexed, true);
return ToolHelper.runArtNoVerificationErrors(out.toString(), DEFAULT_MAIN_CLASS_NAME);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -464,7 +465,7 @@ public class SmaliTestBase {
AndroidApp app = writeDex(application, options);
Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
Path oatFile = temp.getRoot().toPath().resolve("oat-file");
- app.writeToZip(dexOut);
+ app.writeToZip(dexOut, OutputMode.Indexed);
ToolHelper.runDex2Oat(dexOut, oatFile);
} catch (IOException e) {
throw new RuntimeException(e);