Desugared library: add dump support

Bug: 171195238
Change-Id: I5ebf1ae4d055380b85030a544b22b06726a643fe
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 9896987..5627d6e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -1,7 +1,6 @@
 // Copyright (c) 2019, 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.ir.desugar;
 
 import com.android.tools.r8.errors.CompilationError;
@@ -34,16 +33,15 @@
 import java.util.stream.Collectors;
 
 public class DesugaredLibraryConfiguration {
-
   public static final String FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX = "j$/";
   public static final boolean FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY = true;
-
   public static final DesugaredLibraryConfiguration EMPTY_DESUGARED_LIBRARY_CONFIGURATION =
       new DesugaredLibraryConfiguration(
           AndroidApiLevel.B,
           false,
           FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
           null,
+          null,
           FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
           ImmutableMap.of(),
           ImmutableMap.of(),
@@ -53,11 +51,11 @@
           ImmutableSet.of(),
           ImmutableList.of(),
           ImmutableList.of());
-
   private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final String synthesizedLibraryClassesPackagePrefix;
   private final String identifier;
+  private final String jsonSource;
   // Setting supportAllCallbacksFromLibrary reduces the number of generated call-backs,
   // more specifically:
   // - no call-back is generated for emulated interface method overrides (forEach, etc.)
@@ -86,6 +84,7 @@
         true,
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
         "testingOnlyVersion",
+        null,
         FALL_BACK_SUPPORT_ALL_CALLBACKS_FROM_LIBRARY,
         prefix,
         ImmutableMap.of(),
@@ -106,6 +105,7 @@
       boolean libraryCompilation,
       String packagePrefix,
       String identifier,
+      String jsonSource,
       boolean supportAllCallbacksFromLibrary,
       Map<String, String> rewritePrefix,
       Map<DexType, DexType> emulateLibraryInterface,
@@ -119,6 +119,7 @@
     this.libraryCompilation = libraryCompilation;
     this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
     this.identifier = identifier;
+    this.jsonSource = jsonSource;
     this.supportAllCallbacksFromLibrary = supportAllCallbacksFromLibrary;
     this.rewritePrefix = rewritePrefix;
     this.emulateLibraryInterface = emulateLibraryInterface;
@@ -159,7 +160,6 @@
   public Map<DexType, DexType> getEmulateLibraryInterface() {
     return emulateLibraryInterface;
   }
-
   // If the method is retargeted, answers the retargeted method, else null.
   public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
     Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
@@ -198,17 +198,20 @@
     return extraKeepRules;
   }
 
-  public static class Builder {
+  public String getJsonSource() {
+    return jsonSource;
+  }
 
+  public static class Builder {
     private final DexItemFactory factory;
     private final Reporter reporter;
     private final Origin origin;
-
     private AndroidApiLevel requiredCompilationAPILevel;
     private boolean libraryCompilation = false;
     private String synthesizedLibraryClassesPackagePrefix =
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX;
     private String identifier;
+    private String jsonSource;
     private Map<String, String> rewritePrefix = new HashMap<>();
     private Map<DexType, DexType> emulateLibraryInterface = new IdentityHashMap<>();
     private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>();
@@ -224,7 +227,6 @@
       this.reporter = reporter;
       this.origin = origin;
     }
-
     // Utility to set values. Currently assumes the key is fresh.
     private <K, V> void put(Map<K, V> map, K key, V value, String desc) {
       if (map.containsKey(key)) {
@@ -251,6 +253,11 @@
       return this;
     }
 
+    public Builder setJsonSource(String jsonSource) {
+      this.jsonSource = jsonSource;
+      return this;
+    }
+
     public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) {
       this.requiredCompilationAPILevel = requiredCompilationAPILevel;
       return this;
@@ -369,6 +376,7 @@
           libraryCompilation,
           synthesizedLibraryClassesPackagePrefix,
           identifier,
+          jsonSource,
           supportAllCallbacksFromLibrary,
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableMap.copyOf(emulateLibraryInterface),
@@ -392,6 +400,5 @@
                 origin));
       }
     }
-
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index a20c8e0..9b0da2d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -93,15 +93,18 @@
     } else {
       configurationBuilder.setProgramCompilation();
     }
+    String jsonConfigString;
     JsonObject jsonConfig;
     try {
-      String jsonConfigString = stringResource.getString();
+      jsonConfigString = stringResource.getString();
       JsonParser parser = new JsonParser();
       jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
     } catch (Exception e) {
       throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
     }
 
+    configurationBuilder.setJsonSource(jsonConfigString);
+
     JsonElement formatVersionElement = required(jsonConfig, CONFIGURATION_FORMAT_VERSION_KEY);
     int formatVersion = formatVersionElement.getAsInt();
     if (formatVersion > MAX_SUPPORTED_VERSION) {
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 2bbe3fe..c426a09 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -83,6 +83,7 @@
 
   private static final String dumpVersionFileName = "r8-version";
   private static final String dumpBuildPropertiesFileName = "build.properties";
+  private static final String dumpDesugaredLibraryFileName = "desugared-library.json";
   private static final String dumpProgramFileName = "program.jar";
   private static final String dumpClasspathFileName = "classpath.jar";
   private static final String dumpLibraryFileName = "library.jar";
@@ -465,6 +466,18 @@
           dumpBuildPropertiesFileName,
           getBuildPropertiesContents(options).getBytes(),
           ZipEntry.DEFLATED);
