blob: cc4cffe86c1f7a41043e351b5632e4b0e1e5d887 [file] [log] [blame]
// 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.
}
}
}
}