Version 2.0.28

Cherry-pick: Provide an API to retreive the list of backported methods
CL: https://r8-review.googlesource.com/c/r8/+/48310

Cherry-pick: Extend list of backported methoods
CL: https://r8-review.googlesource.com/c/r8/+/48428

Bug: 140368601
Bug: 140367927
Change-Id: Icec3a85d2c47d3209eeff3e7495cf209fd9e777e
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodList.java b/src/main/java/com/android/tools/r8/BackportedMethodList.java
new file mode 100644
index 0000000..befc767
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodList.java
@@ -0,0 +1,111 @@
+// 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.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Tool to extract the list of methods which is backported by the D8 and R8 compilers.
+ *
+ * <p>The D8 and R8 compilers will backport some simple Java APIs which are not present on all API
+ * levels. One example of this is the static method <code>int Integer.divideUnsigned(int a, int b)
+ * </code> which was added from API level 26.
+ *
+ * <p>As these backported methods is supported on all API levels, tools like linters and code
+ * checkers need this information to avoid false negatives when analyzing code.
+ *
+ * <p>This tool will generate a list of all the backported methods for the associated version of D8
+ * and R8.
+ *
+ * <p>This tool will <strong>not</strong> provide information about the APIs supported when using
+ * library desugaring. That information is provided in the dependencies used for library desugaring.
+ * However, in place of library desugaring backporting will be able to backport additional methods.
+ * If library desugaring is used, then passing information about that to this tool will provide the
+ * more precise list. See b/149078312.
+ *
+ * <p>The tool is invoked by calling {@link #run(BackportedMethodListCommand)
+ * BackportedMethodList.run} with an appropriate {@link BackportedMethodListCommand}.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *   BackportedMethodList.run(BackportedMethodListCommand.builder()
+ *       .setMinApiLevel(apiLevel)
+ *       .setOutputPath(Paths.get("methods-list.txt"))
+ *       .build());
+ * </pre>
+ *
+ * The above generates the list of backported methods for a compilation with a min API of <code>
+ * apiLevel</code> into the file <code>methods-list.txt</code>.
+ */
+@Keep
+public class BackportedMethodList {
+
+  static final String USAGE_MESSAGE =
+      StringUtils.joinLines(
+          "Usage: BackportedMethodList [options]",
+          " Options are:",
+          "  --output <file>         # Output result in <file>.",
+          "  --min-api <number>      # Minimum Android API level for the application",
+          "  --desugared-lib <file>  # Desugared library configuration (JSON from the",
+          "                          # configuration)",
+          "  --lib <file>            # The compilation SDK library (android.jar)",
+          "  --version               # Print the version of BackportedMethodList.",
+          "  --help                  # Print this message.");
+
+  private static String formatMethod(DexMethod method) {
+    return DescriptorUtils.getClassBinaryNameFromDescriptor(method.holder.descriptor.toString())
+        + '#'
+        + method.name
+        + method.proto.toDescriptorString();
+  }
+
+  public static void run(BackportedMethodListCommand command) throws CompilationFailedException {
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("BackportedMethodList " + Version.getVersionString());
+      return;
+    }
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    try {
+      ExceptionUtils.withD8CompilationHandler(
+          command.getReporter(),
+          () -> {
+            BackportedMethodRewriter.generateListOfBackportedMethods(
+                    command.getInputApp(), options, executorService)
+                .stream()
+                .map(BackportedMethodList::formatMethod)
+                .sorted()
+                .forEach(
+                    formattedMethod ->
+                        command
+                            .getBackportedMethodListConsumer()
+                            .accept(formattedMethod, command.getReporter()));
+            command.getBackportedMethodListConsumer().finished(command.getReporter());
+          });
+    } finally {
+      executorService.shutdown();
+    }
+  }
+
+  public static void run(String[] args) throws CompilationFailedException {
+    run(BackportedMethodListCommand.parse(args).build());
+  }
+
+  public static void main(String[] args) {
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
new file mode 100644
index 0000000..a4af022
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -0,0 +1,341 @@
+// Copyright (c) 2020, 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.graph.DexItemFactory;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+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.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Immutable command structure for an invocation of the {@link BackportedMethodList} tool.
+ *
+ * <p>To build a BackportedMethodList command use the {@link BackportedMethodListCommand.Builder}
+ * class. For example:
+ *
+ * <pre>
+ *   BackportedMethodListCommand command = BackportedMethodListCommand.builder()
+ *     .setMinApiLevel(apiLevel)
+ *     .setOutputPath(Paths.get("methods-list.txt"))
+ *     .build();
+ * </pre>
+ */
+@Keep
+public class BackportedMethodListCommand {
+
+  private final boolean printHelp;
+  private final boolean printVersion;
+  private final Reporter reporter;
+  private final int minApiLevel;
+  private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+  private final AndroidApp app;
+  private final StringConsumer backportedMethodListConsumer;
+  private final DexItemFactory factory;
+
+  public boolean isPrintHelp() {
+    return printHelp;
+  }
+
+  public boolean isPrintVersion() {
+    return printVersion;
+  }
+
+  Reporter getReporter() {
+    return reporter;
+  }
+
+  public int getMinApiLevel() {
+    return minApiLevel;
+  }
+
+  public DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
+    return desugaredLibraryConfiguration;
+  }
+
+  public StringConsumer getBackportedMethodListConsumer() {
+    return backportedMethodListConsumer;
+  }
+
+  AndroidApp getInputApp() {
+    return app;
+  }
+
+  private BackportedMethodListCommand(boolean printHelp, boolean printVersion) {
+    this.printHelp = printHelp;
+    this.printVersion = printVersion;
+    this.reporter = new Reporter();
+    this.minApiLevel = -1;
+    this.desugaredLibraryConfiguration = null;
+    this.app = null;
+    this.backportedMethodListConsumer = null;
+    this.factory = null;
+  }
+
+  private BackportedMethodListCommand(
+      Reporter reporter,
+      int minApiLevel,
+      DesugaredLibraryConfiguration desugaredLibraryConfiguration,
+      AndroidApp app,
+      StringConsumer backportedMethodListConsumer,
+      DexItemFactory factory) {
+    this.printHelp = false;
+    this.printVersion = false;
+    this.reporter = reporter;
+    this.minApiLevel = minApiLevel;
+    this.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+    this.app = app;
+    this.backportedMethodListConsumer = backportedMethodListConsumer;
+    this.factory = factory;
+  }
+
+  InternalOptions getInternalOptions() {
+    InternalOptions options = new InternalOptions(factory, getReporter());
+    options.minApiLevel = minApiLevel;
+    options.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
+    return options;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  public static Builder parse(String[] args) {
+    final Set<String> OPTIONS_WITH_PARAMETER =
+        ImmutableSet.of("--output", "--min-api", "--desugared-lib", "--lib");
+
+    boolean hasDefinedApiLevel = false;
+    Builder builder = builder();
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i].trim();
+      String nextArg = null;
+      if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+        if (++i < args.length) {
+          nextArg = args[i];
+        } else {
+          builder.error(new StringDiagnostic("Missing parameter for " + args[i - 1] + "."));
+          break;
+        }
+      }
+      if (arg.equals("--help")) {
+        builder.setPrintHelp(true);
+      } else if (arg.equals("--version")) {
+        builder.setPrintVersion(true);
+      } else if (arg.equals("--min-api")) {
+        if (hasDefinedApiLevel) {
+          builder.error(new StringDiagnostic("Cannot set multiple --min-api options"));
+        } else {
+          parseMinApi(builder, nextArg);
+          hasDefinedApiLevel = true;
+        }
+      } else if (arg.equals("--desugared-lib")) {
+        builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else if (arg.equals("--lib")) {
+        builder.addLibraryFiles(Paths.get(nextArg));
+      } else if (arg.equals("--output")) {
+        builder.setOutputPath(Paths.get(nextArg));
+      } else {
+        builder.error(new StringDiagnostic("Unknown option: " + arg));
+      }
+    }
+    return builder;
+  }
+
+  private static void parseMinApi(Builder builder, String minApiString) {
+    int minApi;
+    try {
+      minApi = Integer.parseInt(minApiString);
+    } catch (NumberFormatException e) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+      return;
+    }
+    if (minApi < 1) {
+      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
+      return;
+    }
+    builder.setMinApiLevel(minApi);
+  }
+
+  @Keep
+  public static class Builder {
+
+    private final Reporter reporter;
+    private int minApiLevel = AndroidApiLevel.B.getLevel();
+    private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
+    private final AndroidApp.Builder app;
+    private StringConsumer backportedMethodListConsumer;
+    private boolean printHelp = false;
+    private boolean printVersion = false;
+
+    private Builder() {
+      this(new DiagnosticsHandler() {});
+    }
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.app = AndroidApp.builder();
+      this.reporter = new Reporter(diagnosticsHandler);
+    }
+
+    /**
+     * Set the minimum API level for the application compiled.
+     *
+     * <p>The tool will only report backported methods which are not present at this API level.
+     *
+     * <p>The default is 1 if never set.
+     */
+    public Builder setMinApiLevel(int minApiLevel) {
+      if (minApiLevel <= 0) {
+        reporter.error(new StringDiagnostic("Invalid minApiLevel: " + minApiLevel));
+      } else {
+        this.minApiLevel = minApiLevel;
+      }
+      return this;
+    }
+
+    public int getMinApiLevel() {
+      return minApiLevel;
+    }
+
+    /** Desugared library configuration */
+    public Builder addDesugaredLibraryConfiguration(StringResource configuration) {
+      desugaredLibraryConfigurationResources.add(configuration);
+      return this;
+    }
+
+    /** Desugared library configuration */
+    public Builder addDesugaredLibraryConfiguration(String configuration) {
+      return addDesugaredLibraryConfiguration(
+          StringResource.fromString(configuration, Origin.unknown()));
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
+      app.addLibraryResourceProvider(provider);
+      return this;
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryFiles(Path... files) {
+      addLibraryFiles(Arrays.asList(files));
+      return this;
+    }
+
+    /** The compilation SDK library (android.jar) */
+    public Builder addLibraryFiles(Collection<Path> files) {
+      for (Path path : files) {
+        app.addLibraryFile(path);
+      }
+      return this;
+    }
+
+    DesugaredLibraryConfiguration getDesugaredLibraryConfiguration(DexItemFactory factory) {
+      if (desugaredLibraryConfigurationResources.isEmpty()) {
+        return DesugaredLibraryConfiguration.empty();
+      }
+      if (desugaredLibraryConfigurationResources.size() > 1) {
+        reporter.fatalError("Only one desugared library configuration is supported.");
+      }
+      StringResource desugaredLibraryConfigurationResource =
+          desugaredLibraryConfigurationResources.get(0);
+      DesugaredLibraryConfigurationParser libraryParser =
+          new DesugaredLibraryConfigurationParser(factory, null, false, getMinApiLevel());
+      return libraryParser.parse(desugaredLibraryConfigurationResource);
+    }
+
+    /** Output file for the backported method list */
+    public Builder setOutputPath(Path outputPath) {
+      backportedMethodListConsumer =
+          new StringConsumer.FileConsumer(outputPath) {
+            @Override
+            public void accept(String string, DiagnosticsHandler handler) {
+              super.accept(string, handler);
+              super.accept(System.lineSeparator(), handler);
+            }
+          };
+      return this;
+    }
+
+    /** Consumer receiving the the backported method list */
+    public Builder setConsumer(StringConsumer consumer) {
+      this.backportedMethodListConsumer = consumer;
+      return this;
+    }
+
+    /** True if the print-help flag is enabled. */
+    public boolean isPrintHelp() {
+      return printHelp;
+    }
+
+    /** Set the value of the print-help flag. */
+    public Builder setPrintHelp(boolean printHelp) {
+      this.printHelp = printHelp;
+      return this;
+    }
+
+    /** True if the print-version flag is enabled. */
+    public boolean isPrintVersion() {
+      return printVersion;
+    }
+
+    /** Set the value of the print-version flag. */
+    public Builder setPrintVersion(boolean printVersion) {
+      this.printVersion = printVersion;
+      return this;
+    }
+
+    private void error(Diagnostic diagnostic) {
+      reporter.error(diagnostic);
+    }
+
+    public BackportedMethodListCommand build() {
+      AndroidApp library = app.build();
+      if (!desugaredLibraryConfigurationResources.isEmpty()
+          && library.getLibraryResourceProviders().isEmpty()) {
+        reporter.error(
+            new StringDiagnostic("With desugared library configuration a library is required"));
+      }
+
+      if (isPrintHelp() || isPrintVersion()) {
+        return new BackportedMethodListCommand(isPrintHelp(), isPrintVersion());
+      }
+
+      if (backportedMethodListConsumer == null) {
+        backportedMethodListConsumer =
+            new StringConsumer() {
+              @Override
+              public void accept(String string, DiagnosticsHandler handler) {
+                System.out.println(string);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            };
+      }
+      DexItemFactory factory = new DexItemFactory();
+      return new BackportedMethodListCommand(
+          reporter,
+          minApiLevel,
+          getDesugaredLibraryConfiguration(factory),
+          library,
+          backportedMethodListConsumer,
+          factory);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java b/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
deleted file mode 100644
index 90f577b..0000000
--- a/src/main/java/com/android/tools/r8/GenerateBackportedMethodList.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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.graph.DexMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.utils.AndroidApiLevel;
-
-public class GenerateBackportedMethodList {
-
-  public static void main(String[] args) {
-    BackportedMethodRewriter.generateListOfBackportedMethods(AndroidApiLevel.B).stream()
-        .map(DexMethod::toSourceString)
-        .sorted()
-        .forEach(System.out::println);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index d13081c..89aa64e 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -79,6 +79,9 @@
       case "l8":
         L8.main(shift(args));
         break;
+      case "backportedmethods":
+        BackportedMethodList.main(shift(args));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 076b8a3..b3252ab 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.0.27";
+  public static final String LABEL = "2.0.28";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 8c632a2..5d1671b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -43,10 +49,13 @@
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -81,15 +90,29 @@
     this.rewritableMethods = new RewritableMethods(appView.options(), appView);
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(AndroidApiLevel apiLevel) {
-    List<DexMethod> methods = new ArrayList<>();
-    InternalOptions options = new InternalOptions();
-    options.minApiLevel = apiLevel.getLevel();
-    AppView<?> appView = AppView.createForD8(null, options);
-    BackportedMethodRewriter.RewritableMethods rewritableMethods =
-        new BackportedMethodRewriter.RewritableMethods(options, appView);
-    rewritableMethods.visit(methods::add);
-    return methods;
+  public static List<DexMethod> generateListOfBackportedMethods(
+      AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+    try {
+      List<DexMethod> methods = new ArrayList<>();
+      PrefixRewritingMapper rewritePrefix =
+          options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+      AppInfo appInfo = null;
+      if (androidApp != null) {
+        DexApplication app =
+            new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+        appInfo =
+            options.desugaredLibraryConfiguration.getRewritePrefix().isEmpty()
+                ? new AppInfo(app)
+                : new AppInfoWithClassHierarchy(app);
+      }
+      AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+      BackportedMethodRewriter.RewritableMethods rewritableMethods =
+          new BackportedMethodRewriter.RewritableMethods(options, appView);
+      rewritableMethods.visit(methods::add);
+      return methods;
+    } catch (ExecutionException e) {
+      throw unwrapExecutionException(e);
+    }
   }
 
   public void desugar(IRCode code) {
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
new file mode 100644
index 0000000..bb344d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2020, 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackportedMethodListTest {
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  enum Mode {
+    NO_LIBRARY,
+    LIBRARY,
+    LIBRARY_DESUGAR
+  }
+
+  @Parameterized.Parameters(name = "Mode: {0}")
+  public static Object[] data() {
+    return Mode.values();
+  }
+
+  private final Mode mode;
+
+  public BackportedMethodListTest(Mode mode) {
+    this.mode = mode;
+  }
+
+  private static class ListStringConsumer implements StringConsumer {
+    List<String> strings = new ArrayList<>();
+    boolean finished = false;
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      strings.add(string);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      finished = true;
+    }
+  }
+
+  private void checkContent(int apiLevel, List<String> backports) {
+    // Java 8 methods added at various API levels.
+    assertEquals(
+        apiLevel < AndroidApiLevel.K.getLevel(), backports.contains("java/lang/Byte#compare(BB)I"));
+    assertEquals(
+        apiLevel < AndroidApiLevel.N.getLevel(),
+        backports.contains("java/lang/Integer#hashCode(I)I"));
+    assertEquals(
+        apiLevel < AndroidApiLevel.O.getLevel(),
+        backports.contains("java/lang/Short#toUnsignedLong(S)J"));
+
+    // Java 9, 10 and 11 Optional methods which require Android N or library desugaring.
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains(
+            "java/util/Optional#or(Ljava/util/function/Supplier;)Ljava/util/Optional;"));
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains("java/util/OptionalInt#orElseThrow()I"));
+    assertEquals(
+        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        backports.contains("java/util/OptionalLong#isEmpty()Z"));
+
+    // Java 9, 10 and 11 methods.
+    assertTrue(backports.contains("java/lang/StrictMath#multiplyExact(JI)J"));
+    assertTrue(backports.contains("java/util/List#copyOf(Ljava/util/Collection;)Ljava/util/List;"));
+    assertTrue(backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+  }
+
+  private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
+    builder
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+  }
+
+  @Test
+  public void testConsumer() throws Exception {
+    for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+      ListStringConsumer consumer = new ListStringConsumer();
+      BackportedMethodListCommand.Builder builder =
+          BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setConsumer(consumer);
+      if (mode == Mode.LIBRARY) {
+        builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+      } else if (mode == Mode.LIBRARY_DESUGAR) {
+        addLibraryDesugaring(builder);
+      }
+      BackportedMethodList.run(builder.build());
+      assertTrue(consumer.finished);
+      checkContent(apiLevel, consumer.strings);
+    }
+  }
+
+  @Test
+  public void testFile() throws Exception {
+    for (int apiLevel = 1; apiLevel < AndroidApiLevel.LATEST.getLevel(); apiLevel++) {
+      Path output = temp.newFile().toPath();
+      BackportedMethodListCommand.Builder builder =
+          BackportedMethodListCommand.builder().setMinApiLevel(apiLevel).setOutputPath(output);
+      if (mode == Mode.LIBRARY) {
+        builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+      } else if (mode == Mode.LIBRARY_DESUGAR) {
+        addLibraryDesugaring(builder);
+      }
+      BackportedMethodList.run(builder.build());
+      checkContent(apiLevel, Files.readAllLines(output));
+    }
+  }
+
+  @Test
+  public void testFullList() throws Exception {
+    Assume.assumeTrue(mode == Mode.NO_LIBRARY);
+    ListStringConsumer consumer = new ListStringConsumer();
+    // Not setting neither min API level not library should produce the full list.
+    BackportedMethodList.run(BackportedMethodListCommand.builder().setConsumer(consumer).build());
+    assertTrue(consumer.finished);
+    checkContent(1, consumer.strings);
+  }
+
+  @Test
+  public void requireLibraryForDesugar() {
+    Assume.assumeTrue(mode == Mode.LIBRARY_DESUGAR);
+    // Require library when a desugar configuration is passed.
+    try {
+      BackportedMethodList.run(
+          BackportedMethodListCommand.builder()
+              .addDesugaredLibraryConfiguration(
+                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+              .setConsumer(new ListStringConsumer())
+              .build());
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 2c7cbe5..83682b2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -34,8 +36,11 @@
       // Check that the backported methods for each API level are are not present in the
       // android.jar for that level.
       CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
+      InternalOptions options = new InternalOptions();
+      options.minApiLevel = apiLevel.getLevel();
       List<DexMethod> backportedMethods =
-          BackportedMethodRewriter.generateListOfBackportedMethods(apiLevel);
+          BackportedMethodRewriter.generateListOfBackportedMethods(
+              null, options, ThreadUtils.getExecutorService(options));
       for (DexMethod method : backportedMethods) {
         // Two different DexItemFactories are in play, but as toSourceString is used for lookup
         // that is not an issue.