Merge "Add support for Proguard option -printconfiguration"
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5f27356..b5ec328 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -673,6 +673,13 @@
       internal.outline.enabled = false;
     }
 
+    // Setup a configuration consumer.
+    if (proguardConfiguration.isPrintConfiguration()) {
+      internal.configurationConsumer = proguardConfiguration.getPrintConfigurationFile() != null
+          ? new StringConsumer.FileConsumer(proguardConfiguration.getPrintConfigurationFile())
+          : new StringConsumer.StreamConsumer(StandardOutOrigin.instance(), System.out);
+    }
+
     // Setup a usage information consumer.
     if (proguardConfiguration.isPrintUsage()) {
       internal.usageInformationConsumer = proguardConfiguration.getPrintUsageFile() != null
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 b413bfe..30664a2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -260,6 +260,11 @@
       String deadCode,
       ProguardMapSupplier proguardMapSupplier,
       String proguardSeedsData) {
+    if (options.configurationConsumer != null) {
+      ExceptionUtils.withConsumeResourceHandler(
+          options.reporter, options.configurationConsumer,
+          options.proguardConfiguration.getParsedConfiguration());
+    }
     if (options.usageInformationConsumer != null && deadCode != null) {
       ExceptionUtils.withConsumeResourceHandler(
           options.reporter, options.usageInformationConsumer, deadCode);
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 c4a4478..2a31604a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -20,6 +20,7 @@
 
   public static class Builder {
 
+    private final List<String> parsedConfiguration = new ArrayList<>();
     private final List<FilteredClassPath> injars = new ArrayList<>();
     private final List<FilteredClassPath> libraryjars = new ArrayList<>();
     private final Reporter reporter;
@@ -30,6 +31,8 @@
     private boolean optimizing = true;
     private boolean obfuscating = true;
     private boolean shrinking = true;
+    private boolean printConfiguration;
+    private Path printConfigurationFile;
     private boolean printUsage;
     private Path printUsageFile;
     private boolean printMapping;
@@ -59,6 +62,10 @@
       this.reporter = reporter;
     }
 
+    public void addParsedConfiguration(String source) {
+      parsedConfiguration.add(source);
+    }
+
     public void addInjars(List<FilteredClassPath> injars) {
       this.injars.addAll(injars);
     }
@@ -113,6 +120,15 @@
       shrinking = false;
     }
 
+    public void setPrintConfiguration(boolean printConfiguration) {
+      this.printConfiguration = printConfiguration;
+    }
+
+    public void setPrintConfigurationFile(Path file) {
+      assert printConfiguration;
+      this.printConfigurationFile = file;
+    }
+
     public void setPrintUsage(boolean printUsage) {
       this.printUsage = printUsage;
     }
@@ -217,6 +233,7 @@
     public ProguardConfiguration buildRaw() {
 
       ProguardConfiguration configuration = new ProguardConfiguration(
+          String.join(System.lineSeparator(), parsedConfiguration),
           dexItemFactory,
           injars,
           libraryjars,
@@ -227,6 +244,8 @@
           optimizing,
           obfuscating,
           shrinking,
+          printConfiguration,
+          printConfigurationFile,
           printUsage,
           printUsageFile,
           printMapping,
@@ -279,6 +298,7 @@
     }
   }
 
+  private final String parsedConfiguration;
   private final DexItemFactory dexItemFactory;
   private final ImmutableList<FilteredClassPath> injars;
   private final ImmutableList<FilteredClassPath> libraryjars;
@@ -289,6 +309,8 @@
   private final boolean optimizing;
   private final boolean obfuscating;
   private final boolean shrinking;
+  private final boolean printConfiguration;
+  private final Path printConfigurationFile;
   private final boolean printUsage;
   private final Path printUsageFile;
   private final boolean printMapping;
@@ -310,6 +332,7 @@
   private final ProguardClassFilter adaptClassStrings;
 
   private ProguardConfiguration(
+      String parsedConfiguration,
       DexItemFactory factory,
       List<FilteredClassPath> injars,
       List<FilteredClassPath> libraryjars,
@@ -320,6 +343,8 @@
       boolean optimizing,
       boolean obfuscating,
       boolean shrinking,
+      boolean printConfiguration,
+      Path printConfigurationFile,
       boolean printUsage,
       Path printUsageFile,
       boolean printMapping,
@@ -339,6 +364,7 @@
       boolean useUniqueClassMemberNames,
       boolean keepParameterNames,
       ProguardClassFilter adaptClassStrings) {
+    this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
     this.injars = ImmutableList.copyOf(injars);
     this.libraryjars = ImmutableList.copyOf(libraryjars);
@@ -349,6 +375,8 @@
     this.optimizing = optimizing;
     this.obfuscating = obfuscating;
     this.shrinking = shrinking;
+    this.printConfiguration = printConfiguration;
+    this.printConfigurationFile = printConfigurationFile;
     this.printUsage = printUsage;
     this.printUsageFile = printUsageFile;
     this.printMapping = printMapping;
@@ -378,6 +406,10 @@
     return new Builder(dexItemFactory, reporter);
   }
 
+  public String getParsedConfiguration() {
+    return parsedConfiguration;
+  }
+
   public DexItemFactory getDexItemFactory() {
     return dexItemFactory;
   }
@@ -434,6 +466,14 @@
     return shrinking;
   }
 
