blob: a4af02245040e77e3b5a7df375cf7fea04502b87 [file] [log] [blame]
// Copyright (c) 2020, 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.graph.DexItemFactory;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableSet;
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;
import java.util.Set;
/**
* Immutable command structure for an invocation of the {@link BackportedMethodList} tool.
*
* <p>To build a BackportedMethodList command use the {@link BackportedMethodListCommand.Builder}
* class. For example:
*
* <pre>
* BackportedMethodListCommand command = BackportedMethodListCommand.builder()
* .setMinApiLevel(apiLevel)
* .setOutputPath(Paths.get("methods-list.txt"))
* .build();
* </pre>
*/
@Keep
public class BackportedMethodListCommand {
private final boolean printHelp;
private final boolean printVersion;
private final Reporter reporter;
private final int minApiLevel;
private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
private final AndroidApp app;
private final StringConsumer backportedMethodListConsumer;
private final DexItemFactory factory;
public boolean isPrintHelp() {
return printHelp;
}
public boolean isPrintVersion() {
return printVersion;
}
Reporter getReporter() {
return reporter;
}
public int getMinApiLevel() {
return minApiLevel;
}
public DesugaredLibraryConfiguration getDesugaredLibraryConfiguration() {
return desugaredLibraryConfiguration;
}
public StringConsumer getBackportedMethodListConsumer() {
return backportedMethodListConsumer;
}
AndroidApp getInputApp() {
return app;
}
private BackportedMethodListCommand(boolean printHelp, boolean printVersion) {
this.printHelp = printHelp;
this.printVersion = printVersion;
this.reporter = new Reporter();
this.minApiLevel = -1;
this.desugaredLibraryConfiguration = null;
this.app = null;
this.backportedMethodListConsumer = null;
this.factory = null;
}
private BackportedMethodListCommand(
Reporter reporter,
int minApiLevel,
DesugaredLibraryConfiguration desugaredLibraryConfiguration,
AndroidApp app,
StringConsumer backportedMethodListConsumer,
DexItemFactory factory) {
this.printHelp = false;
this.printVersion = false;
this.reporter = reporter;
this.minApiLevel = minApiLevel;
this.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
this.app = app;
this.backportedMethodListConsumer = backportedMethodListConsumer;
this.factory = factory;
}
InternalOptions getInternalOptions() {
InternalOptions options = new InternalOptions(factory, getReporter());
options.minApiLevel = minApiLevel;
options.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
return options;
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
return new Builder(diagnosticsHandler);
}
public static Builder parse(String[] args) {
final Set<String> OPTIONS_WITH_PARAMETER =
ImmutableSet.of("--output", "--min-api", "--desugared-lib", "--lib");
boolean hasDefinedApiLevel = false;
Builder builder = builder();
for (int i = 0; i < args.length; i++) {
String arg = args[i].trim();
String nextArg = null;
if (OPTIONS_WITH_PARAMETER.contains(arg)) {
if (++i < args.length) {
nextArg = args[i];
} else {
builder.error(new StringDiagnostic("Missing parameter for " + args[i - 1] + "."));
break;
}
}
if (arg.equals("--help")) {
builder.setPrintHelp(true);
} else if (arg.equals("--version")) {
builder.setPrintVersion(true);
} else if (arg.equals("--min-api")) {
if (hasDefinedApiLevel) {
builder.error(new StringDiagnostic("Cannot set multiple --min-api options"));
} else {
parseMinApi(builder, nextArg);
hasDefinedApiLevel = true;
}
} else if (arg.equals("--desugared-lib")) {
builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
} else if (arg.equals("--lib")) {
builder.addLibraryFiles(Paths.get(nextArg));
} else if (arg.equals("--output")) {
builder.setOutputPath(Paths.get(nextArg));
} else {
builder.error(new StringDiagnostic("Unknown option: " + arg));
}
}
return builder;
}
private static void parseMinApi(Builder builder, String minApiString) {
int minApi;
try {
minApi = Integer.parseInt(minApiString);
} catch (NumberFormatException e) {
builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
return;
}
if (minApi < 1) {
builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString));
return;
}
builder.setMinApiLevel(minApi);
}
@Keep
public static class Builder {
private final Reporter reporter;
private int minApiLevel = AndroidApiLevel.B.getLevel();
private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
private final AndroidApp.Builder app;
private StringConsumer backportedMethodListConsumer;
private boolean printHelp = false;
private boolean printVersion = false;
private Builder() {
this(new DiagnosticsHandler() {});
}
private Builder(DiagnosticsHandler diagnosticsHandler) {
this.app = AndroidApp.builder();
this.reporter = new Reporter(diagnosticsHandler);
}
/**
* Set the minimum API level for the application compiled.
*
* <p>The tool will only report backported methods which are not present at this API level.
*
* <p>The default is 1 if never set.
*/
public Builder setMinApiLevel(int minApiLevel) {
if (minApiLevel <= 0) {
reporter.error(new StringDiagnostic("Invalid minApiLevel: " + minApiLevel));
} else {
this.minApiLevel = minApiLevel;
}
return this;
}
public int getMinApiLevel() {
return minApiLevel;
}
/** Desugared library configuration */
public Builder addDesugaredLibraryConfiguration(StringResource configuration) {
desugaredLibraryConfigurationResources.add(configuration);
return this;
}
/** Desugared library configuration */
public Builder addDesugaredLibraryConfiguration(String configuration) {
return addDesugaredLibraryConfiguration(
StringResource.fromString(configuration, Origin.unknown()));
}
/** The compilation SDK library (android.jar) */
public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
app.addLibraryResourceProvider(provider);
return this;
}
/** The compilation SDK library (android.jar) */
public Builder addLibraryFiles(Path... files) {
addLibraryFiles(Arrays.asList(files));
return this;
}
/** The compilation SDK library (android.jar) */
public Builder addLibraryFiles(Collection<Path> files) {
for (Path path : files) {
app.addLibraryFile(path);
}
return this;
}
DesugaredLibraryConfiguration getDesugaredLibraryConfiguration(DexItemFactory factory) {
if (desugaredLibraryConfigurationResources.isEmpty()) {
return DesugaredLibraryConfiguration.empty();
}
if (desugaredLibraryConfigurationResources.size() > 1) {
reporter.fatalError("Only one desugared library configuration is supported.");
}
StringResource desugaredLibraryConfigurationResource =
desugaredLibraryConfigurationResources.get(0);
DesugaredLibraryConfigurationParser libraryParser =
new DesugaredLibraryConfigurationParser(factory, null, false, getMinApiLevel());
return libraryParser.parse(desugaredLibraryConfigurationResource);
}
/** Output file for the backported method list */
public Builder setOutputPath(Path outputPath) {
backportedMethodListConsumer =
new StringConsumer.FileConsumer(outputPath) {
@Override
public void accept(String string, DiagnosticsHandler handler) {
super.accept(string, handler);
super.accept(System.lineSeparator(), handler);
}
};
return this;
}
/** Consumer receiving the the backported method list */
public Builder setConsumer(StringConsumer consumer) {
this.backportedMethodListConsumer = consumer;
return this;
}
/** True if the print-help flag is enabled. */
public boolean isPrintHelp() {
return printHelp;
}
/** Set the value of the print-help flag. */
public Builder setPrintHelp(boolean printHelp) {
this.printHelp = printHelp;
return this;
}
/** True if the print-version flag is enabled. */
public boolean isPrintVersion() {
return printVersion;
}
/** Set the value of the print-version flag. */
public Builder setPrintVersion(boolean printVersion) {
this.printVersion = printVersion;
return this;
}
private void error(Diagnostic diagnostic) {
reporter.error(diagnostic);
}
public BackportedMethodListCommand build() {
AndroidApp library = app.build();
if (!desugaredLibraryConfigurationResources.isEmpty()
&& library.getLibraryResourceProviders().isEmpty()) {
reporter.error(
new StringDiagnostic("With desugared library configuration a library is required"));
}
if (isPrintHelp() || isPrintVersion()) {
return new BackportedMethodListCommand(isPrintHelp(), isPrintVersion());
}
if (backportedMethodListConsumer == null) {
backportedMethodListConsumer =
new StringConsumer() {
@Override
public void accept(String string, DiagnosticsHandler handler) {
System.out.println(string);
}
@Override
public void finished(DiagnosticsHandler handler) {}
};
}
DexItemFactory factory = new DexItemFactory();
return new BackportedMethodListCommand(
reporter,
minApiLevel,
getDesugaredLibraryConfiguration(factory),
library,
backportedMethodListConsumer,
factory);
}
}
}