Merge "Introduce dedicated proguard usage-info consumer."
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index d18e04f..2ca5e73 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -69,14 +69,6 @@
throws IOException;
/**
- * Provides the raw bytes that would be generated for the <code>-printusage</code> flag.
- * <p>
- * This method is only invoked by R8 and only if R8 is instructed to generate printusage
- * information.
- */
- void writePrintUsedInformation(byte[] contents) throws IOException;
-
- /**
* Provides the raw bytes that would be generated for the <code>-printseeds</code> flag.
* <p>
* This method is only invoked by R8 and only if R8 is instructed to generate seeds information.
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 58e92e7..73a5b1a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -104,7 +104,7 @@
ExecutorService executorService,
DexApplication application,
OutputSink outputSink,
- byte[] deadCode,
+ String deadCode,
NamingLens namingLens,
byte[] proguardSeedsData,
InternalOptions options,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0a5efc5..b5db328 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -560,6 +560,13 @@
internal.inlineAccessors = false;
}
+ // Setup a usage information consumer.
+ if (proguardConfiguration.isPrintUsage()) {
+ internal.usageInformationConsumer = proguardConfiguration.getPrintUsageFile() != null
+ ? new StringConsumer.FileConsumer(proguardConfiguration.getPrintUsageFile())
+ : new StringConsumer.StreamConsumer(StandardOutOrigin.instance(), System.out);
+ }
+
// Amend the proguard-map consumer with options from the proguard configuration.
{
StringConsumer wrappedConsumer;
diff --git a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
new file mode 100644
index 0000000..fb4fa1e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import 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 java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+/**
+ * Interface for receiving usage feedback from R8.
+ *
+ * The data is in the format defined for Proguard's <code>-printusage</code> flag. The information
+ * will be produced if a consumer is provided. A consumer is automatically setup when running R8
+ * with the Proguard <code>-printusage</code> flag set.
+ */
+public interface UsageInformationConsumer {
+
+ /**
+ * Callback to receive the usage-information data.
+ *
+ * <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 data UTF-8 encoded usage information.
+ * @param handler Diagnostics handler for reporting.
+ */
+ void acceptUsageInformation(byte[] data, DiagnosticsHandler handler);
+
+ static EmptyConsumer emptyConsumer() {
+ return EmptyConsumer.EMPTY_CONSUMER;
+ }
+
+ /** Empty consumer to request usage information but ignore the result. */
+ class EmptyConsumer implements UsageInformationConsumer {
+
+ private static EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
+
+ private EmptyConsumer() {}
+
+ @Override
+ public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+ // Ignore content.
+ }
+ }
+
+ /** Forwarding consumer to delegate to an optional existing consumer. */
+ class ForwardingConsumer implements UsageInformationConsumer {
+
+ private final UsageInformationConsumer consumer;
+
+ /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */
+ public ForwardingConsumer(UsageInformationConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.acceptUsageInformation(data, handler);
+ }
+ }
+ }
+
+ /** File consumer to write contents to a file-system file. */
+ class FileConsumer extends ForwardingConsumer {
+
+ private final Path outputPath;
+
+ /** Consumer that writes to {@param outputPath}. */
+ public FileConsumer(Path outputPath) {
+ this(outputPath, null);
+ }
+
+ /** Consumer that forwards to {@param consumer} and also writes to {@param outputPath}. */
+ public FileConsumer(Path outputPath, UsageInformationConsumer consumer) {
+ super(consumer);
+ this.outputPath = outputPath;
+ }
+
+ @Override
+ public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+ super.acceptUsageInformation(data, handler);
+ try {
+ FileUtils.writeToFile(outputPath, null, data);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
+ }
+ }
+ }
+
+ /**
+ * Stream consumer to write contents to an output stream.
+ *
+ * <p>Note: No close events are given to this stream so it should either be a permanent stream or
+ * the closing needs to happen outside of the compilation itself. If the stream is not one of the
+ * standard streams, i.e., System.out or System.err, you should likely implement yor own consumer.
+ */
+ class StreamConsumer extends ForwardingConsumer {
+
+ private final Origin origin;
+ private final OutputStream outputStream;
+
+ /** Consumer that writes to {@param outputStream}. */
+ public StreamConsumer(Origin origin, OutputStream outputStream) {
+ this(origin, outputStream, null);
+ }
+
+ /** Consumer that forwards to {@param consumer} and also writes to {@param outputStream}. */
+ public StreamConsumer(
+ Origin origin, OutputStream outputStream, UsageInformationConsumer consumer) {
+ super(consumer);
+ this.origin = origin;
+ this.outputStream = outputStream;
+ }
+
+ @Override
+ public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
+ super.acceptUsageInformation(data, handler);
+ try {
+ outputStream.write(data);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+ }
+}
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 11de83c..a20f3ed 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -45,7 +45,7 @@
public class ApplicationWriter {
public final DexApplication application;
- public final byte[] deadCode;
+ public final String deadCode;
public final NamingLens namingLens;
public final byte[] proguardSeedsData;
public final InternalOptions options;
@@ -113,7 +113,7 @@
DexApplication application,
InternalOptions options,
Marker marker,
- byte[] deadCode,
+ String deadCode,
NamingLens namingLens,
byte[] proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
@@ -208,10 +208,9 @@
// Wait for all files to be processed before moving on.
ThreadUtils.awaitFutures(dexDataFutures);
- if (options.proguardConfiguration.isPrintUsage()) {
- if (deadCode != null) {
- outputSink.writePrintUsedInformation(deadCode);
- }
+ if (options.usageInformationConsumer != null && deadCode != null) {
+ ExceptionUtils.withConsumeResourceHandler(
+ options.reporter, deadCode, options.usageInformationConsumer);
}
// 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.
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 242add5..c05b025 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import com.google.common.primitives.Bytes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -25,7 +24,7 @@
ProgramClassCollection programClasses;
public final ImmutableSet<DexType> mainDexList;
- public final byte[] deadCode;
+ public final String deadCode;
private final ClassNameMapper proguardMap;
@@ -43,7 +42,7 @@
ClassNameMapper proguardMap,
ProgramClassCollection programClasses,
ImmutableSet<DexType> mainDexList,
- byte[] deadCode,
+ String deadCode,
DexItemFactory dexItemFactory,
DexString highestSortingString,
Timing timing) {
@@ -106,7 +105,7 @@
final Timing timing;
DexString highestSortingString;
- byte[] deadCode;
+ String deadCode;
final Set<DexType> mainDexList = Sets.newIdentityHashSet();
private final Collection<DexProgramClass> synthesizedClasses;
@@ -144,7 +143,7 @@
return self();
}
- public T appendDeadCode(byte[] deadCodeAtAnotherRound) {
+ public T appendDeadCode(String deadCodeAtAnotherRound) {
if (deadCodeAtAnotherRound == null) {
return self();
}
@@ -152,8 +151,8 @@
this.deadCode = deadCodeAtAnotherRound;
return self();
}
- // Concatenate existing byte[] and the given byte[].
- this.deadCode = Bytes.concat(this.deadCode, deadCodeAtAnotherRound);
+ // Concatenate existing deadCode info with next round.
+ this.deadCode += deadCodeAtAnotherRound;
return self();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f67e808..1e0b1e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -24,7 +24,7 @@
private DirectMappedDexApplication(ClassNameMapper proguardMap,
ProgramClassCollection programClasses,
ImmutableMap<DexType, DexLibraryClass> libraryClasses,
- ImmutableSet<DexType> mainDexList, byte[] deadCode,
+ ImmutableSet<DexType> mainDexList, String deadCode,
DexItemFactory dexItemFactory, DexString highestSortingString,
Timing timing) {
super(proguardMap, programClasses, mainDexList, deadCode,
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index c35b10e..97911a1 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -27,7 +27,7 @@
ProgramClassCollection programClasses,
ClasspathClassCollection classpathClasses,
LibraryClassCollection libraryClasses,
- ImmutableSet<DexType> mainDexList, byte[] deadCode,
+ ImmutableSet<DexType> mainDexList, String deadCode,
DexItemFactory dexItemFactory, DexString highestSortingString,
Timing timing) {
super(proguardMap, programClasses, mainDexList, deadCode,
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 8adfc10..f496133 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -51,7 +51,7 @@
}
DexApplication result;
try {
- result = removeUnused(application).appendDeadCode(usagePrinter.toByteArray()).build();
+ result = removeUnused(application).appendDeadCode(usagePrinter.toStringContent()).build();
} finally {
application.timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
index 7e41671..30021c1 100644
--- a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
+++ b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
@@ -21,6 +21,10 @@
writer = new StringBuilder();
}
+ String toStringContent() {
+ return writer.toString();
+ }
+
byte[] toByteArray() {
return writer.toString().getBytes(StandardCharsets.UTF_8);
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index a45dbe9..84c7e2d 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -8,6 +8,7 @@
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;
@@ -22,10 +23,13 @@
private boolean closed = false;
private StringConsumer proguardMapConsumer = null;
+ private StringConsumer usageInformationConsumer = null;
public AndroidAppOutputSink(OutputSink forwardTo, InternalOptions options) {
super(forwardTo);
options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer);
+ options.usageInformationConsumer =
+ wrapUsageInformationConsumer(options.usageInformationConsumer);
}
public AndroidAppOutputSink() {
@@ -38,15 +42,29 @@
proguardMapConsumer =
new StringConsumer.ForwardingConsumer(consumer) {
@Override
- public void accept(String data, DiagnosticsHandler handler) {
- super.accept(data, handler);
- builder.setProguardMapData(data);
+ 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 {
@@ -76,12 +94,6 @@
}
@Override
- public void writePrintUsedInformation(byte[] contents) throws IOException {
- builder.setDeadCode(contents);
- super.writePrintUsedInformation(contents);
- }
-
- @Override
public void writeProguardSeedsFile(byte[] contents) throws IOException {
builder.setProguardSeedsData(contents);
super.writeProguardSeedsFile(contents);
diff --git a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
index 0941b75..6623bda 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -34,12 +34,6 @@
return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + extension;
}
-
- @Override
- public void writePrintUsedInformation(byte[] contents) throws IOException {
- FileUtils.writeToFile(options.proguardConfiguration.getPrintUsageFile(), System.out, contents);
- }
-
@Override
public void writeProguardSeedsFile(byte[] contents) throws IOException {
FileUtils.writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
diff --git a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
index d6867eb..31191e3 100644
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
@@ -39,11 +39,6 @@
}
@Override
- public void writePrintUsedInformation(byte[] contents) throws IOException {
- forwardTo.writePrintUsedInformation(contents);
- }
-
- @Override
public void writeProguardSeedsFile(byte[] contents) throws IOException {
forwardTo.writeProguardSeedsFile(contents);
}
diff --git a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
index d0eb364..8359244 100644
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
@@ -26,11 +26,6 @@
}
@Override
- public void writePrintUsedInformation(byte[] contents) {
- // Intentionally left empty.
- }
-
- @Override
public void writeProguardSeedsFile(byte[] contents) {
// 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 c54317a..6c2b63e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -175,6 +175,10 @@
// If non null it must be and passed to the consumer.
public StringConsumer proguardMapConsumer = null;
+ // If null, no usage information needs to be computed.
+ // If non-null, it must be and is passed to the consumer.
+ public StringConsumer usageInformationConsumer = null;
+
public Path proguardCompatibilityRulesOutput = null;
public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {