blob: 8ce6eed817acbef62d4e7e3d1410a8148a385f4f [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.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.DexProgramClass.ChecksumSupplier;
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.Comparator;
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);
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.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);
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(),
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 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.
Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
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)) {
boolean allMethodsAddad = true;
for (DexEncodedMethod method : clazz.methods()) {
if (supported.test(method)) {
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.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 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 =
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,
SupportedMethods 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.supportedMethods.forEach(
(clazz, methods) -> {
String classBinaryName =
DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
for (DexEncodedMethod method : methods) {
desugaredApisSignatures.add(
classBinaryName
+ '#'
+ method.method.name
+ method.method.proto.toDescriptorString());
}
} else {
desugaredApisSignatures.add(classBinaryName);
}
addMethodsToHeaderJar(builder, clazz, methods);
});
DexApplication app = builder.build();
// 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.
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 {
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.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();
}
}