Introduce dedicated proguard-map consumer.

This is the first change in a series of changes splitting the OutputSink in to several independent consumers.

Change-Id: I5f5cbf3c68d16f476951cff994ac2c27509021d1
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7d37f43..adb780e 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -93,7 +93,8 @@
     try {
       try {
         InternalOptions options = command.getInternalOptions();
-        AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink());
+        AndroidAppOutputSink compatSink =
+            new AndroidAppOutputSink(command.getOutputSink(), options);
         CompilationResult result =
             run(command.getInputApp(), compatSink, options, executor);
         assert result != null;
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index a73391a..d18e04f 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -77,14 +77,6 @@
   void writePrintUsedInformation(byte[] contents) throws IOException;
 
   /**
-   * Provides the raw bytes that would be generated for the <code>-printmapping</code> flag.
-   * <p>
-   * This method is only invoked by R8 and only if R8 is instructed to generate a proguard map and
-   * if such map is non-empty.
-   */
-  void writeProguardMapFile(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 627f3f7..58e92e7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -493,7 +493,7 @@
   public static AndroidApp runInternal(R8Command command, ExecutorService executor)
       throws IOException, CompilationException {
     InternalOptions options = command.getInternalOptions();
-    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink());
+    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
     run(command.getInputApp(), compatSink, options, executor);
     return compatSink.build();
   }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 171d546..9a3c388 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.origin.StandardOutOrigin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -270,25 +271,29 @@
       boolean useDiscardedChecker = discardedChecker.orElse(true);
       boolean useMinification = minification.orElse(configuration.isObfuscating());
 
-      R8Command command = new R8Command(
-          getAppBuilder().build(),
-          getOutputPath(),
-          getOutputMode(),
-          mainDexKeepRules,
-          mainDexListOutput,
-          configuration,
-          getMode(),
-          getMinApiLevel(),
-          reporter,
-          getEnableDesugaring(),
-          useTreeShaking,
-          useDiscardedChecker,
-          useMinification,
-          ignoreMissingClasses,
-          forceProguardCompatibility,
-          ignoreMissingClassesWhenNotShrinking,
-          proguardMapOutput,
-          proguardCompatibilityRulesOutput);
+      Utf8Consumer proguardMapConsumer =
+          proguardMapOutput != null ? new Utf8Consumer.FileConsumer(proguardMapOutput) : null;
+
+      R8Command command =
+          new R8Command(
+              getAppBuilder().build(),
+              getOutputPath(),
+              getOutputMode(),
+              mainDexKeepRules,
+              mainDexListOutput,
+              configuration,
+              getMode(),
+              getMinApiLevel(),
+              reporter,
+              getEnableDesugaring(),
+              useTreeShaking,
+              useDiscardedChecker,
+              useMinification,
+              ignoreMissingClasses,
+              forceProguardCompatibility,
+              ignoreMissingClassesWhenNotShrinking,
+              proguardMapConsumer,
+              proguardCompatibilityRulesOutput);
 
       return command;
     }
@@ -332,7 +337,7 @@
   private final boolean ignoreMissingClasses;
   private final boolean forceProguardCompatibility;
   private final boolean ignoreMissingClassesWhenNotShrinking;
-  private final Path proguardMapOutput;
+  private final Utf8Consumer proguardMapConsumer;
   private final Path proguardCompatibilityRulesOutput;
 
   public static Builder builder() {
@@ -468,7 +473,7 @@
       boolean ignoreMissingClasses,
       boolean forceProguardCompatibility,
       boolean ignoreMissingClassesWhenNotShrinking,
-      Path proguardMapOutput,
+      Utf8Consumer proguardMapConsumer,
       Path proguardCompatibilityRulesOutput) {
     super(inputApp, outputPath, outputMode, mode, minApiLevel, reporter,
         enableDesugaring);
@@ -484,7 +489,7 @@
     this.ignoreMissingClasses = ignoreMissingClasses;
     this.forceProguardCompatibility = forceProguardCompatibility;
     this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking;
-    this.proguardMapOutput = proguardMapOutput;
+    this.proguardMapConsumer = proguardMapConsumer;
     this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
   }
 
@@ -499,7 +504,7 @@
     ignoreMissingClasses = false;
     forceProguardCompatibility = false;
     ignoreMissingClassesWhenNotShrinking = false;
-    proguardMapOutput = null;
+    proguardMapConsumer = null;
     proguardCompatibilityRulesOutput = null;
   }
   public boolean useTreeShaking() {
@@ -554,7 +559,26 @@
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.inlineAccessors = false;
     }
