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