Add direct CLI options for dump to file and directory

For some build system integrations setting the JVM system
properties is not easily available having a command line option
can be convenient.

The two command line options are not advertised in the help,
but just silently there.

Change-Id: Ie1aa70c50654abf72584de591faec16d96931fea
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 555713b..69c1ec8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
@@ -47,7 +48,8 @@
   private final BiPredicate<String, Long> dexClassChecksumFilter;
   private final List<AssertionsConfiguration> assertionsConfiguration;
   private final List<Consumer<Inspector>> outputInspections;
-  private int threadCount;
+  private final int threadCount;
+  private final DumpInputFlags dumpInputFlags;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -63,6 +65,7 @@
     assertionsConfiguration = new ArrayList<>();
     outputInspections = null;
     threadCount = ThreadUtils.NOT_SPECIFIED;
+    dumpInputFlags = DumpInputFlags.noDump();
   }
 
   BaseCompilerCommand(
@@ -78,7 +81,8 @@
       BiPredicate<String, Long> dexClassChecksumFilter,
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
-      int threadCount) {
+      int threadCount,
+      DumpInputFlags dumpInputFlags) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -94,6 +98,7 @@
     this.assertionsConfiguration = assertionsConfiguration;
     this.outputInspections = outputInspections;
     this.threadCount = threadCount;