-    internal.proguardMapOutput = proguardMapOutput;
+
+    // Amend the proguard-map consumer with options from the proguard configuration.
+    {
+      Utf8Consumer wrappedConsumer;
+      if (proguardConfiguration.isPrintMapping()) {
+        if (proguardConfiguration.getPrintMappingFile() != null) {
+          wrappedConsumer =
+              new Utf8Consumer.FileConsumer(
+                  proguardConfiguration.getPrintMappingFile(), proguardMapConsumer);
+        } else {
+          wrappedConsumer =
+              new Utf8Consumer.StreamConsumer(
+                  StandardOutOrigin.instance(), System.out, proguardMapConsumer);
+        }
+      } else {
+        wrappedConsumer = proguardMapConsumer;
+      }
+      internal.proguardMapConsumer = wrappedConsumer;
+    }
+
     internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
 
     // EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/Utf8Consumer.java b/src/main/java/com/android/tools/r8/Utf8Consumer.java
new file mode 100644
index 0000000..4da33d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/Utf8Consumer.java
@@ -0,0 +1,123 @@
+// 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 UTF8 encoded text data. */
+public interface Utf8Consumer {
+
+  /**
+   * Callback to receive UTF8 encoded text 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 text data.
+   * @param handler Diagnostics handler for reporting.
+   */
+  void accept(byte[] data, DiagnosticsHandler handler);
+
+  static EmptyConsumer emptyConsumer() {
+    return EmptyConsumer.EMPTY_CONSUMER;
+  }
+
+  /** Empty consumer to request the production of the resource but ignore its value. */
+  class EmptyConsumer implements Utf8Consumer {
+
+    private static EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
+
+    @Override
+    public void accept(byte[] data, DiagnosticsHandler handler) {
+      // Ignore content.
+    }
+  }
+
+  /** Forwarding consumer to delegate to an optional existing consumer. */
+  class ForwardingConsumer implements Utf8Consumer {
+
+    private final Utf8Consumer consumer;
+
+    /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */
+    public ForwardingConsumer(Utf8Consumer consumer) {
+      this.consumer = consumer;
+    }
+
+    @Override
+    public void accept(byte[] data, DiagnosticsHandler handler) {
+      if (consumer != null) {
+        consumer.accept(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, Utf8Consumer consumer) {
+      super(consumer);
+      this.outputPath = outputPath;
+    }
+
+    @Override
+    public void accept(byte[] data, DiagnosticsHandler handler) {
+      super.accept(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, Utf8Consumer consumer) {
+      super(consumer);
+      this.origin = origin;
+      this.outputStream = outputStream;
+    }
+
+    @Override
+    public void accept(byte[] data, DiagnosticsHandler handler) {
+      super.accept(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 f08048c..6c54510 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 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;
@@ -214,12 +215,11 @@
       }
       // 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.
-      if (proguardMapSupplier != null
-          && (options.proguardMapOutput != null
-              || options.proguardConfiguration.isPrintMapping())) {
+      if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
         byte[] proguardMapResult = proguardMapSupplier.get();
         if (proguardMapResult != null) {
-          outputSink.writeProguardMapFile(proguardMapResult);
+          ExceptionUtils.withConsumeResourceHandler(
+              options.reporter, proguardMapResult, options.proguardMapConsumer);
         }
       }
       if (options.proguardConfiguration.isPrintSeeds()) {
diff --git a/src/main/java/com/android/tools/r8/origin/StandardOutOrigin.java b/src/main/java/com/android/tools/r8/origin/StandardOutOrigin.java
new file mode 100644
index 0000000..6c6f347
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/origin/StandardOutOrigin.java
@@ -0,0 +1,22 @@
+// 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.origin;
+
+public class StandardOutOrigin extends Origin {
+
+  private static StandardOutOrigin INSTANCE = new StandardOutOrigin();
+
+  public static StandardOutOrigin instance() {
+    return INSTANCE;
+  }
+
+  private StandardOutOrigin() {
+    super(Origin.root());
+  }
+
+  @Override
+  public String part() {
+    return "stdout";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 8fe9355..fe95e2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -116,6 +116,7 @@
     }
 
     public void setPrintMappingFile(Path file) {
+      assert printMapping;
       this.printMappingFile = file;
     }
 
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 62a5e76..1acfdd8 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -3,7 +3,9 @@
 // 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.Utf8Consumer;
 import com.android.tools.r8.origin.Origin;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -19,14 +21,32 @@
   private final List<DescriptorsWithContents> classFiles = new ArrayList<>();
   private boolean closed = false;
 
-  public AndroidAppOutputSink(OutputSink forwardTo) {
+  private Utf8Consumer proguardMapConsumer = null;
+
+  public AndroidAppOutputSink(OutputSink forwardTo, InternalOptions options) {
     super(forwardTo);
+    options.proguardMapConsumer = wrapProguardMapConsumer(options.proguardMapConsumer);
   }
 
   public AndroidAppOutputSink() {
     super(new IgnoreContentsOutputSink());
   }
 
+  private Utf8Consumer wrapProguardMapConsumer(Utf8Consumer consumer) {
+    assert proguardMapConsumer == null;
+    if (consumer != null) {
+      proguardMapConsumer =
+          new Utf8Consumer.ForwardingConsumer(consumer) {
+            @Override
+            public void accept(byte[] data, DiagnosticsHandler handler) {
+              super.accept(data, handler);
+              builder.setProguardMapData(data);
+            }
+          };
+    }
+    return proguardMapConsumer;
+  }
+
   @Override
   public synchronized void writeDexFile(byte[] contents, Set<String> classDescriptors, int fileId)
       throws IOException {
@@ -62,12 +82,6 @@
   }
 
   @Override
-  public void writeProguardMapFile(byte[] contents) throws IOException {
-    builder.setProguardMapData(contents);
-    super.writeProguardMapFile(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/DirectoryOutputSink.java b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
index 6c1090a..0bca98a 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
@@ -40,7 +40,7 @@
       throws IOException {
     Path target = outputDirectory.resolve(getOutputFileName(fileId));
     Files.createDirectories(target.getParent());
-    writeToFile(target, null, contents);
+    FileUtils.writeToFile(target, null, contents);
   }
 
   @Override
@@ -59,7 +59,7 @@
       throws IOException {
     Path target = outputDirectory.resolve(getOutputFileName(descriptor, extension));
     Files.createDirectories(target.getParent());
-    writeToFile(target, null, contents);
+    FileUtils.writeToFile(target, null, contents);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
new file mode 100644
index 0000000..9e061c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -0,0 +1,19 @@
+// 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.Utf8Consumer;
+
+public abstract class ExceptionUtils {
+
+  public static void withConsumeResourceHandler(
+      Reporter reporter, byte[] data, Utf8Consumer consumer) {
+    // Unchecked exceptions simply propagate out, aborting the compilation forcefully.
+    consumer.accept(data, reporter);
+    // Fail fast for now. We might consider delaying failure since consumer failure does not affect
+    // the compilation. We might need to be careful to correctly identify errors so as to exit
+    // compilation with an error code.
+    reporter.failIfPendingErrors();
+  }
+}
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 1ca0cf9..0941b75 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -4,11 +4,8 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.OutputSink;
-import com.google.common.io.Closer;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 
 public abstract class FileSystemOutputSink implements OutputSink {
 
@@ -40,42 +37,17 @@
 
   @Override
   public void writePrintUsedInformation(byte[] contents) throws IOException {
-    writeToFile(options.proguardConfiguration.getPrintUsageFile(), System.out, contents);
-  }
-
-  @Override
-  public void writeProguardMapFile(byte[] contents) throws IOException {
-    if (options.proguardConfiguration.getPrintMappingFile() != null) {
-      writeToFile(options.proguardConfiguration.getPrintMappingFile(), System.out, contents);
-    }
-    if (options.proguardMapOutput != null) {
-      writeToFile(options.proguardMapOutput, System.out, contents);
-    }
+    FileUtils.writeToFile(options.proguardConfiguration.getPrintUsageFile(), System.out, contents);
   }
 
   @Override
   public void writeProguardSeedsFile(byte[] contents) throws IOException {
-    writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
+    FileUtils.writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
   }
 
   @Override
   public void writeMainDexListFile(byte[] contents) throws IOException {
-    writeToFile(options.printMainDexListFile, System.out, contents);
-  }
-
-  protected void writeToFile(Path output, OutputStream defValue, byte[] contents)
-      throws IOException {
-    try (Closer closer = Closer.create()) {
-      OutputStream outputStream =
-          FileUtils.openPathWithDefault(
-              closer,
-              output,
-              defValue,
-              StandardOpenOption.CREATE,
-              StandardOpenOption.TRUNCATE_EXISTING,
-              StandardOpenOption.WRITE);
-      outputStream.write(contents);
-    }
+    FileUtils.writeToFile(options.printMainDexListFile, System.out, contents);
   }
 
   protected OutputMode getOutputMode() {
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 85bb29e..580b7b7 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -11,6 +11,7 @@
 import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -149,4 +150,19 @@
     }
     return true;
   }
+
+  public static void writeToFile(Path output, OutputStream defValue, byte[] contents)
+      throws IOException {
+    try (Closer closer = Closer.create()) {
+      OutputStream outputStream =
+          openPathWithDefault(
+              closer,
+              output,
+              defValue,
+              StandardOpenOption.CREATE,
+              StandardOpenOption.TRUNCATE_EXISTING,
+              StandardOpenOption.WRITE);
+      outputStream.write(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 f176329..d6867eb 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 writeProguardMapFile(byte[] contents) throws IOException {
-    forwardTo.writeProguardMapFile(contents);
-  }
-
-  @Override
   public void writeProguardSeedsFile(byte[] contents) throws IOException {
     forwardTo.writeProguardSeedsFile(contents);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java
index 3ebb621..642f2d7 100644
--- a/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/IOExceptionDiagnostic.java
@@ -31,7 +31,7 @@
     message = extractMessage(e);
   }
 
-  private String extractMessage(IOException e) {
+  private static String extractMessage(IOException e) {
     String message = e.getMessage();
     if (message == null || message.isEmpty()) {
       if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
@@ -43,7 +43,7 @@
     return message;
   }
 
-  private Origin extractOrigin(IOException e) {
+  private static Origin extractOrigin(IOException e) {
     Origin origin = Origin.unknown();
 
     if (e instanceof FileSystemException) {
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 384c7d5..d0eb364 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 writeProguardMapFile(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 7eec141..457f0ba 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.Utf8Consumer;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -170,7 +171,9 @@
   // the code contains unsupported byte codes.
   public boolean skipReadingDexCode = false;
 
-  public Path proguardMapOutput = null;
+  // If null, no proguad map needs to be computed.
+  // If non null it must be and passed to the consumer.
+  public Utf8Consumer proguardMapConsumer = null;
 
   public Path proguardCompatibilityRulesOutput = null;
 
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9e54813..208a8f9 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -706,7 +706,7 @@
     if (optionsConsumer != null) {
       optionsConsumer.accept(options);
     }
-    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink());
+    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
     R8.runForTesting(app, compatSink, options);
     return compatSink.build();
   }
@@ -728,8 +728,9 @@
     } catch (CompilationFailedException e) {
       throw new RuntimeException(e);
     }
-    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink());
-    R8.runForTesting(command.getInputApp(), compatSink, command.getInternalOptions());
+    InternalOptions options = command.getInternalOptions();
+    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
+    R8.runForTesting(command.getInputApp(), compatSink, options);
     return compatSink.build();
   }
 
@@ -756,7 +757,7 @@
     if (optionsConsumer != null) {
       optionsConsumer.accept(options);
     }
-    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink());
+    AndroidAppOutputSink compatSink = new AndroidAppOutputSink(command.getOutputSink(), options);
     D8.runForTesting(command.getInputApp(), compatSink, options);
     return compatSink.build();
   }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
index b1bf712..00a1bcd 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.Utf8Consumer;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -23,21 +24,26 @@
     // First compilation.
     AndroidApp app = AndroidApp.fromProgramDirectory(Paths.get(GMSCORE_V7_DIR));
     AndroidApp app1 =
-        ToolHelper.runR8(app, options -> {
-          options.minApiLevel = AndroidApiLevel.L.getLevel();
-          options.proguardMapOutput = Paths.get("this-path-is-ignored-anyway.map");
-        });
+        ToolHelper.runR8(
+            app,
+            options -> {
+              options.minApiLevel = AndroidApiLevel.L.getLevel();
+              options.proguardMapConsumer = Utf8Consumer.emptyConsumer();
+            });
 
     // Second compilation.
     // Add option --skip-outline-opt for second compilation. The second compilation can find
     // additional outlining opportunities as member rebinding from the first compilation can move
     // methods.
     // See b/33410508 and b/33475705.
-    AndroidApp app2 = ToolHelper.runR8(app1, options -> {
-      options.outline.enabled = false;
-      options.minApiLevel = AndroidApiLevel.L.getLevel();
-      options.proguardMapOutput = Paths.get("this-path-is-ignored-anyway.map");
-    });
+    AndroidApp app2 =
+        ToolHelper.runR8(
+            app1,
+            options -> {
+              options.outline.enabled = false;
+              options.minApiLevel = AndroidApiLevel.L.getLevel();
+              options.proguardMapConsumer = Utf8Consumer.emptyConsumer();
+            });
 
     // TODO: Require that the results of the two compilations are the same.
     assertEquals(