| // 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.r8; |
| |
| import static com.android.tools.r8.D8Command.USAGE_MESSAGE; |
| |
| import com.android.tools.r8.dex.ApplicationReader; |
| import com.android.tools.r8.dex.ApplicationWriter; |
| import com.android.tools.r8.dex.Marker; |
| import com.android.tools.r8.dex.Marker.Tool; |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.errors.MainDexError; |
| import com.android.tools.r8.graph.AppInfo; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.ir.conversion.IRConverter; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.CfgPrinter; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.Timing; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.FileAlreadyExistsException; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Paths; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| |
| /** |
| * The D8 dex compiler. |
| * |
| * <p>D8 performs modular compilation to DEX bytecode. It supports compilation of Java bytecode and |
| * Android DEX bytecode to DEX bytecode including merging a mix of these input formats. |
| * |
| * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a command. If |
| * this API does not suffice please contact the D8/R8 team. |
| * |
| * <p>The compiler is invoked by calling {@link #run(D8Command) D8.run} with an appropriate {@link |
| * D8Command}. For example: |
| * |
| * <pre> |
| * D8.run(D8Command.builder() |
| * .addProgramFiles(inputPathA, inputPathB) |
| * .setOutputPath(outputPath) |
| * .build()); |
| * </pre> |
| * |
| * The above reads the input files denoted by {@code inputPathA} and {@code inputPathB}, compiles |
| * them to DEX bytecode (compiling from Java bytecode for such inputs and merging for DEX inputs), |
| * and then writes the result to the directory or zip archive specified by {@code outputPath}. |
| */ |
| public final class D8 { |
| |
| private static final String kVersion = "v0.1.0"; |
| private static final int STATUS_ERROR = 1; |
| |
| private D8() {} |
| |
| /** |
| * Main API entry for the D8 dexer. |
| * |
| * @param command D8 command. |
| * @return the compilation result. |
| */ |
| public static D8Output run(D8Command command) throws IOException { |
| InternalOptions options = command.getInternalOptions(); |
| CompilationResult result = runForTesting(command.getInputApp(), options); |
| assert result != null; |
| D8Output output = new D8Output(result.androidApp, command.getOutputMode()); |
| if (command.getOutputPath() != null) { |
| output.write(command.getOutputPath()); |
| } |
| return output; |
| } |
| |
| /** |
| * Main API entry for the D8 dexer. |
| * |
| * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a set of |
| * inputs. If the API does not suffice please contact the R8 team. |
| * |
| * @param command D8 command. |
| * @param executor executor service from which to get threads for multi-threaded processing. |
| * @return the compilation result. |
| */ |
| public static D8Output run(D8Command command, ExecutorService executor) throws IOException { |
| InternalOptions options = command.getInternalOptions(); |
| CompilationResult result = runForTesting( |
| command.getInputApp(), options, executor); |
| assert result != null; |
| D8Output output = new D8Output(result.androidApp, command.getOutputMode()); |
| if (command.getOutputPath() != null) { |
| output.write(command.getOutputPath()); |
| } |
| return output; |
| } |
| |
| private static void run(String[] args) throws IOException, CompilationException { |
| D8Command.Builder builder = D8Command.parse(args); |
| if (builder.getOutputPath() == null) { |
| builder.setOutputPath(Paths.get(".")); |
| } |
| D8Command command = builder.build(); |
| if (command.isPrintHelp()) { |
| System.out.println(USAGE_MESSAGE); |
| return; |
| } |
| if (command.isPrintVersion()) { |
| System.out.println("D8 " + kVersion); |
| return; |
| } |
| run(command); |
| } |
| |
| /** Command-line entry to D8. */ |
| public static void main(String[] args) throws IOException { |
| if (args.length == 0) { |
| System.err.println(USAGE_MESSAGE); |
| System.exit(STATUS_ERROR); |
| } |
| try { |
| run(args); |
| } catch (NoSuchFileException e) { |
| System.err.println("File not found: " + e.getFile()); |
| System.exit(STATUS_ERROR); |
| } catch (FileAlreadyExistsException e) { |
| System.err.println("File already exists: " + e.getFile()); |
| System.exit(STATUS_ERROR); |
| } catch (IOException e) { |
| System.err.println("Failed to read or write application files: " + e.getMessage()); |
| System.exit(STATUS_ERROR); |
| } catch (RuntimeException e) { |
| System.err.println("Compilation failed with an internal error."); |
| Throwable cause = e.getCause() == null ? e : e.getCause(); |
| cause.printStackTrace(); |
| System.exit(STATUS_ERROR); |
| } catch (CompilationException e) { |
| System.err.println("Compilation failed: " + e.getMessage()); |
| System.err.println(USAGE_MESSAGE); |
| System.exit(STATUS_ERROR); |
| } |
| } |
| |
| static CompilationResult runForTesting(AndroidApp inputApp, InternalOptions options) |
| throws IOException { |
| ExecutorService executor = ThreadUtils.getExecutorService(options); |
| try { |
| return runForTesting(inputApp, options, executor); |
| } finally { |
| executor.shutdown(); |
| } |
| } |
| |
| // Compute the marker to be placed in the main dex file. |
| private static Marker getMarker(InternalOptions options) { |
| if (options.hasMarker()) { |
| return options.getMarker(); |
| } |
| return new Marker(Tool.D8) |
| .put("version", kVersion) |
| .put("min-api", options.minApiLevel); |
| } |
| |
| private static CompilationResult runForTesting( |
| AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException { |
| try { |
| assert !inputApp.hasPackageDistribution(); |
| |
| // Disable global optimizations. |
| options.skipMinification = true; |
| options.inlineAccessors = false; |
| options.outline.enabled = false; |
| |
| Timing timing = new Timing("DX timer"); |
| DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor); |
| AppInfo appInfo = new AppInfo(app); |
| app = optimize(app, appInfo, options, timing, executor); |
| |
| // If a method filter is present don't produce output since the application is likely partial. |
| if (options.hasMethodsFilter()) { |
| System.out.println("Finished compilation with method filter: "); |
| options.methodsFilter.forEach((m) -> System.out.println(" - " + m)); |
| return null; |
| } |
| Marker marker = getMarker(options); |
| CompilationResult output = |
| new CompilationResult( |
| new ApplicationWriter( |
| app, appInfo, options, marker, null, NamingLens.getIdentityLens(), null) |
| .write(null, executor), |
| app, |
| appInfo); |
| |
| options.printWarnings(); |
| return output; |
| } catch (MainDexError mainDexError) { |
| throw new CompilationError(mainDexError.getMessageForD8()); |
| } catch (ExecutionException e) { |
| if (e.getCause() instanceof CompilationError) { |
| throw (CompilationError) e.getCause(); |
| } |
| throw new RuntimeException(e.getMessage(), e.getCause()); |
| } |
| } |
| |
| private static DexApplication optimize( |
| DexApplication application, AppInfo appInfo, InternalOptions options, |
| Timing timing, ExecutorService executor) |
| throws IOException, ExecutionException { |
| final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; |
| |
| IRConverter converter = new IRConverter(timing, application, appInfo, options, printer); |
| application = converter.convertToDex(executor); |
| |
| if (options.printCfg) { |
| if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { |
| System.out.print(printer.toString()); |
| } else { |
| try (OutputStreamWriter writer = new OutputStreamWriter( |
| new FileOutputStream(options.printCfgFile), |
| StandardCharsets.UTF_8)) { |
| writer.write(printer.toString()); |
| } |
| } |
| } |
| return application; |
| } |
| } |