+      if (options.desugaredLibraryConfiguration.getJsonSource() != null) {
+        writeToZipStream(
+            out,
+            dumpDesugaredLibraryFileName,
+            options.desugaredLibraryConfiguration.getJsonSource().getBytes(),
+            ZipEntry.DEFLATED);
+        if (options.dumpInputToFile != null) {
+          options.reporter.warning(
+              "Dumping a compilation with desugared library on a file may prevent reproduction,"
+                  + " use dumpInputToDirectory property instead.");
+        }
+      }
       if (options.getProguardConfiguration() != null) {
         String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
         writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index c230a6d..a32e296 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -66,6 +68,7 @@
     OutputMode outputMode = OutputMode.DexIndexed;
     Path outputPath = null;
     Path pgMapOutput = null;
+    Path desugaredLibJson = null;
     CompilationMode compilationMode = CompilationMode.RELEASE;
     List<Path> program = new ArrayList<>();
     Map<Path, Path> features = new LinkedHashMap<>();
@@ -134,6 +137,11 @@
               pgMapOutput = Paths.get(operand);
               break;
             }
+          case "--desugared-lib":
+            {
+              desugaredLibJson = Paths.get(operand);
+              break;
+            }
           case "--threads":
             {
               threads = Integer.parseInt(operand);
@@ -172,6 +180,9 @@
             .addProguardConfigurationFiles(config)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode);
+    if (desugaredLibJson != null) {
+      commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
+    }
     if (outputMode != OutputMode.ClassFile) {
       commandBuilder.setMinApiLevel(minApi);
     }
@@ -198,4 +209,18 @@
       R8.run(command);
     }
   }
+
+  // We cannot use StringResource since this class is added to the class path and has access only
+  // to the public APIs.
+  private static String readAllBytesJava7(Path filePath) {
+    String content = "";
+
+    try {
+      content = new String(Files.readAllBytes(filePath));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    return content;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
new file mode 100644
index 0000000..faba098
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2019, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryDumpInputsTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameterized.Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public DesugaredLibraryDumpInputsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8DumpToDirectory() throws Exception {
+    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path dumpDir = temp.newFolder().toPath();
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (minAPILevel, keepRules, shrink) ->
+                this.buildDesugaredLibrary(
+                    minAPILevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    opt -> opt.dumpInputToDirectory = dumpDir.toString()),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("PT42S");
+    verifyDumpDir(dumpDir);
+  }
+
+  @Test
+  public void testR8DumpToDirectory() throws Exception {
+    Assume.assumeTrue(requiresAnyCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path dumpDir = temp.newFolder().toPath();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+        .allowDiagnosticInfoMessages()
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (minAPILevel, keepRules, shrink) ->
+                this.buildDesugaredLibrary(
+                    minAPILevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    opt -> opt.dumpInputToDirectory = dumpDir.toString()),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("PT42S");
+    verifyDumpDir(dumpDir);
+  }
+
+  private void verifyDumpDir(Path dumpDir) throws IOException {
+    assertTrue(Files.isDirectory(dumpDir));
+    List<Path> paths = Files.walk(dumpDir, 1).collect(Collectors.toList());
+    boolean hasVerified = false;
+    for (Path path : paths) {
+      if (!path.equals(dumpDir)) {
+        // The non-external run here results in assert code calling application read.
+        verifyDump(path);
+        hasVerified = true;
+      }
+    }
+    assertTrue(hasVerified);
+  }
+
+  private void verifyDump(Path dumpFile) throws IOException {
+    assertTrue(Files.exists(dumpFile));
+    Path unzipped = temp.newFolder().toPath();
+    ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
+    assertTrue(Files.exists(unzipped.resolve("r8-version")));
+    assertTrue(Files.exists(unzipped.resolve("program.jar")));
+    assertTrue(Files.exists(unzipped.resolve("library.jar")));
+    assertTrue(Files.exists(unzipped.resolve("desugared-library.json")));
+    assertTrue(Files.exists(unzipped.resolve("build.properties")));
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println(Duration.ofSeconds(42));
+    }
+  }
+}
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 90a696d..fa9518c 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -78,6 +78,10 @@
       help='Set min-api (default read from dump properties file)',
       default=None)
   parser.add_argument(
+    '--desugared-lib',
+    help='Set desugared-library (default set from dump)',
+    default=None)
+  parser.add_argument(
     '--loop',
     help='Run the compilation in a loop',
     default=False,
@@ -119,6 +123,9 @@
   def classpath_jar(self):
     return self.if_exists('classpath.jar')
 
+  def desugared_library_json(self):
+    return self.if_exists('desugared-library.json')
+
   def build_properties_file(self):
     return self.if_exists('build.properties')
 
@@ -252,6 +259,8 @@
       cmd.extend(['--lib', dump.library_jar()])
     if dump.classpath_jar():
       cmd.extend(['--classpath', dump.classpath_jar()])
+    if dump.desugared_library_json():
+      cmd.extend(['--desugared-lib', dump.desugared_library_json()])
     if compiler != 'd8' and dump.config_file():
       cmd.extend(['--pg-conf', dump.config_file()])
     if compiler != 'd8':