Introduce dedicated main-dex-list consumer.

Change-Id: I714cafcd816f9fa733b891edea3fec34ea3edaa8
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3ddf91d..4a3fc2e 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -16,10 +16,6 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.file.Files;
-import java.nio.file.StandardOpenOption;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -50,13 +46,8 @@
         .sorted()
         .collect(Collectors.toList());
 
-    if (options.printMainDexListFile != null) {
-      try (OutputStream mainDexOut = Files.newOutputStream(options.printMainDexListFile,
-          StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
-        PrintWriter writer = new PrintWriter(mainDexOut);
-        result.forEach(writer::println);
-        writer.flush();
-      }
+    if (options.mainDexListConsumer != null) {
+      options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
     }
 
     return result;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index a50eb2b..5b66b6e 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -25,14 +25,15 @@
 public class GenerateMainDexListCommand extends BaseCommand {
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
-  private final Path mainDexListOutput;
+  private final StringConsumer mainDexListConsumer;
   private final DexItemFactory factory;
 
-  /**
-   * Get the output path for the main-dex list. Null if not set.
-   */
+  /** Get the output path for the main-dex list. Null if not set. */
+  @Deprecated
   public Path getMainDexListOutputPath() {
-    return mainDexListOutput;
+    return mainDexListConsumer instanceof StringConsumer.FileConsumer
+        ? ((StringConsumer.FileConsumer) mainDexListConsumer).getOutputPath()
+        : null;
   }
 
   public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
@@ -114,11 +115,11 @@
         mainDexKeepRules = parser.getConfig().getRules();
       }
 
+      StringConsumer mainDexListConsumer =
+          mainDexListOutput != null ? new StringConsumer.FileConsumer(mainDexListOutput) : null;
+
       return new GenerateMainDexListCommand(
-          factory,
-          getAppBuilder().build(),
-          mainDexKeepRules,
-          mainDexListOutput);
+          factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer);
     }
   }
 
@@ -173,18 +174,18 @@
       DexItemFactory factory,
       AndroidApp inputApp,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
-      Path mainDexListOutput) {
+      StringConsumer mainDexListConsumer) {
     super(inputApp);
     this.factory = factory;
     this.mainDexKeepRules = mainDexKeepRules;
-    this.mainDexListOutput = mainDexListOutput;
+    this.mainDexListConsumer = mainDexListConsumer;
   }
 
   private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     this.factory = new DexItemFactory();
     this.mainDexKeepRules = ImmutableList.of();
-    this.mainDexListOutput = null;
+    this.mainDexListConsumer = null;
   }
 
   @Override
@@ -192,9 +193,7 @@
     InternalOptions internal =
         new InternalOptions(factory, new Reporter(new DefaultDiagnosticsHandler()));
     internal.mainDexKeepRules = mainDexKeepRules;
-    if (mainDexListOutput != null) {
-      internal.printMainDexListFile = mainDexListOutput;
-    }
+    internal.mainDexListConsumer = mainDexListConsumer;
     internal.minimalMainDex = internal.debug;
     internal.removeSwitchMaps = false;
     internal.inlineAccessors = false;
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index 2ca5e73..316df01 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -76,15 +76,6 @@
   void writeProguardSeedsFile(byte[] contents) throws IOException;
 
   /**
-   * Provides the raw bytes that would be generated by R8 or an R8-based helper tool when instructed
-   * to generate a main-dex list.
-   * <p>
-   * This method is only invoked by R8 or R8-based tools and only if R8 is instructed to generate
-   * a main-dex list.
-   */
-  void writeMainDexListFile(byte[] contents) throws IOException;
-
-  /**
    * Closes the output sink.
    * <p>
    * This method is invokes once all output has been generated.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index b5db328..d2455f3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -271,6 +271,9 @@
       boolean useDiscardedChecker = discardedChecker.orElse(true);
       boolean useMinification = minification.orElse(configuration.isObfuscating());
 
+      StringConsumer mainDexListConsumer =
+          mainDexListOutput != null ? new StringConsumer.FileConsumer(mainDexListOutput) : null;
+
       StringConsumer proguardMapConsumer =
           proguardMapOutput != null ? new StringConsumer.FileConsumer(proguardMapOutput) : null;
 
@@ -280,7 +283,7 @@
               getOutputPath(),
               getOutputMode(),
               mainDexKeepRules,
-              mainDexListOutput,
+              mainDexListConsumer,
               configuration,
               getMode(),
               getMinApiLevel(),
@@ -329,7 +332,7 @@
       "  --help                   # Print this message."));
 
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
-  private final Path mainDexListOutput;
+  private final StringConsumer mainDexListConsumer;
   private final ProguardConfiguration proguardConfiguration;
   private final boolean useTreeShaking;
   private final boolean useDiscardedChecker;
@@ -461,7 +464,7 @@
       Path outputPath,
       OutputMode outputMode,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
-      Path mainDexListOutput,
+      StringConsumer mainDexListConsumer,
       ProguardConfiguration proguardConfiguration,
       CompilationMode mode,
       int minApiLevel,
@@ -481,7 +484,7 @@
     assert mainDexKeepRules != null;
     assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
     this.mainDexKeepRules = mainDexKeepRules;
-    this.mainDexListOutput = mainDexListOutput;
+    this.mainDexListConsumer = mainDexListConsumer;
     this.proguardConfiguration = proguardConfiguration;
     this.useTreeShaking = useTreeShaking;
     this.useDiscardedChecker = useDiscardedChecker;
@@ -496,7 +499,7 @@
   private R8Command(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     mainDexKeepRules = ImmutableList.of();
-    mainDexListOutput = null;
+    mainDexListConsumer = null;
     proguardConfiguration = null;
     useTreeShaking = false;
     useDiscardedChecker = false;
@@ -549,9 +552,8 @@
     assert !internal.verbose;
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.minimalMainDex = internal.debug;
-    if (mainDexListOutput != null) {
-      internal.printMainDexListFile = mainDexListOutput;
-    }
+    internal.mainDexListConsumer = mainDexListConsumer;
+
     internal.outputMode = getOutputMode();
     if (internal.debug) {
       // TODO(zerny): Should we support removeSwitchMaps in debug mode? b/62936642
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index a26b736..9e5ad47 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -80,12 +80,22 @@
       this.outputPath = outputPath;
     }
 
-    /** Set the encoding. Defaults to UTF8. */
+    /** Get the output path that the consumer will write to. */
+    public Path getOutputPath() {
+      return outputPath;
+    }
+
+    /** Set the output encoding. Defaults to UTF8. */
     public void setEncoding(Charset encoding) {
       assert encoding != null;
       this.encoding = encoding;
     }
 
