blob: fa0a09446d668e1dd8b5015b32177686b2081037 [file] [log] [blame]
// Copyright (c) 2018, 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 static com.android.tools.r8.ParseFlagInfoImpl.flag0;
import static com.android.tools.r8.ParseFlagInfoImpl.flag1;
import static com.android.tools.r8.ParseFlagInfoImpl.flag2;
import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
import com.android.tools.r8.profile.startup.StartupProfileProviderUtils;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.MapIdTemplateProvider;
import com.android.tools.r8.utils.SourceFileTemplateProvider;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
static final String ISOLATED_SPLITS_FLAG = "--isolated-splits";
// Note: this must be a super-set of OPTIONS_WITH_TWO_PARAMETERS.
private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
ImmutableSet.of(
"--output",
"--lib",
"--classpath",
MIN_API_FLAG,
"--main-dex-rules",
"--main-dex-list",
"--feature",
"--android-resources",
"--main-dex-list-output",
"--pg-conf",
"--pg-conf-output",
"--pg-map",
"--pg-map-output",
"--partition-map-output",
"--desugared-lib",
"--desugared-lib-pg-conf-output",
"--map-id-template",
"--source-file-template",
ART_PROFILE_FLAG,
STARTUP_PROFILE_FLAG,
THREAD_COUNT_FLAG);
// Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER.
private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS =
ImmutableSet.of(ART_PROFILE_FLAG, "--feature", "--android-resources");
// Due to the family of flags (for assertions and diagnostics) we can't base the one/two args
// on this setup of flags. Thus, the flag collection just encodes the descriptive content.
static List<ParseFlagInfoImpl> getFlags() {
return ImmutableList.<ParseFlagInfoImpl>builder()
.add(ParseFlagInfoImpl.getRelease(true))
.add(ParseFlagInfoImpl.getDebug(false))
.add(ParseFlagInfoImpl.getDex(true))
.add(ParseFlagInfoImpl.getClassfile())
.add(ParseFlagInfoImpl.getOutput())
.add(ParseFlagInfoImpl.getLib())
.add(ParseFlagInfoImpl.getClasspath())
.add(ParseFlagInfoImpl.getMinApi())
.add(flag0("--pg-compat", "Compile with R8 in Proguard compatibility mode."))
.add(ParseFlagInfoImpl.getPgConf())
.add(flag1("--pg-conf-output", "<file>", "Output the collective configuration to <file>."))
.add(
ParseFlagInfoImpl.flag1(
"--pg-map",
"<file>",
"Use <file> as a mapping file for distribution "
+ "and composition with output mapping file."))
.add(ParseFlagInfoImpl.getPgMapOutput())
.add(ParseFlagInfoImpl.getPartitionMapOutput())
.add(ParseFlagInfoImpl.getDesugaredLib())
.add(
flag1(
"--desugared-lib-pg-conf-output",
"<file>",
"Output the Proguard configuration for L8 to <file>."))
.add(flag0("--no-tree-shaking", "Force disable tree shaking of unreachable classes."))
.add(flag0("--no-minification", "Force disable minification of names."))
.add(flag0("--no-data-resources", "Ignore all data resources."))
.add(flag0("--no-desugaring", "Force disable desugaring."))
.add(ParseFlagInfoImpl.getMainDexRules())
.add(ParseFlagInfoImpl.getMainDexList())
.add(
flag2(
"--android-resources",
"<input>",
"<output>",
"Add android resource input and output to be used in resource shrinking. Both ",
"input and output must be specified."))
.add(
flag2(
"--feature",
"<input>[:|;<res-input>]",
"<output>[:|;<res-output>]",
"Add feature <input> file to <output> file. Several ",
"occurrences can map to the same output. If <res-input> and <res-output> are ",
"specified use these as resource shrinker input and output. Separator is : on ",
"linux/mac, ; on windows. It is possible to supply resource only features by ",
" using an empty string for <input> and <output>, e.g. --feature :in.ap_ :out.ap_"))
.add(ParseFlagInfoImpl.getIsolatedSplits())
.add(flag1("--main-dex-list-output", "<file>", "Output the full main-dex list in <file>."))
.addAll(ParseFlagInfoImpl.getAssertionsFlags())
.add(ParseFlagInfoImpl.getThreadCount())
.add(ParseFlagInfoImpl.getMapDiagnostics())
.add(
flag1(
"--map-id-template",
"<template>",
"Set the map-id to <template>.",
"The <template> can reference the variables:",
" %MAP_HASH: compiler generated mapping hash."))
.add(
flag1(
"--source-file-template",
"<template>",
"Set all source-file attributes to <template>",
"The <template> can reference the variables:",
" %MAP_ID: map id (e.g., value of --map-id-template).",
" %MAP_HASH: compiler generated mapping hash."))
.add(ParseFlagInfoImpl.getAndroidPlatformBuild())
.add(ParseFlagInfoImpl.getArtProfile())
.add(ParseFlagInfoImpl.getStartupProfile())
.add(ParseFlagInfoImpl.getVersion("r8"))
.add(ParseFlagInfoImpl.getHelp())
.build();
}
static String getUsageMessage() {
StringBuilder builder = new StringBuilder();
StringUtils.appendLines(
builder,
"Usage: r8 [options] [@<argfile>] <input-files>",
" where <input-files> are any combination class, zip, or jar files",
" and each <argfile> is a file containing additional arguments (one per line)",
" and options are:");
new ParseFlagPrinter().addFlags(ImmutableList.copyOf(getFlags())).appendLinesToBuilder(builder);
return builder.toString();
}
// Internal state to verify parsing properties not enforced by the builder.
private static class ParseState {
CompilationMode mode = null;
OutputMode outputMode = null;
Path outputPath = null;
boolean hasDefinedApiLevel = false;
private boolean includeDataResources = true;
}
/**
* Parse the R8 command-line.
*
* <p>Parsing will set the supplied options or their default value if they have any.
*
* @param args Command-line arguments array.
* @param origin Origin description of the command-line arguments.
* @return R8 command builder with state set up according to parsed command line.
*/
public static R8Command.Builder parse(String[] args, Origin origin) {
return new R8CommandParser().parse(args, origin, R8Command.builder());
}
/**
* Parse the R8 command-line.
*
* <p>Parsing will set the supplied options or their default value if they have any.
*
* @param args Command-line arguments array.
* @param origin Origin description of the command-line arguments.
* @param handler Custom defined diagnostics handler.
* @return R8 command builder with state set up according to parsed command line.
*/
public static R8Command.Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
return new R8CommandParser().parse(args, origin, R8Command.builder(handler));
}
private R8Command.Builder parse(String[] args, Origin origin, R8Command.Builder builder) {
ParseState state = new ParseState();
parse(args, origin, builder, state);
if (state.mode != null) {
builder.setMode(state.mode);
}
Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
builder.setOutput(outputPath, outputMode, state.includeDataResources);
builder.setEnableExperimentalMissingLibraryApiModeling(true);
return builder;
}
private void parse(
String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
FeatureSplitConfigCollector featureSplitConfigCollector = new FeatureSplitConfigCollector();
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
String nextNextArg = null;
if (OPTIONS_WITH_ONE_PARAMETER.contains(arg)) {
if (++i < expandedArgs.length) {
nextArg = expandedArgs[i];
} else {
builder.error(
new StringDiagnostic(
"Missing parameter for " + expandedArgs[i - 1] + ".", argsOrigin));
break;
}
if (OPTIONS_WITH_TWO_PARAMETERS.contains(arg)) {
if (++i < expandedArgs.length) {
nextNextArg = expandedArgs[i];
} else {
builder.error(
new StringDiagnostic(
"Missing parameter for " + expandedArgs[i - 2] + ".", argsOrigin));
break;
}
}
}
if (arg.length() == 0) {
continue;
} else if (arg.equals("--help")) {
builder.setPrintHelp(true);
} else if (arg.equals("--version")) {
builder.setPrintVersion(true);
} else if (arg.equals("--debug")) {
if (state.mode == CompilationMode.RELEASE) {
builder.error(
new StringDiagnostic(
"Cannot compile in both --debug and --release mode.", argsOrigin));
}
state.mode = CompilationMode.DEBUG;
} else if (arg.equals("--release")) {
if (state.mode == CompilationMode.DEBUG) {
builder.error(
new StringDiagnostic(
"Cannot compile in both --debug and --release mode.", argsOrigin));
}
state.mode = CompilationMode.RELEASE;
} else if (arg.equals("--pg-compat")) {
builder.setProguardCompatibility(true);
} else if (arg.equals("--dex")) {
if (state.outputMode == OutputMode.ClassFile) {
builder.error(
new StringDiagnostic(
"Cannot compile in both --dex and --classfile output mode.", argsOrigin));
}
state.outputMode = OutputMode.DexIndexed;
} else if (arg.equals("--classfile")) {
if (state.outputMode == OutputMode.DexIndexed) {
builder.error(
new StringDiagnostic(
"Cannot compile in both --dex and --classfile output mode.", argsOrigin));
}
state.outputMode = OutputMode.ClassFile;
} else if (arg.equals("--output")) {
if (state.outputPath != null) {
builder.error(
new StringDiagnostic(
"Cannot output both to '"
+ state.outputPath.toString()
+ "' and '"
+ nextArg
+ "'",
argsOrigin));
}
state.outputPath = Paths.get(nextArg);
} else if (arg.equals("--lib")) {
addLibraryArgument(builder, argsOrigin, nextArg);
} else if (arg.equals("--classpath")) {
builder.addClasspathFiles(Paths.get(nextArg));
} else if (arg.equals(MIN_API_FLAG)) {
if (state.hasDefinedApiLevel) {
builder.error(
new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", argsOrigin));
} else {
parsePositiveIntArgument(
builder::error, MIN_API_FLAG, nextArg, argsOrigin, builder::setMinApiLevel);
state.hasDefinedApiLevel = true;
}
} else if (arg.equals(THREAD_COUNT_FLAG)) {
parsePositiveIntArgument(
builder::error, THREAD_COUNT_FLAG, nextArg, argsOrigin, builder::setThreadCount);
} else if (arg.equals("--no-tree-shaking")) {
builder.setDisableTreeShaking(true);
} else if (arg.equals("--no-minification")) {
builder.setDisableMinification(true);
} else if (arg.equals("--no-desugaring")) {
builder.setDisableDesugaring(true);
} else if (arg.equals("--main-dex-rules")) {
builder.addMainDexRulesFiles(Paths.get(nextArg));
} else if (arg.equals("--android-resources")) {
Path inputPath = Paths.get(nextArg);
Path outputPath = Paths.get(nextNextArg);
builder.setAndroidResourceProvider(
new ArchiveProtoAndroidResourceProvider(inputPath, new PathOrigin(inputPath)));
builder.setAndroidResourceConsumer(
new ArchiveProtoAndroidResourceConsumer(outputPath, inputPath));
} else if (arg.equals("--feature")) {
featureSplitConfigCollector.addInputOutput(nextArg, nextNextArg);
} else if (arg.equals(ISOLATED_SPLITS_FLAG)) {
builder.setEnableIsolatedSplits(true);
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list-output")) {
builder.setMainDexListOutputPath(Paths.get(nextArg));
} else if (arg.equals("--optimize-multidex-for-linearalloc")) {
builder.setOptimizeMultidexForLinearAlloc(true);
} else if (arg.equals("--pg-conf")) {
builder.addProguardConfigurationFiles(Paths.get(nextArg));
} else if (arg.equals("--pg-conf-output")) {
FileConsumer consumer = new FileConsumer(Paths.get(nextArg));
builder.setProguardConfigurationConsumer(consumer);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapInputFile(Paths.get(nextArg));
} else if (arg.equals("--pg-map-output")) {
builder.setProguardMapOutputPath(Paths.get(nextArg));
} else if (arg.equals("--partition-map-output")) {
builder.setPartitionMapOutputPath(Paths.get(nextArg));
} else if (arg.equals("--desugared-lib")) {
builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
} else if (arg.equals("--desugared-lib-pg-conf-output")) {
StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg));
builder.setDesugaredLibraryKeepRuleConsumer(consumer);
} else if (arg.equals("--no-data-resources")) {
state.includeDataResources = false;
} else if (arg.equals("--map-id-template")) {
builder.setMapIdProvider(MapIdTemplateProvider.create(nextArg, builder.getReporter()));
} else if (arg.equals("--source-file-template")) {
builder.setSourceFileProvider(
SourceFileTemplateProvider.create(nextArg, builder.getReporter()));
} else if (arg.equals("--android-platform-build")) {
builder.setAndroidPlatformBuild(true);
} else if (arg.equals(ART_PROFILE_FLAG)) {
Path artProfilePath = Paths.get(nextArg);
Path rewrittenArtProfilePath = Paths.get(nextNextArg);
builder.addArtProfileForRewriting(
ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfilePath),
ArtProfileConsumerUtils.create(rewrittenArtProfilePath));
} else if (arg.equals(STARTUP_PROFILE_FLAG)) {
Path startupProfilePath = Paths.get(nextArg);
builder.addStartupProfileProviders(
StartupProfileProviderUtils.createFromHumanReadableArtProfile(startupProfilePath));
} else if (arg.startsWith("--")) {
if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
continue;
}
int argsConsumed = tryParseMapDiagnostics(builder, arg, expandedArgs, i, argsOrigin);
if (argsConsumed >= 0) {
i += argsConsumed;
continue;
}
argsConsumed = tryParseDump(builder, arg, expandedArgs, i, argsOrigin);
if (argsConsumed >= 0) {
i += argsConsumed;
continue;
}
builder.error(new StringDiagnostic("Unknown option: " + arg, argsOrigin));
} else if (arg.startsWith("@")) {
builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", argsOrigin));
} else {
builder.addProgramFiles(Paths.get(arg));
}
}
addFeatureSplitConfigs(builder, featureSplitConfigCollector.getConfigs());
}
private void addFeatureSplitConfigs(
R8Command.Builder builder, Collection<FeatureSplitConfig> featureSplitConfigs) {
for (FeatureSplitConfig featureSplitConfig : featureSplitConfigs) {
builder.addFeatureSplit(
featureSplitGenerator -> {
if (featureSplitConfig.outputJar != null) {
featureSplitGenerator.setProgramConsumer(
builder.createProgramOutputConsumer(
featureSplitConfig.outputJar, OutputMode.DexIndexed, true));
}
for (Path inputPath : featureSplitConfig.inputJars) {
featureSplitGenerator.addProgramResourceProvider(
ArchiveProgramResourceProvider.fromArchive(inputPath));
}
if (featureSplitConfig.inputResources != null) {
featureSplitGenerator.setAndroidResourceProvider(
new ArchiveProtoAndroidResourceProvider(
featureSplitConfig.inputResources,
new PathOrigin(featureSplitConfig.inputResources)));
}
if (featureSplitConfig.outputResources != null) {
featureSplitGenerator.setAndroidResourceConsumer(
new ArchiveProtoAndroidResourceConsumer(
featureSplitConfig.outputResources, featureSplitConfig.inputResources));
}
return featureSplitGenerator.build();
});
}
}
// Represents a set of paths parsed from a string that may contain a ":" (";" on windows).
// Supported examples are:
// pathA -> first = pathA, second = null
// pathA:pathB -> first = pathA, second = pathB
// :pathB -> first = null, second = pathB
// pathA: -> first = pathA, second = null
private static class PossibleDoublePath {
public final Path first;
public final Path second;
private PossibleDoublePath(Path first, Path second) {
this.first = first;
this.second = second;
}
public static PossibleDoublePath parse(String input) {
Path first = null, second = null;
List<String> inputSplit = StringUtils.split(input, File.pathSeparatorChar);
if (inputSplit.size() == 0 || inputSplit.size() > 2) {
throw new IllegalArgumentException("Feature input/output takes one or two paths.");
}
String firstString = inputSplit.get(0);
if (!firstString.isEmpty()) {
first = Paths.get(firstString);
}
if (inputSplit.size() == 2) {
// "a:".split() gives just ["a"], so we should never get here if we don't have
// a second string. ":b".split gives ["", "b"] which is handled for first above.
assert inputSplit.get(1).length() > 0;
second = Paths.get(inputSplit.get(1));
}
return new PossibleDoublePath(first, second);
}
}
private static class FeatureSplitConfig {
private List<Path> inputJars = new ArrayList<>();
private Path inputResources;
private Path outputResources;
private Path outputJar;
}
private static class FeatureSplitConfigCollector {
private List<FeatureSplitConfig> resourceOnlySplits = new ArrayList<>();
private Map<Path, FeatureSplitConfig> withCodeSplits = new HashMap<>();
public void addInputOutput(String input, String output) {
PossibleDoublePath inputPaths = PossibleDoublePath.parse(input);
PossibleDoublePath outputPaths = PossibleDoublePath.parse(output);
FeatureSplitConfig featureSplitConfig;
if (outputPaths.first != null) {
featureSplitConfig =
withCodeSplits.computeIfAbsent(outputPaths.first, k -> new FeatureSplitConfig());
featureSplitConfig.outputJar = outputPaths.first;
// We support adding resources independently of the input jars, which later --feature
// can add, so we might have no input jars here, example:
// ... --feature :input_feature.ap_ out.jar:out_feature.ap_ --feature in.jar out.jar
if (inputPaths.first != null) {
featureSplitConfig.inputJars.add(inputPaths.first);
}
} else {
featureSplitConfig = new FeatureSplitConfig();
resourceOnlySplits.add(featureSplitConfig);
}
if (Objects.isNull(inputPaths.second) != Objects.isNull(outputPaths.second)) {
throw new IllegalArgumentException(
"Both input and output for feature resources must be provided");
}
featureSplitConfig.inputResources = inputPaths.second;
featureSplitConfig.outputResources = outputPaths.second;
}
public Collection<FeatureSplitConfig> getConfigs() {
ArrayList<FeatureSplitConfig> featureSplitConfigs = new ArrayList<>(resourceOnlySplits);
featureSplitConfigs.addAll(withCodeSplits.values());
return featureSplitConfigs;
}
}
}