blob: 27dbc0f53331c1365cd03e5e3c50a0456965dc0a [file] [log] [blame]
// Copyright (c) 2017, 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.ArchiveProgramResourceProvider;
import com.android.tools.r8.AssertionsConfiguration;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.DexFilePerClassFileConsumer;
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.ResourceException;
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.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 D8ApiUsageSample {
private static final Origin origin =
new Origin(Origin.root()) {
@Override
public String part() {
return "D8ApiUsageSample";
}
};
private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
/**
* Example invocation:
*
* <pre>
* java -jar d8-api-uses.jar \
* --output path/to/output/dir \
* --min-api minApiLevel \
* --lib path/to/library.jar \
* --classpath path/to/classpath.jar \
* path/to/input{1,2,3}.{jar,class}
* </pre>
*/
public static void main(String[] args) {
// Parse arguments with the commandline parser to make use of its API.
D8Command.Builder cmd = D8Command.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> classpath = new ArrayList<>(args.length);
List<Path> mainDexList = new ArrayList<>(1);
List<Path> mainDexRules = 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("--classpath")) {
classpath.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 (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 (classpath.isEmpty()) {
throw new RuntimeException("Must supply classpath 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");
}
useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
useProgramData(minApiLevel, libraries, classpath, inputs);
useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
useMainDexRulesFiles(minApiLevel, libraries, classpath, inputs, mainDexRules);
useMainDexRules(minApiLevel, libraries, classpath, inputs, mainDexRules);
useAssertionConfig(minApiLevel, libraries, classpath, inputs);
useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
}
// 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> classpath,
Collection<Path> inputs) {
try {
D8.run(
D8Command.builder(handler)
.setMode(mode)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.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> classpath,
Collection<Path> inputs) {
try {
D8Command.Builder builder =
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath);
for (ClassFileContent classfile : readClassFiles(inputs)) {
builder.addClassProgramData(classfile.data, classfile.origin);
}
for (Path input : inputs) {
if (isDexFile(input)) {
builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
}
}
D8.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> classpath,
Collection<Path> inputs) {
try {
D8Command.Builder builder =
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath);
for (Path input : inputs) {
if (isArchive(input)) {
builder.addProgramResourceProvider(
ArchiveProgramResourceProvider.fromArchive(
input, ArchiveProgramResourceProvider::includeClassFileEntries));
} else if (isClassFile(input)) {
builder.addProgramResourceProvider(
new ProgramResourceProvider() {
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
}
});
} else if (isDexFile(input)) {
builder.addProgramResourceProvider(
new ProgramResourceProvider() {
@Override
public Collection<ProgramResource> getProgramResources() throws ResourceException {
return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
}
});
}
}
D8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void useLibraryAndClasspathProvider(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> classpath,
Collection<Path> inputs) {
try {
D8Command.Builder builder =
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addProgramFiles(inputs);
for (Path library : libraries) {
builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(library));
}
for (Path path : classpath) {
builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(path));
}
D8.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> classpath,
Collection<Path> inputs,
Collection<Path> mainDexList) {
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.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> classpath,
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("/", "."));
}
}
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.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> classpath,
Collection<Path> inputs,
Collection<Path> mainDexRules) {
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.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> classpath,
Collection<Path> inputs,
Collection<Path> mainDexRulesFiles) {
try {
D8Command.Builder builder =
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.addProgramFiles(inputs);
for (Path mainDexRulesFile : mainDexRulesFiles) {
builder.addMainDexRules(
Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
}
D8.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> classpath,
Collection<Path> inputs) {
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.addProgramFiles(inputs)
.addAssertionsConfiguration(b -> b.setScopeAll().setEnable().build())
.addAssertionsConfiguration(b -> b.setScopeAll().setDisable().build())
.addAssertionsConfiguration(
b -> b.setScopePackage("com.android.tools.apiusagesample").setEnable().build())
.addAssertionsConfiguration(
b ->
b.setScopePackage("com.android.tools.apiusagesample")
.setPassthrough()
.build())
.addAssertionsConfiguration(
b -> b.setScopePackage("com.android.tools.apiusagesample").setDisable().build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setEnable()
.build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setPassthrough()
.build())
.addAssertionsConfiguration(
b ->
b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
.setDisable()
.build())
.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions)
.addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
.addAssertionsConfiguration(AssertionsConfiguration.Builder::disableAllAssertions)
.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> classpath,
List<Path> inputs,
List<Path> mainDexList) {
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries.get(0))
.addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
.addClasspathFiles(classpath.get(0))
.addClasspathFiles(classpath.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))
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
}
private static void incrementalCompileAndMerge(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> classpath,
Collection<Path> inputs) {
// Compile and merge via index intermediates.
mergeIntermediates(
minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
// Compile and merge via per-classfile intermediates.
mergeIntermediates(
minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
}
private static Collection<byte[]> compileToIndexedIntermediates(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> classpath,
Collection<Path> inputs) {
IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setIntermediate(true)
.setProgramConsumer(consumer)
.addClasspathFiles(classpath)
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
.setDisableDesugaring(false)
.setDesugarGraphConsumer(new MyDesugarGraphConsumer())
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
return consumer.bytes;
}
private static Collection<byte[]> compileToPerClassFileIntermediates(
int minApiLevel,
Collection<Path> libraries,
Collection<Path> classpath,
Collection<Path> inputs) {
PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
try {
D8.run(
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(consumer)
.addLibraryFiles(libraries)
.addClasspathFiles(classpath)
.addProgramFiles(inputs)
.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected compilation exceptions", e);
}
return consumer.bytes;
}
private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
D8Command.Builder builder =
D8Command.builder(handler)
.setMinApiLevel(minApiLevel)
.setProgramConsumer(new EnsureOutputConsumer())
.setDisableDesugaring(true);
for (byte[] intermediate : intermediates) {
builder.addDexProgramData(intermediate, Origin.unknown());
}
try {
D8.run(builder.build());
} catch (CompilationFailedException e) {
throw new RuntimeException("Unexpected merging error", 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 isDexFile(Path file) {
return isDexFile(file.toString());
}
private static boolean isDexFile(String file) {
file = file.toLowerCase();
return file.endsWith(".dex");
}
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 IndexIntermediatesConsumer implements DexIndexedConsumer {
List<byte[]> bytes = new ArrayList<>();
@Override
public synchronized void accept(
int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
bytes.add(data);
}
@Override
public void finished(DiagnosticsHandler handler) {}
}
private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
List<byte[]> bytes = new ArrayList<>();
@Override
public synchronized void accept(
String primaryClassDescriptor,
byte[] data,
Set<String> descriptors,
DiagnosticsHandler handler) {
bytes.add(data);
}
@Override
public void finished(DiagnosticsHandler handler) {}
}
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 class MyDesugarGraphConsumer implements DesugarGraphConsumer {
@Override
public void accept(Origin dependent, Origin dependency) {
}
public void finished() {
}
}
}