+    this.dumpInputFlags = dumpInputFlags;
   }
 
   /**
@@ -174,6 +179,10 @@
     return threadCount;
   }
 
+  DumpInputFlags getDumpInputFlags() {
+    return dumpInputFlags;
+  }
+
   Reporter getReporter() {
     return reporter;
   }
@@ -207,6 +216,7 @@
     private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
     private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
     protected StringConsumer proguardMapConsumer = null;
+    private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -583,6 +593,20 @@
       return self();
     }
 
+    B dumpInputToFile(Path file) {
+      dumpInputFlags = DumpInputFlags.dumpToFile(file);
+      return self();
+    }
+
+    B dumpInputToDirectory(Path directory) {
+      dumpInputFlags = DumpInputFlags.dumpToDirectory(directory);
+      return self();
+    }
+
+    DumpInputFlags getDumpInputFlags() {
+      return dumpInputFlags;
+    }
+
     @Override
     void validate() {
       Reporter reporter = getReporter();
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index ca4e9e4..1316be8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -21,6 +21,8 @@
   protected static final String MIN_API_FLAG = "--min-api";
   protected static final String THREAD_COUNT_FLAG = "--thread-count";
   protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";
+  protected static final String DUMP_INPUT_TO_FILE = "--dumpinputtofile";
+  protected static final String DUMP_INPUT_TO_DIRECTORY = "--dumpinputtodirectory";
 
   static final Iterable<String> ASSERTIONS_USAGE_MESSAGE =
       Arrays.asList(
@@ -211,6 +213,23 @@
     return 2;
   }
 
+  int tryParseDump(B builder, String arg, String[] args, int argsIndex, Origin origin) {
+    if (!arg.equals(DUMP_INPUT_TO_FILE) && !arg.equals(DUMP_INPUT_TO_DIRECTORY)) {
+      return -1;
+    }
+    if (args.length <= argsIndex + 1) {
+      builder.error(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin));
+      return args.length - argsIndex;
+    }
+    if (arg.equals(DUMP_INPUT_TO_FILE)) {
+      builder.dumpInputToFile(Paths.get(args[argsIndex + 1]));
+    } else {
+      assert arg.equals(DUMP_INPUT_TO_DIRECTORY);
+      builder.dumpInputToDirectory(Paths.get(args[argsIndex + 1]));
+    }
+    return 1;
+  }
+
   /**
    * This method must match the lookup in
    * {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 7e20368..7dbcb32 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
+import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -317,6 +318,7 @@
           minimalMainDex,
           mainDexKeepRules,
           getThreadCount(),
+          getDumpInputFlags(),
           factory);
     }
   }
@@ -397,6 +399,7 @@
       boolean minimalMainDex,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
       int threadCount,
+      DumpInputFlags dumpInputFlags,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -411,7 +414,8 @@
         dexClassChecksumFilter,
         assertionsConfiguration,
         outputInspections,
-        threadCount);
+        threadCount,
+        dumpInputFlags);
     this.intermediate = intermediate;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
@@ -502,10 +506,7 @@
       internal.threadCount = getThreadCount();
     }
 
-    if (skipDump) {
-      internal.dumpInputToDirectory = null;
-      internal.dumpInputToFile = null;
-    }
+    internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
 
     return internal;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 8f90ade..9e729e0 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -290,6 +290,11 @@
           i += argsConsumed;
           continue;
         }
+        argsConsumed = tryParseDump(builder, arg, expandedArgs, i, origin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
         builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       } else if (arg.startsWith("@")) {
         builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 1fc7689..27108a1 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
+import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Pair;
@@ -95,6 +96,7 @@
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
       int threadCount,
+      DumpInputFlags dumpInputFlags,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -109,7 +111,8 @@
         dexClassChecksumFilter,
         assertionsConfiguration,
         outputInspections,
-        threadCount);
+        threadCount,
+        dumpInputFlags);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
@@ -196,6 +199,8 @@
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
     }
+
+    internal.setDumpInputFlags(getDumpInputFlags(), false);
     internal.dumpOptions = dumpOptions();
 
     return internal;
@@ -395,6 +400,7 @@
           getAssertionsConfiguration(),
           getOutputInspections(),
           getThreadCount(),
+          getDumpInputFlags(),
           factory);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 402e59a..4224ad8 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -176,6 +176,11 @@
           i += argsConsumed;
           continue;
         }
+        argsConsumed = tryParseDump(builder, arg, expandedArgs, i, origin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
         builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       } else {
         builder.addProgramFiles(Paths.get(arg));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 2b3f988..e3de56c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
+import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -585,7 +586,8 @@
               getOutputInspections(),
               synthesizedClassPrefix,
               skipDump,
-              getThreadCount());
+              getThreadCount(),
+              getDumpInputFlags());
 
       return command;
     }
@@ -750,7 +752,8 @@
       List<Consumer<Inspector>> outputInspections,
       String synthesizedClassPrefix,
       boolean skipDump,
-      int threadCount) {
+      int threadCount,
+      DumpInputFlags dumpInputFlags) {
     super(
         inputApp,
         mode,
@@ -764,7 +767,8 @@
         dexClassChecksumFilter,
         assertionsConfiguration,
         outputInspections,
-        threadCount);
+        threadCount,
+        dumpInputFlags);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -956,10 +960,7 @@
       internal.threadCount = getThreadCount();
     }
 
-    if (skipDump) {
-      internal.dumpInputToDirectory = null;
-      internal.dumpInputToFile = null;
-    }
+    internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
 
     return internal;
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 277bec8..ab664c3 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -271,6 +271,11 @@
           i += argsConsumed;
           continue;
         }
+        argsConsumed = tryParseDump(builder, arg, expandedArgs, i, argsOrigin);
+        if (argsConsumed >= 0) {
+          i += argsConsumed;
+          continue;
+        }
         builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
       } else if (arg.startsWith("@")) {
         builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin));
diff --git a/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
new file mode 100644
index 0000000..d37de80
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DumpInputFlags.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, 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 java.nio.file.Path;
+
+public abstract class DumpInputFlags {
+
+  public static DumpInputFlags noDump() {
+    return new DumpInputFlags() {
+      @Override
+      Path getDumpInputToFile() {
+        return null;
+      }
+
+      @Override
+      Path getDumpInputToDirectory() {
+        return null;
+      }
+    };
+  }
+
+  public static DumpInputFlags dumpToFile(Path file) {
+    return new DumpInputFlags() {
+      @Override
+      Path getDumpInputToFile() {
+        return file;
+      }
+
+      @Override
+      Path getDumpInputToDirectory() {
+        return null;
+      }
+    };
+  }
+
+  public static DumpInputFlags dumpToDirectory(Path file) {
+    return new DumpInputFlags() {
+      @Override
+      Path getDumpInputToFile() {
+        return null;
+      }
+
+      @Override
+      Path getDumpInputToDirectory() {
+        return file;
+      }
+    };
+  }
+
+  abstract Path getDumpInputToFile();
+
+  abstract Path getDumpInputToDirectory();
+}
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 ee29d30..79e63c0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DataResourceConsumer;
@@ -391,6 +390,21 @@
     return marker;
   }
 
+  public void setDumpInputFlags(DumpInputFlags dumpInputFlags, boolean skipDump) {
+    if (skipDump) {
+      dumpInputToDirectory = null;
+      dumpInputToFile = null;
+      return;
+    }
+
+    if (dumpInputFlags.getDumpInputToFile() != null) {
+      dumpInputToFile = dumpInputFlags.getDumpInputToFile().toString();
+    }
+    if (dumpInputFlags.getDumpInputToDirectory() != null) {
+      dumpInputToDirectory = dumpInputFlags.getDumpInputToDirectory().toString();
+    }
+  }
+
   public boolean hasConsumer() {
     return programConsumer != null;
   }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 10b8afe..ca3e2f0 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -59,6 +59,9 @@
   // Allow test proguard options
   private boolean allowTestProguardOptions = false;
 
+  private String dumpInputToFile = null;
+  private String dumpInputToDirectory = null;
+
   private boolean addR8ExternalDeps = false;
 
   private List<String> jvmFlags = new ArrayList<>();
@@ -95,6 +98,16 @@
     return self();
   }
 
+  public ExternalR8TestBuilder dumpInputToFile(String arg) {
+    dumpInputToFile = arg;
+    return self();
+  }
+
+  public ExternalR8TestBuilder dumpInputToDirectory(String arg) {
+    dumpInputToDirectory = arg;
+    return self();
+  }
+
   @Override
   ExternalR8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
@@ -155,6 +168,14 @@
           command.add(libJar.toAbsolutePath().toString());
         }
       }
+      if (dumpInputToFile != null) {
+        command.add("--dumpinputtofile");
+        command.add(dumpInputToFile);
+      }
+      if (dumpInputToDirectory != null) {
+        command.add("--dumpinputtodirectory");
+        command.add(dumpInputToDirectory);
+      }
       command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList()));
 
       ProcessBuilder processBuilder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
index 008846d..5437118 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
@@ -40,7 +40,7 @@
   }
 
   @Test
-  public void testDumpToFile() throws Exception {
+  public void testDumpToFileSystemProperty() throws Exception {
     Path dump = temp.newFolder().toPath().resolve("dump.zip");
     try {
       testForExternalR8(parameters.getBackend(), parameters.getRuntime())
@@ -55,7 +55,22 @@
   }
 
   @Test
-  public void testDumpToDirectory() throws Exception {
+  public void testDumpToFileCLI() throws Exception {
+    Path dump = temp.newFolder().toPath().resolve("dump.zip");
+    try {
+      testForExternalR8(parameters.getBackend(), parameters.getRuntime())
+          .dumpInputToFile(dump.toString())
+          .addProgramClasses(TestClass.class)
+          .compile();
+    } catch (AssertionError e) {
+      verifyDump(dump, false, true);
+      return;
+    }
+    fail("Expected external compilation to exit");
+  }
+
+  @Test
+  public void testDumpToDirectorySystemProperty() throws Exception {
     Path dumpDir = temp.newFolder().toPath();
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
@@ -69,17 +84,19 @@
         .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world");
-    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, false, false);
-        hasVerified = true;
-      }
-    }
-    assertTrue(hasVerified);
+
+    verifyDumpDirectory(dumpDir, false, false);
+  }
+
+  @Test
+  public void testDumpToDirectoryCLI() throws Exception {
+    Path dumpDir = temp.newFolder().toPath();
+    testForExternalR8(parameters.getBackend(), parameters.getRuntime())
+        .dumpInputToDirectory(dumpDir.toString())
+        .addProgramClasses(TestClass.class)
+        .compile();
+
+    verifyDumpDirectory(dumpDir, false, false);
   }
 
   private void verifyDump(Path dumpFile, boolean hasClasspath, boolean hasProguardConfig)
@@ -105,6 +122,21 @@
                 DescriptorUtils.javaTypeToDescriptor(TestClass.class.getTypeName()))));
   }
 
+  private void verifyDumpDirectory(Path dumpDir, boolean hasClasspath, boolean hasProguardConfig)
+      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, hasClasspath, hasProguardConfig);
+        hasVerified = true;
+      }
+    }
+    assertTrue(hasVerified);
+  }
+
   static class TestClass {
     public static void main(String[] args) {
       System.out.println("Hello, world");