Introduce dedicated dex-file consumers.
The consumers for DEX files are split into two. One for indexed mode and one for
file-per-class-file mode. The type of consumer defines what output-mode
compilation is in. The explicit output-mode and output-path getters and setters
are deprecated and will be removed once the API supports setting up the
consumers directly.
Change-Id: I58ade03d221f1571a8db79f7f7bc573f8a479c8b
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 33f9aeb..4a2f7b0 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -3,17 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
-import com.android.tools.r8.utils.FileSystemOutputSink;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.IOExceptionDiagnostic;
-import com.android.tools.r8.utils.IgnoreContentsOutputSink;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Reporter;
-import java.io.IOException;
import java.nio.file.Path;
/**
@@ -23,18 +18,15 @@
*/
abstract class BaseCompilerCommand extends BaseCommand {
- private final Path outputPath;
- private final OutputMode outputMode;
+ private final ProgramConsumer programConsumer;
private final CompilationMode mode;
private final int minApiLevel;
private final Reporter reporter;
private final boolean enableDesugaring;
- private OutputSink outputSink;
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
- outputPath = null;
- outputMode = OutputMode.Indexed;
+ programConsumer = null;
mode = null;
minApiLevel = 0;
reporter = new Reporter(new DefaultDiagnosticsHandler());
@@ -43,25 +35,24 @@
BaseCompilerCommand(
AndroidApp app,
- Path outputPath,
- OutputMode outputMode,
CompilationMode mode,
+ ProgramConsumer programConsumer,
int minApiLevel,
Reporter reporter,
boolean enableDesugaring) {
super(app);
- assert mode != null;
assert minApiLevel > 0;
- this.outputPath = outputPath;
- this.outputMode = outputMode;
+ assert mode != null;
this.mode = mode;
+ this.programConsumer = programConsumer;
this.minApiLevel = minApiLevel;
this.reporter = reporter;
this.enableDesugaring = enableDesugaring;
}
+ @Deprecated
public Path getOutputPath() {
- return outputPath;
+ return programConsumer == null ? null : programConsumer.getOutputPath();
}
public CompilationMode getMode() {
@@ -72,35 +63,21 @@
return minApiLevel;
}
+ @Deprecated
public OutputMode getOutputMode() {
- return outputMode;
+ return programConsumer instanceof DexFilePerClassFileConsumer
+ ? OutputMode.FilePerInputClass
+ : OutputMode.Indexed;
+ }
+
+ public ProgramConsumer getProgramConsumer() {
+ return programConsumer;
}
public Reporter getReporter() {
return reporter;
}
- private OutputSink createOutputSink() {
- if (outputPath == null) {
- return new IgnoreContentsOutputSink();
- } else {
- try {
- // TODO(zerny): Calling getInternalOptions here is incorrect since any modifications by an
- // options consumer will not be visible to the sink.
- return FileSystemOutputSink.create(outputPath, getInternalOptions());
- } catch (IOException e) {
- throw reporter.fatalError(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
- }
- }
- }
-
- public OutputSink getOutputSink() {
- if (outputSink == null) {
- outputSink = createOutputSink();
- }
- return outputSink;
- }
-
public boolean getEnableDesugaring() {
return enableDesugaring;
}
@@ -160,22 +137,26 @@
return outputMode;
}
- /**
- * Set an output path. Must be an existing directory or a zip file.
- */
+ /** Set an output path. Must be an existing directory or a zip file. */
+ @Deprecated
public B setOutputPath(Path outputPath) {
this.outputPath = outputPath;
return self();
}
- /**
- * Set an output mode.
- */
+ /** Set an output mode. */
+ @Deprecated
public B setOutputMode(OutputMode outputMode) {
this.outputMode = outputMode;
return self();
}
+ public ProgramConsumer getProgramConsumer() {
+ return getOutputMode() == OutputMode.Indexed
+ ? createIndexedConsumer()
+ : createPerClassFileConsumer();
+ }
+
/**
* Get the minimum API level (aka SDK version).
*/
@@ -210,5 +191,19 @@
FileUtils.validateOutputFile(outputPath, reporter);
super.validate();
}
+
+ protected DexIndexedConsumer createIndexedConsumer() {
+ Path path = getOutputPath();
+ return FileUtils.isArchive(path)
+ ? new DexIndexedConsumer.ArchiveConsumer(path)
+ : new DexIndexedConsumer.DirectoryConsumer(path);
+ }
+
+ protected DexFilePerClassFileConsumer createPerClassFileConsumer() {
+ Path path = getOutputPath();
+ return FileUtils.isArchive(path)
+ ? new DexFilePerClassFileConsumer.ArchiveConsumer(path)
+ : new DexFilePerClassFileConsumer.DirectoryConsumer(path);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index f55e362..eea50af 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -40,22 +40,15 @@
void accept(byte[] data, String descriptor, DiagnosticsHandler handler);
/** Empty consumer to request the production of the resource but ignore its value. */
- class EmptyConsumer implements ClassFileConsumer {
-
- @Override
- public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
- // Ignore data.
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- // Nothing to close.
- }
+ static ClassFileConsumer emptyConsumer() {
+ return ForwardingConsumer.EMPTY_CONSUMER;
}
/** Forwarding consumer to delegate to an optional existing consumer. */
class ForwardingConsumer implements ClassFileConsumer {
+ private static final ClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
+
private final ClassFileConsumer consumer;
public ForwardingConsumer(ClassFileConsumer consumer) {
@@ -75,6 +68,11 @@
consumer.finished(handler);
}
}
+
+ @Override
+ public Path getOutputPath() {
+ return consumer == null ? null : consumer.getOutputPath();
+ }
}
/** Archive consumer to write program resources to a zip archive. */
@@ -116,6 +114,11 @@
}
}
+ @Override
+ public Path getOutputPath() {
+ return archive;
+ }
+
private ZipOutputStream getStream(DiagnosticsHandler handler) {
assert !closed;
if (stream == null) {
@@ -176,6 +179,11 @@
super.finished(handler);
}
+ @Override
+ public Path getOutputPath() {
+ return directory;
+ }
+
private static void writeFileFromDescriptor(byte[] contents, Path target) throws IOException {
Files.createDirectories(target.getParent());
FileUtils.writeToFile(target, null, contents);
diff --git a/src/main/java/com/android/tools/r8/CompilationResult.java b/src/main/java/com/android/tools/r8/CompilationResult.java
index 283562b..9a81b52 100644
--- a/src/main/java/com/android/tools/r8/CompilationResult.java
+++ b/src/main/java/com/android/tools/r8/CompilationResult.java
@@ -8,12 +8,10 @@
public class CompilationResult {
- public final OutputSink outputSink;
public final DexApplication dexApplication;
public final AppInfo appInfo;
- public CompilationResult(OutputSink outputSink, DexApplication dexApplication, AppInfo appInfo) {
- this.outputSink = outputSink;
+ public CompilationResult(DexApplication dexApplication, AppInfo appInfo) {
this.dexApplication = dexApplication;
this.appInfo = appInfo;
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 0e7f329..f05e4c6 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.IOExceptionDiagnostic;
import com.android.tools.r8.utils.InternalOptions;
@@ -93,10 +93,8 @@
try {
try {
InternalOptions options = command.getInternalOptions();
- AndroidAppOutputSink compatSink =
- new AndroidAppOutputSink(command.getOutputSink(), options);
- CompilationResult result =
- run(command.getInputApp(), compatSink, options, executor);
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
+ CompilationResult result = run(command.getInputApp(), options, executor);
assert result != null;
D8Output d8Output = new D8Output(compatSink.build(), command.getOutputMode());
command.getReporter().failIfPendingErrors();
@@ -126,7 +124,7 @@
Version.printToolVersion("D8");
return;
}
- runForTesting(command.getInputApp(), command.getOutputSink(), command.getInternalOptions());
+ runForTesting(command.getInputApp(), command.getInternalOptions());
}
/** Command-line entry to D8. */
@@ -161,12 +159,11 @@
}
}
- static CompilationResult runForTesting(AndroidApp inputApp, OutputSink outputSink,
- InternalOptions options)
+ static CompilationResult runForTesting(AndroidApp inputApp, InternalOptions options)
throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
try {
- return run(inputApp, outputSink, options, executor);
+ return run(inputApp, options, executor);
} finally {
executor.shutdown();
}
@@ -187,8 +184,7 @@
}
private static CompilationResult run(
- AndroidApp inputApp, OutputSink outputSink, InternalOptions options,
- ExecutorService executor)
+ AndroidApp inputApp, InternalOptions options, ExecutorService executor)
throws IOException, CompilationException {
try {
// Disable global optimizations.
@@ -209,16 +205,15 @@
}
Marker marker = getMarker(options);
new ApplicationWriter(app, options, marker, null, NamingLens.getIdentityLens(), null, null)
- .write(outputSink, executor);
- CompilationResult output = new CompilationResult(outputSink, app, appInfo);
+ .write(executor);
+ CompilationResult output = new CompilationResult(app, appInfo);
options.printWarnings();
return output;
} catch (ExecutionException e) {
R8.unwrapExecutionException(e);
throw new AssertionError(e); // unwrapping method should have thrown
} finally {
- options.closeProgramConsumer();
- outputSink.close();
+ options.signalFinishedToProgramConsumer();
}
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 35aa36c..235b165 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -109,14 +109,27 @@
return new D8Command(
getAppBuilder().build(),
- getOutputPath(),
- getOutputMode(),
getMode(),
+ getProgramConsumer(),
getMinApiLevel(),
reporter,
getEnableDesugaring(),
intermediate);
}
+
+ @Override
+ protected DexIndexedConsumer createIndexedConsumer() {
+ return getOutputPath() == null
+ ? DexIndexedConsumer.emptyConsumer()
+ : super.createIndexedConsumer();
+ }
+
+ @Override
+ protected DexFilePerClassFileConsumer createPerClassFileConsumer() {
+ return getOutputPath() == null
+ ? DexFilePerClassFileConsumer.emptyConsumer()
+ : super.createPerClassFileConsumer();
+ }
}
static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
@@ -221,15 +234,13 @@
private D8Command(
AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
CompilationMode mode,
+ ProgramConsumer programConsumer,
int minApiLevel,
Reporter diagnosticsHandler,
boolean enableDesugaring,
boolean intermediate) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
- enableDesugaring);
+ super(inputApp, mode, programConsumer, minApiLevel, diagnosticsHandler, enableDesugaring);
this.intermediate = intermediate;
}
@@ -242,6 +253,7 @@
InternalOptions internal = new InternalOptions(new DexItemFactory(), getReporter());
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
+ internal.programConsumer = getProgramConsumer();
internal.minimalMainDex = internal.debug;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
@@ -261,7 +273,6 @@
assert internal.propagateMemberValue;
internal.propagateMemberValue = false;
- internal.outputMode = getOutputMode();
internal.enableDesugaring = getEnableDesugaring();
return internal;
}
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
new file mode 100644
index 0000000..bb3b16b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -0,0 +1,252 @@
+// 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.DEX_EXTENSION;
+
+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.android.tools.r8.utils.IOExceptionDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Consumer for DEX encoded programs.
+ *
+ * <p>This consumer receives DEX file content for each Java class-file input.
+ */
+public interface DexFilePerClassFileConsumer extends ProgramConsumer {
+
+ /**
+ * Callback to receive DEX data for a single Java class-file input and its companion classes.
+ *
+ * <p>There is no guaranteed order and files might be written concurrently.
+ *
+ * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
+ * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
+ * then the compiler guaranties to exit with an error.
+ *
+ * @param primaryClassDescriptor Class descriptor of the class from the input class-file.
+ * @param data DEX encoded data.
+ * @param descriptors Class descriptors for all classes defined in the DEX data.
+ * @param handler Diagnostics handler for reporting.
+ */
+ void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler);
+
+ /** Empty consumer to request the production of the resource but ignore its value. */
+ static DexFilePerClassFileConsumer emptyConsumer() {
+ return ForwardingConsumer.EMPTY_CONSUMER;
+ }
+
+ /** Forwarding consumer to delegate to an optional existing consumer. */
+ class ForwardingConsumer implements DexFilePerClassFileConsumer {
+
+ private static final DexFilePerClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
+
+ private final DexFilePerClassFileConsumer consumer;
+
+ public ForwardingConsumer(DexFilePerClassFileConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.accept(primaryClassDescriptor, data, descriptors, handler);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.finished(handler);
+ }
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return consumer == null ? null : consumer.getOutputPath();
+ }
+ }
+
+ /** Archive consumer to write program resources to a zip archive. */
+ class ArchiveConsumer extends ForwardingConsumer {
+
+ private static String getDexFileName(String classDescriptor) {
+ assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + DEX_EXTENSION;
+ }
+
+ private final Path archive;
+ private final Origin origin;
+ private ZipOutputStream stream = null;
+ private boolean closed = false;
+
+ public ArchiveConsumer(Path archive) {
+ this(archive, null);
+ }
+
+ public ArchiveConsumer(Path archive, DexFilePerClassFileConsumer consumer) {
+ super(consumer);
+ this.archive = archive;
+ origin = new PathOrigin(archive);
+ }
+
+ @Override
+ public void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ super.accept(primaryClassDescriptor, data, descriptors, handler);
+ synchronizedWrite(getDexFileName(primaryClassDescriptor), data, handler);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ assert !closed;
+ closed = true;
+ try {
+ if (stream != null) {
+ stream.close();
+ stream = null;
+ }
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return archive;
+ }
+
+ private ZipOutputStream getStream(DiagnosticsHandler handler) {
+ assert !closed;
+ if (stream == null) {
+ try {
+ stream =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+ return stream;
+ }
+
+ private synchronized void synchronizedWrite(
+ String entry, byte[] content, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(getStream(handler), entry, content);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ public static void writeResources(
+ Path archive,
+ List<ProgramResource> resources,
+ Map<Resource, String> primaryClassDescriptors)
+ throws IOException {
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (Closer closer = Closer.create()) {
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
+ for (ProgramResource resource : resources) {
+ String primaryClassDescriptor = primaryClassDescriptors.get(resource);
+ String entryName = getDexFileName(primaryClassDescriptor);
+ byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes);
+ }
+ }
+ }
+ }
+ }
+
+ /** Directory consumer to write program resources to a directory. */
+ class DirectoryConsumer extends ForwardingConsumer {
+
+ private final Path directory;
+
+ public DirectoryConsumer(Path directory) {
+ this(directory, null);
+ }
+
+ public DirectoryConsumer(Path directory, DexFilePerClassFileConsumer consumer) {
+ super(consumer);
+ this.directory = directory;
+ }
+
+ @Override
+ public void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ super.accept(primaryClassDescriptor, data, descriptors, handler);
+ Path target = getTargetDexFile(directory, primaryClassDescriptor);
+ try {
+ writeFile(data, target);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return directory;
+ }
+
+ public static void writeResources(
+ Path directory,
+ List<ProgramResource> resources,
+ Map<Resource, String> primaryClassDescriptors)
+ throws IOException {
+ try (Closer closer = Closer.create()) {
+ for (ProgramResource resource : resources) {
+ String primaryClassDescriptor = primaryClassDescriptors.get(resource);
+ Path target = getTargetDexFile(directory, primaryClassDescriptor);
+ writeFile(ByteStreams.toByteArray(closer.register(resource.getStream())), target);
+ }
+ }
+ }
+
+ private static Path getTargetDexFile(Path directory, String primaryClassDescriptor) {
+ return directory.resolve(ArchiveConsumer.getDexFileName(primaryClassDescriptor));
+ }
+
+ private static void writeFile(byte[] contents, Path target) throws IOException {
+ Files.createDirectories(target.getParent());
+ FileUtils.writeToFile(target, null, contents);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
new file mode 100644
index 0000000..1bf16ce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -0,0 +1,257 @@
+// 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 com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IOExceptionDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Consumer for DEX encoded programs.
+ *
+ * <p>This consumer receives DEX file content using standard indexed-multidex for programs larger
+ * than a single DEX file. This is the default consumer for DEX programs.
+ */
+public interface DexIndexedConsumer extends ProgramConsumer {
+
+ /**
+ * Callback to receive DEX data for a compilation output.
+ *
+ * <p>This is the equivalent to writing out the files classes.dex, classes2.dex, etc., where
+ * fileIndex gives the current file count (with the first file having index zero).
+ *
+ * <p>There is no guaranteed order and files might be written concurrently.
+ *
+ * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
+ * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
+ * then the compiler guaranties to exit with an error.
+ *
+ * @param fileIndex Index of the DEX file for multi-dexing. Files are zero-indexed.
+ * @param data DEX encoded data.
+ * @param descriptors Class descriptors for all classes defined in the DEX data.
+ * @param handler Diagnostics handler for reporting.
+ */
+ void accept(int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler);
+
+ /** Empty consumer to request the production of the resource but ignore its value. */
+ static DexIndexedConsumer emptyConsumer() {
+ return ForwardingConsumer.EMPTY_CONSUMER;
+ }
+
+ /** Forwarding consumer to delegate to an optional existing consumer. */
+ class ForwardingConsumer implements DexIndexedConsumer {
+
+ private static final DexIndexedConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
+
+ private final DexIndexedConsumer consumer;
+
+ public ForwardingConsumer(DexIndexedConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.accept(fileIndex, data, descriptors, handler);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.finished(handler);
+ }
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return consumer == null ? null : consumer.getOutputPath();
+ }
+ }
+
+ /** Archive consumer to write program resources to a zip archive. */
+ class ArchiveConsumer extends ForwardingConsumer {
+
+ private static String getDexFileName(int fileIndex) {
+ return fileIndex == 0
+ ? "classes.dex"
+ : ("classes" + (fileIndex + 1) + FileUtils.DEX_EXTENSION);
+ }
+
+ private final Path archive;
+ private final Origin origin;
+ private ZipOutputStream stream = null;
+ private boolean closed = false;
+
+ public ArchiveConsumer(Path archive) {
+ this(archive, null);
+ }
+
+ public ArchiveConsumer(Path archive, DexIndexedConsumer consumer) {
+ super(consumer);
+ this.archive = archive;
+ origin = new PathOrigin(archive);
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ super.accept(fileIndex, data, descriptors, handler);
+ synchronizedWrite(getDexFileName(fileIndex), data, handler);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ assert !closed;
+ closed = true;
+ try {
+ if (stream != null) {
+ stream.close();
+ stream = null;
+ }
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return archive;
+ }
+
+ private ZipOutputStream getStream(DiagnosticsHandler handler) {
+ assert !closed;
+ if (stream == null) {
+ try {
+ stream =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+ return stream;
+ }
+
+ private synchronized void synchronizedWrite(
+ String entry, byte[] content, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(getStream(handler), entry, content);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ public static void writeResources(Path archive, List<ProgramResource> resources)
+ throws IOException {
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (Closer closer = Closer.create()) {
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
+ for (int i = 0; i < resources.size(); i++) {
+ ProgramResource resource = resources.get(i);
+ String entryName = getDexFileName(i);
+ byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getStream()));
+ ZipUtils.writeToZipStream(out, entryName, bytes);
+ }
+ }
+ }
+ }
+ }
+
+ /** Directory consumer to write program resources to a directory. */
+ class DirectoryConsumer extends ForwardingConsumer {
+
+ private final Path directory;
+ private boolean preparedDirectory = false;
+
+ public DirectoryConsumer(Path directory) {
+ this(directory, null);
+ }
+
+ public DirectoryConsumer(Path directory, DexIndexedConsumer consumer) {
+ super(consumer);
+ this.directory = directory;
+ }
+
+ @Override
+ public void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ super.accept(fileIndex, data, descriptors, handler);
+ Path target = getTargetDexFile(directory, fileIndex);
+ try {
+ prepareDirectory();
+ writeFile(data, target);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ }
+
+ @Override
+ public Path getOutputPath() {
+ return directory;
+ }
+
+ private synchronized void prepareDirectory() throws IOException {
+ if (preparedDirectory) {
+ return;
+ }
+ preparedDirectory = true;
+ deleteClassesDexFiles(directory);
+ }
+
+ public static void deleteClassesDexFiles(Path directory) throws IOException {
+ try (Stream<Path> filesInDir = Files.list(directory)) {
+ for (Path path : filesInDir.collect(Collectors.toList())) {
+ if (FileUtils.isClassesDexFile(path)) {
+ Files.delete(path);
+ }
+ }
+ }
+ }
+
+ public static void writeResources(Path directory, List<ProgramResource> resources)
+ throws IOException {
+ deleteClassesDexFiles(directory);
+ try (Closer closer = Closer.create()) {
+ for (int i = 0; i < resources.size(); i++) {
+ Resource resource = resources.get(i);
+ Path target = getTargetDexFile(directory, i);
+ writeFile(ByteStreams.toByteArray(closer.register(resource.getStream())), target);
+ }
+ }
+ }
+
+ private static Path getTargetDexFile(Path directory, int fileIndex) {
+ return directory.resolve(ArchiveConsumer.getDexFileName(fileIndex));
+ }
+
+ private static void writeFile(byte[] contents, Path target) throws IOException {
+ Files.createDirectories(target.getParent());
+ FileUtils.writeToFile(target, null, contents);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
deleted file mode 100644
index a8d934a..0000000
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ /dev/null
@@ -1,62 +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;
-
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * Interface used by D8 and R8 to output the generated results.
- * <p>
- * Implementations must be able to cope with concurrent calls to these methods and non-determinism
- * in the order of calls. For example, {@link #writeDexFile} may be called concurrently and in any
- * order. It is the responsibility of an implementation to ensure deterministic output, if such is
- * desired.
- * <p>
- * The two versions of {@link #writeDexFile} are not used simulatneously. Normally, D8 and R8 will
- * write output using the {@link #writeDexFile(byte[], Set, int)} method. Only if instructed to
- * generated a DEX file per class ({@link com.android.tools.r8.utils.OutputMode#FilePerInputClass})
- * will D8 invoke {@link #writeDexFile(byte[], Set, String)} to generate a corresponding DEX file.
- * <p>
- * See {@link com.android.tools.r8.utils.ForwardingOutputSink} for a helper class that can be used
- * to wrap an existing sink and override only certain behavior.
- */
-public interface OutputSink {
-
- /**
- * Write a DEX file containing the definitions for all classes in classDescriptors into the DEX
- * file numbered as fileId.
- * <p>
- * This is the equivalent to writing out the files classes.dex, classes2.dex, etc., where fileId
- * gives the current file count.
- * <p>
- * Files are not necessarily generated in order and files might be written concurrently. However,
- * for each fileId only one file is ever written. If this method is called, the other writeDexFile
- * and writeClassFile methods will not be called.
- */
- void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId) throws IOException;
-
- /**
- * Write a DEX file that contains the class primaryClassName and its companion classes.
- * <p>
- * This is equivalent to writing out the file com/foo/bar/Test.dex given a primaryClassName of
- * com.foo.bar.Test.
- * <p>
- * There is no guaranteed order and files might be written concurrently. However, for each
- * primaryClassName only one file is ever written.
- * <p>
- * This method is only invoked by D8 and only if compiling each class into its own dex file, e.g.,
- * for incremental compilation. If this method is called, the other writeDexFile and
- * writeClassFile methods will not be called.
- */
- void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException;
-
- /**
- * Closes the output sink.
- * <p>
- * This method is invokes once all output has been generated.
- */
- void close() throws IOException;
-}
diff --git a/src/main/java/com/android/tools/r8/ProgramConsumer.java b/src/main/java/com/android/tools/r8/ProgramConsumer.java
index 061741c..fda6390 100644
--- a/src/main/java/com/android/tools/r8/ProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/ProgramConsumer.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import java.nio.file.Path;
+
/**
* Base for all program consumers to allow abstracting which concrete consumer is provided to D8/R8.
*/
@@ -16,4 +18,9 @@
* @param handler Diagnostics handler for reporting.
*/
void finished(DiagnosticsHandler handler);
+
+ @Deprecated
+ default Path getOutputPath() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3e30ebf..7da70d4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -47,7 +47,7 @@
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.IOExceptionDiagnostic;
@@ -103,7 +103,6 @@
public static void writeApplication(
ExecutorService executorService,
DexApplication application,
- OutputSink outputSink,
String deadCode,
NamingLens namingLens,
String proguardSeedsData,
@@ -124,7 +123,7 @@
namingLens,
proguardSeedsData,
proguardMapSupplier)
- .write(outputSink, executorService);
+ .write(executorService);
}
} catch (IOException e) {
throw new RuntimeException("Cannot write application", e);
@@ -138,11 +137,11 @@
return result;
}
- static void runForTesting(AndroidApp app, OutputSink outputSink, InternalOptions options)
+ static void runForTesting(AndroidApp app, InternalOptions options)
throws IOException, CompilationException {
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
- run(app, outputSink, options, executor);
+ run(app, options, executor);
} finally {
executor.shutdown();
}
@@ -150,15 +149,15 @@
private static void run(
AndroidApp app,
- OutputSink outputSink,
InternalOptions options,
ExecutorService executor)
throws IOException, CompilationException {
- new R8(options).run(app, outputSink, executor);
+ new R8(options).run(app, executor);
}
- private void run(AndroidApp inputApp, OutputSink outputSink, ExecutorService executorService)
+ private void run(AndroidApp inputApp, ExecutorService executorService)
throws IOException, CompilationException {
+ assert options.programConsumer != null;
if (options.quiet) {
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
@@ -380,7 +379,6 @@
writeApplication(
executorService,
application,
- outputSink,
application.deadCode,
namingLens,
proguardSeedsData,
@@ -392,8 +390,7 @@
unwrapExecutionException(e);
throw new AssertionError(e); // unwrapping method should have thrown
} finally {
- outputSink.close();
- options.closeProgramConsumer();
+ options.signalFinishedToProgramConsumer();
// Dump timings.
if (options.printTimes) {
timing.report();
@@ -477,7 +474,7 @@
try {
try {
InternalOptions options = command.getInternalOptions();
- run(command.getInputApp(), command.getOutputSink(), options, executor);
+ run(command.getInputApp(), options, executor);
} catch (IOException io) {
throw command.getReporter().fatalError(new IOExceptionDiagnostic(io));
} catch (CompilationException e) {
@@ -495,9 +492,9 @@
public static AndroidApp runInternal(R8Command command, ExecutorService executor)
throws IOException, CompilationException {
InternalOptions options = command.getInternalOptions();
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
- run(command.getInputApp(), compatSink, options, executor);
- return compatSink.build();
+ AndroidAppConsumers compatConsumers = new AndroidAppConsumers(options);
+ run(command.getInputApp(), options, executor);
+ return compatConsumers.build();
}
private static void run(String[] args)
@@ -518,7 +515,7 @@
InternalOptions options = command.getInternalOptions();
ExecutorService executorService = ThreadUtils.getExecutorService(options);
try {
- run(command.getInputApp(), command.getOutputSink(), options, executorService);
+ run(command.getInputApp(), options, executorService);
} finally {
executorService.shutdown();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index dd8d0ff..065dbc8 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -280,8 +280,7 @@
R8Command command =
new R8Command(
getAppBuilder().build(),
- getOutputPath(),
- getOutputMode(),
+ getOutputPath() == null ? null : getProgramConsumer(),
mainDexKeepRules,
mainDexListConsumer,
configuration,
@@ -351,7 +350,6 @@
return new Builder(diagnosticsHandler);
}
-
// Internal builder to start from an existing AndroidApp.
static Builder builder(AndroidApp app) {
return new Builder(app);
@@ -461,8 +459,7 @@
private R8Command(
AndroidApp inputApp,
- Path outputPath,
- OutputMode outputMode,
+ ProgramConsumer programConsumer,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
StringConsumer mainDexListConsumer,
ProguardConfiguration proguardConfiguration,
@@ -478,8 +475,7 @@
boolean ignoreMissingClassesWhenNotShrinking,
StringConsumer proguardMapConsumer,
Path proguardCompatibilityRulesOutput) {
- super(inputApp, outputPath, outputMode, mode, minApiLevel, reporter,
- enableDesugaring);
+ super(inputApp, mode, programConsumer, minApiLevel, reporter, enableDesugaring);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
@@ -510,6 +506,7 @@
proguardMapConsumer = null;
proguardCompatibilityRulesOutput = null;
}
+
public boolean useTreeShaking() {
return useTreeShaking;
}
@@ -527,6 +524,7 @@
InternalOptions internal = new InternalOptions(proguardConfiguration, getReporter());
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
+ internal.programConsumer = getProgramConsumer();
internal.minApiLevel = getMinApiLevel();
// -dontoptimize disables optimizations by flipping related flags.
if (!proguardConfiguration.isOptimizing()) {
@@ -554,7 +552,6 @@
internal.minimalMainDex = internal.debug;
internal.mainDexListConsumer = mainDexListConsumer;
- internal.outputMode = getOutputMode();
if (internal.debug) {
// TODO(zerny): Should we support removeSwitchMaps in debug mode? b/62936642
internal.removeSwitchMaps = false;
diff --git a/src/main/java/com/android/tools/r8/R8Output.java b/src/main/java/com/android/tools/r8/R8Output.java
deleted file mode 100644
index 5134a39..0000000
--- a/src/main/java/com/android/tools/r8/R8Output.java
+++ /dev/null
@@ -1,22 +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;
-
-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 R8 compilation. */
-public class R8Output extends BaseOutput {
-
- R8Output(AndroidApp app, OutputMode outputMode) {
- super(app, outputMode);
- }
-
- @Override
- public void write(Path output) throws IOException {
- getAndroidApp().write(output, getOutputMode());
- }
-}
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 570d773..4ea47a2 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
@@ -177,9 +177,9 @@
private static void writeApp(DexApplication app, Path output, ExecutorService executor)
throws IOException, ExecutionException, DexOverflowException {
InternalOptions options = new InternalOptions();
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
ApplicationWriter writer = new ApplicationWriter(app, options, null, null, null, null, null);
- writer.write(compatSink, executor);
+ writer.write(executor);
compatSink.build().writeToDirectory(output, OutputMode.Indexed);
}
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 0d92201..c9580ae 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.ApiLevelException;
-import com.android.tools.r8.OutputSink;
import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -27,7 +26,6 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ObjectArrays;
import java.io.IOException;
@@ -132,7 +130,7 @@
throws ExecutionException, IOException, DexOverflowException {
// Distribute classes into dex files.
VirtualFile.Distributor distributor;
- if (options.outputMode == OutputMode.FilePerInputClass) {
+ if (options.isGeneratingDexFilePerClassFile()) {
distributor = new VirtualFile.FilePerInputClassDistributor(this);
} else if (!options.canUseMultidex()
&& options.mainDexKeepRules.isEmpty()
@@ -145,7 +143,7 @@
return distributor.run();
}
- public void write(OutputSink outputSink, ExecutorService executorService)
+ public void write(ExecutorService executorService)
throws IOException, ExecutionException, DexOverflowException {
application.timing.begin("DexApplication.write");
try {
@@ -183,19 +181,29 @@
for (VirtualFile virtualFile : offsetMappingFutures.keySet()) {
assert !virtualFile.isEmpty();
final ObjectToOffsetMapping mapping = offsetMappingFutures.get(virtualFile).get();
- dexDataFutures.add(executorService.submit(() -> {
- byte[] result = writeDexFile(mapping);
- if (virtualFile.getPrimaryClassDescriptor() != null) {
- outputSink.writeDexFile(
- result,
- virtualFile.getClassDescriptors(),
- virtualFile.getPrimaryClassDescriptor());
- } else {
- outputSink
- .writeDexFile(result, virtualFile.getClassDescriptors(), virtualFile.getId());
- }
- return true;
- }));
+ dexDataFutures.add(
+ executorService.submit(
+ () -> {
+ byte[] result = writeDexFile(mapping);
+ if (virtualFile.getPrimaryClassDescriptor() != null) {
+ options
+ .getDexFilePerClassFileConsumer()
+ .accept(
+ virtualFile.getPrimaryClassDescriptor(),
+ result,
+ virtualFile.getClassDescriptors(),
+ options.reporter);
+ } else {
+ options
+ .getDexIndexedConsumer()
+ .accept(
+ virtualFile.getId(),
+ result,
+ virtualFile.getClassDescriptors(),
+ options.reporter);
+ }
+ return true;
+ }));
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for future.", e);
@@ -205,6 +213,8 @@
offsetMappingFutures.clear();
// Wait for all files to be processed before moving on.
ThreadUtils.awaitFutures(dexDataFutures);
+ // Fail if there are pending errors, e.g., the program consumers may have reported errors.
+ options.reporter.failIfPendingErrors();
if (options.usageInformationConsumer != null && deadCode != null) {
ExceptionUtils.withConsumeResourceHandler(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9004a44..a5d3c66 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -94,6 +94,7 @@
boolean enableWholeProgramOptimizations) {
assert appInfo != null;
assert options != null;
+ assert options.programConsumer != null;
this.timing = timing != null ? timing : new Timing("internal");
this.appInfo = appInfo;
this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
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 5302f51..7703142 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -9,11 +9,12 @@
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.Resource;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.shaking.FilteredClassPath;
@@ -27,13 +28,9 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
-import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -42,9 +39,6 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
/**
* Collection of program files needed for processing.
@@ -303,38 +297,12 @@
* Write the dex program resources and proguard resource to @code{directory}.
*/
public void writeToDirectory(Path directory, OutputMode outputMode) throws IOException {
+ List<ProgramResource> dexProgramSources = getDexProgramResources();
if (outputMode == OutputMode.Indexed) {
- try (Stream<Path> filesInDir = Files.list(directory)) {
- for (Path path : filesInDir.collect(Collectors.toList())) {
- if (FileUtils.isClassesDexFile(path)) {
- Files.delete(path);
- }
- }
- }
- }
- CopyOption[] options = new CopyOption[] {StandardCopyOption.REPLACE_EXISTING};
- try (Closer closer = Closer.create()) {
- List<ProgramResource> dexProgramSources = getDexProgramResources();
- for (int i = 0; i < dexProgramSources.size(); i++) {
- Path filePath = directory.resolve(getOutputPath(outputMode, dexProgramSources.get(i), i));
- if (!Files.exists(filePath.getParent())) {
- Files.createDirectories(filePath.getParent());
- }
- Files.copy(closer.register(dexProgramSources.get(i).getStream()), filePath, options);
- }
- }
- }
-
- private String getOutputPath(OutputMode outputMode, Resource resource, int index) {
- switch (outputMode) {
- case Indexed:
- return index == 0 ? "classes.dex" : ("classes" + (index + 1) + ".dex");
- case FilePerInputClass:
- String classDescriptor = programResourcesMainDescriptor.get(resource);
- assert classDescriptor!= null && DescriptorUtils.isClassDescriptor(classDescriptor);
- return classDescriptor.substring(1, classDescriptor.length() - 1) + ".dex";
- default:
- throw new Unreachable("Unknown output mode: " + outputMode);
+ DexIndexedConsumer.DirectoryConsumer.writeResources(directory, dexProgramSources);
+ } else {
+ DexFilePerClassFileConsumer.DirectoryConsumer.writeResources(
+ directory, dexProgramSources, programResourcesMainDescriptor);
}
}
@@ -356,21 +324,12 @@
* Write the dex program resources to @code{archive} and the proguard resource as its sibling.
*/
public void writeToZip(Path archive, OutputMode outputMode) throws IOException {
- OpenOption[] options =
- new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
- try (Closer closer = Closer.create()) {
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
- List<ProgramResource> dexProgramSources = getDexProgramResources();
- for (int i = 0; i < dexProgramSources.size(); i++) {
- ZipEntry zipEntry = new ZipEntry(getOutputPath(outputMode, dexProgramSources.get(i), i));
- byte[] bytes =
- ByteStreams.toByteArray(closer.register(dexProgramSources.get(i).getStream()));
- zipEntry.setSize(bytes.length);
- out.putNextEntry(zipEntry);
- out.write(bytes);
- out.closeEntry();
- }
- }
+ List<ProgramResource> resources = getDexProgramResources();
+ if (outputMode == OutputMode.Indexed) {
+ DexIndexedConsumer.ArchiveConsumer.writeResources(archive, resources);
+ } else {
+ DexFilePerClassFileConsumer.ArchiveConsumer.writeResources(
+ archive, resources, programResourcesMainDescriptor);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
new file mode 100644
index 0000000..b0eb8e6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -0,0 +1,203 @@
+// 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.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.origin.Origin;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class AndroidAppConsumers {
+
+ private final AndroidApp.Builder builder = AndroidApp.builder();
+ private boolean closed = false;
+
+ private ProgramConsumer programConsumer = null;
+ private StringConsumer mainDexListConsumer = null;
+ private StringConsumer proguardMapConsumer = null;
+ private StringConsumer usageInformationConsumer = null;
+
+ public AndroidAppConsumers(InternalOptions options) {
+ options.programConsumer = wrapProgramConsumer(options.programConsumer);
+ options.mainDexListConsumer = wrapMainDexListConsumer(options.mainDexListConsumer);
+ options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer);
+ options.usageInformationConsumer =
+ wrapUsageInformationConsumer(options.usageInformationConsumer);
+ }
+
+ private ProgramConsumer wrapProgramConsumer(ProgramConsumer consumer) {
+ assert programConsumer == null;
+ if (consumer instanceof ClassFileConsumer) {
+ programConsumer = wrapClassFileConsumer((ClassFileConsumer) consumer);
+ } else if (consumer instanceof DexIndexedConsumer) {
+ programConsumer = wrapDexIndexedConsumer((DexIndexedConsumer) consumer);
+ } else if (consumer instanceof DexFilePerClassFileConsumer) {
+ programConsumer = wrapDexFilePerClassFileConsumer((DexFilePerClassFileConsumer) consumer);
+ } else {
+ // TODO(zerny): Refine API to disallow running without a program consumer.
+ assert consumer == null;
+ programConsumer = wrapDexIndexedConsumer(null);
+ }
+ return programConsumer;
+ }
+
+ private StringConsumer wrapMainDexListConsumer(StringConsumer consumer) {
+ assert mainDexListConsumer == null;
+ if (consumer != null) {
+ mainDexListConsumer =
+ new StringConsumer.ForwardingConsumer(consumer) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ super.accept(string, handler);
+ builder.setMainDexListOutputData(string.getBytes(StandardCharsets.UTF_8));
+ }
+ };
+ }
+ return mainDexListConsumer;
+ }
+
+ private StringConsumer wrapProguardMapConsumer(StringConsumer consumer) {
+ assert proguardMapConsumer == null;
+ if (consumer != null) {
+ proguardMapConsumer =
+ new StringConsumer.ForwardingConsumer(consumer) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ super.accept(string, handler);
+ builder.setProguardMapData(string);
+ }
+ };
+ }
+ return proguardMapConsumer;
+ }
+
+ private StringConsumer wrapUsageInformationConsumer(StringConsumer consumer) {
+ assert usageInformationConsumer == null;
+ if (consumer != null) {
+ usageInformationConsumer =
+ new StringConsumer.ForwardingConsumer(consumer) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ super.accept(string, handler);
+ builder.setDeadCode(string.getBytes(StandardCharsets.UTF_8));
+ }
+ };
+ }
+ return usageInformationConsumer;
+ }
+
+ private DexIndexedConsumer wrapDexIndexedConsumer(DexIndexedConsumer consumer) {
+ return new ForwardingConsumer(consumer) {
+
+ // Sort the files by id so that their order is deterministic. Some tests depend on this.
+ private Int2ReferenceSortedMap<DescriptorsWithContents> files =
+ new Int2ReferenceAVLTreeMap<>();
+
+ @Override
+ public void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ super.accept(fileIndex, data, descriptors, handler);
+ addDexFile(fileIndex, data, descriptors);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ closed = true;
+ files.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors));
+ files = null;
+ }
+
+ synchronized void addDexFile(int fileIndex, byte[] data, Set<String> descriptors) {
+ files.put(fileIndex, new DescriptorsWithContents(descriptors, data));
+ }
+ };
+ }
+
+ private DexFilePerClassFileConsumer wrapDexFilePerClassFileConsumer(
+ DexFilePerClassFileConsumer consumer) {
+ return new DexFilePerClassFileConsumer.ForwardingConsumer(consumer) {
+
+ // Sort the files by their name for good measure.
+ private TreeMap<String, DescriptorsWithContents> files = new TreeMap<>();
+
+ @Override
+ public void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ super.accept(primaryClassDescriptor, data, descriptors, handler);
+ addDexFile(primaryClassDescriptor, data, descriptors);
+ }
+
+ synchronized void addDexFile(
+ String primaryClassDescriptor, byte[] data, Set<String> descriptors) {
+ files.put(primaryClassDescriptor, new DescriptorsWithContents(descriptors, data));
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ closed = true;
+ files.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors, v));
+ files = null;
+ }
+ };
+ }
+
+ private ClassFileConsumer wrapClassFileConsumer(ClassFileConsumer consumer) {
+ return new ClassFileConsumer.ForwardingConsumer(consumer) {
+
+ private List<DescriptorsWithContents> files = new ArrayList<>();
+
+ @Override
+ public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+ super.accept(data, descriptor, handler);
+ addClassFile(data, descriptor);
+ }
+
+ synchronized void addClassFile(byte[] data, String descriptor) {
+ files.add(new DescriptorsWithContents(Collections.singleton(descriptor), data));
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ closed = true;
+ files.forEach(
+ d -> builder.addClassProgramData(d.contents, Origin.unknown(), d.descriptors));
+ files = null;
+ }
+ };
+ }
+
+ public AndroidApp build() {
+ assert closed;
+ return builder.build();
+ }
+
+ private static class DescriptorsWithContents {
+
+ final Set<String> descriptors;
+ final byte[] contents;
+
+ private DescriptorsWithContents(Set<String> descriptors, byte[] contents) {
+ this.descriptors = descriptors;
+ this.contents = contents;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
deleted file mode 100644
index ee771ff..0000000
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ /dev/null
@@ -1,139 +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.DiagnosticsHandler;
-import com.android.tools.r8.OutputSink;
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.origin.Origin;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeMap;
-
-public class AndroidAppOutputSink extends ForwardingOutputSink {
-
- private final AndroidApp.Builder builder = AndroidApp.builder();
- private final TreeMap<String, DescriptorsWithContents> dexFilesWithPrimary = new TreeMap<>();
- private final TreeMap<Integer, DescriptorsWithContents> dexFilesWithId = new TreeMap<>();
- private final List<DescriptorsWithContents> classFiles = new ArrayList<>();
- private boolean closed = false;
-
- private StringConsumer mainDexListConsumer = null;
- private StringConsumer proguardMapConsumer = null;
- private StringConsumer usageInformationConsumer = null;
-
- public AndroidAppOutputSink(OutputSink forwardTo, InternalOptions options) {
- super(forwardTo);
- options.mainDexListConsumer = wrapMainDexListConsumer(options.mainDexListConsumer);
- options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer);
- options.usageInformationConsumer =
- wrapUsageInformationConsumer(options.usageInformationConsumer);
- }
-
- public AndroidAppOutputSink() {
- super(new IgnoreContentsOutputSink());
- }
-
- private StringConsumer wrapMainDexListConsumer(StringConsumer consumer) {
- assert mainDexListConsumer == null;
- if (consumer != null) {
- mainDexListConsumer =
- new StringConsumer.ForwardingConsumer(consumer) {
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- super.accept(string, handler);
- builder.setMainDexListOutputData(string.getBytes(StandardCharsets.UTF_8));
- }
- };
- }
- return mainDexListConsumer;
- }
-
- private StringConsumer wrapProguardMapConsumer(StringConsumer consumer) {
- assert proguardMapConsumer == null;
- if (consumer != null) {
- proguardMapConsumer =
- new StringConsumer.ForwardingConsumer(consumer) {
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- super.accept(string, handler);
- builder.setProguardMapData(string);
- }
- };
- }
- return proguardMapConsumer;
- }
-
- private StringConsumer wrapUsageInformationConsumer(StringConsumer consumer) {
- assert usageInformationConsumer == null;
- if (consumer != null) {
- usageInformationConsumer = new StringConsumer.ForwardingConsumer(consumer) {
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- super.accept(string, handler);
- builder.setDeadCode(string.getBytes(StandardCharsets.UTF_8));
- }
- };
- }
- return usageInformationConsumer;
- }
-
- @Override
- public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
- throws IOException {
- assert dexFilesWithPrimary.isEmpty() && classFiles.isEmpty();
- // Sort the files by id so that their order is deterministic. Some tests depend on this.
- dexFilesWithId.put(fileId, new DescriptorsWithContents(classDescriptors, contents));
- super.writeDexFile(contents, classDescriptors, fileId);
- }
-
- @Override
- public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors,
- String primaryClassName)
- throws IOException {
- assert dexFilesWithId.isEmpty() && classFiles.isEmpty();
- // Sort the files by their name for good measure.
- dexFilesWithPrimary
- .put(primaryClassName, new DescriptorsWithContents(classDescriptors, contents));
- super.writeDexFile(contents, classDescriptors, primaryClassName);
- }
-
- @Override
- public void close() throws IOException {
- assert !closed;
- if (!dexFilesWithPrimary.isEmpty()) {
- assert dexFilesWithId.isEmpty() && classFiles.isEmpty();
- dexFilesWithPrimary.forEach(
- (v, d) -> builder.addDexProgramData(d.contents, d.descriptors, v));
- } else if (!dexFilesWithId.isEmpty()) {
- assert dexFilesWithPrimary.isEmpty() && classFiles.isEmpty();
- dexFilesWithId.forEach((v, d) -> builder.addDexProgramData(d.contents, d.descriptors));
- } else if (!classFiles.isEmpty()) {
- assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty();
- classFiles.forEach(
- d -> builder.addClassProgramData(d.contents, Origin.unknown(), d.descriptors));
- }
- closed = true;
- super.close();
- }
-
- public AndroidApp build() {
- assert closed;
- return builder.build();
- }
-
- private static class DescriptorsWithContents {
-
- final Set<String> descriptors;
- final byte[] contents;
-
- private DescriptorsWithContents(Set<String> descriptors, byte[] contents) {
- this.descriptors = descriptors;
- this.contents = contents;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
deleted file mode 100644
index 74b1b21..0000000
--- a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
+++ /dev/null
@@ -1,62 +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 static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-public class DirectoryOutputSink extends FileSystemOutputSink {
-
- private final Path outputDirectory;
-
- public DirectoryOutputSink(Path outputDirectory, InternalOptions options) throws IOException {
- super(options);
- this.outputDirectory = outputDirectory;
- cleanUpOutputDirectory();
- }
-
- private void cleanUpOutputDirectory() throws IOException {
- if (getOutputMode() == OutputMode.Indexed) {
- try (Stream<Path> filesInDir = Files.list(outputDirectory)) {
- for (Path path : filesInDir.collect(Collectors.toList())) {
- if (FileUtils.isClassesDexFile(path)) {
- Files.delete(path);
- }
- }
- }
- }
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
- throws IOException {
- Path target = outputDirectory.resolve(getOutputFileName(fileId));
- Files.createDirectories(target.getParent());
- FileUtils.writeToFile(target, null, contents);
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- writeFileFromDescriptor(contents, primaryClassName, DEX_EXTENSION);
- }
-
- private void writeFileFromDescriptor(byte[] contents, String descriptor, String extension)
- throws IOException {
- Path target = outputDirectory.resolve(getOutputFileName(descriptor, extension));
- Files.createDirectories(target.getParent());
- FileUtils.writeToFile(target, null, contents);
- }
-
- @Override
- public void close() throws IOException {
- // Intentionally left empty.
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
deleted file mode 100644
index b2699fc..0000000
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ /dev/null
@@ -1,39 +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.OutputSink;
-import java.io.IOException;
-import java.nio.file.Path;
-
-public abstract class FileSystemOutputSink implements OutputSink {
-
- private final InternalOptions options;
-
- protected FileSystemOutputSink(InternalOptions options) {
- this.options = options;
- }
-
- public static FileSystemOutputSink create(Path outputPath, InternalOptions options)
- throws IOException {
- if (FileUtils.isArchive(outputPath)) {
- return new ZipFileOutputSink(outputPath, options);
- } else {
- return new DirectoryOutputSink(outputPath, options);
- }
- }
-
- String getOutputFileName(int index) {
- return index == 0 ? "classes.dex" : ("classes" + (index + 1) + FileUtils.DEX_EXTENSION);
- }
-
- String getOutputFileName(String classDescriptor, String extension) throws IOException {
- assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
- return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + extension;
- }
-
- protected OutputMode getOutputMode() {
- return options.outputMode;
- }
-}
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 580b7b7..23db21b 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -126,7 +126,7 @@
return mapOut;
}
- static boolean isClassesDexFile(Path file) {
+ public static boolean isClassesDexFile(Path file) {
String name = file.getFileName().toString().toLowerCase();
if (!name.startsWith("classes") || !name.endsWith(DEX_EXTENSION)) {
return false;
diff --git a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
deleted file mode 100644
index 71e48cf..0000000
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ /dev/null
@@ -1,39 +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.OutputSink;
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * Implementation of an {@link OutputSink} that forwards all calls to another sink.
- * <p>
- * Useful for layering output sinks and intercept some output.
- */
-public abstract class ForwardingOutputSink implements OutputSink {
-
- private final OutputSink forwardTo;
-
- protected ForwardingOutputSink(OutputSink forwardTo) {
- this.forwardTo = forwardTo;
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
- throws IOException {
- forwardTo.writeDexFile(contents, classDescriptors, fileId);
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- forwardTo.writeDexFile(contents, classDescriptors, primaryClassName);
- }
-
- @Override
- public void close() throws IOException {
- forwardTo.close();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
deleted file mode 100644
index 76f03bd..0000000
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ /dev/null
@@ -1,26 +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.OutputSink;
-import java.io.IOException;
-import java.util.Set;
-
-public class IgnoreContentsOutputSink implements OutputSink {
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId) {
- // Intentionally left empty.
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName) {
- // Intentionally left empty.
- }
-
- @Override
- public void close() throws IOException {
- // Intentionally left empty.
- }
-}
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 9ae315f..7c97839 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.dex.Marker;
@@ -103,18 +105,34 @@
}
public boolean isGeneratingDex() {
- return programConsumer == null;
+ return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile();
+ }
+
+ public boolean isGeneratingDexIndexed() {
+ return programConsumer instanceof DexIndexedConsumer;
+ }
+
+ public boolean isGeneratingDexFilePerClassFile() {
+ return programConsumer instanceof DexFilePerClassFileConsumer;
}
public boolean isGeneratingClassFiles() {
return programConsumer instanceof ClassFileConsumer;
}
+ public DexIndexedConsumer getDexIndexedConsumer() {
+ return (DexIndexedConsumer) programConsumer;
+ }
+
+ public DexFilePerClassFileConsumer getDexFilePerClassFileConsumer() {
+ return (DexFilePerClassFileConsumer) programConsumer;
+ }
+
public ClassFileConsumer getClassFileConsumer() {
return (ClassFileConsumer) programConsumer;
}
- public void closeProgramConsumer() {
+ public void signalFinishedToProgramConsumer() {
if (programConsumer != null) {
programConsumer.finished(reporter);
}
@@ -133,9 +151,6 @@
// Defines try-with-resources rewriter behavior.
public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto;
- // Application writing mode.
- public OutputMode outputMode = OutputMode.Indexed;
-
public boolean useTreeShaking = true;
public boolean useDiscardedChecker = true;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java b/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
deleted file mode 100644
index 1e1037e..0000000
--- a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
+++ /dev/null
@@ -1,46 +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 static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Set;
-import java.util.zip.ZipOutputStream;
-
-public class ZipFileOutputSink extends FileSystemOutputSink {
-
- private final ZipOutputStream outputStream;
-
- public ZipFileOutputSink(Path outputPath, InternalOptions options) throws IOException {
- super(options);
- outputStream = new ZipOutputStream(
- Files.newOutputStream(outputPath, StandardOpenOption.CREATE,
- StandardOpenOption.TRUNCATE_EXISTING));
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
- throws IOException {
- writeToZipFile(getOutputFileName(fileId), contents);
- }
-
- @Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- writeToZipFile(getOutputFileName(primaryClassName, DEX_EXTENSION), contents);
- }
-
- @Override
- public void close() throws IOException {
- outputStream.close();
- }
-
- private synchronized void writeToZipFile(String outputPath, byte[] content) throws IOException {
- ZipUtils.writeToZipStream(outputStream, outputPath, content);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 13e2438..29ece33 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -53,9 +53,9 @@
}
@Override
- void build(Path testJarFile, Path out) throws Throwable {
+ void build(Path testJarFile, Path out, OutputMode mode) throws Throwable {
Map<String, Resource> files = compileClassesTogether(testJarFile, null);
- mergeClassFiles(Lists.newArrayList(files.values()), out);
+ mergeClassFiles(Lists.newArrayList(files.values()), out, mode);
}
// Dex classes separately.
@@ -180,7 +180,11 @@
}
Resource mergeClassFiles(List<Resource> dexFiles, Path out) throws Throwable {
- D8Command.Builder builder = D8Command.builder();
+ return mergeClassFiles(dexFiles, out, OutputMode.Indexed);
+ }
+
+ Resource mergeClassFiles(List<Resource> dexFiles, Path out, OutputMode mode) throws Throwable {
+ D8Command.Builder builder = D8Command.builder().setOutputMode(mode);
for (Resource dexFile : dexFiles) {
builder.addDexProgramData(readResource(dexFile), dexFile.getOrigin());
}
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 5351152..77df3ff 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.OutputMode;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
@@ -44,10 +43,9 @@
return withBuilderTransformation(b -> b.addClasspathFiles(classpath));
}
-
@Override
- void build(Path inputFile, Path out) throws Throwable {
- D8Command.Builder builder = D8Command.builder();
+ void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
+ D8Command.Builder builder = D8Command.builder().setOutputMode(mode);
for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
@@ -631,11 +629,10 @@
test(packageName + "intermediate", packageName, "N/A")
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withMinApiLevel(minApi)
- .withOptionConsumer(option -> option.outputMode = outputMode)
.withIntermediate(true);
Path intermediateDex =
temp.getRoot().toPath().resolve(packageName + "intermediate" + ZIP_EXTENSION);
- intermediate.build(input, intermediateDex);
+ intermediate.build(input, intermediateDex, outputMode);
TestRunner<?> end =
test(packageName + "dex", packageName, "N/A")
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index a7354e2..936a21f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.OutputMode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
@@ -58,8 +59,8 @@
}
@Override
- void build(Path inputFile, Path out) throws Throwable {
- R8Command.Builder builder = R8Command.builder();
+ void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
+ R8Command.Builder builder = R8Command.builder().setOutputMode(mode);
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 0963631..bb0c129 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -256,7 +256,7 @@
case R8: {
ToolHelper.runR8(R8Command.builder()
.addProgramFiles(getInputFile())
- .setOutputPath(output == Output.CF ? null : getOutputFile())
+ .setOutputPath(getOutputFile())
.setMode(mode)
.build(),
options -> {
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 7af3b05..97108e1 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -33,12 +33,11 @@
AndroidApp input = AndroidApp.fromProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"));
ExecutorService executorService = Executors.newSingleThreadExecutor();
Timing timing = new Timing("R8UnreachableCodeTest");
+ InternalOptions options = new InternalOptions();
+ options.programConsumer = DexIndexedConsumer.emptyConsumer();
DirectMappedDexApplication application =
- new ApplicationReader(input, new InternalOptions(), timing)
- .read(executorService)
- .toDirect();
- IRConverter converter =
- new IRConverter(new AppInfoWithSubtyping(application), new InternalOptions());
+ new ApplicationReader(input, options, timing).read(executorService).toDirect();
+ IRConverter converter = new IRConverter(new AppInfoWithSubtyping(application), options);
converter.optimize(application);
DexProgramClass clazz = application.classes().iterator().next();
assertEquals(4, clazz.directMethods().length);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 3bd3086..2a313a3 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
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 com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -167,7 +168,11 @@
return self();
}
- abstract void build(Path inputFile, Path out) throws Throwable;
+ void build(Path inputFile, Path out) throws Throwable {
+ build(inputFile, out, OutputMode.Indexed);
+ }
+
+ abstract void build(Path inputFile, Path out, OutputMode mode) throws Throwable;
}
private static List<String> minSdkErrorExpected =
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 46a769d..9c88029 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
@@ -706,8 +706,8 @@
if (optionsConsumer != null) {
optionsConsumer.accept(options);
}
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
- R8.runForTesting(app, compatSink, options);
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
+ R8.runForTesting(app, options);
return compatSink.build();
}
@@ -729,8 +729,8 @@
throw new RuntimeException(e);
}
InternalOptions options = command.getInternalOptions();
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
- R8.runForTesting(command.getInputApp(), compatSink, options);
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
+ R8.runForTesting(command.getInputApp(), options);
return compatSink.build();
}
@@ -757,8 +757,8 @@
if (optionsConsumer != null) {
optionsConsumer.accept(options);
}
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
- D8.runForTesting(command.getInputApp(), compatSink, options);
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
+ D8.runForTesting(command.getInputApp(), options);
return compatSink.build();
}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 1f5df06..b99bf6d 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -3,11 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.errors.DexOverflowException;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
@@ -28,9 +29,7 @@
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.IgnoreContentsOutputSink;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThreadUtils;
@@ -131,16 +130,16 @@
classes.forEach(builder::addProgramClass);
DexApplication application = builder.build();
+ CollectInfoConsumer consumer = new CollectInfoConsumer();
InternalOptions options = new InternalOptions(dexItemFactory,
new Reporter(new DefaultDiagnosticsHandler()));
- options.outputMode = OutputMode.FilePerInputClass;
+ options.programConsumer = consumer;
ApplicationWriter writer =
new ApplicationWriter(
application, options, null, null, NamingLens.getIdentityLens(), null, null);
ExecutorService executorService = ThreadUtils.getExecutorService(options);
- CollectInfoOutputSink sink = new CollectInfoOutputSink();
- writer.write(sink, executorService);
- List<Set<String>> generatedDescriptors = sink.getDescriptors();
+ writer.write(executorService);
+ List<Set<String>> generatedDescriptors = consumer.getDescriptors();
// Check all files present.
Assert.assertEquals(NUMBER_OF_FILES, generatedDescriptors.size());
// And each file contains two classes of which one is the shared one.
@@ -151,23 +150,28 @@
}
}
- private static class CollectInfoOutputSink extends IgnoreContentsOutputSink {
+ private static class CollectInfoConsumer implements DexFilePerClassFileConsumer {
private final List<Set<String>> descriptors = new ArrayList<>();
@Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId) {
- throw new Unreachable();
+ public void accept(
+ String primaryClassDescriptor,
+ byte[] data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ addDescriptors(descriptors);
}
- @Override
- public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors,
- String primaryClassName) {
- descriptors.add(classDescriptors);
+ synchronized void addDescriptors(Set<String> descriptors) {
+ this.descriptors.add(descriptors);
}
public List<Set<String>> getDescriptors() {
return descriptors;
}
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 4ac8f0e..2656467 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -20,7 +20,7 @@
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -73,6 +73,7 @@
public final List<IRCode> additionalCode;
public final ValueNumberGenerator valueNumberGenerator;
public final InternalOptions options;
+ public final AndroidAppConsumers consumers;
public TestApplication(
DexApplication application,
@@ -96,6 +97,7 @@
this.additionalCode = additionalCode;
this.valueNumberGenerator = valueNumberGenerator;
this.options = options;
+ consumers = new AndroidAppConsumers(options);
}
public int countArgumentInstructions() {
@@ -118,20 +120,17 @@
private AndroidApp writeDex(DexApplication application, InternalOptions options)
throws DexOverflowException {
try {
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
R8.writeApplication(
Executors.newSingleThreadExecutor(),
application,
- compatSink,
null,
NamingLens.getIdentityLens(),
null,
options,
null);
- options.closeProgramConsumer();
- compatSink.close();
- return compatSink.build();
- } catch (ExecutionException | IOException e) {
+ options.signalFinishedToProgramConsumer();
+ return consumers.build();
+ } catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
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 9d9702f..b86a2cd 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -51,7 +51,7 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
@@ -624,14 +624,13 @@
new ApplicationWriter(
application, options, null, null, NamingLens.getIdentityLens(), null, null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
- AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
+ AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
try {
- writer.write(compatSink, executor);
+ writer.write(executor);
} finally {
executor.shutdown();
}
- compatSink.close();
- options.closeProgramConsumer();
+ options.signalFinishedToProgramConsumer();
return compatSink.build();
}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 64bc82d..7d95e7f 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.errors.DexOverflowException;
@@ -98,8 +100,10 @@
// This returns the full backingstore from MemoryDataStore, which by default is 1024k bytes.
// We process it via our reader and writer to trim it to the exact size and update its checksum.
byte[] data = dataStore.getData();
+ SingleFileConsumer consumer = new SingleFileConsumer();
AndroidApp app = AndroidApp.fromDexProgramData(data);
InternalOptions options = new InternalOptions();
+ options.programConsumer = consumer;
ExecutorService executor = ThreadUtils.getExecutorService(1);
try {
DexApplication dexApp = new ApplicationReader(
@@ -107,29 +111,24 @@
ApplicationWriter writer =
new ApplicationWriter(
dexApp, options, null, null, NamingLens.getIdentityLens(), null, null);
- SingleFileSink sink = new SingleFileSink();
- writer.write(sink, executor);
- return sink.contents;
+ writer.write(executor);
+ return consumer.contents;
} finally {
executor.shutdown();
}
}
- private static class SingleFileSink extends IgnoreContentsOutputSink {
+ private static class SingleFileConsumer implements DexIndexedConsumer {
byte[] contents;
@Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors,
- String primaryClassName) {
- assert contents != null;
- this.contents = contents;
+ public void accept(
+ int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+ contents = data;
}
@Override
- public void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId) {
- assert contents != null;
- this.contents = contents;
- }
+ public void finished(DiagnosticsHandler handler) {}
}
}