+  public boolean isPrintConfiguration() {
+    return printConfiguration;
+  }
+
+  public Path getPrintConfigurationFile() {
+    return printConfigurationFile;
+  }
+
   public boolean isPrintUsage() {
     return printUsage;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index c1af9f4..3d426dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -70,7 +70,6 @@
       "whyarenotsimple");
 
   private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
-      "printconfiguration",
       // TODO -outjars (http://b/37137994) and -adaptresourcefilecontents (http://b/37139570)
       // should be reported as errors, not just as warnings!
       "outjars",
@@ -165,6 +164,7 @@
     private final String name;
     private final String contents;
     private int position = 0;
+    private int positionAfterInclude = 0;
     private int line = 1;
     private int lineStartPosition = 0;
     private Path baseDirectory;
@@ -181,6 +181,9 @@
       do {
         skipWhitespace();
       } while (parseOption());
+      // Collect the parsed configuration.
+      configurationBuilder.addParsedConfiguration(
+          contents.substring(positionAfterInclude, contents.length()));
     }
 
     private boolean parseOption()
@@ -283,6 +286,12 @@
         configurationBuilder.setOverloadAggressively(true);
       } else if (acceptString("allowaccessmodification")) {
         configurationBuilder.setAllowAccessModification(true);
+      } else if (acceptString("printconfiguration")) {
+        configurationBuilder.setPrintConfiguration(true);
+        skipWhitespace();
+        if (isOptionalArgumentGiven()) {
+          configurationBuilder.setPrintConfigurationFile(parseFileName());
+        }
       } else if (acceptString("printmapping")) {
         configurationBuilder.setPrintMapping(true);
         skipWhitespace();
@@ -298,8 +307,12 @@
         ProguardAssumeValuesRule rule = parseAssumeValuesRule();
         configurationBuilder.addRule(rule);
       } else if (acceptString("include")) {
+        // Collect the parsed configuration until the include.
+        configurationBuilder.addParsedConfiguration(
+            contents.substring(positionAfterInclude, position - ("include".length() + 1)));
         skipWhitespace();
         parseInclude();
+        positionAfterInclude = position;
       } else if (acceptString("basedirectory")) {
         skipWhitespace();
         baseDirectory = parseFileName();
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 42775d2..04c5f72 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -251,6 +251,10 @@
   // If non-null, it must be and is passed to the consumer.
   public StringConsumer usageInformationConsumer = null;
 
+  // If null, no configuration information needs to be printed.
+  // If non-null, configuration must be passed to the consumer.
+  public StringConsumer configurationConsumer = null;
+
   public Path proguardCompatibilityRulesOutput = null;
 
   public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 5d7bec0..a89cbd4 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -373,6 +373,17 @@
   }
 
   @Test
