| // 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.internal.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(createReporter(handler))); | |
| } | |
| static Reporter createReporter(DiagnosticsHandler handler) { | |
| if (handler instanceof Reporter) { | |
| return (Reporter) handler; | |
| } else { | |
| return 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. | |
| } | |
| } | |
| } | |
| } |