| // 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 com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.keepanno.annotations.KeepForApi; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.origin.PathOrigin; |
| import com.android.tools.r8.utils.AbortException; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.Box; |
| import com.android.tools.r8.utils.ExceptionDiagnostic; |
| import com.android.tools.r8.utils.ExceptionUtils; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.Reporter; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Base class for commands and command builders for applications/tools which take an Android |
| * application sources (and optional main-dex list) as input. |
| * |
| * <p>For concrete builders, see for example {@link D8Command.Builder} and {@link |
| * R8Command.Builder}. |
| */ |
| @KeepForApi |
| public abstract class BaseCommand { |
| |
| private final boolean printHelp; |
| private final boolean printVersion; |
| |
| private final AndroidApp app; |
| |
| BaseCommand(boolean printHelp, boolean printVersion) { |
| this.printHelp = printHelp; |
| this.printVersion = printVersion; |
| // All other fields are initialized with stub/invalid values. |
| this.app = null; |
| } |
| |
| BaseCommand(AndroidApp app) { |
| assert app != null; |
| this.app = app; |
| // Print options are not set. |
| printHelp = false; |
| printVersion = false; |
| } |
| |
| public boolean isPrintHelp() { |
| return printHelp; |
| } |
| |
| public boolean isPrintVersion() { |
| return printVersion; |
| } |
| |
| // Internal access to the input resources. |
| AndroidApp getInputApp() { |
| return app; |
| } |
| |
| // Internal access to the internal options. |
| abstract InternalOptions getInternalOptions(); |
| |
| abstract static class InputFileOrigin extends PathOrigin { |
| private final String inputType; |
| |
| public InputFileOrigin(String inputType, Path file) { |
| super(file); |
| this.inputType = inputType; |
| } |
| |
| @Override |
| public String part() { |
| return inputType + " '" + super.part() + "'"; |
| } |
| } |
| |
| private static class ProgramInputOrigin extends InputFileOrigin { |
| |
| public ProgramInputOrigin(Path file) { |
| super("program input", file); |
| } |
| } |
| |
| static class LibraryInputOrigin extends InputFileOrigin { |
| |
| public LibraryInputOrigin(Path file) { |
| super("library input", file); |
| } |
| } |
| |
| /** |
| * Base builder for commands. |
| * |
| * @param <C> Command the builder is building, e.g., {@link R8Command} or {@link D8Command}. |
| * @param <B> Concrete builder extending this base, e.g., {@link R8Command.Builder} or {@link |
| * D8Command.Builder}. |
| */ |
| @KeepForApi |
| public abstract static class Builder<C extends BaseCommand, B extends Builder<C, B>> { |
| |
| private final Reporter reporter; |
| private boolean printHelp = false; |
| private boolean printVersion = false; |
| private final AndroidApp.Builder app; |
| |
| List<Path> programFiles = new ArrayList<>(); |
| |
| Builder() { |
| this(AndroidApp.builder()); |
| } |
| |
| Builder(DiagnosticsHandler handler) { |
| this(AndroidApp.builder(new Reporter(handler))); |
| } |
| |
| Builder(AndroidApp.Builder builder) { |
| this.app = builder; |
| this.reporter = builder.getReporter(); |
| } |
| |
| abstract B self(); |
| |
| /** |
| * Build the final command. |
| * |
| * <p>Building the command will complete the validation of builders state and build the final |
| * command. If any errors occur during validation or building the errors are reported to the |
| * associated diagnostics handler and a {@link CompilationFailedException} exception is thrown. |
| */ |
| public final C build() throws CompilationFailedException { |
| Box<C> box = new Box<>(null); |
| ExceptionUtils.withCompilationHandler( |
| reporter, |
| () -> { |
| validate(); |
| box.set(makeCommand()); |
| reporter.failIfPendingErrors(); |
| }); |
| return box.get(); |
| } |
| |
| // Helper to construct the actual command. Called as part of {@link build()}. |
| abstract C makeCommand(); |
| |
| // Internal accessor for the application resources. |
| AndroidApp.Builder getAppBuilder() { |
| return app; |
| } |
| |
| /** Add program file resources. */ |
| public B addProgramFiles(Path... files) { |
| addProgramFiles(Arrays.asList(files)); |
| return self(); |
| } |
| |
| Reporter getReporter() { |
| return reporter; |
| } |
| |
| /** Add program file resources. */ |
| public B addProgramFiles(Collection<Path> files) { |
| guard( |
| () -> { |
| for (Path path : files) { |
| try { |
| app.addProgramFile(path); |
| programFiles.add(path); |
| } catch (CompilationError e) { |
| error(new ProgramInputOrigin(path), e); |
| } |
| } |
| }); |
| return self(); |
| } |
| |
| /** Add a resource provider for program resources. */ |
| public B addProgramResourceProvider(ProgramResourceProvider programProvider) { |
| app.addProgramResourceProvider(programProvider); |
| return self(); |
| } |
| |
| /** Add library file resource provider. */ |
| public B addLibraryResourceProvider(ClassFileResourceProvider provider) { |
| guard(() -> getAppBuilder().addLibraryResourceProvider(provider)); |
| return self(); |
| } |
| |
| /** Add library file resources. */ |
| public B addLibraryFiles(Path... files) { |
| addLibraryFiles(Arrays.asList(files)); |
| return self(); |
| } |
| |
| /** Add library file resources. */ |
| public B addLibraryFiles(Collection<Path> files) { |
| guard( |
| () -> { |
| for (Path path : files) { |
| try { |
| app.addLibraryFile(path); |
| } catch (CompilationError e) { |
| error(new LibraryInputOrigin(path), e); |
| } |
| } |
| }); |
| return self(); |
| } |
| |
| /** Add classpath file resources. */ |
| public B addClasspathFiles(Path... files) { |
| guard(() -> Arrays.stream(files).forEach(this::addClasspathFile)); |
| return self(); |
| } |
| |
| /** Add classpath file resources. */ |
| public B addClasspathFiles(Collection<Path> files) { |
| guard(() -> files.forEach(this::addClasspathFile)); |
| return self(); |
| } |
| |
| private void addClasspathFile(Path file) { |
| guard(() -> getAppBuilder().addClasspathFile(file)); |
| } |
| |
| /** Add classfile resources provider for class-path resources. */ |
| public B addClasspathResourceProvider(ClassFileResourceProvider provider) { |
| guard(() -> getAppBuilder().addClasspathResourceProvider(provider)); |
| return self(); |
| } |
| |
| /** Add Java-bytecode program-data. */ |
| public B addClassProgramData(byte[] data, Origin origin) { |
| guard(() -> app.addClassProgramData(data, origin)); |
| return self(); |
| } |
| |
| /** Add Java-bytecode program-data. */ |
| B addDexProgramData(byte[] data, Origin origin) { |
| guard(() -> app.addDexProgramData(data, origin)); |
| return self(); |
| } |
| |
| /** |
| * Add main-dex list files. |
| * |
| * Each line in each of the files specifies one class to keep in the primary dex file |
| * (<code>classes.dex</code>). |
| * |
| * A class is specified using the following format: "com/example/MyClass.class". That is |
| * "/" as separator between package components, and a trailing ".class". |
| */ |
| public B addMainDexListFiles(Path... files) { |
| guard(() -> { |
| try { |
| app.addMainDexListFiles(files); |
| } catch (NoSuchFileException e) { |
| reporter.error(new StringDiagnostic( |
| "Main-dex-list file does not exist", new PathOrigin(Paths.get(e.getFile())))); |
| } |
| }); |
| return self(); |
| } |
| |
| /** |
| * Add main-dex list files. |
| * |
| * @see #addMainDexListFiles(Path...) |
| */ |
| public B addMainDexListFiles(Collection<Path> files) { |
| guard( |
| () -> { |
| try { |
| app.addMainDexListFiles(files); |
| } catch (NoSuchFileException e) { |
| reporter.error( |
| new StringDiagnostic( |
| "Main-dex-list file does not exist", new PathOrigin(Paths.get(e.getFile())))); |
| } |
| }); |
| return self(); |
| } |
| |
| /** |
| * Add main-dex classes. |
| * |
| * <p>Add classes to keep in the primary dex file (<code>classes.dex</code>). |
| * |
| * <p>NOTE: The name of the classes is specified using the Java fully qualified names format |
| * (e.g. "com.example.MyClass$A"), and <i>not</i> the format used by the main-dex list file. |
| */ |
| public B addMainDexClasses(String... classes) { |
| guard(() -> app.addMainDexClasses(classes)); |
| return self(); |
| } |
| |
| /** |
| * Add main-dex classes. |
| * |
| * Add classes to keep in the primary dex file (<code>classes.dex</code>). |
| * |
| * NOTE: The name of the classes is specified using the Java fully qualified names format |
| * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file. |
| */ |
| public B addMainDexClasses(Collection<String> classes) { |
| guard(() -> app.addMainDexClasses(classes)); |
| return self(); |
| } |
| |
| /** True if the print-help flag is enabled. */ |
| public boolean isPrintHelp() { |
| return printHelp; |
| } |
| |
| /** Set the value of the print-help flag. */ |
| public B setPrintHelp(boolean printHelp) { |
| this.printHelp = printHelp; |
| return self(); |
| } |
| |
| /** True if the print-version flag is enabled. */ |
| public boolean isPrintVersion() { |
| return printVersion; |
| } |
| |
| /** Set the value of the print-version flag. */ |
| public B setPrintVersion(boolean printVersion) { |
| this.printVersion = printVersion; |
| return self(); |
| } |
| |
| /** Signal an error. */ |
| public void error(Diagnostic diagnostic) { |
| reporter.error(diagnostic); |
| } |
| |
| /** |
| * Signal an error and throw {@link AbortException}. |
| * |
| * @throws AbortException always. |
| */ |
| public RuntimeException fatalError(Diagnostic diagnostic) { |
| return reporter.fatalError(diagnostic); |
| } |
| |
| // Internal helper for compat tools to make them ignore DEX code in input archives. |
| void setIgnoreDexInArchive(boolean value) { |
| guard(() -> app.setIgnoreDexInArchive(value)); |
| } |
| |
| // Helper that validates the command content. Called as part of {@link build()}. |
| void validate() {} |
| |
| // Helper to signify an error. |
| void error(Origin origin, Throwable throwable) { |
| reporter.error(new ExceptionDiagnostic(throwable, origin)); |
| } |
| |
| // Helper to guard and handle exceptions. |
| void guard(Runnable action) { |
| try { |
| action.run(); |
| } catch (CompilationError e) { |
| reporter.error(e.toStringDiagnostic()); |
| } catch (AbortException e) { |
| // Error was reported and exception will be thrown by build. |
| } |
| } |
| |
| } |
| } |