+  public void printsConfigurationOnStdout() throws Throwable {
+    Path proguardPrintConfigurationConfiguration =
+        temp.newFile("printconfiguration.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(
+        proguardPrintConfigurationConfiguration, ImmutableList.of("-printconfiguration"));
+    ProcessResult result = runR8OnShaking1(proguardPrintConfigurationConfiguration);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stdout.contains("-printconfiguration"));
+  }
+
+  @Test
   public void printsPrintSeedsOnStdout() throws Throwable {
     Path proguardPrintSeedsConfiguration = temp.newFile("printseeds.txt").toPath().toAbsolutePath();
     FileUtils.writeTextFile(proguardPrintSeedsConfiguration, ImmutableList.of("-printseeds"));
@@ -402,6 +413,21 @@
     assertTrue(result.stdout.contains("shaking1.Unused"));
   }
 
+  @Test
+  public void printsPrintSeedsAndPrintUsageAndPrintConfigurationOnStdout() throws Throwable {
+    Path proguardPrintSeedsConfiguration =
+        temp.newFile("printseedsandprintusageandprintconfiguration.txt").toPath().toAbsolutePath();
+    FileUtils.writeTextFile(proguardPrintSeedsConfiguration,
+        ImmutableList.of("-printseeds", "-printusage", "-printconfiguration"));
+    ProcessResult result = runR8OnShaking1(proguardPrintSeedsConfiguration);
+    assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+    assertTrue(result.stdout.contains("void main(java.lang.String[])"));
+    assertTrue(result.stdout.contains("shaking1.Unused"));
+    assertTrue(result.stdout.contains("-printseeds"));
+    assertTrue(result.stdout.contains("-printusage"));
+    assertTrue(result.stdout.contains("-printconfiguration"));
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
new file mode 100644
index 0000000..7621522
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, 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.shaking;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+
+class PrintConfigurationTestClass {
+
+  public static void main(String[] args) {
+
+  }
+}
+
+public class PrintConfigurationTest extends TestBase {
+  @Test
+  public void testSingleCOnfiguration() throws Exception {
+    Class mainClass = PrintConfigurationTestClass.class;
+    String proguardConfig = keepMainProguardConfiguration(mainClass);
+    Path printConfigurationFile = temp.newFile().toPath();
+    proguardConfig += "\n-printconfiguration " + printConfigurationFile.toString();
+    compileWithR8(ImmutableList.of(mainClass), proguardConfig);
+    assertEquals(proguardConfig, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8));
+  }
+
+  @Test
+  public void testIncludeFile() throws Exception {
+    Class mainClass = PrintConfigurationTestClass.class;
+    String includeProguardConfig = keepMainProguardConfiguration(mainClass);
+    Path includeFile = temp.newFile().toPath();
+    FileUtils.writeTextFile(includeFile, includeProguardConfig);
+    Path printConfigurationFile = temp.newFile().toPath();
+    String proguardConfig = String.join(System.lineSeparator(), ImmutableList.of(
+        "-include " + includeFile.toString(),
+        "-printconfiguration " + printConfigurationFile.toString()
+    ));
+
+    String expected = String.join(System.lineSeparator(), ImmutableList.of(
+        "",  // The -include line turns into an empty line.
+        includeProguardConfig,
+        "",  // Writing to the file adds an ending line separator
+        "",  // An empty line is emitted between two parts
+        "-printconfiguration " + printConfigurationFile.toString()
+    ));
+    compileWithR8(ImmutableList.of(mainClass), proguardConfig);
+    assertEquals(expected, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 5dc4ec6..83ae311 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -944,6 +944,32 @@
   }
 
   @Test
+  public void parse_printconfiguration_noArguments() throws Exception {
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(createConfigurationForTesting(ImmutableList.of(
+        "-printconfiguration"
+    )));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertTrue(config.isPrintConfiguration());
+    assertNull(config.getPrintConfigurationFile());
+  }
+
+  @Test
+  public void parse_printconfiguration_argument() throws Exception {
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(createConfigurationForTesting(ImmutableList.of(
+        "-printconfiguration file_name"
+    )));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertTrue(config.isPrintConfiguration());
+    assertEquals("./file_name", config.getPrintConfigurationFile().toString());
+  }
+
+  @Test
   public void parsePrintUsage() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);