Add CLI support for feature splits
Bug: 156854830
Change-Id: I0288f4c535fdb675eeecdd28d2028120973df6fc
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index d31606d..6220eb4 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -11,7 +11,11 @@
import com.google.common.collect.Iterables;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
public class R8CommandParser extends BaseCompilerCommandParser<R8Command, R8Command.Builder> {
@@ -24,12 +28,15 @@
MIN_API_FLAG,
"--main-dex-rules",
"--main-dex-list",
+ "--feature",
"--main-dex-list-output",
"--pg-conf",
"--pg-map-output",
"--desugared-lib",
THREAD_COUNT_FLAG);
+ private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
+
public static void main(String[] args) throws CompilationFailedException {
R8Command command = parse(args, Origin.root()).build();
if (command.isPrintHelp()) {
@@ -83,6 +90,9 @@
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
" --main-dex-list <file> # List of classes to place in the primary dex file.",
+ " --feature <input> <output> ",
+ " # Add feature <input> file to <output> file. Several ",
+ " # occurrences can map to the same output.",
" --main-dex-list-output <file> ",
" # Output the full main-dex list in <file>."),
ASSERTIONS_USAGE_MESSAGE,
@@ -131,9 +141,11 @@
private void parse(
String[] args, Origin argsOrigin, R8Command.Builder builder, ParseState state) {
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+ Map<Path, List<Path>> featureSplitJars = new HashMap<>();
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
String nextArg = null;
+ String nextNextArg = null;
if (OPTIONS_WITH_PARAMETER.contains(arg)) {
if (++i < expandedArgs.length) {
nextArg = expandedArgs[i];
@@ -143,6 +155,16 @@
"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;
@@ -214,6 +236,10 @@
builder.setDisableDesugaring(true);
} else if (arg.equals("--main-dex-rules")) {
builder.addMainDexRulesFiles(Paths.get(nextArg));
+ } else if (arg.equals("--feature")) {
+ featureSplitJars
+ .computeIfAbsent(Paths.get(nextNextArg), k -> new ArrayList<>())
+ .add(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list-output")) {
@@ -239,5 +265,20 @@
builder.addProgramFiles(Paths.get(arg));
}
}
+ featureSplitJars.forEach(
+ (outputPath, inputJars) -> addFeatureJar(builder, outputPath, inputJars));
+ }
+
+ public void addFeatureJar(R8Command.Builder builder, Path outputPath, List<Path> inputJarPaths) {
+ builder.addFeatureSplit(
+ featureSplitGenerator -> {
+ featureSplitGenerator.setProgramConsumer(
+ builder.createProgramOutputConsumer(outputPath, OutputMode.DexIndexed, true));
+ for (Path inputPath : inputJarPaths) {
+ featureSplitGenerator.addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(inputPath));
+ }
+ return featureSplitGenerator.build();
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 4f8806c..b5c518c 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -120,6 +120,52 @@
}
@Test
+ public void passFeatureSplit() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
+ Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
+ Path library = ToolHelper.getDefaultAndroidJar();
+ Path output = working.resolve("classes.dex");
+ Path featureOutput = working.resolve("feature.zip");
+ assertFalse(Files.exists(output));
+ assertFalse(Files.exists(featureOutput));
+ ProcessResult result =
+ ToolHelper.forkR8(
+ working,
+ input.toString(),
+ "--lib",
+ library.toAbsolutePath().toString(),
+ "--feature",
+ inputFeature.toAbsolutePath().toString(),
+ featureOutput.toAbsolutePath().toString(),
+ "--no-tree-shaking");
+ assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+ assertTrue(Files.exists(output));
+ assertTrue(Files.exists(featureOutput));
+ }
+
+ @Test
+ public void featureOnlyOneArgument() throws Throwable {
+ Path working = temp.getRoot().toPath();
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar").toAbsolutePath();
+ Path inputFeature = Paths.get(EXAMPLES_BUILD_DIR, "arrayaccess.jar").toAbsolutePath();
+ Path library = ToolHelper.getDefaultAndroidJar();
+ Path output = working.resolve("classes.dex");
+ assertFalse(Files.exists(output));
+ ProcessResult result =
+ ToolHelper.forkR8(
+ working,
+ input.toString(),
+ "--lib",
+ library.toAbsolutePath().toString(),
+ "--no-tree-shaking",
+ "--feature",
+ inputFeature.toAbsolutePath().toString());
+ assertNotEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
+ assertTrue(result.stderr.contains("Missing parameter for"));
+ }
+
+ @Test
public void flagsFile() throws Throwable {
Path working = temp.getRoot().toPath();
Path library = ToolHelper.getDefaultAndroidJar();