Add D8 based desugar command

Skeleton D8 desugar command taking the same arguments as Desugar together with simple test which invoke it directly.

RELNOTES: None.
PiperOrigin-RevId: 306993018
diff --git a/src/BUILD b/src/BUILD
index 42fe065..f2a1bae 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -161,7 +161,7 @@
     # WARNING: Only adjust the number in `expect` if you are intentionally
     # adding or removing embedded tools. Know that the more embedded tools there
     # are in Bazel, the bigger the binary becomes and the slower Bazel starts.
-    expect = 470,
+    expect = 500,
     margin = 5,  # percentage
 )
 
diff --git a/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarBasicTest.java b/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarBasicTest.java
new file mode 100644
index 0000000..df4ca9d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarBasicTest.java
@@ -0,0 +1,91 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.objectweb.asm.Opcodes.V1_7;
+
+import com.google.devtools.build.android.r8.FileUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.function.Consumer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+
+/** Basic test of D8 desugar */
+@RunWith(JUnit4.class)
+public class DesugarBasicTest {
+  private Path basic;
+  private Path desugared;
+
+  @Before
+  public void setup() {
+    // Jar file with the compiled Java code in the sub-package basic before desugaring.
+    basic = Paths.get(System.getProperty("DesugarBasicTest.testdata_basic"));
+    // Jar file with the compiled Java code in the sub-package basic after desugaring.
+    desugared = Paths.get(System.getProperty("DesugarBasicTest.testdata_basic_desugared"));
+  }
+
+  @Test
+  public void checkBeforeDesugar() throws Exception {
+    DesugarInfoCollector desugarInfoCollector = new DesugarInfoCollector();
+    forAllClasses(basic, desugarInfoCollector);
+    assertThat(desugarInfoCollector.getLargestMajorClassFileVersion()).isGreaterThan(V1_7);
+    assertThat(desugarInfoCollector.getNumberOfInvokeDynamic()).isGreaterThan(0);
+    assertThat(desugarInfoCollector.getNumberOfDefaultMethods()).isGreaterThan(0);
+    assertThat(desugarInfoCollector.getNumberOfDesugaredLambdas()).isEqualTo(0);
+    assertThat(desugarInfoCollector.getNumberOfCompanionClasses()).isEqualTo(0);
+  }
+
+  @Test
+  public void checkAfterDesugar() throws Exception {
+    DesugarInfoCollector desugarInfoCollector = new DesugarInfoCollector();
+    forAllClasses(desugared, desugarInfoCollector);
+    // TODO(b/153971249): The class file version of desugared class files should be Java 7.
+    // assertThat(lambdaUse.getMajorCfVersion()).isEqualTo(V1_7);
+    assertThat(desugarInfoCollector.getNumberOfInvokeDynamic()).isEqualTo(0);
+    assertThat(desugarInfoCollector.getNumberOfDefaultMethods()).isEqualTo(0);
+    assertThat(desugarInfoCollector.getNumberOfDesugaredLambdas()).isEqualTo(1);
+    assertThat(desugarInfoCollector.getNumberOfCompanionClasses()).isEqualTo(1);
+  }
+
+  private static void forAllClasses(Path jar, ClassVisitor classVisitor) throws Exception {
+    forAllClasses(
+        jar,
+        classReader ->
+            classReader.accept(classVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES));
+  }
+
+  private static void forAllClasses(Path jar, Consumer<ClassReader> classReader) throws Exception {
+
+    try (JarInputStream jarInputStream =
+        new JarInputStream(Files.newInputStream(jar, StandardOpenOption.READ))) {
+      JarEntry entry;
+      while ((entry = jarInputStream.getNextJarEntry()) != null) {
+        String entryName = entry.getName();
+        if (FileUtils.isClassFile(entryName)) {
+          classReader.accept(new ClassReader(jarInputStream));
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarInfoCollector.java b/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarInfoCollector.java
new file mode 100644
index 0000000..d60fabb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/desugar/DesugarInfoCollector.java
@@ -0,0 +1,132 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.desugar;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * ASM visitor to collect summary information from class files for checking that desugaring has been
+ * applied
+ */
+public class DesugarInfoCollector extends ClassVisitor {
+  private int largestClassFileVersion;
+  private int numberOfInvokeDynamic;
+  private int numberOfDefaultMethods;
+  private int numberOfDesugaredLambdas;
+  private int numberOfCompanionClasses;
+
+  private int currentClassAccess;
+
+  public DesugarInfoCollector() {
+    this(null);
+  }
+
+  public DesugarInfoCollector(ClassVisitor classVisitor) {
+    super(ASM7, classVisitor);
+  }
+
+  public int getNumberOfInvokeDynamic() {
+    return numberOfInvokeDynamic;
+  }
+
+  public int getNumberOfDefaultMethods() {
+    return numberOfDefaultMethods;
+  }
+
+  public int getLargestMajorClassFileVersion() {
+    return computeMajorClassFileVersion(largestClassFileVersion);
+  }
+
+  public int getNumberOfDesugaredLambdas() {
+    return numberOfDesugaredLambdas;
+  }
+
+  public int getNumberOfCompanionClasses() {
+    return numberOfCompanionClasses;
+  }
+
+  @Override
+  public void visit(
+      int version,
+      int access,
+      java.lang.String name,
+      java.lang.String signature,
+      java.lang.String superName,
+      java.lang.String[] interfaces) {
+    super.visit(version, access, name, signature, superName, interfaces);
+    assertThat(computeMinorClassFileVersion(version)).isEqualTo(0);
+    largestClassFileVersion = Math.max(version, largestClassFileVersion);
+    if (classNameFromBinaryName(name).startsWith("-$$Lambda$")) {
+      numberOfDesugaredLambdas++;
+    }
+    if (name.endsWith("$-CC")) {
+      numberOfCompanionClasses++;
+    }
+    currentClassAccess = access;
+  }
+
+  @Override
+  public MethodVisitor visitMethod(
+      int access, String name, String descriptor, String signature, String[] exceptions) {
+    MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+    if (isInterface(currentClassAccess) && !isAbstract(access)) {
+      numberOfDefaultMethods++;
+    }
+    return new LambdaUseMethodVisitor(api, mv);
+  }
+
+  private class LambdaUseMethodVisitor extends MethodVisitor {
+
+    LambdaUseMethodVisitor(int api, MethodVisitor methodVisitor) {
+      super(api, methodVisitor);
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+      super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+      numberOfInvokeDynamic++;
+    }
+  }
+
+  private static int computeMinorClassFileVersion(int version) {
+    return (version >> 16) & 0xffff;
+  }
+
+  private static int computeMajorClassFileVersion(int version) {
+    return version & 0xffff;
+  }
+
+  private static boolean isAbstract(int access) {
+    return (access & ACC_ABSTRACT) != 0;
+  }
+
+  private static boolean isInterface(int access) {
+    return (access & ACC_INTERFACE) != 0;
+  }
+
+  private static String classNameFromBinaryName(String name) {
+    int index = name.lastIndexOf('/');
+    if (index == -1) {
+      return name;
+    }
+    return name.substring(index + 1);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/desugar/basic/A.java b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/A.java
new file mode 100644
index 0000000..fe729c2
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/A.java
@@ -0,0 +1,16 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.desugar.basic;
+
+class A implements I {}
diff --git a/src/test/java/com/google/devtools/build/android/r8/desugar/basic/I.java b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/I.java
new file mode 100644
index 0000000..febb3cc
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/I.java
@@ -0,0 +1,20 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.desugar.basic;
+
+interface I {
+  default void foo() {
+    System.out.println("I::foo");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/android/r8/desugar/basic/TestClass.java b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/TestClass.java
new file mode 100644
index 0000000..4ccb66a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/r8/desugar/basic/TestClass.java
@@ -0,0 +1,29 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8.desugar.basic;
+
+class TestClass {
+
+  public static void main(String[] args) {
+    Runnable runnable =
+        () -> {
+          System.out.println("Hello, world!");
+        };
+    runnable.run();
+
+    new A().foo();
+  }
+
+  private TestClass() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/r8/Desugar.java
new file mode 100644
index 0000000..6da2d60
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/Desugar.java
@@ -0,0 +1,426 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.r8;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionMetadataTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.List;
+
+/** Desugar compatible wrapper based on D8 desugaring engine */
+public class Desugar {
+  /** Commandline options for {@link com.google.devtools.build.android.r8.Desugar}. */
+  public static class DesugarOptions extends OptionsBase {
+
+    @Option(
+        name = "input",
+        allowMultiple = true,
+        defaultValue = "",
+        category = "input",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        converter = ExistingPathConverter.class,
+        abbrev = 'i',
+        help =
+            "Input Jar or directory with classes to desugar (required, the n-th input is paired"
+                + " with the n-th output).")
+    public List<Path> inputJars;
+
+    @Option(
+        name = "classpath_entry",
+        allowMultiple = true,
+        defaultValue = "",
+        category = "input",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        converter = ExistingPathConverter.class,
+        help =
+            "Ordered classpath (Jar or directory) to resolve symbols in the --input Jar, like "
+                + "javac's -cp flag.")
+    public List<Path> classpath;
+
+    @Option(
+        name = "bootclasspath_entry",
+        allowMultiple = true,
+        defaultValue = "",
+        category = "input",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        converter = ExistingPathConverter.class,
+        help =
+            "Bootclasspath that was used to compile the --input Jar with, like javac's "
+                + "-bootclasspath flag (required).")
+    public List<Path> bootclasspath;
+
+    @Option(
+        name = "allow_empty_bootclasspath",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN})
+    public boolean allowEmptyBootclasspath;
+
+    @Option(
+        name = "only_desugar_javac9_for_lint",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "A temporary flag specifically for android lint, subject to removal anytime (DO NOT"
+                + " USE)")
+    public boolean onlyDesugarJavac9ForLint;
+
+    @Option(
+        name = "rewrite_calls_to_long_compare",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Rewrite calls to Long.compare(long, long) to the JVM instruction lcmp "
+                + "regardless of --min_sdk_version.",
+        category = "misc")
+    public boolean alwaysRewriteLongCompare;
+
+    @Option(
+        name = "output",
+        allowMultiple = true,
+        defaultValue = "",
+        category = "output",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        converter = PathConverter.class,
+        abbrev = 'o',
+        help =
+            "Output Jar or directory to write desugared classes into (required, the n-th output is "
+                + "paired with the n-th input, output must be a Jar if input is a Jar).")
+    public List<Path> outputJars;
+
+    @Option(
+        name = "verbose",
+        defaultValue = "false",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        abbrev = 'v',
+        help = "Enables verbose debugging output.")
+    public boolean verbose;
+
+    @Option(
+        name = "min_sdk_version",
+        defaultValue = "1",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Minimum targeted sdk version.  If >= 24, enables default methods in interfaces.")
+    public int minSdkVersion;
+
+    @Option(
+        name = "emit_dependency_metadata_as_needed",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Whether to emit META-INF/desugar_deps as needed for later consistency checking.")
+    public boolean emitDependencyMetadata;
+
+    @Option(
+        name = "best_effort_tolerate_missing_deps",
+        defaultValue = "true",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Whether to tolerate missing dependencies on the classpath in some cases.  You should "
+                + "strive to set this flag to false.")
+    public boolean tolerateMissingDependencies;
+
+    @Option(
+        name = "desugar_supported_core_libs",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Enable core library desugaring, which requires configuration with related flags.")
+    public boolean desugarCoreLibs;
+
+    @Option(
+        name = "desugar_interface_method_bodies_if_needed",
+        defaultValue = "true",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Rewrites default and static methods in interfaces if --min_sdk_version < 24. This "
+                + "only works correctly if subclasses of rewritten interfaces as well as uses of "
+                + "static interface methods are run through this tool as well.")
+    public boolean desugarInterfaceMethodBodiesIfNeeded;
+
+    @Option(
+        name = "desugar_try_with_resources_if_needed",
+        defaultValue = "true",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Rewrites try-with-resources statements if --min_sdk_version < 19.")
+    public boolean desugarTryWithResourcesIfNeeded;
+
+    @Option(
+        name = "desugar_try_with_resources_omit_runtime_classes",
+        defaultValue = "false",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Omits the runtime classes necessary to support try-with-resources from the output."
+                + " This property has effect only if --desugar_try_with_resources_if_needed is"
+                + " used.")
+    public boolean desugarTryWithResourcesOmitRuntimeClasses;
+
+    @Option(
+        name = "generate_base_classes_for_default_methods",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "If desugaring default methods, generate abstract base classes for them. "
+                + "This reduces default method stubs in hand-written subclasses.")
+    public boolean generateBaseClassesForDefaultMethods;
+
+    @Option(
+        name = "copy_bridges_from_classpath",
+        defaultValue = "false",
+        category = "misc",
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Copy bridges from classpath to desugared classes.")
+    public boolean copyBridgesFromClasspath;
+
+    @Option(
+        name = "core_library",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Enables rewriting to desugar java.* classes.")
+    public boolean coreLibrary;
+
+    /** Type prefixes that we'll move to a custom package. */
+    @Option(
+        name = "rewrite_core_library_prefix",
+        defaultValue = "", // ignored
+        allowMultiple = true,
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Assume the given java.* prefixes are desugared.")
+    public List<String> rewriteCoreLibraryPrefixes;
+
+    /** Interfaces whose default and static interface methods we'll emulate. */
+    @Option(
+        name = "emulate_core_library_interface",
+        defaultValue = "", // ignored
+        allowMultiple = true,
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Assume the given java.* interfaces are emulated.")
+    public List<String> emulateCoreLibraryInterfaces;
+
+    /** Members that we will retarget to the given new owner. */
+    @Option(
+        name = "retarget_core_library_member",
+        defaultValue = "", // ignored
+        allowMultiple = true,
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Method invocations to retarget, given as \"class/Name#member->new/class/Name\".  "
+                + "The new owner is blindly assumed to exist.")
+    public List<String> retargetCoreLibraryMembers;
+
+    /** Members not to rewrite. */
+    @Option(
+        name = "dont_rewrite_core_library_invocation",
+        defaultValue = "", // ignored
+        allowMultiple = true,
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help = "Method invocations not to rewrite, given as \"class/Name#method\".")
+    public List<String> dontTouchCoreLibraryMembers;
+
+    @Option(
+        name = "preserve_core_library_override",
+        defaultValue = "", // ignored
+        allowMultiple = true,
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Core library methods given as \"class/Name#method\" whose overrides should be"
+                + " preserved.  Typically this is useful when the given class itself isn't"
+                + " desugared.")
+    public List<String> preserveCoreLibraryOverrides;
+
+    /** Set to work around b/62623509 with JaCoCo versions prior to 0.7.9. */
+    // TODO(kmb): Remove when Android Studio doesn't need it anymore (see b/37116789)
+    @Option(
+        name = "legacy_jacoco_fix",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Consider setting this flag if you're using JaCoCo versions prior to 0.7.9 to work"
+                + " around issues with coverage instrumentation in default and static interface"
+                + " methods. This flag may be removed when no longer needed.")
+    public boolean legacyJacocoFix;
+
+    /** Convert Java 11 nest-based access control to bridge-based access control. */
+    @Option(
+        name = "desugar_nest_based_private_access",
+        defaultValue = "true",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Desugar JVM 11 native supported accessing private nest members with bridge method"
+                + " based accessors. This flag includes desugaring private interface methods.")
+    public boolean desugarNestBasedPrivateAccess;
+
+    /**
+     * Convert Java 9 invokedynamic-based string concatenations to StringBuilder-based
+     * concatenations. @see https://openjdk.java.net/jeps/280
+     */
+    @Option(
+        name = "desugar_indy_string_concat",
+        defaultValue = "true",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Desugar JVM 9 string concatenation operations to string builder based"
+                + " implementations.")
+    public boolean desugarIndifyStringConcat;
+
+    @Option(
+        name = "persistent_worker",
+        defaultValue = "false",
+        documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        metadataTags = {OptionMetadataTag.HIDDEN},
+        help = "Run as a Bazel persistent worker.")
+    public boolean persistentWorker;
+  }
+
+  private static DesugarOptions parseCommandLineOptions(String[] args) {
+    OptionsParser parser =
+        OptionsParser.builder()
+            .optionsClasses(DesugarOptions.class)
+            .allowResidue(false)
+            .argsPreProcessor(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()))
+            .build();
+    parser.parseAndExitUponError(args);
+    DesugarOptions options = parser.getOptions(DesugarOptions.class);
+
+    return options;
+  }
+
+  private static void desugar(DesugarOptions options) throws CompilationFailedException {
+    D8.run(
+        D8Command.builder()
+            .addLibraryFiles(options.bootclasspath)
+            .addProgramFiles(options.inputJars)
+            .setMinApiLevel(options.minSdkVersion)
+            .setOutput(options.outputJars.get(0), OutputMode.ClassFile)
+            .build());
+  }
+
+  private static void validateOptions(DesugarOptions options) {
+    if (options.allowEmptyBootclasspath) {
+      throw new AssertionError("--allow_empty_bootclasspath is not supported");
+    }
+    if (options.onlyDesugarJavac9ForLint) {
+      throw new AssertionError("--only_desugar_javac9_for_lint is not supported");
+    }
+    if (options.alwaysRewriteLongCompare) {
+      throw new AssertionError("--rewrite_calls_to_long_compare has no effect");
+    }
+    if (options.emitDependencyMetadata) {
+      throw new AssertionError("--emit_dependency_metadata_as_needed is not supported");
+    }
+    if (!options.tolerateMissingDependencies) {
+      throw new AssertionError("--best_effort_tolerate_missing_deps must be enabled");
+    }
+    if (options.desugarCoreLibs) {
+      throw new AssertionError("--desugar_supported_core_libs is not supported");
+    }
+    if (!options.desugarInterfaceMethodBodiesIfNeeded) {
+      throw new AssertionError("--desugar_interface_method_bodies_if_needed must be enabled");
+    }
+    if (!options.desugarTryWithResourcesIfNeeded) {
+      throw new AssertionError("--desugar_try_with_resources_if_needed must be enabled");
+    }
+    if (options.desugarTryWithResourcesOmitRuntimeClasses) {
+      throw new AssertionError(
+          "--desugar_try_with_resources_omit_runtime_classes is not supported");
+    }
+    if (options.generateBaseClassesForDefaultMethods) {
+      throw new AssertionError("--generate_base_classes_for_default_methods is not supported");
+    }
+    if (options.copyBridgesFromClasspath) {
+      throw new AssertionError("--copy_bridges_from_classpath is not supported");
+    }
+    if (options.coreLibrary) {
+      throw new AssertionError("--core_library is not supported");
+    }
+    if (!options.rewriteCoreLibraryPrefixes.isEmpty()) {
+      throw new AssertionError("--rewrite_core_library_prefix is not supported");
+    }
+    if (!options.emulateCoreLibraryInterfaces.isEmpty()) {
+      throw new AssertionError("--emulate_core_library_interface is not supported");
+    }
+    if (!options.retargetCoreLibraryMembers.isEmpty()) {
+      throw new AssertionError("--retarget_core_library_member is not supported");
+    }
+    if (!options.dontTouchCoreLibraryMembers.isEmpty()) {
+      throw new AssertionError("--dont_rewrite_core_library_invocation is not supported");
+    }
+    if (!options.preserveCoreLibraryOverrides.isEmpty()) {
+      throw new AssertionError("--preserve_core_library_override is not supported");
+    }
+    if (options.legacyJacocoFix) {
+      throw new AssertionError("--legacy_jacoco_fix is not supported");
+    }
+    if (!options.desugarNestBasedPrivateAccess) {
+      throw new AssertionError("--desugar_nest_based_private_access must be enabled");
+    }
+    if (!options.desugarIndifyStringConcat) {
+      throw new AssertionError("--desugar_indy_string_concat must be enabled");
+    }
+    if (options.persistentWorker) {
+      throw new AssertionError("--persistent_worker is not supported");
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    DesugarOptions options = parseCommandLineOptions(args);
+    validateOptions(options);
+
+    desugar(options);
+  }
+
+  private Desugar() {}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java b/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java
index e54cfe0..51296e0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java
+++ b/src/tools/android/java/com/google/devtools/build/android/r8/FileUtils.java
@@ -16,7 +16,8 @@
 import com.google.common.base.Ascii;
 import java.nio.file.Path;
 
-class FileUtils {
+/** File related utilities */
+public class FileUtils {
   public static final String AAR_EXTENSION = ".aar";
   public static final String APK_EXTENSION = ".apk";
   public static final String CLASS_EXTENSION = ".class";
@@ -34,11 +35,11 @@
     return hasExtension(path.getFileName().toString(), extension);
   }
 
-  static boolean isDexFile(Path path) {
+  public static boolean isDexFile(Path path) {
     return hasExtension(path, DEX_EXTENSION);
   }
 
-  static boolean isClassFile(String name) {
+  public static boolean isClassFile(String name) {
     name = Ascii.toLowerCase(name);
     // Android does not support Java 9 module, thus skip module-info.
     if (name.equals(MODULE_INFO_CLASS)) {
@@ -50,43 +51,43 @@
     return name.endsWith(CLASS_EXTENSION);
   }
 
-  static boolean isClassFile(Path path) {
+  public static boolean isClassFile(Path path) {
     return isClassFile(path.getFileName().toString());
   }
 
-  static boolean isJarFile(String name) {
+  public static boolean isJarFile(String name) {
     return hasExtension(name, JAR_EXTENSION);
   }
 
-  static boolean isJarFile(Path path) {
+  public static boolean isJarFile(Path path) {
     return hasExtension(path, JAR_EXTENSION);
   }
 
-  static boolean isZipFile(String name) {
+  public static boolean isZipFile(String name) {
     return hasExtension(name, ZIP_EXTENSION);
   }
 
-  static boolean isZipFile(Path path) {
+  public static boolean isZipFile(Path path) {
     return hasExtension(path, ZIP_EXTENSION);
   }
 
-  static boolean isApkFile(String name) {
+  public static boolean isApkFile(String name) {
     return hasExtension(name, APK_EXTENSION);
   }
 
-  static boolean isApkFile(Path path) {
+  public static boolean isApkFile(Path path) {
     return hasExtension(path, APK_EXTENSION);
   }
 
-  static boolean isAarFile(String name) {
+  public static boolean isAarFile(String name) {
     return hasExtension(name, AAR_EXTENSION);
   }
 
-  static boolean isAarFile(Path path) {
+  public static boolean isAarFile(Path path) {
     return hasExtension(path, AAR_EXTENSION);
   }
 
-  static boolean isArchive(Path path) {
+  public static boolean isArchive(Path path) {
     return isApkFile(path) || isJarFile(path) || isZipFile(path) || isAarFile(path);
   }