| // 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.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> 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) |
| .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() { |
| |
| } |
| } |
| } |