Add desugared lib in command line

Bug: 142621961
Change-Id: I0f74750bbe234c2aebbbaaa06a759fc7af80ca8e
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 792f8b9..1099cf9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -30,7 +30,8 @@
           "--classpath",
           "--min-api",
           "--main-dex-list",
-          "--main-dex-list-output");
+          "--main-dex-list-output",
+          "--desugared-lib");
 
   private static final String APK_EXTENSION = ".apk";
   private static final String JAR_EXTENSION = ".jar";
@@ -124,6 +125,8 @@
               "                          # merging.",
               "  --file-per-class        # Produce a separate dex file per input class",
               "  --no-desugaring         # Force disable desugaring.",
+              "  --desugared-lib <file>  # Specify desugared library configuration.",
+              "                          # <file> is a desugared library configuration (json).",
               "  --main-dex-list <file>  # List of classes to place in the primary dex file.",
               "  --main-dex-list-output <file>",
               "                          # Output resulting main dex list in <file>.",
@@ -245,6 +248,8 @@
         builder.setIntermediate(true);
       } else if (arg.equals("--no-desugaring")) {
         builder.setDisableDesugaring(true);
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else {
         if (arg.startsWith("--")) {
           builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 50ea501..a692e1d 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.L8Command.USAGE_MESSAGE;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
 import com.android.tools.r8.dex.ApplicationReader;
@@ -15,11 +16,13 @@
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.L8TreePruner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
@@ -144,4 +147,31 @@
       }
     }
   }
