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