1// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file 2// for details. All rights reserved. Use of this source code is governed by a 3// BSD-style license that can be found in the LICENSE file. 4package com.android.tools.r8; 5 6import static com.android.tools.r8.D8Command.USAGE_MESSAGE; 7 8import com.android.tools.r8.dex.ApplicationReader; 9import com.android.tools.r8.dex.ApplicationWriter; 10import com.android.tools.r8.errors.CompilationError; 11import com.android.tools.r8.graph.AppInfo; 12import com.android.tools.r8.graph.DexApplication; 13import com.android.tools.r8.ir.conversion.IRConverter; 14import com.android.tools.r8.naming.NamingLens; 15import com.android.tools.r8.utils.AndroidApp; 16import com.android.tools.r8.utils.CfgPrinter; 17import com.android.tools.r8.utils.InternalOptions; 18import com.android.tools.r8.utils.ThreadUtils; 19import com.android.tools.r8.utils.Timing; 20import java.io.IOException; 21import java.nio.file.FileAlreadyExistsException; 22import java.nio.file.NoSuchFileException; 23import java.nio.file.Paths; 24import java.util.concurrent.ExecutionException; 25import java.util.concurrent.ExecutorService; 26 27/** 28 * The D8 dex compiler. 29 * 30 * <p>D8 performs modular compilation to DEX bytecode. It supports compilation of Java bytecode and 31 * Android DEX bytecode to DEX bytecode including merging a mix of these input formats. 32 * 33 * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a command. If 34 * this API does not suffice please contact the D8/R8 team. 35 * 36 * <p>The compiler is invoked by calling {@link #run(D8Command) D8.run} with an appropriate {@link 37 * D8Command}. For example: 38 * 39 * <pre> 40 * D8.run(D8Command.builder() 41 * .addProgramFiles(inputPathA, inputPathB) 42 * .setOutputPath(outputPath) 43 * .build()); 44 * </pre> 45 * 46 * The above reads the input files denoted by {@code inputPathA} and {@code inputPathB}, compiles 47 * them to DEX bytecode (compiling from Java bytecode for such inputs and merging for DEX inputs), 48 * and then writes the result to the directory or zip archive specified by {@code outputPath}. 49 */ 50public final class D8 { 51 52 private static final int STATUS_ERROR = 1; 53 54 private D8() {} 55 56 /** 57 * Main API entry for the D8 dexer. 58 * 59 * @param command D8 command. 60 * @return the compilation result. 61 */ 62 public static D8Output run(D8Command command) throws IOException { 63 InternalOptions options = command.getInternalOptions(); 64 CompilationResult result = runForTesting(command.getInputApp(), options); 65 assert result != null; 66 D8Output output = new D8Output(result.androidApp, command.getOutputMode()); 67 if (command.getOutputPath() != null) { 68 output.write(command.getOutputPath()); 69 } 70 return output; 71 } 72 73 /** 74 * Main API entry for the D8 dexer. 75 * 76 * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a set of 77 * inputs. If the API does not suffice please contact the R8 team. 78 * 79 * @param command D8 command. 80 * @param executor executor service from which to get threads for multi-threaded processing. 81 * @return the compilation result. 82 */ 83 public static D8Output run(D8Command command, ExecutorService executor) throws IOException { 84 InternalOptions options = command.getInternalOptions(); 85 CompilationResult result = runForTesting( 86 command.getInputApp(), options, executor); 87 assert result != null; 88 D8Output output = new D8Output(result.androidApp, command.getOutputMode()); 89 if (command.getOutputPath() != null) { 90 output.write(command.getOutputPath()); 91 } 92 return output; 93 } 94 95 private static void run(String[] args) throws IOException, CompilationException { 96 D8Command.Builder builder = D8Command.parse(args); 97 if (builder.getOutputPath() == null) { 98 builder.setOutputPath(Paths.get(".")); 99 } 100 D8Command command = builder.build(); 101 if (command.isPrintHelp()) { 102 System.out.println(USAGE_MESSAGE); 103 return; 104 } 105 if (command.isPrintVersion()) { 106 System.out.println("D8 v0.0.1"); 107 return; 108 } 109 run(command); 110 } 111 112 /** Command-line entry to D8. */ 113 public static void main(String[] args) throws IOException { 114 if (args.length == 0) { 115 System.err.println(USAGE_MESSAGE); 116 System.exit(STATUS_ERROR); 117 } 118 try { 119 run(args); 120 } catch (NoSuchFileException e) { 121 System.err.println("File not found: " + e.getFile()); 122 System.exit(STATUS_ERROR); 123 } catch (FileAlreadyExistsException e) { 124 System.err.println("File already exists: " + e.getFile()); 125 System.exit(STATUS_ERROR); 126 } catch (IOException e) { 127 System.err.println("Failed to read or write application files: " + e.getMessage()); 128 System.exit(STATUS_ERROR); 129 } catch (RuntimeException e) { 130 System.err.println("Compilation failed with an internal error."); 131 Throwable cause = e.getCause() == null ? e : e.getCause(); 132 cause.printStackTrace(); 133 System.exit(STATUS_ERROR); 134 } catch (CompilationException e) { 135 System.err.println("Compilation failed: " + e.getMessage()); 136 System.err.println(USAGE_MESSAGE); 137 System.exit(STATUS_ERROR); 138 } 139 } 140 141 static CompilationResult runForTesting(AndroidApp inputApp, InternalOptions options) 142 throws IOException { 143 ExecutorService executor = ThreadUtils.getExecutorService(options); 144 try { 145 return runForTesting(inputApp, options, executor); 146 } finally { 147 executor.shutdown(); 148 } 149 } 150 151 static CompilationResult runForTesting( 152 AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException { 153 try { 154 assert !inputApp.hasPackageDistribution(); 155 156 // Disable global optimizations. 157 options.skipMinification = true; 158 options.allowAccessModification = false; 159 options.inlineAccessors = false; 160 options.outline.enabled = false; 161 162 Timing timing = new Timing("DX timer"); 163 DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor); 164 AppInfo appInfo = new AppInfo(app); 165 app = optimize(app, appInfo, options, timing, executor); 166 167 // If a method filter is present don't produce output since the application is likely partial. 168 if (options.hasMethodsFilter()) { 169 System.out.println("Finished compilation with method filter: "); 170 options.methodsFilter.forEach((m) -> System.out.println(" - " + m)); 171 return null; 172 } 173 174 CompilationResult output = 175 new CompilationResult( 176 new ApplicationWriter(app, appInfo, options, NamingLens.getIdentityLens(), null) 177 .write(null, executor), 178 app, 179 appInfo); 180 181 options.printWarnings(); 182 return output; 183 } catch (ExecutionException e) { 184 if (e.getCause() instanceof CompilationError) { 185 throw (CompilationError) e.getCause(); 186 } else { 187 throw new RuntimeException(e.getMessage(), e.getCause()); 188 } 189 } 190 } 191 192 private static DexApplication optimize( 193 DexApplication application, AppInfo appInfo, InternalOptions options, 194 Timing timing, ExecutorService executor) 195 throws IOException, ExecutionException { 196 final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; 197 198 IRConverter converter = new IRConverter(timing, application, appInfo, options, printer); 199 application = converter.convertToDex(executor); 200 201 if (options.printCfg) { 202 if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { 203 System.out.print(printer.toString()); 204 } else { 205 java.io.FileWriter writer = new java.io.FileWriter(options.printCfgFile); 206 writer.write(printer.toString()); 207 writer.close(); 208 } 209 } 210 return application; 211 } 212} 213