+
+  private static void run(String[] args) throws CompilationFailedException {
+    L8Command command = L8Command.parse(args, CommandLineOrigin.INSTANCE).build();
+    if (command.isPrintHelp()) {
+      SelfRetraceTest.test();
+      System.out.println(USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("L8 " + Version.getVersionString());
+      return;
+    }
+    run(command);
+  }
+
+  /**
+   * Command-line entry to L8.
+   *
+   * <p>See {@link L8Command#USAGE_MESSAGE} or run {@code l8 --help} for usage information.
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.err.println(USAGE_MESSAGE);
+      System.exit(ExceptionUtils.STATUS_ERROR);
+    }
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 1346623..4a587aa 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -24,9 +24,11 @@
 @Keep
 public final class L8Command extends BaseCompilerCommand {
 
+  static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
+
   private final D8Command d8Command;
   private final R8Command r8Command;
-  private final com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration libraryConfiguration;
+  private final DesugaredLibraryConfiguration libraryConfiguration;
   private final DexItemFactory factory;
 
   boolean isShrinking() {
@@ -41,6 +43,33 @@
     return r8Command;
   }
 
+  /**
+   * Parse the L8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return L8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin) {
+    return L8CommandParser.parse(args, origin);
+  }
+
+  /**
+   * Parse the L8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return L8 command builder with state set up according to parsed command line.
+   */
+  public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return L8CommandParser.parse(args, origin, handler);
+  }
+
   private L8Command(
       R8Command r8Command,
       D8Command d8Command,
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
new file mode 100644
index 0000000..1dc83c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -0,0 +1,164 @@
+// 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;
+
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Set;
+
+public class L8CommandParser extends BaseCompilerCommandParser {
+
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of("--output", "--lib", "--min-api", "--desugared-lib");
+
+  public static void main(String[] args) throws CompilationFailedException {
+    L8Command command = parse(args, Origin.root()).build();
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      System.exit(1);
+    }
+    L8.run(command);
+  }
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Arrays.asList(
+              "Usage: l8 [options] <input-files>",
+              " where <input-files> are any combination of dex, class, zip, jar, or apk files",
+              " and options are:",
+              "  --debug                 # Compile with debugging information (default).",
+              "  --release               # Compile without debugging information.",
+              "  --output <file>         # Output result in <outfile>.",
+              "                          # <file> must be an existing directory or a zip file.",
+              "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
+              "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  + AndroidApiLevel.getDefault().getLevel()
+                  + ".",
+              "  --pg-conf <file>        # Proguard configuration <file>.",
+              "  --desugared-lib <file>  # Specify desugared library configuration.",
+              "                          # <file> is a desugared library configuration (json).",
+              "  --version               # Print the version of l8.",
+              "  --help                  # Print this message."));
+
+  /**
+   * Parse the D8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static L8Command.Builder parse(String[] args, Origin origin) {
+    return parse(args, origin, L8Command.builder());
+  }
+
+  /**
+   * Parse the D8 command-line.
+   *
+   * <p>Parsing will set the supplied options or their default value if they have any.
+   *
+   * @param args Command-line arguments array.
+   * @param origin Origin description of the command-line arguments.
+   * @param handler Custom defined diagnostics handler.
+   * @return D8 command builder with state set up according to parsed command line.
+   */
+  public static L8Command.Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+    return parse(args, origin, L8Command.builder(handler));
+  }
+
+  private static L8Command.Builder parse(String[] args, Origin origin, L8Command.Builder builder) {
+    CompilationMode compilationMode = null;
+    Path outputPath = null;
+    OutputMode outputMode = null;
+    boolean hasDefinedApiLevel = false;
+    OrderedClassFileResourceProvider.Builder classpathBuilder =
+        OrderedClassFileResourceProvider.builder();
+    String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
+    for (int i = 0; i < expandedArgs.length; i++) {
+      String arg = expandedArgs[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < expandedArgs.length) {
+          nextArg = expandedArgs[i];
+        } else {
+          builder.error(
+              new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+          break;
+        }
+      }
+      if (arg.length() == 0) {
+        continue;
+      } else if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--debug")) {
+        if (compilationMode == CompilationMode.RELEASE) {
+          builder.error(
+              new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+          continue;
+        }
+        compilationMode = CompilationMode.DEBUG;
+      } else if (arg.equals("--release")) {
+        if (compilationMode == CompilationMode.DEBUG) {
+          builder.error(
+              new StringDiagnostic("Cannot compile in both --debug and --release mode.", origin));
+          continue;
+        }
+        compilationMode = CompilationMode.RELEASE;
+      } else if (arg.equals("--output")) {
+        if (outputPath != null) {
+          builder.error(
+              new StringDiagnostic(
+                  "Cannot output both to '" + outputPath.toString() + "' and '" + nextArg + "'",
+                  origin));
+          continue;
+        }
+        outputPath = Paths.get(nextArg);
+      } else if (arg.equals("--min-api")) {
+        if (hasDefinedApiLevel) {
+          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
+        } else {
+          parseMinApi(builder, nextArg, origin);
+          hasDefinedApiLevel = true;
+        }
+      } else if (arg.equals("--lib")) {
+        addLibraryArgument(builder, origin, nextArg);
+      } else if (arg.equals("--pg-conf")) {
+        builder.addProguardConfigurationFiles(Paths.get(nextArg));
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else {
+        if (arg.startsWith("--")) {
+          builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
+          continue;
+        }
+        builder.addProgramFiles(Paths.get(arg));
+      }
+    }
+    if (!classpathBuilder.isEmpty()) {
+      builder.addClasspathResourceProvider(classpathBuilder.build());
+    }
+    if (compilationMode != null) {
+      builder.setMode(compilationMode);
+    }
+    if (outputMode == null) {
+      outputMode = OutputMode.DexIndexed;
+    }
+    if (outputPath == null) {
+      outputPath = Paths.get(".");
+    }
+    return builder.setOutput(outputPath, outputMode);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index b0acdb6..dd9ab8e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -25,7 +25,8 @@
           "--main-dex-list",
           "--main-dex-list-output",
           "--pg-conf",
-          "--pg-map-output");
+          "--pg-map-output",
+          "--desugared-lib");
 
   public static void main(String[] args) throws CompilationFailedException {
     R8Command command = parse(args, Origin.root()).build();
@@ -65,6 +66,8 @@
                   + ".",
               "  --pg-conf <file>         # Proguard configuration <file>.",
               "  --pg-map-output <file>   # Output the resulting name and line mapping to <file>.",
+              "  --desugared-lib <file>   # Specify desugared library configuration.",
+              "                           # <file> is a desugared library configuration (json).",
               "  --no-tree-shaking        # Force disable tree shaking of unreachable classes.",
               "  --no-minification        # Force disable minification of names.",
               "  --no-data-resources      # Ignore all data resources.",
@@ -206,6 +209,8 @@
         builder.addProguardConfigurationFiles(Paths.get(nextArg));
       } else if (arg.equals("--pg-map-output")) {
         builder.setProguardMapOutputPath(Paths.get(nextArg));
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
       } else {
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index f54233b..d13081c 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -76,6 +76,9 @@
       case "r8":
         R8.main(shift(args));
         break;
+      case "l8":
+        L8.main(shift(args));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ae63272..db0baa3 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -525,6 +525,13 @@
         "Missing parameter", handler -> parse(handler, "--output"));
   }
 
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    D8Command d8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        d8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 0a55b8a..558ba0d 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.charset.StandardCharsets;
@@ -68,6 +70,27 @@
     Marker marker = markers.iterator().next();
   }
 
+  @Test
+  public void testMarkerCommandLine() throws Throwable {
+    Path output = temp.newFolder().toPath().resolve("desugar_jdk_libs.zip");
+    L8Command l8Command =
+        parse(
+            ToolHelper.getDesugarJDKLibs().toString(),
+            "--lib",
+            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+            "--min-api",
+            "20",
+            "--desugared-lib",
+            ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+            "--output",
+            output.toString());
+    L8.run(l8Command);
+    Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(output);
+    // TODO(b/134732760): Shouldn't we remove the D8/R8 marker?
+    assertEquals(2, markers.size());
+    Marker marker = markers.iterator().next();
+  }
+
   private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
     return L8Command.builder(handler)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
@@ -156,4 +179,15 @@
     assertTrue(builder2.isShrinking());
     assertNotNull(builder2.build().getR8Command());
   }
+
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    L8Command l8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        l8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
+  private L8Command parse(String... args) throws CompilationFailedException {
+    return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 57d278e..165b510 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -206,7 +206,8 @@
     Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
     Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
     parse("--main-dex-rules", mainDexRules1.toString());
-    parse("--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+    parse(
+        "--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -637,6 +638,13 @@
         "Missing parameter", handler -> parse(handler, "--output"));
   }
 
+  @Test
+  public void desugaredLibrary() throws CompilationFailedException {
+    R8Command r8Command = parse("--desugared-lib", "src/library_desugar/desugar_jdk_libs.json");
+    assertFalse(
+        r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }