|  | // 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.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}. | 
|  | */ | 
|  | @Keep | 
|  | 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}. | 
|  | */ | 
|  | @Keep | 
|  | 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. | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  | } |