blob: e7bf9dbfea7642707e81abee0f4deff83b03f5d3 [file] [log] [blame]
// Copyright (c) 2016, 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.dex;
import static com.android.tools.r8.dex.Constants.ANDROID_N_API;
import static com.android.tools.r8.dex.Constants.ANDROID_N_DEX_VERSION;
import static com.android.tools.r8.dex.Constants.ANDROID_O_API;
import static com.android.tools.r8.dex.Constants.ANDROID_O_DEX_VERSION;
import static com.android.tools.r8.dex.Constants.DEFAULT_ANDROID_API;
import static com.android.tools.r8.utils.FileUtils.DEFAULT_DEX_FILENAME;
import com.android.tools.r8.Resource;
import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.naming.ProguardMapReader;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyClassCollection;
import com.android.tools.r8.utils.MainDexList;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.io.Closer;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ApplicationReader {
final InternalOptions options;
final DexItemFactory itemFactory;
final Timing timing;
private final AndroidApp inputApp;
public ApplicationReader(AndroidApp inputApp, InternalOptions options, Timing timing) {
this.options = options;
itemFactory = options.itemFactory;
this.timing = timing;
this.inputApp = inputApp;
}
public DexApplication read() throws IOException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
return read(executor);
} finally {
executor.shutdown();
}
}
public final DexApplication read(ExecutorService executorService)
throws IOException, ExecutionException {
timing.begin("DexApplication.read");
final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
try (Closer closer = Closer.create()) {
List<Future<?>> futures = new ArrayList<>();
readProguardMap(builder, executorService, futures, closer);
readMainDexList(builder, executorService, futures, closer);
readDexSources(builder, executorService, futures, closer);
readClassSources(builder, closer);
initializeLazyClassCollection(builder);
ThreadUtils.awaitFutures(futures);
} finally {
timing.end();
}
return builder.build();
}
private void readClassSources(DexApplication.Builder builder, Closer closer)
throws IOException, ExecutionException {
JarApplicationReader application = new JarApplicationReader(options);
JarClassFileReader reader = new JarClassFileReader(
application, builder::addClassIgnoringLibraryDuplicates);
for (Resource input : inputApp.getClassProgramResources()) {
reader.read(DEFAULT_DEX_FILENAME, ClassKind.PROGRAM, input.getStream(closer));
}
for (Resource input : inputApp.getClassClasspathResources()) {
reader.read(DEFAULT_DEX_FILENAME, ClassKind.CLASSPATH, input.getStream(closer));
}
for (Resource input : inputApp.getClassLibraryResources()) {
reader.read(DEFAULT_DEX_FILENAME, ClassKind.LIBRARY, input.getStream(closer));
}
}
private void initializeLazyClassCollection(DexApplication.Builder builder) {
List<ResourceProvider> classpathProviders = inputApp.getClasspathResourceProviders();
List<ResourceProvider> libraryProviders = inputApp.getLibraryResourceProviders();
if (!classpathProviders.isEmpty() || !libraryProviders.isEmpty()) {
builder.setLazyClassCollection(new LazyClassCollection(
new JarApplicationReader(options), classpathProviders, libraryProviders));
}
}
private void readDexSources(DexApplication.Builder builder, ExecutorService executorService,
List<Future<?>> futures, Closer closer)
throws IOException, ExecutionException {
List<Resource> dexProgramSources = inputApp.getDexProgramResources();
List<Resource> dexClasspathSources = inputApp.getDexClasspathResources();
List<Resource> dexLibrarySources = inputApp.getDexLibraryResources();
int numberOfFiles = dexProgramSources.size()
+ dexLibrarySources.size() + dexClasspathSources.size();
if (numberOfFiles > 0) {
List<DexFileReader> fileReaders = new ArrayList<>(numberOfFiles);
int computedMinApiLevel = options.minApiLevel;
for (Resource input : dexProgramSources) {
DexFile file = new DexFile(input.getStream(closer));
computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
fileReaders.add(new DexFileReader(file, ClassKind.PROGRAM, itemFactory));
}
for (Resource input : dexClasspathSources) {
DexFile file = new DexFile(input.getStream(closer));
fileReaders.add(new DexFileReader(file, ClassKind.CLASSPATH, itemFactory));
}
for (Resource input : dexLibrarySources) {
DexFile file = new DexFile(input.getStream(closer));
computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
fileReaders.add(new DexFileReader(file, ClassKind.LIBRARY, itemFactory));
}
options.minApiLevel = computedMinApiLevel;
for (DexFileReader reader : fileReaders) {
DexFileReader.populateIndexTables(reader);
}
// Read the DexCode items and DexProgramClass items in parallel.
for (DexFileReader reader : fileReaders) {
futures.add(executorService.submit(() -> {
reader.addCodeItemsTo(); // Depends on Everything for parsing.
reader.addClassDefsTo(builder::addClass); // Depends on Methods, Code items etc.
}));
}
}
}
private int verifyOrComputeMinApiLevel(int computedMinApiLevel, DexFile file) {
int version = file.getDexVersion();
if (options.minApiLevel == DEFAULT_ANDROID_API) {
computedMinApiLevel = Math.max(computedMinApiLevel, dexVersionToMinSdk(version));
} else if (!minApiMatchesDexVersion(version)) {
throw new CompilationError("Dex file with version '" + version +
"' cannot be used with min sdk level '" + options.minApiLevel + "'.");
}
return computedMinApiLevel;
}
private boolean minApiMatchesDexVersion(int version) {
switch (version) {
case ANDROID_O_DEX_VERSION:
return options.minApiLevel >= ANDROID_O_API;
case ANDROID_N_DEX_VERSION:
return options.minApiLevel >= ANDROID_N_API;
default:
return true;
}
}
private int dexVersionToMinSdk(int version) {
switch (version) {
case ANDROID_O_DEX_VERSION:
return ANDROID_O_API;
case ANDROID_N_DEX_VERSION:
return ANDROID_N_API;
default:
return DEFAULT_ANDROID_API;
}
}
private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
List<Future<?>> futures, Closer closer)
throws IOException {
// Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
if (inputApp.hasProguardMap()) {
futures.add(executorService.submit(() -> {
try {
InputStream map = inputApp.getProguardMap(closer);
builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map));
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
}
private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
List<Future<?>> futures, Closer closer)
throws IOException {
if (inputApp.hasMainDexList()) {
futures.add(executorService.submit(() -> {
try {
InputStream input = inputApp.getMainDexList(closer);
builder.addToMainDexList(MainDexList.parse(input, itemFactory));
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
}
}