| // 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.CompilationFailedException; | |
| import com.android.tools.r8.CompilationMode; | |
| import com.android.tools.r8.D8; | |
| import com.android.tools.r8.D8Command; | |
| 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> 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 (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"); | |
| } | |
| 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); | |
| 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); | |
| } | |
| } | |
| // 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) | |
| .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")); | |
| } | |
| } | |
| } | |
| } |