Support dump in TraceReferences

Change-Id: I06cba6273ca5695a9fbaa4f98f8db08531403a01
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 911789d..4367a38 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -33,7 +33,8 @@
     D8,
     R8,
     L8,
-    Relocator;
+    Relocator,
+    TraceReferences;
 
     public static Tool[] valuesR8andD8() {
       return new Tool[] {Tool.D8, Tool.R8};
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 27dddcc..ef3a443 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -46,6 +46,7 @@
   private static final String ENABLE_MISSING_LIBRARY_API_MODELING =
       "enable-missing-library-api-modeling";
   private static final String ANDROID_PLATFORM_BUILD = "android-platform-build";
+  private static final String TRACE_REFERENCES_CONSUMER = "trace_references_consumer";
 
   private final Tool tool;
   private final CompilationMode compilationMode;
@@ -70,6 +71,9 @@
 
   private final Map<String, String> systemProperties;
 
+  // TraceReferences only.
+  private final String traceReferencesConsumer;
+
   // Reporting only.
   private final boolean dumpInputToFile;
 
@@ -93,7 +97,8 @@
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
       Map<String, String> systemProperties,
-      boolean dumpInputToFile) {
+      boolean dumpInputToFile,
+      String traceReferencesConsumer) {
     this.tool = tool;
     this.compilationMode = compilationMode;
     this.minApi = minAPI;
@@ -114,6 +119,7 @@
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
     this.systemProperties = systemProperties;
     this.dumpInputToFile = dumpInputToFile;
+    this.traceReferencesConsumer = traceReferencesConsumer;
   }
 
   public String getBuildPropertiesFileContent() {
@@ -126,29 +132,33 @@
   public Map<String, String> getBuildProperties() {
     Map<String, String> buildProperties = new LinkedHashMap<>();
     addDumpEntry(buildProperties, TOOL_KEY, tool.name());
-    // We keep the following values for backward compatibility.
-    addDumpEntry(
-        buildProperties,
-        MODE_KEY,
-        compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE);
-    addDumpEntry(buildProperties, MIN_API_KEY, minApi);
-    addDumpEntry(
-        buildProperties, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc);
     if (threadCount != ThreadUtils.NOT_SPECIFIED) {
       addDumpEntry(buildProperties, THREAD_COUNT_KEY, threadCount);
     }
-    addDumpEntry(buildProperties, DESUGAR_STATE_KEY, desugarState);
-    addDumpEntry(
-        buildProperties, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
-    if (isAndroidPlatformBuild) {
-      addDumpEntry(buildProperties, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
+    if (tool != Tool.TraceReferences) {
+      // We keep the following values for backward compatibility.
+      addDumpEntry(
+          buildProperties,
+          MODE_KEY,
+          compilationMode == CompilationMode.DEBUG ? DEBUG_MODE_VALUE : RELEASE_MODE_VALUE);
+      addDumpEntry(buildProperties, MIN_API_KEY, minApi);
+      addDumpEntry(
+          buildProperties, OPTIMIZE_MULTIDEX_FOR_LINEAR_ALLOC_KEY, optimizeMultidexForLinearAlloc);
+      addDumpEntry(buildProperties, DESUGAR_STATE_KEY, desugarState);
+      addDumpEntry(
+          buildProperties, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
+      if (isAndroidPlatformBuild) {
+        addDumpEntry(buildProperties, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
+      }
+      addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate);
+      addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
+      addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking);
+      addOptionalDumpEntry(
+          buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
+    } else {
+      addDumpEntry(buildProperties, TRACE_REFERENCES_CONSUMER, traceReferencesConsumer);
     }
-    addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate);
-    addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
-    addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking);
     addOptionalDumpEntry(buildProperties, MINIFICATION_KEY, minification);
-    addOptionalDumpEntry(
-        buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
     ArrayList<String> sortedKeys = new ArrayList<>(systemProperties.keySet());
     sortedKeys.sort(String::compareTo);
     sortedKeys.forEach(
@@ -212,6 +222,9 @@
       case FORCE_PROGUARD_COMPATIBILITY_KEY:
         builder.setForceProguardCompatibility(Boolean.parseBoolean(value));
         return;
+      case TRACE_REFERENCES_CONSUMER:
+        builder.setTraceReferencesConsumer(value);
+        return;
       default:
         if (key.startsWith(SYSTEM_PROPERTY_PREFIX)) {
           builder.setSystemProperty(key.substring(SYSTEM_PROPERTY_PREFIX.length()), value);
@@ -311,6 +324,8 @@
     private boolean enableMissingLibraryApiModeling = false;
     private boolean isAndroidPlatformBuild = false;
 
+    private String traceReferencesConsumer = null;
+
     private Map<String, String> systemProperties = new HashMap<>();
 
     // Reporting only.
@@ -323,6 +338,11 @@
       return this;
     }
 
+    public Builder setTraceReferencesConsumer(String traceReferencesConsumer) {
+      this.traceReferencesConsumer = traceReferencesConsumer;
+      return this;
+    }
+
     public Builder setCompilationMode(CompilationMode compilationMode) {
       this.compilationMode = compilationMode;
       return this;
@@ -456,7 +476,8 @@
           enableMissingLibraryApiModeling,
           isAndroidPlatformBuild,
           systemProperties,
-          dumpInputToFile);
+          dumpInputToFile,
+          traceReferencesConsumer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 1344816..416a9a4 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -28,10 +28,7 @@
 public class TraceReferences {
 
   public static void run(TraceReferencesCommand command) throws CompilationFailedException {
-    InternalOptions options = new InternalOptions();
-    options.loadAllClassDefinitions = true;
-    ExceptionUtils.withCompilationHandler(
-        command.getReporter(), () -> runInternal(command, options));
+    runForTesting(command, command.getInternalOptions());
   }
 
   private static void forEachDescriptor(ProgramResourceProvider provider, Consumer<String> consumer)
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index 9aff1d0..2635b95 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -18,12 +18,15 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
@@ -396,4 +399,21 @@
   TraceReferencesConsumer getConsumer() {
     return consumer;
   }
+
+  InternalOptions getInternalOptions() {
+    InternalOptions options = new InternalOptions();
+    options.loadAllClassDefinitions = true;
+    TraceReferencesConsumer consumer = getConsumer();
+    DumpOptions.Builder builder =
+        DumpOptions.builder(Tool.TraceReferences)
+            .readCurrentSystemProperties()
+            // The behavior of TraceReferences greatly differs depending if we have a CheckConsumer
+            // or a KeepRules consumer. We log the consumer type and obfuscation if relevant.
+            .setTraceReferencesConsumer(consumer.getClass().getName());
+    if (consumer instanceof TraceReferencesKeepRules) {
+      builder.setMinification(((TraceReferencesKeepRules) consumer).allowObfuscation());
+    }
+    options.dumpOptions = builder.build();
+    return options;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java
index f648da4..52ea13c 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesKeepRules.java
@@ -40,6 +40,10 @@
     this.allowObfuscation = allowObfuscation;
   }
 
+  public boolean allowObfuscation() {
+    return allowObfuscation;
+  }
+
   /**
    * Builder for constructing a {@link TraceReferencesKeepRules].
    *
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
new file mode 100644
index 0000000..963c2e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceDumpInputsTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2022, 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.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringConsumer.FileConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TraceReferenceDumpInputsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withSystemRuntime().build();
+  }
+
+  public TraceReferenceDumpInputsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDumpToDirectory() throws Exception {
+    Path dumpDir = temp.newFolder().toPath();
+    TraceReferencesKeepRules keepRulesConsumer =
+        TraceReferencesKeepRules.builder()
+            .setAllowObfuscation(true)
+            .setOutputConsumer(new FileConsumer(temp.newFile().toPath()))
+            .build();
+    TraceReferencesCommand command =
+        TraceReferencesCommand.builder()
+            .setConsumer(keepRulesConsumer)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+            .addTargetFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+            .addSourceFiles(ToolHelper.getClassFileForTestClass(OtherTestClass.class))
+            .build();
+    InternalOptions internalOptions = command.getInternalOptions();
+    internalOptions.setDumpInputFlags(DumpInputFlags.dumpToDirectory(dumpDir));
+    TraceReferences.runForTesting(command, internalOptions);
+    verifyDumpDirectory(dumpDir);
+  }
+
+  private void verifyDumpDirectory(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)) {
+        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("build.properties")));
+    assertTrue(Files.exists(unzipped.resolve("program.jar")));
+    assertTrue(Files.exists(unzipped.resolve("library.jar")));
+    assertTrue(Files.exists(unzipped.resolve("classpath.jar")));
+    contains(unzipped, "program.jar", OtherTestClass.class);
+    contains(unzipped, "classpath.jar", TestClass.class);
+    checkProperties(unzipped.resolve("build.properties"));
+  }
+
+  private void checkProperties(Path properties) throws IOException {
+    List<String> lines = Files.readAllLines(properties);
+    assertEquals(4, lines.size());
+    assertEquals("tool=TraceReferences", lines.get(0));
+    assertEquals(
+        "trace_references_consumer=com.android.tools.r8.tracereferences.TraceReferencesKeepRules",
+        lines.get(2));
+    assertEquals("minification=true", lines.get(3));
+  }
+
+  private void contains(Path unzipped, String jar, Class<?> clazz) throws IOException {
+    Set<String> entries = new HashSet<>();
+    ZipUtils.iter(unzipped.resolve(jar).toString(), (entry, input) -> entries.add(entry.getName()));
+    assertTrue(
+        entries.contains(
+            DescriptorUtils.getClassFileName(
+                DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()))));
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      System.out.println("Hello, world");
+    }
+  }
+
+  static class OtherTestClass {
+    public static void main(String[] args) {
+      TestClass.main(args);
+    }
+  }
+}