+    /** Get the output encoding. Defaults to UTF8. */
+    public Charset getEncoding() {
+      return encoding;
+    }
+
     @Override
     public void accept(String string, DiagnosticsHandler handler) {
       super.accept(string, handler);
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 a20f3ed..76c756b 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -30,9 +30,7 @@
 import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ObjectArrays;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -223,9 +221,9 @@
           outputSink.writeProguardSeedsFile(proguardSeedsData);
         }
       }
-      byte[] mainDexList = writeMainDexList();
-      if (mainDexList != null) {
-        outputSink.writeMainDexListFile(mainDexList);
+      if (options.mainDexListConsumer != null) {
+        ExceptionUtils.withConsumeResourceHandler(
+            options.reporter, writeMainDexList(), options.mainDexListConsumer);
       }
     } finally {
       application.timing.end();
@@ -342,16 +340,9 @@
         .replace('.', '/') + ".class";
   }
 
-  private byte[] writeMainDexList() {
-    if (application.mainDexList.isEmpty()) {
-      return null;
-    }
-    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-    PrintWriter writer = new PrintWriter(bytes);
-    application.mainDexList.forEach(
-        type -> writer.println(mapMainDexListName(type))
-    );
-    writer.flush();
-    return bytes.toByteArray();
+  private String writeMainDexList() {
+    StringBuilder builder = new StringBuilder();
+    application.mainDexList.forEach(type -> builder.append(mapMainDexListName(type)).append('\n'));
+    return builder.toString();
   }
 }
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 84c7e2d..23580a5 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -22,11 +22,13 @@
   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);
@@ -36,6 +38,21 @@
     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) {
@@ -100,12 +117,6 @@
   }
 
   @Override
-  public void writeMainDexListFile(byte[] contents) throws IOException {
-    builder.setMainDexListOutputData(contents);
-    super.writeMainDexListFile(contents);
-  }
-
-  @Override
   public void close() throws IOException {
     assert !closed;
     if (!dexFilesWithPrimary.isEmpty()) {
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 6623bda..6e5b33f 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -39,11 +39,6 @@
     FileUtils.writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
   }
 
-  @Override
-  public void writeMainDexListFile(byte[] contents) throws IOException {
-    FileUtils.writeToFile(options.printMainDexListFile, System.out, contents);
-  }
-
   protected OutputMode getOutputMode() {
     return options.outputMode;
   }
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 31191e3..a6fb545 100644
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
@@ -44,11 +44,6 @@
   }
 
   @Override
-  public void writeMainDexListFile(byte[] contents) throws IOException {
-    forwardTo.writeMainDexListFile(contents);
-  }
-
-  @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
index 8359244..21f98f6 100644
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
@@ -31,11 +31,6 @@
   }
 
   @Override
-  public void writeMainDexListFile(byte[] contents) {
-    // 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 6c2b63e..dd9f970 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -120,7 +120,6 @@
 
   public boolean printCfg = false;
   public String printCfgFile;
-  public Path printMainDexListFile;
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
@@ -171,6 +170,10 @@
   // the code contains unsupported byte codes.
   public boolean skipReadingDexCode = false;
 
+  // If null, no main-dex list needs to be computed.
+  // If non null it must be and passed to the consumer.
+  public StringConsumer mainDexListConsumer = null;
+
   // If null, no proguad map needs to be computed.
   // If non null it must be and passed to the consumer.
   public StringConsumer proguardMapConsumer = null;