blob: a47844f6cd460e6e0bf0d55e6fccebec900abcbf [file] [log] [blame]
// 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.CfVersion;
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.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.ClassAccessFlags;
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.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
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.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.PrintStream;
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.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;
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 Path desugaredLibraryImplementation;
private final Path outputDirectory;
private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
public GenerateLintFiles(
String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
throws Exception {
this.desugaredLibraryConfiguration =
readDesugaredLibraryConfiguration(desugarConfigurationPath);
this.desugaredLibraryImplementation = Paths.get(desugarImplementationPath);
this.outputDirectory = Paths.get(outputDirectory);
if (!Files.isDirectory(this.outputDirectory)) {
throw new Exception("Output directory " + outputDirectory + " is not a directory");
}
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);
DexType baseStreamType =
factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
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);
// Also filter out the generated bridges for the covariant return type.
parallelMethod =
factory.createMethod(
streamType, factory.createProto(baseStreamType), 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.getHolderType() == clazz.type;
CfCode code = null;
if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
code = buildEmptyThrowingCfCode(method.getReference());
}
DexEncodedMethod throwingMethod =
new DexEncodedMethod(
method.getReference(),
method.accessFlags,
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
code,
false,
CfVersion.V1_6,
false);
if (method.isStatic() || method.isDirectMethod()) {
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);
assert !options.encodeChecksums;
ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
DexProgramClass programClass =
new DexProgramClass(
clazz.type,
null,
Origin.unknown(),
clazz.accessFlags,
clazz.superType,
clazz.interfaces,
null,
null,
Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY,
DexEncodedField.EMPTY_ARRAY,
directMethodsArray,
virtualMethodsArray,
false,
checksumSupplier);
builder.addProgramClass(programClass);
}
public static class SupportedMethods {
public final Set<DexClass> classesWithAllMethodsSupported;
public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
public SupportedMethods(
Set<DexClass> classesWithAllMethodsSupported,
Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
this.supportedMethods = supportedMethods;
}
}
private SupportedMethods collectSupportedMethods(
AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported) throws Exception {
// Read the android.jar for the compilation API level. Read it as program instead of library
// to get the local information for parameter names.
AndroidApp library =
AndroidApp.builder().addProgramFiles(getAndroidJarPath(compilationApiLevel)).build();
DirectMappedDexApplication dexApplication =
new ApplicationReader(library, options, Timing.empty()).read().toDirect();
AndroidApp implementation =
AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
DirectMappedDexApplication implementationApplication =
new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
// Collect all the methods that the library desugar configuration adds support for.
Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
for (DexProgramClass clazz : dexApplication.classes()) {
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)) {
DexProgramClass implementationClass =
implementationApplication.programDefinitionFor(clazz.getType());
if (implementationClass == null) {
throw new Exception("Implementation class not found for " + clazz.toSourceString());
}
boolean allMethodsAddad = true;
for (DexEncodedMethod method : clazz.methods()) {
if (!method.isPublic()) {
continue;
}
ProgramMethod implementationMethod =
implementationClass.lookupProgramMethod(method.getReference());
// Don't include methods which are not implemented by the desugared library.
if (supported.test(method) && implementationMethod != null) {
supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
} else {
allMethodsAddad = false;
}
}
if (allMethodsAddad) {
classesWithAllMethodsSupported.add(clazz);
}
}
}
// All retargeted methods are supported.
for (DexEncodedMethod method : clazz.methods()) {
if (desugaredLibraryConfiguration
.getRetargetCoreLibMember()
.keySet()
.contains(method.getReference().name)) {
if (desugaredLibraryConfiguration
.getRetargetCoreLibMember()
.get(method.getReference().name)
.containsKey(clazz.type)) {
if (supported.test(method)) {
supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
}
}
}
}
// All emulated interfaces static and default methods are supported.
if (desugaredLibraryConfiguration.getEmulateLibraryInterface().containsKey(clazz.type)) {
assert clazz.isInterface();
for (DexEncodedMethod method : clazz.methods()) {
if (!method.isDefaultMethod() && !method.isStatic()) {
continue;
}
if (supported.test(method)) {
supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
}
}
}
}
return new SupportedMethods(classesWithAllMethodsSupported, 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 = outputDirectory.resolve("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,
SupportedMethods supportedMethods)
throws Exception {
// Build a plain text file with the desugared APIs.
List<String> desugaredApisSignatures = new ArrayList<>();
LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
supportedMethods.supportedMethods.forEach(
(clazz, methods) -> {
String classBinaryName =
DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
for (DexEncodedMethod method : methods) {
if (method.isInstanceInitializer() || method.isClassInitializer()) {
// No new constructors are added.
continue;
}
desugaredApisSignatures.add(
classBinaryName
+ '#'
+ method.getReference().name
+ method.getReference().proto.toDescriptorString());
}
} else {
desugaredApisSignatures.add(classBinaryName);
}
addMethodsToHeaderJar(builder, clazz, methods);
});
// Write a plain text file with the desugared APIs.
desugaredApisSignatures.sort(Comparator.naturalOrder());
FileUtils.writeTextFile(
lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
// Write a header jar with the desugared APIs.
AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(builder.build()));
CfApplicationWriter writer =
new CfApplicationWriter(
appView,
options.getMarker(Tool.L8),
GraphLens.getIdentityLens(),
NamingLens.getIdentityLens(),
null);
ClassFileConsumer consumer =
new ClassFileConsumer.ArchiveConsumer(
lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
writer.write(consumer);
consumer.finished(options.reporter);
}
private void generateLintFiles(
AndroidApiLevel compilationApiLevel,
Predicate<AndroidApiLevel> generateForThisMinApiLevel,
BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
throws Exception {
System.out.print(" - generating for min API:");
for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
if (!generateForThisMinApiLevel.test(minApiLevel)) {
continue;
}
System.out.print(" " + minApiLevel);
SupportedMethods supportedMethods =
collectSupportedMethods(
compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
}
System.out.println();
}
private void run() throws Exception {
// Run over all the API levels that the desugared library can be compiled with.
for (int apiLevel = AndroidApiLevel.LATEST.getLevel();
apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel();
apiLevel--) {
System.out.println("Generating lint files for compile API " + apiLevel);
run(apiLevel);
}
}
public void run(int apiLevel) throws Exception {
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.getReference());
});
}
private static class StringBuilderWithIndent {
String NL = System.lineSeparator();
StringBuilder builder = new StringBuilder();
String indent = "";
StringBuilderWithIndent() {}
StringBuilderWithIndent indent(String indent) {
this.indent = indent;
return this;
}
StringBuilderWithIndent appendLine(String line) {
builder.append(indent);
builder.append(line);
builder.append(NL);
return this;
}
StringBuilderWithIndent emptyLine() {
builder.append(NL);
return this;
}
@Override
public String toString() {
return builder.toString();
}
}
private abstract static class SourceBuilder<B extends SourceBuilder> {
protected final DexClass clazz;
protected final boolean newClass;
protected List<DexEncodedField> fields = new ArrayList<>();
protected List<DexEncodedMethod> constructors = new ArrayList<>();
protected List<DexEncodedMethod> methods = new ArrayList<>();
String className;
String packageName;
private SourceBuilder(DexClass clazz, boolean newClass) {
this.clazz = clazz;
this.newClass = newClass;
this.className = clazz.type.toSourceString();
int index = this.className.lastIndexOf('.');
this.packageName = index > 0 ? this.className.substring(0, index) : "";
}
public abstract B self();
private B addField(DexEncodedField field) {
fields.add(field);
return self();
}
private B addMethod(DexEncodedMethod method) {
assert !method.isClassInitializer();
if (method.isInitializer()) {
constructors.add(method);
} else {
methods.add(method);
}
return self();
}
protected String typeInPackage(String typeName, String packageName) {
if (typeName.startsWith(packageName)
&& typeName.length() > packageName.length()
&& typeName.charAt(packageName.length()) == '.'
&& typeName.indexOf('.', packageName.length() + 1) == -1) {
return typeName.substring(packageName.length() + 1);
}
return null;
}
protected String typeInPackage(String typeName) {
String result = typeInPackage(typeName, packageName);
if (result == null) {
result = typeInPackage(typeName, "java.lang");
}
if (result == null) {
result = typeName;
}
return result.replace('$', '.');
}
protected String typeInPackage(DexType type) {
if (type.isPrimitiveType()) {
return type.toSourceString();
}
return typeInPackage(type.toSourceString());
}
protected String accessFlags(ClassAccessFlags accessFlags) {
List<String> flags = new ArrayList<>();
if (accessFlags.isPublic()) {
flags.add("public");
}
if (accessFlags.isProtected()) {
flags.add("protected");
}
if (accessFlags.isPrivate()) {
assert false;
flags.add("private");
}
if (accessFlags.isPackagePrivate()) {
assert false;
flags.add("/* package */");
}
if (accessFlags.isAbstract() && !accessFlags.isInterface()) {
flags.add("abstract");
}
if (accessFlags.isStatic()) {
flags.add("static");
}
if (accessFlags.isFinal()) {
flags.add("final");
}
return String.join(" ", flags);
}
protected String accessFlags(FieldAccessFlags accessFlags) {
List<String> flags = new ArrayList<>();
if (accessFlags.isPublic()) {
flags.add("public");
}
if (accessFlags.isProtected()) {
flags.add("protected");
}
if (accessFlags.isPrivate()) {
assert false;
flags.add("private");
}
if (accessFlags.isPackagePrivate()) {
assert false;
flags.add("/* package */");
}
if (accessFlags.isStatic()) {
flags.add("static");
}
if (accessFlags.isFinal()) {
flags.add("final");
}
return String.join(" ", flags);
}
protected String accessFlags(MethodAccessFlags accessFlags) {
List<String> flags = new ArrayList<>();
if (accessFlags.isPublic()) {
flags.add("public");
}
if (accessFlags.isProtected()) {
flags.add("protected");
}
if (accessFlags.isPrivate()) {
assert false;
flags.add("private");
}
if (accessFlags.isPackagePrivate()) {
assert false;
flags.add("/* package */");
}
if (accessFlags.isAbstract()) {
flags.add("abstract");
}
if (accessFlags.isStatic()) {
flags.add("static");
}
if (accessFlags.isFinal()) {
flags.add("final");
}
return String.join(" ", flags);
}
public String arguments(DexEncodedMethod method) {
DexProto proto = method.getReference().proto;
StringBuilder argsBuilder = new StringBuilder();
boolean firstArg = true;
int argIndex = method.isVirtualMethod() || method.accessFlags.isConstructor() ? 1 : 0;
int argNumber = 0;
argsBuilder.append("(");
for (DexType type : proto.parameters.values) {
if (!firstArg) {
argsBuilder.append(", ");
}
if (method.hasCode()) {
String name = "p" + argNumber;
for (LocalVariableInfo localVariable : method.getCode().asCfCode().getLocalVariables()) {
if (localVariable.getIndex() == argIndex) {
assert !localVariable.getLocal().name.toString().equals("this");
name = localVariable.getLocal().name.toString();
}
}
argsBuilder.append(typeInPackage(type)).append(" ").append(name);
} else {
argsBuilder.append(typeInPackage(type)).append(" p").append(argNumber);
}
firstArg = false;
argIndex += type.isWideType() ? 2 : 1;
argNumber++;
}
argsBuilder.append(")");
return argsBuilder.toString();
}
}
private static class HTMLBuilder extends StringBuilderWithIndent {
private String indent = "";
private void increaseIndent() {
indent += " ";
indent(indent);
}
private void decreaseIndent() {
indent = indent.substring(0, indent.length() - 2);
indent(indent);
}
HTMLBuilder appendTdCode(String s) {
appendLine("<td><code>" + s + "</code></td>");
return this;
}
HTMLBuilder appendTdP(String s) {
appendLine("<td><p>" + s + "</p></td>");
return this;
}
HTMLBuilder appendLiCode(String s) {
appendLine("<li><code>" + s + "</code></li>");
return this;
}
HTMLBuilder start(String tag) {
appendLine("<" + tag + ">");
increaseIndent();
return this;
}
HTMLBuilder end(String tag) {
decreaseIndent();
appendLine("</" + tag + ">");
return this;
}
}
public static class HTMLSourceBuilder extends SourceBuilder<HTMLSourceBuilder> {
private final Set<DexMethod> parallelMethods;
public HTMLSourceBuilder(DexClass clazz, boolean newClass, Set<DexMethod> parallelMethods) {
super(clazz, newClass);
this.parallelMethods = parallelMethods;
}
@Override
public HTMLSourceBuilder self() {
return this;
}
@Override
public String toString() {
HTMLBuilder builder = new HTMLBuilder();
builder.start("tr");
if (packageName.length() > 0) {
builder.appendTdCode(packageName);
}
builder.appendTdCode(typeInPackage(className));
builder.start("td").start("ul");
if (!fields.isEmpty()) {
assert newClass; // Currently no fields are added to existing classes.
for (DexEncodedField field : fields) {
builder.appendLiCode(
accessFlags(field.accessFlags)
+ " "
+ typeInPackage(field.getReference().type)
+ " "
+ field.getReference().name);
}
}
if (!constructors.isEmpty()) {
for (DexEncodedMethod constructor : constructors) {
builder.appendLiCode(
accessFlags(constructor.accessFlags)
+ " "
+ typeInPackage(className)
+ arguments(constructor));
}
}
List<String> parallelM = new ArrayList<>();
if (!methods.isEmpty()) {
for (DexEncodedMethod method : methods) {
builder.appendLiCode(
accessFlags(method.accessFlags)
+ " "
+ typeInPackage(method.getReference().proto.returnType)
+ " "
+ method.getReference().name
+ arguments(method));
if (parallelMethods.contains(method.getReference())) {
parallelM.add(method.getReference().name.toString());
}
}
}
builder.end("ul").end("td");
StringBuilder commentBuilder = new StringBuilder();
if (newClass) {
commentBuilder.append("Fully implemented class.");
} else {
commentBuilder.append("Additional methods on existing class.");
}
if (!parallelM.isEmpty()) {
commentBuilder.append(newClass ? "" : "<br>");
if (parallelM.size() == 1) {
commentBuilder
.append("The method <code>")
.append(parallelM.get(0))
.append("</code> is only supported from API level 21.");
} else {
commentBuilder.append("The following methods are only supported from API level 21:<br>");
for (int i = 0; i < parallelM.size(); i++) {
commentBuilder.append("<code>").append(parallelM.get(i)).append("</code><br>");
}
}
}
builder.appendTdP(commentBuilder.toString());
builder.end("tr");
return builder.toString();
}
}
private void generateClassHTML(
PrintStream ps,
DexClass clazz,
boolean newClass,
Predicate<DexEncodedField> fieldsFilter,
Predicate<DexEncodedMethod> methodsFilter) {
SourceBuilder builder = new HTMLSourceBuilder(clazz, newClass, parallelMethods);
StreamSupport.stream(clazz.fields().spliterator(), false)
.filter(fieldsFilter)
.filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
.sorted(Comparator.comparing(DexEncodedField::toSourceString))
.forEach(builder::addField);
StreamSupport.stream(clazz.methods().spliterator(), false)
.filter(methodsFilter)
.filter(
method ->
(method.accessFlags.isPublic() || method.accessFlags.isProtected())
&& !method.accessFlags.isBridge())
.sorted(Comparator.comparing(DexEncodedMethod::toSourceString))
.forEach(builder::addMethod);
ps.println(builder);
}
private void generateDesugaredLibraryApisDocumetation() throws Exception {
PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
// Full classes added.
SupportedMethods supportedMethods = collectSupportedMethods(AndroidApiLevel.Q, x -> true);
supportedMethods.classesWithAllMethodsSupported.stream()
.sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
.forEach(clazz -> generateClassHTML(ps, clazz, true, field -> true, method -> true));
// Methods added to existing classes.
supportedMethods.supportedMethods.keySet().stream()
.filter(clazz -> !supportedMethods.classesWithAllMethodsSupported.contains(clazz))
.sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
.forEach(
clazz ->
generateClassHTML(
ps,
clazz,
false,
field -> false,
method -> supportedMethods.supportedMethods.get(clazz).contains(method)));
}
public static void main(String[] args) throws Exception {
if (args.length == 3) {
new GenerateLintFiles(args[0], args[1], args[2]).run();
return;
}
if (args.length == 4 && args[0].equals("--generate-api-docs")) {
new GenerateLintFiles(args[1], args[2], args[3]).generateDesugaredLibraryApisDocumetation();
return;
}
throw new RuntimeException(
StringUtils.joinLines(
"Invalid invocation.",
"Usage: GenerateLineFiles [--generate-api-docs] "
+ "<desugar configuration> <desugar implementation> <output directory>"));
}
}