Generate method lists for lint based on library desugar configuration

The generated files are included in the maven artifact with library
desugar configuration.

Bug: 134732760
Change-Id: I3bf8c43ef8e3004ba802d3584a636e31d4ded1ed
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
new file mode 100644
index 0000000..2c63a69
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -0,0 +1,335 @@
+// 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.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.naming.NamingLens;
+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.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public class GenerateLintFiles {
+
+  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+
+  private final DexItemFactory factory = new DexItemFactory();
+  private final Reporter reporter = new Reporter();
+  private final InternalOptions options = new InternalOptions(factory, reporter);
+
+  private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
+  private final String outputDirectory;
+
+  private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
+
+  private GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) {
+    this.desugaredLibraryConfiguration =
+        readDesugaredLibraryConfiguration(desugarConfigurationPath);
+    this.outputDirectory =
+        outputDirectory.endsWith("/") ? outputDirectory : outputDirectory + File.separator;
+
+    DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
+    DexMethod parallelMethod =
+        factory.createMethod(
+            factory.collectionType,
+            factory.createProto(streamType),
+            factory.createString("parallelStream"));
+    parallelMethods.add(parallelMethod);
+    for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
+      streamType =
+          factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(streamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
+    }
+  }
+
+  private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+    String jar = String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    return Paths.get(jar);
+  }
+
+  private DesugaredLibraryConfiguration readDesugaredLibraryConfiguration(
+      String desugarConfigurationPath) {
+    return new DesugaredLibraryConfigurationParser(
+            factory, reporter, false, AndroidApiLevel.B.getLevel())
+        .parse(StringResource.fromFile(Paths.get(desugarConfigurationPath)));
+  }
+
+  private CfCode buildEmptyThrowingCfCode(DexMethod method) {
+    CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
+    return new CfCode(
+        method.holder,
+        1,
+        method.proto.parameters.size() + 1,
+        Arrays.asList(insn),
+        Collections.emptyList(),
+        Collections.emptyList());
+  }
+
+  private void addMethodsToHeaderJar(
+      DexApplication.Builder builder, DexClass clazz, List<DexEncodedMethod> methods) {
+    if (methods.size() == 0) {
+      return;
+    }
+
+    List<DexEncodedMethod> directMethods = new ArrayList<>();
+    List<DexEncodedMethod> virtualMethods = new ArrayList<>();
+    for (DexEncodedMethod method : methods) {
+      assert method.method.holder == clazz.type;
+      CfCode code = null;
+      if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
+        code = buildEmptyThrowingCfCode(method.method);
+      }
+      DexEncodedMethod throwingMethod =
+          new DexEncodedMethod(
+              method.method,
+              method.accessFlags,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              code,
+              50);
+      if (method.accessFlags.isStatic()) {
+        directMethods.add(throwingMethod);
+      } else {
+        virtualMethods.add(throwingMethod);
+      }
+    }
+
+    DexEncodedMethod[] directMethodsArray = new DexEncodedMethod[directMethods.size()];
+    DexEncodedMethod[] virtualMethodsArray = new DexEncodedMethod[virtualMethods.size()];
+    directMethods.toArray(directMethodsArray);
+    virtualMethods.toArray(virtualMethodsArray);
+    builder.addProgramClass(
+        new DexProgramClass(
+            clazz.type,
+            null,
+            Origin.unknown(),
+            clazz.accessFlags,
+            clazz.superType,
+            clazz.interfaces,
+            null,
+            null,
+            Collections.emptyList(),
+            null,
+            Collections.emptyList(),
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            directMethodsArray,
+            virtualMethodsArray,
+            false));
+  }
+
+  private Map<DexClass, List<DexEncodedMethod>> collectSupportedMethods(
+      AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported)
+      throws IOException, ExecutionException {
+
+    // Read the android.jar for the compilation API level.
+    AndroidApp library =
+        AndroidApp.builder().addLibraryFiles(getAndroidJarPath(compilationApiLevel)).build();
+    DirectMappedDexApplication dexApplication =
+        new ApplicationReader(library, options, new Timing()).read().toDirect();
+
+    // collect all the methods that the library desugar configuration adds support for.
+    Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
+    for (DexLibraryClass clazz : dexApplication.libraryClasses()) {
+      String className = clazz.toSourceString();
+      // All the methods with the rewritten prefix are supported.
+      for (String prefix : desugaredLibraryConfiguration.getRewritePrefix().keySet()) {
+        if (clazz.accessFlags.isPublic() && className.startsWith(prefix)) {
+          for (DexEncodedMethod method : clazz.methods()) {
+            if (supported.test(method)) {
+              supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
+            }
+          }
+        }
+      }
+
+      // All retargeted methods are supported.
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (desugaredLibraryConfiguration
+            .getRetargetCoreLibMember()
+            .keySet()
+            .contains(method.method.name)) {
+          if (desugaredLibraryConfiguration
+              .getRetargetCoreLibMember()
+              .get(method.method.name)
+              .containsKey(clazz.type)) {
+            if (supported.test(method)) {
+              supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
+            }
+          }
+        }
+      }
+      // All emulated interfaces methods are supported.
+      if (desugaredLibraryConfiguration.getEmulateLibraryInterface().containsKey(clazz.type)) {
+        for (DexEncodedMethod method : clazz.methods()) {
+          if (supported.test(method)) {
+            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
+          }
+        }
+      }
+    }
+
+    return supportedMethods;
+  }
+
+  private String lintBaseFileName(
+      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel) {
+    return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
+  }
+
+  private Path lintFile(
+      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension)
+      throws Exception {
+    Path directory =
+        Paths.get(outputDirectory + "compile_api_level_" + compilationApiLevel.getLevel());
+    Files.createDirectories(directory);
+    return Paths.get(
+        directory
+            + File.separator
+            + lintBaseFileName(compilationApiLevel, minApiLevel)
+            + extension);
+  }
+
+  private void writeLintFiles(
+      AndroidApiLevel compilationApiLevel,
+      AndroidApiLevel minApiLevel,
+      Map<DexClass, List<DexEncodedMethod>> supportedMethods)
+      throws Exception {
+    // Build a plain text file with the desugared APIs.
+    List<String> desugaredApisSignatures = new ArrayList<>();
+
+    DexApplication.Builder builder = DexApplication.builder(options, new Timing());
+    supportedMethods.forEach(
+        (clazz, methods) -> {
+          String classBinaryName =
+              DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
+          for (DexEncodedMethod method : methods) {
+            desugaredApisSignatures.add(
+                classBinaryName
+                    + '/'
+                    + method.method.name
+                    + method.method.proto.toDescriptorString());
+          }
+
+          addMethodsToHeaderJar(builder, clazz, methods);
+        });
+    DexApplication app = builder.build();
+
+    // Write a plain text file with the desugared APIs.
+    FileUtils.writeTextFile(
+        lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
+
+    // Write a header jar with the desugared APIs.
+    AppInfo appInfo = new AppInfo(app);
+    AppView<?> appView = AppView.createForD8(appInfo, options);
+    CfApplicationWriter writer =
+        new CfApplicationWriter(
+            builder.build(),
+            appView,
+            options,
+            options.getMarker(Tool.L8),
+            GraphLense.getIdentityLense(),
+            NamingLens.getIdentityLens(),
+            null);
+    ClassFileConsumer consumer =
+        new ClassFileConsumer.ArchiveConsumer(
+            lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
+    writer.write(consumer, ThreadUtils.getExecutorService(options));
+    consumer.finished(options.reporter);
+  }
+
+  private void generateLintFiles(
+      AndroidApiLevel compilationApiLevel,
+      Predicate<AndroidApiLevel> generateForThisMinApiLevel,
+      BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
+      throws Exception {
+    for (AndroidApiLevel value : AndroidApiLevel.values()) {
+      if (!generateForThisMinApiLevel.test(value)) {
+        continue;
+      }
+
+      Map<DexClass, List<DexEncodedMethod>> supportedMethods =
+          collectSupportedMethods(
+              compilationApiLevel, (method -> supportedForMinApiLevel.test(value, method)));
+      writeLintFiles(compilationApiLevel, value, supportedMethods);
+    }
+  }
+
+  private void run() throws Exception {
+    // Run over all the API levels that the desugared library can be compiled with.
+    for (int apiLevel = AndroidApiLevel.Q.getLevel();
+        apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel();
+        apiLevel--) {
+      System.out.println("Generating lint files for compile API " + apiLevel);
+      generateLintFiles(
+          AndroidApiLevel.getAndroidApiLevel(apiLevel),
+          minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
+          (minApiLevel, method) -> {
+            assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
+            if (minApiLevel == AndroidApiLevel.L) {
+              return true;
+            }
+            assert minApiLevel == AndroidApiLevel.B;
+            return !parallelMethods.contains(method.method);
+          });
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    if (args.length != 2) {
+      System.out.println("Usage: GenerateLineFiles <desuage configuration> <output directory>");
+      System.exit(1);
+    }
+    new GenerateLintFiles(args[0], args[1]).run();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ac75baa..9ccabb9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -218,6 +218,7 @@
   public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;");
   public final DexString listDescriptor = createString("Ljava/util/List;");
   public final DexString setDescriptor = createString("Ljava/util/Set;");
+  public final DexString collectionDescriptor = createString("Ljava/util/Collection;");
   public final DexString comparatorDescriptor = createString("Ljava/util/Comparator;");
   public final DexString callableDescriptor = createString("Ljava/util/concurrent/Callable;");
   public final DexString supplierDescriptor = createString("Ljava/util/function/Supplier;");
@@ -307,6 +308,7 @@
   public final DexType serviceLoaderType = createType(serviceLoaderDescriptor);
   public final DexType listType = createType(listDescriptor);
   public final DexType setType = createType(setDescriptor);
+  public final DexType collectionType = createType(collectionDescriptor);
   public final DexType comparatorType = createType(comparatorDescriptor);
   public final DexType callableType = createType(callableDescriptor);
   public final DexType supplierType = createType(supplierDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index d3dc54d..e1f2181 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
@@ -22,6 +23,7 @@
 public class DesugaredLibraryConfiguration {
 
   // TODO(b/134732760): should use DexString, DexType, DexMethod or so on when possible.
+  private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final Map<String, String> rewritePrefix;
   private final Map<DexType, DexType> emulateLibraryInterface;
@@ -36,6 +38,7 @@
 
   public static DesugaredLibraryConfiguration empty() {
     return new DesugaredLibraryConfiguration(
+        AndroidApiLevel.B,
         false,
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -46,6 +49,7 @@
   }
 
   public DesugaredLibraryConfiguration(
+      AndroidApiLevel requiredCompilationAPILevel,
       boolean libraryCompilation,
       Map<String, String> rewritePrefix,
       Map<DexType, DexType> emulateLibraryInterface,
@@ -53,6 +57,7 @@
       Map<DexType, DexType> backportCoreLibraryMember,
       Map<DexType, DexType> customConversions,
       List<Pair<DexType, DexString>> dontRewriteInvocation) {
+    this.requiredCompilationAPILevel = requiredCompilationAPILevel;
     this.libraryCompilation = libraryCompilation;
     this.rewritePrefix = rewritePrefix;
     this.emulateLibraryInterface = emulateLibraryInterface;
@@ -68,6 +73,10 @@
         : new DesugarPrefixRewritingMapper(rewritePrefix, factory);
   }
 
+  public AndroidApiLevel getRequiredCompilationApiLevel() {
+    return requiredCompilationAPILevel;
+  }
+
   public boolean isLibraryCompilation() {
     return libraryCompilation;
   }
@@ -100,6 +109,7 @@
 
     private final DexItemFactory factory;
 
+    private AndroidApiLevel requiredCompilationAPILevel;
     private boolean libraryCompilation = false;
     private Map<String, String> rewritePrefix = new HashMap<>();
     private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>();
@@ -112,6 +122,11 @@
       this.factory = dexItemFactory;
     }
 
+    public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) {
+      this.requiredCompilationAPILevel = requiredCompilationAPILevel;
+      return this;
+    }
+
     public Builder setProgramCompilation() {
       libraryCompilation = false;
       return this;
@@ -185,6 +200,7 @@
 
     public DesugaredLibraryConfiguration build() {
       return new DesugaredLibraryConfiguration(
+          requiredCompilationAPILevel,
           libraryCompilation,
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableMap.copyOf(emulateLibraryInterface),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index 94467de..fbf3d1f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.gson.JsonArray;
@@ -57,6 +58,10 @@
               "Unsupported desugared library configuration version, please upgrade the D8/R8"
                   + " compiler."));
     }
+    int required_compilation_api_level =
+        jsonConfig.get("required_compilation_api_level").getAsInt();
+    configurationBuilder.setRequiredCompilationAPILevel(
+        AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level));
     JsonArray jsonFlags =
         libraryCompilation
             ? jsonConfig.getAsJsonArray("library_flags")
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java
index de16ed5..843551e 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
@@ -39,6 +40,7 @@
               options ->
                   options.desugaredLibraryConfiguration =
                       new DesugaredLibraryConfiguration(
+                          AndroidApiLevel.B,
                           false,
                           x,
                           ImmutableMap.of(),
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
new file mode 100644
index 0000000..87a6140
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
@@ -0,0 +1,94 @@
+// 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.desugar.corelib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.GenerateLintFiles;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class LintFilesTest extends TestBase {
+
+  private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
+    // Just do some light probing in the generated lint files.
+    List<String> methods = FileUtils.readAllLines(lintFile);
+    assertTrue(methods.contains("java/util/List/spliterator()Ljava/util/Spliterator;"));
+    assertTrue(methods.contains("java/util/Optional/empty()Ljava/util/Optional;"));
+    assertTrue(methods.contains("java/util/OptionalInt/empty()Ljava/util/OptionalInt;"));
+    assertEquals(
+        minApiLevel == AndroidApiLevel.L,
+        methods.contains("java/util/Collection/parallelStream()Ljava/util/stream/Stream;"));
+    assertEquals(
+        minApiLevel == AndroidApiLevel.L,
+        methods.contains(
+            "java/util/stream/DoubleStream/parallel()Ljava/util/stream/DoubleStream;"));
+    assertEquals(
+        minApiLevel == AndroidApiLevel.L,
+        methods.contains("java/util/stream/IntStream/parallel()Ljava/util/stream/IntStream;"));
+  }
+
+  @Test
+  public void testFileContent() throws Exception {
+    // Test require r8.jar not r8lib.jar, as the class com.android.tools.r8.GenerateLintFiles in
+    // not kept.
+    Assume.assumeTrue(!ToolHelper.isTestingR8Lib());
+
+    Path directory = temp.newFolder().toPath();
+    GenerateLintFiles.main(
+        new String[] {ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), directory.toString()});
+    InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter());
+    DesugaredLibraryConfiguration desugaredLibraryConfiguration =
+        new DesugaredLibraryConfigurationParser(
+                options.itemFactory, options.reporter, false, AndroidApiLevel.B.getLevel())
+            .parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      if (apiLevel == AndroidApiLevel.R) {
+        // Skip API level 30 for now.
+        continue;
+      }
+
+      if (apiLevel.getLevel()
+          >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel()) {
+        Path compileApiLevelDirectory =
+            directory.resolve("compile_api_level_" + apiLevel.getLevel());
+        assertTrue(Files.exists(compileApiLevelDirectory));
+        for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+          String desugaredApisBaseName =
+              "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
+          if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
+            assertTrue(
+                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+            assertTrue(
+                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+            checkFileContent(
+                minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
+          } else {
+            assertFalse(
+                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+            assertFalse(
+                Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
index a08b6ce..3353fd2 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.memberrebinding.b135627418.library.Drawable;
 import com.android.tools.r8.memberrebinding.b135627418.library.DrawableWrapper;
 import com.android.tools.r8.memberrebinding.b135627418.library.InsetDrawable;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -79,6 +80,7 @@
                 options ->
                     options.desugaredLibraryConfiguration =
                         new DesugaredLibraryConfiguration(
+                            AndroidApiLevel.B,
                             false,
                             ImmutableMap.of(packageName + ".runtime", packageName + ".library"),
                             ImmutableMap.of(),
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index b4343f3..30c14c8 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -6,6 +6,7 @@
 import argparse
 import gradle
 import hashlib
+import jdk
 import json
 from os import makedirs
 from os.path import join
@@ -338,6 +339,19 @@
     configuration_dir = join(tmp_dir, 'META-INF', 'desugar', 'd8')
     makedirs(configuration_dir)
     copyfile(configuration, join(configuration_dir, 'desugar.json'))
+
+    lint_dir = join(configuration_dir, 'lint')
+    makedirs(lint_dir)
+    cmd = [
+        jdk.GetJavaExecutable(),
+        '-cp',
+        utils.R8_JAR,
+        'com.android.tools.r8.GenerateLintFiles',
+        configuration,
+        lint_dir]
+    utils.PrintCmd(cmd)
+    subprocess.check_call(cmd)
+
     make_archive(destination, 'zip', tmp_dir)
     move(destination + '.zip', destination)