blob: 292f103369a70fd330ae196d84e3186ae5fd2b86 [file] [log] [blame]
// 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.utils;
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.R8Command.Builder;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
*
* <p>This wrapper will be added to the classpath so it *must* only refer to the public API. See
* {@code tools/compiledump.py}.
*
* <p>It is tempting to have this class share the R8 parser code, but such refactoring would not be
* valid on past version of the R8 API. Thus there is little else to do than reimplement the parts
* we want to support for reading dumps.
*/
public class CompileDumpCompatR8 {
private static final List<String> VALID_OPTIONS =
Arrays.asList("--classfile", "--compat", "--debug", "--release");
private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
Arrays.asList(
"--output",
"--lib",
"--classpath",
"--min-api",
"--main-dex-rules",
"--main-dex-list",
"--main-dex-list-output",
"--pg-conf",
"--pg-map-output",
"--desugared-lib",
"--threads");
private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
Arrays.asList("--feature-jar");
private static boolean FileUtils_isArchive(Path path) {
String name = path.getFileName().toString().toLowerCase();
return name.endsWith(".apk")
|| name.endsWith(".jar")
|| name.endsWith(".zip")
|| name.endsWith(".aar");
}
public static void main(String[] args) throws CompilationFailedException {
boolean isCompatMode = false;
OutputMode outputMode = OutputMode.DexIndexed;
Path outputPath = null;
Path pgMapOutput = null;
Path desugaredLibJson = null;
CompilationMode compilationMode = CompilationMode.RELEASE;
List<Path> program = new ArrayList<>();
Map<Path, Path> features = new LinkedHashMap<>();
List<Path> library = new ArrayList<>();
List<Path> classpath = new ArrayList<>();
List<Path> config = new ArrayList<>();
List<Path> mainDexRulesFiles = new ArrayList<>();
int minApi = 1;
int threads = -1;
for (int i = 0; i < args.length; i++) {
String option = args[i];
if (VALID_OPTIONS.contains(option)) {
switch (option) {
case "--classfile":
{
outputMode = OutputMode.ClassFile;
break;
}
case "--compat":
{
isCompatMode = true;
break;
}
case "--debug":
{
compilationMode = CompilationMode.DEBUG;
break;
}
case "--release":
{
compilationMode = CompilationMode.RELEASE;
break;
}
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
} else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
String operand = args[++i];
switch (option) {
case "--output":
{
outputPath = Paths.get(operand);
break;
}
case "--lib":
{
library.add(Paths.get(operand));
break;
}
case "--classpath":
{
classpath.add(Paths.get(operand));
break;
}
case "--min-api":
{
minApi = Integer.parseInt(operand);
break;
}
case "--pg-conf":
{
config.add(Paths.get(operand));
break;
}
case "--pg-map-output":
{
pgMapOutput = Paths.get(operand);
break;
}
case "--desugared-lib":
{
desugaredLibJson = Paths.get(operand);
break;
}
case "--threads":
{
threads = Integer.parseInt(operand);
break;
}
case "--main-dex-rules":
{
mainDexRulesFiles.add(Paths.get(operand));
break;
}
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
} else if (VALID_OPTIONS_WITH_TWO_OPERANDS.contains(option)) {
String firstOperand = args[++i];
String secondOperand = args[++i];
switch (option) {
case "--feature-jar":
{
Path featureIn = Paths.get(firstOperand);
Path featureOut = Paths.get(secondOperand);
if (!FileUtils_isArchive(featureIn)) {
throw new IllegalArgumentException(
"Expected an archive, got `" + featureIn.toString() + "`.");
}
features.put(featureIn, featureOut);
break;
}
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
} else {
program.add(Paths.get(option));
}
}
Builder commandBuilder =
new CompatProguardCommandBuilder(isCompatMode)
.addProgramFiles(program)
.addLibraryFiles(library)
.addClasspathFiles(classpath)
.addProguardConfigurationFiles(config)
.addMainDexRulesFiles(mainDexRulesFiles)
.setOutput(outputPath, outputMode)
.setMode(compilationMode);
if (desugaredLibJson != null) {
commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
}
if (outputMode != OutputMode.ClassFile) {
commandBuilder.setMinApiLevel(minApi);
}
features.forEach(
(in, out) ->
commandBuilder.addFeatureSplit(
featureBuilder ->
featureBuilder
.addProgramResourceProvider(ArchiveResourceProvider.fromArchive(in, true))
.setProgramConsumer(new ArchiveConsumer(out))
.build()));
if (pgMapOutput != null) {
commandBuilder.setProguardMapOutputPath(pgMapOutput);
}
R8Command command = commandBuilder.build();
if (threads != -1) {
ExecutorService executor = Executors.newWorkStealingPool(threads);
try {
R8.run(command, executor);
} finally {
executor.shutdown();
}
} else {
R8.run(command);
}
}
// We cannot use StringResource since this class is added to the class path and has access only
// to the public APIs.
private static String readAllBytesJava7(Path filePath) {
String content = "";
try {
content = new String(Files.readAllBytes(filePath));
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
}