blob: 4b86cea5cbc275ba7bb1f4980aad724eb06bfefb [file] [log] [blame]
// Copyright (c) 2018, 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.apiusagesample;
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.ArchiveProgramResourceProvider;
import com.android.tools.r8.AssertionsConfiguration;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.Version;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.StringDiagnostic;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class R8ApiUsageSample {
private static final Origin origin =
new Origin(Origin.root()) {
@Override
public String part() {
return "R8ApiUsageSample";
}
};
private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
/**
* Example invocation:
*
* <pre>
* java -jar r8-api-uses.jar \
* --output path/to/output/dir \
* --min-api minApiLevel \
* --lib path/to/library.jar \
* path/to/input{1,2,3}.{jar,class}
* </pre>
*/
public static void main(String[] args) {
// Check version API
checkVersionApi();
// Parse arguments with the commandline parser to make use of its API.
R8Command.Builder cmd = R8Command.parse(args, origin);
CompilationMode mode = cmd.getMode();
Path temp = cmd.getOutputPath();
int minApiLevel = cmd.getMinApiLevel();
// The Builder API does not provide access to the concrete paths
// (everything is put into providers) so manually parse them here.
List<Path> libraries = new ArrayList<>(1);
List<Path> mainDexList = new ArrayList<>(1);
List<Path> mainDexRules = new ArrayList<>(1);
List<Path> pgConf = new ArrayList<>(1);
List<Path> inputs = new ArrayList<>(args.length);
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--lib")) {
libraries.add(Paths.get(args[++i]));
} else if (args[i].equals("--main-dex-list")) {
mainDexList.add(Paths.get(args[++i]));
} else if (args[i].equals("--main-dex-rules")) {
mainDexRules.add(Paths.get(args[++i]));
} else if (args[i].equals("--pg-conf")) {
pgConf.add(Paths.get(args[++i]));
} else if (isArchive(args[i]) || isClassFile(args[i])) {
inputs.add(Paths.get(args[i]));
}
}
if (!Files.exists(temp) || !Files.isDirectory(temp)) {
throw new RuntimeException("Must supply a temp/output directory");
}
if (inputs.isEmpty()) {
throw new RuntimeException("Must supply program inputs");
}
if (libraries.isEmpty()) {
throw new RuntimeException("Must supply library inputs");
}
if (mainDexList.isEmpty()) {
throw new RuntimeException("Must supply main-dex-list inputs");
}
if (mainDexRules.isEmpty()) {
throw new RuntimeException("Must supply main-dex-rules inputs");
}
if (pgConf.isEmpty()) {
throw new RuntimeException("Must supply pg-conf inputs");
}
useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
useProgramData(minApiLevel, libraries, inputs);
useProgramResourceProvider(minApiLevel, libraries, inputs);
useLibraryResourceProvider(minApiLevel, libraries, inputs);
useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
useAssertionConfig(minApiLevel, libraries, inputs);
useVArgVariants(minApiLevel, libraries, inputs, mainDexList, mainDexRules, pgConf);
useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
}
private static class InMemoryStringConsumer implements StringConsumer {
public String value = null;
@Override
public void accept(String string, DiagnosticsHandler handler) {
value = string;
}
}
private static void useProguardConfigConsumers(
int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
try {
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addProguardConfigurationFiles(pgConf)
.setProguardUsageConsumer(usageConsumer)
.setProguardSeedsConsumer(seedsConsumer)
.setProguardConfigurationConsumer(configConsumer)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exception", e);
}
if (usageConsumer.value == null) {
throw new RuntimeException("Expected usage info but had none");
}
if (seedsConsumer.value == null) {
throw new RuntimeException("Expected seeds info but had none");
}
if (configConsumer.value == null) {
throw new RuntimeException("Expected config info but had none");
}
}
// Check API support for compiling Java class-files from the file system.
private static void useProgramFileList(
CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
try {
R8.run(
R8Command.builder(handler)
.setMode(mode)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
// Check API support for compiling Java class-files from byte content.
private static void useProgramData(
int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
try {
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries);
for (ClassFileContent classfile : readClassFiles(inputs)) {
builder.addClassProgramData(classfile.data, classfile.origin);
}
R8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
} catch (IOException e) {
throw new RuntimeException("Unexpected IO exception", e);
}
}
// Check API support for compiling Java class-files from a program provider abstraction.
private static void useProgramResourceProvider(
int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
try {
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries);
for (Path input : inputs) {
if (isArchive(input)) {
builder.addProgramResourceProvider(
ArchiveProgramResourceProvider.fromArchive(
input, ArchiveProgramResourceProvider::includeClassFileEntries));
} else {
builder.addProgramResourceProvider(
new ProgramResourceProvider() {
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
}
});
}
}
R8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void useLibraryResourceProvider(
int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
try {
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addProgramFiles(inputs);
for (Path library : libraries) {
builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
}
R8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
} catch (IOException e) {
throw new RuntimeException("Unexpected IO exception", e);
}
}
private static void useMainDexListFiles(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexList) {
try {
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addMainDexListFiles(mainDexList)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void useMainDexClasses(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexList) {
try {
List<String> mainDexClasses = new ArrayList<>(1);
for (Path path : mainDexList) {
for (String line : Files.readAllLines(path)) {
String entry = line.trim();
if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
continue;
}
mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
}
}
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addMainDexClasses(mainDexClasses)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
} catch (IOException e) {
throw new RuntimeException("Unexpected IO exception", e);
}
}
private static void useMainDexRulesFiles(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexRules) {
try {
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addMainDexRulesFiles(mainDexRules)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void useMainDexRules(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexRulesFiles) {
try {
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs);
for (Path mainDexRulesFile : mainDexRulesFiles) {
builder.addMainDexRules(
Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
}
R8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
} catch (IOException e) {
throw new RuntimeException("Unexpected IO exception", e);
}
}
private static void useProguardConfigFiles(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexList,
List<Path> pgConf) {
try {
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addMainDexListFiles(mainDexList)
.addProguardConfigurationFiles(pgConf)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void useProguardConfigLines(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> inputs,
Collection<Path> mainDexList,
List<Path> pgConf) {
try {
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addMainDexListFiles(mainDexList);
for (Path file : pgConf) {
builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
}
R8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
} catch (IOException e) {
throw new RuntimeException("Unexpected IO exception", e);
}
}
private static void useAssertionConfig(
int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
try {
R8.run(
R8Command.builder(handler)
.setDisableTreeShaking(true)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
.addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
.addAssertionsConfiguration(
b ->
b.setScopePackage("com.android.tools.apiusagesample")
.setCompileTimeEnable()
.build())
.addAssertionsConfiguration(
b ->
b.setScopePackage("com.android.tools.apiusagesample")
.setPassthrough()
.build())
.addAssertionsConfiguration(
b ->
b.setScopePackage("com.android.tools.apiusagesample")
.setCompileTimeDisable()
.build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setCompileTimeEnable()
.build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setPassthrough()
.build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setCompileTimeDisable()
.build())
.addAssertionsConfiguration(
AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
.addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
.addAssertionsConfiguration(
AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
// Check API support for all the varg variants.
private static void useVArgVariants(
int minApiLevel,
List<Path> libraries,
List<Path> inputs,
List<Path> mainDexList,
List<Path> mainDexRules,
List<Path> pgConf) {
try {
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries.get(0))
.addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
.addProgramFiles(inputs.get(0))
.addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
.addMainDexListFiles(mainDexList.get(0))
.addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
.addMainDexRulesFiles(mainDexRules.get(0))
.addMainDexRulesFiles(mainDexRules.stream().skip(1).toArray(Path[]::new))
.addProguardConfigurationFiles(pgConf.get(0))
.addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
// Helpers for tests.
// Some of this reimplements stuff in R8 utils, but that is not public API and we should not
// rely on it.
private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
List<ClassFileContent> classfiles = new ArrayList<>();
for (Path file : files) {
if (isArchive(file)) {
Origin zipOrigin = new PathOrigin(file);
ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
ZipEntry entry;
while (null != (entry = zip.getNextEntry())) {
String name = entry.getName();
if (isClassFile(name)) {
Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
classfiles.add(new ClassFileContent(origin, readBytes(zip)));
}
}
} else if (isClassFile(file)) {
classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
}
}
return classfiles;
}
private static byte[] readBytes(InputStream stream) throws IOException {
try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
byte[] buffer = new byte[0xffff];
for (int length; (length = stream.read(buffer)) != -1; ) {
bytes.write(buffer, 0, length);
}
return bytes.toByteArray();
}
}
private static boolean isClassFile(Path file) {
return isClassFile(file.toString());
}
private static boolean isClassFile(String file) {
file = file.toLowerCase();
return file.endsWith(".class");
}
private static boolean isArchive(Path file) {
return isArchive(file.toString());
}
private static boolean isArchive(String file) {
file = file.toLowerCase();
return file.endsWith(".zip") || file.endsWith(".jar");
}
private static class ClassFileContent {
final Origin origin;
final byte[] data;
public ClassFileContent(Origin origin, byte[] data) {
this.origin = origin;
this.data = data;
}
}
private static class EnsureOutputConsumer implements DexIndexedConsumer {
boolean hasOutput = false;
@Override
public synchronized void accept(
int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
hasOutput = true;
}
@Override
public void finished(DiagnosticsHandler handler) {
if (!hasOutput) {
handler.error(new StringDiagnostic("Expected to produce output but had none"));
}
}
}
private static void checkVersionApi() {
String labelValue;
int labelAccess;
try {
Field field = Version.class.getDeclaredField("LABEL");
labelAccess = field.getModifiers();
labelValue = (String) field.get(Version.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!Modifier.isPublic(labelAccess)
|| !Modifier.isStatic(labelAccess)
|| !Modifier.isFinal(labelAccess)) {
throw new RuntimeException("Expected public static final LABEL");
}
if (labelValue.isEmpty()) {
throw new RuntimeException("Expected LABEL constant");
}
if (Version.LABEL.isEmpty()) {
throw new RuntimeException("Expected LABEL constant");
}
if (Version.getVersionString() == null) {
throw new RuntimeException("Expected getVersionString API");
}
if (Version.getMajorVersion() < -1) {
throw new RuntimeException("Expected getMajorVersion API");
}
if (Version.getMinorVersion() < -1) {
throw new RuntimeException("Expected getMinorVersion API");
}
if (Version.getPatchVersion() < -1) {
throw new RuntimeException("Expected getPatchVersion API");
}
if (Version.getPreReleaseString() == null && false) {
throw new RuntimeException("Expected getPreReleaseString API");
}
if (Version.isDevelopmentVersion() && false) {
throw new RuntimeException("Expected isDevelopmentVersion API");
}
}
}