Add an experimental tool with CLI to extract embedded R8 rules
This can be tested in the R8 repo with something like this:
```
java -cp build/libs/r8.jar com.android.tools.r8.ExtractR8Rules \
third_party/kotlin/kotlin-compiler-2.1.0-Beta1/kotlinc/lib/kotlin-reflect.jar \
third_party/kotlin/kotlin-compiler-2.1.0-Beta1/kotlinc/lib/kotlinx-coroutines-core-jvm.jar \
--include-origin-comments \
--rules-output extracted.rules
```
Then `extracted.rules` will contain the R8 rules for the version of the
compiler in `r8.jar`.
Bug: b/377144587
Change-Id: I8c2b5246d8a69bc27d0aa16610c124ef1054f6d7
diff --git a/src/main/java/com/android/tools/r8/ExtractR8Rules.java b/src/main/java/com/android/tools/r8/ExtractR8Rules.java
new file mode 100644
index 0000000..6d97afb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ExtractR8Rules.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2024, 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.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.EmbeddedRulesExtractor;
+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.SemanticVersion;
+import com.android.tools.r8.utils.SemanticVersionUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.function.Supplier;
+
+@KeepForApi
+public class ExtractR8Rules {
+
+ private static void run(
+ AndroidApp app,
+ StringConsumer consumer,
+ boolean includeOriginComments,
+ SemanticVersion fakeCompilerVersion,
+ Reporter reporter) {
+ Supplier<SemanticVersion> semanticVersionSupplier =
+ SemanticVersionUtils.compilerVersionSemanticVersionSupplier(
+ fakeCompilerVersion,
+ "Using an artificial version newer than any known version for selecting"
+ + " Proguard configurations embedded under META-INF/. This means that"
+ + " all rules with a '-upto-' qualifier will be excluded and all"
+ + " rules with a -from- qualifier will be included.",
+ reporter);
+ for (ProgramResourceProvider provider : app.getProgramResourceProviders()) {
+ DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
+ if (dataResourceProvider == null) {
+ return;
+ }
+ try {
+ EmbeddedRulesExtractor embeddedProguardConfigurationVisitor =
+ new EmbeddedRulesExtractor(reporter, semanticVersionSupplier);
+ dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+ embeddedProguardConfigurationVisitor.visitRelevantRules(
+ rules -> {
+ try {
+ if (includeOriginComments) {
+ consumer.accept("# Rules extracted from:", reporter);
+ consumer.accept(StringUtils.LINE_SEPARATOR, reporter);
+ consumer.accept("# ", reporter);
+ consumer.accept(rules.getOrigin().toString(), reporter);
+ consumer.accept(StringUtils.LINE_SEPARATOR, reporter);
+ }
+ consumer.accept(rules.get(), reporter);
+ consumer.accept(StringUtils.LINE_SEPARATOR, reporter);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ } catch (ResourceException e) {
+ reporter.error(new ExceptionDiagnostic(e));
+ }
+ }
+ consumer.finished(reporter);
+ }
+
+ /** Experimental API to extract embedded rules from libraries. */
+ public static void run(ExtractR8RulesCommand command) throws CompilationFailedException {
+ AndroidApp app = command.getInputApp();
+ StringConsumer rulesConsumer = command.getRulesConsumer();
+ boolean includeOriginComments = command.getIncludeOriginComments();
+ SemanticVersion fakeCompilerVersion = command.getFakeCompilerVersion();
+ InternalOptions options = command.getInternalOptions();
+ ExceptionUtils.withCompilationHandler(
+ options.reporter,
+ () -> {
+ run(app, rulesConsumer, includeOriginComments, fakeCompilerVersion, options.reporter);
+ });
+ }
+
+ public static void main(String[] args) throws CompilationFailedException {
+ ExtractR8RulesCommand.Builder builder = ExtractR8RulesCommand.parse(args);
+ ExtractR8RulesCommand command = builder.build();
+ if (command.isPrintHelp()) {
+ System.out.println(ExtractR8RulesCommand.USAGE_MESSAGE);
+ return;
+ }
+ if (command.isPrintVersion()) {
+ System.out.println("ExtractR8Rules " + Version.LABEL);
+ return;
+ }
+ run(command);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ExtractR8RulesCommand.java b/src/main/java/com/android/tools/r8/ExtractR8RulesCommand.java
new file mode 100644
index 0000000..3a55917
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ExtractR8RulesCommand.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2024, 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.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.CommandLineOrigin;
+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.SemanticVersion;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@KeepForApi
+/** Experimental API to extract embedded rules from libraries. */
+public class ExtractR8RulesCommand extends BaseCommand {
+
+ private final StringConsumer rulesConsumer;
+ private final boolean includeOriginComments;
+ private final SemanticVersion fakeCompilerVersion;
+ private final DexItemFactory factory;
+ private final Reporter reporter;
+
+ @KeepForApi
+ public static class Builder extends BaseCommand.Builder<ExtractR8RulesCommand, Builder> {
+
+ private final DexItemFactory factory = new DexItemFactory();
+ private StringConsumer rulesConsumer = null;
+ private boolean includeOriginComments = false;
+ private SemanticVersion fakeCompilerVersion = null;
+
+ private Builder() {}
+
+ private Builder(DiagnosticsHandler diagnosticsHandler) {
+ super(diagnosticsHandler);
+ }
+
+ @Override
+ ExtractR8RulesCommand.Builder self() {
+ return this;
+ }
+
+ /** TBD */
+ public ExtractR8RulesCommand.Builder setRulesOutputPath(Path rulesOutputPath) {
+ rulesConsumer = new StringConsumer.FileConsumer(rulesOutputPath);
+ return self();
+ }
+
+ /** TBD */
+ public ExtractR8RulesCommand.Builder setRulesConsumer(StringConsumer rulesConsumer) {
+ this.rulesConsumer = rulesConsumer;
+ return self();
+ }
+
+ /** TBD */
+ public ExtractR8RulesCommand.Builder setIncludeOriginComments(boolean include) {
+ this.includeOriginComments = include;
+ return self();
+ }
+
+ /** TBD */
+ public Builder setFakeCompilerVersion(SemanticVersion version) {
+ fakeCompilerVersion = version;
+ return self();
+ }
+
+ @Override
+ protected ExtractR8RulesCommand makeCommand() {
+ // If printing versions ignore everything else.
+ if (isPrintHelp() || isPrintVersion()) {
+ return new ExtractR8RulesCommand(isPrintHelp(), isPrintVersion());
+ }
+
+ return new ExtractR8RulesCommand(
+ factory,
+ getAppBuilder().build(),
+ rulesConsumer,
+ includeOriginComments,
+ fakeCompilerVersion,
+ getReporter());
+ }
+ }
+
+ static final String USAGE_MESSAGE =
+ StringUtils.lines(
+ "Usage: TBD",
+ " --rules-output <file> # Output the extracted keep rules.",
+ " --include-origin-comments # Include comments with origin for extracted rules.",
+ " --version # Print the version.",
+ " --help # Print this message.");
+
+ public static ExtractR8RulesCommand.Builder builder() {
+ return new ExtractR8RulesCommand.Builder();
+ }
+
+ public static ExtractR8RulesCommand.Builder builder(DiagnosticsHandler diagnosticsHandler) {
+ return new ExtractR8RulesCommand.Builder(diagnosticsHandler);
+ }
+
+ public static ExtractR8RulesCommand.Builder parse(String[] args) {
+ ExtractR8RulesCommand.Builder builder = builder();
+ parse(args, builder);
+ return builder;
+ }
+
+ public StringConsumer getRulesConsumer() {
+ return rulesConsumer;
+ }
+
+ public boolean getIncludeOriginComments() {
+ return includeOriginComments;
+ }
+
+ public SemanticVersion getFakeCompilerVersion() {
+ return fakeCompilerVersion;
+ }
+
+ Reporter getReporter() {
+ return reporter;
+ }
+
+ private static void parse(String[] args, ExtractR8RulesCommand.Builder builder) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i].trim();
+ 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("--rules-output")) {
+ builder.setRulesOutputPath(Paths.get(args[++i]));
+ } else if (arg.equals("--include-origin-comments")) {
+ builder.setIncludeOriginComments(true);
+ } else {
+ if (arg.startsWith("--")) {
+ builder
+ .getReporter()
+ .fatalError(
+ new StringDiagnostic("Unknown option: " + arg, CommandLineOrigin.INSTANCE));
+ }
+ builder.addProgramFiles(Paths.get(arg));
+ }
+ }
+ }
+
+ private ExtractR8RulesCommand(
+ DexItemFactory factory,
+ AndroidApp inputApp,
+ StringConsumer rulesConsumer,
+ boolean includeOriginComments,
+ SemanticVersion fakeCompilerVersion,
+ Reporter reporter) {
+ super(inputApp);
+ this.factory = factory;
+ this.rulesConsumer = rulesConsumer;
+ this.includeOriginComments = includeOriginComments;
+ this.fakeCompilerVersion = fakeCompilerVersion;
+ this.reporter = reporter;
+ }
+
+ private ExtractR8RulesCommand(boolean printHelp, boolean printVersion) {
+ super(printHelp, printVersion);
+ this.factory = new DexItemFactory();
+ this.rulesConsumer = null;
+ this.includeOriginComments = false;
+ this.fakeCompilerVersion = null;
+ this.reporter = new Reporter();
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ InternalOptions internal = new InternalOptions(factory, reporter);
+ internal.programConsumer = DexIndexedConsumer.emptyConsumer();
+ assert internal.retainCompileTimeAnnotations;
+ internal.retainCompileTimeAnnotations = false;
+ return internal;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index b9dd50f..c5608b7 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -74,6 +74,9 @@
case "verify":
CfVerifierTool.main(shift(args));
break;
+ case "extractr8rules":
+ ExtractR8Rules.main(shift(args));
+ break;
default:
runDefault(args);
break;
diff --git a/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java b/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java
index 7f68aa3..d9b972a 100644
--- a/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java
+++ b/src/main/java/com/android/tools/r8/utils/EmbeddedRulesExtractor.java
@@ -134,7 +134,15 @@
}
}
+ private List<ProguardConfigurationSource> getRelevantRules() {
+ return !r8Sources.isEmpty() ? r8Sources : proguardSources;
+ }
+
public void parseRelevantRules(ProguardConfigurationParser parser) {
- parse(!r8Sources.isEmpty() ? r8Sources : proguardSources, parser);
+ parse(getRelevantRules(), parser);
+ }
+
+ public void visitRelevantRules(Consumer<ProguardConfigurationSource> consumer) {
+ getRelevantRules().forEach(consumer);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesExtractR8RulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesExtractR8RulesTest.java
new file mode 100644
index 0000000..36e68ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesExtractR8RulesTest.java
@@ -0,0 +1,230 @@
+// Copyright (c) 2024, 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.shaking;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ExtractR8Rules;
+import com.android.tools.r8.ExtractR8RulesCommand;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryProvidedProguardRulesExtractR8RulesTest
+ extends LibraryProvidedProguardRulesTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean includeOriginComments;
+
+ @Parameters(name = "{0}, includeOriginComments: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+ }
+
+ private static final String EXPECTED_A = StringUtils.lines("-keep class A1", "-keep class A2");
+
+ private static final String EXPECTED_B = StringUtils.lines("-keep class B1", "-keep class B2");
+
+ private static final String EXPECTED_C = StringUtils.lines("-keep class C1", "-keep class C2");
+
+ private static final String EXPECTED_D = StringUtils.lines("-keep class D1", "-keep class D2");
+
+ private static final String EXPECTED_E = StringUtils.lines("-keep class E1", "-keep class E2");
+
+ private static final String EXPECTED_X = StringUtils.lines("-keep class X1", "-keep class X2");
+
+ private List<Path> buildLibraries() throws Exception {
+ List<Path> result = new ArrayList<>();
+ List<Pair<String, String>> rules = new ArrayList<>();
+ rules.add(new Pair<>("META-INF/com.android.tools/r8/test1.pro", "-keep class A1"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8/test2.pro", "-keep class A2"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-from-4.0.0/test1.pro", "-keep class B1"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-from-4.0.0/test2.pro", "-keep class B2"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-upto-8.1.0/test1.pro", "-keep class C1"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-upto-8.1.0/test2.pro", "-keep class C2"));
+ rules.add(
+ new Pair<>(
+ "META-INF/com.android.tools/r8-from-5.0.0-upto-8.0.0/test1.pro", "-keep class D1"));
+ rules.add(
+ new Pair<>(
+ "META-INF/com.android.tools/r8-from-5.0.0-upto-8.0.0/test2.pro", "-keep class D2"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-from-10.5.0/test1.pro", "-keep class E1"));
+ rules.add(new Pair<>("META-INF/com.android.tools/r8-from-10.5.0/test2.pro", "-keep class E2"));
+ rules.add(new Pair<>("META-INF/proguard/test1.pro", "-keep class X1"));
+ rules.add(new Pair<>("META-INF/proguard/test2.pro", "-keep class X2"));
+ Path folder = temp.newFolder().toPath();
+ for (int i = 0; i < rules.size(); i++) {
+ Path zipFile = folder.resolve("test" + i + ".jar");
+ ZipBuilder jarBuilder = ZipBuilder.builder(zipFile);
+ jarBuilder.addText(rules.get(i).getFirst(), rules.get(i).getSecond());
+ result.add(jarBuilder.build());
+ }
+ return result;
+ }
+
+ private String filterOriginCommentsFromExtractedRules(String extractedRules) {
+ List<String> newResult = new ArrayList<>();
+ List<String> lines = StringUtils.splitLines(extractedRules);
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ if (line.equals("# Rules extracted from:")) {
+ i++;
+ assertThat(lines.get(i), containsString("test"));
+ assertThat(lines.get(i), containsString(".jar"));
+ } else {
+ newResult.add(line);
+ }
+ }
+ return StringUtils.lines(newResult);
+ }
+
+ private void runTestRulesConsumer(SemanticVersion compilerVersion, String expected)
+ throws Exception {
+ List<Path> libraries = buildLibraries();
+ ExtractR8RulesCommand.Builder builder = ExtractR8RulesCommand.builder();
+ libraries.forEach(builder::addProgramFiles);
+ StringBuilder resultBuilder = new StringBuilder();
+ ExtractR8RulesCommand command =
+ builder
+ .setRulesConsumer((s, h) -> resultBuilder.append(s))
+ .setIncludeOriginComments(includeOriginComments)
+ .setFakeCompilerVersion(compilerVersion)
+ .build();
+ ExtractR8Rules.run(command);
+ String extractedRules = resultBuilder.toString();
+ if (includeOriginComments) {
+ extractedRules = filterOriginCommentsFromExtractedRules(extractedRules);
+ }
+ assertEquals(expected, extractedRules);
+ }
+
+ private void runTestRulesOutputPath(SemanticVersion compilerVersion, String expected)
+ throws Exception {
+ List<Path> libraries = buildLibraries();
+ ExtractR8RulesCommand.Builder builder = ExtractR8RulesCommand.builder();
+ libraries.forEach(builder::addProgramFiles);
+ Path rulesOutput = temp.newFile().toPath();
+ ExtractR8RulesCommand command =
+ builder
+ .setRulesOutputPath(rulesOutput)
+ .setIncludeOriginComments(includeOriginComments)
+ .setFakeCompilerVersion(compilerVersion)
+ .build();
+ ExtractR8Rules.run(command);
+ String extractedRules = FileUtils.readTextFile(rulesOutput, StandardCharsets.UTF_8);
+ if (includeOriginComments) {
+ extractedRules = filterOriginCommentsFromExtractedRules(extractedRules);
+ }
+ assertEquals(expected, extractedRules);
+ }
+
+ private void runTest(SemanticVersion compilerVersion, String expected) throws Exception {
+ runTestRulesConsumer(compilerVersion, expected);
+ runTestRulesOutputPath(compilerVersion, expected);
+ }
+
+ @Test
+ public void runTestVersion3() throws Exception {
+ runTest(
+ SemanticVersion.create(3, 0, 0),
+ StringUtils.lines(EXPECTED_A.trim(), EXPECTED_C.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion4() throws Exception {
+ runTest(
+ SemanticVersion.create(4, 0, 0),
+ StringUtils.lines(
+ EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion5() throws Exception {
+ runTest(
+ SemanticVersion.create(5, 0, 0),
+ StringUtils.lines(
+ EXPECTED_A.trim(),
+ EXPECTED_B.trim(),
+ EXPECTED_C.trim(),
+ EXPECTED_D.trim(),
+ EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion7_99_99() throws Exception {
+ runTest(
+ SemanticVersion.create(7, 99, 99),
+ StringUtils.lines(
+ EXPECTED_A.trim(),
+ EXPECTED_B.trim(),
+ EXPECTED_C.trim(),
+ EXPECTED_D.trim(),
+ EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion8() throws Exception {
+ runTest(
+ SemanticVersion.create(8, 0, 0),
+ StringUtils.lines(
+ EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion8_0_99() throws Exception {
+ runTest(
+ SemanticVersion.create(8, 0, 99),
+ StringUtils.lines(
+ EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion8_1() throws Exception {
+ runTest(
+ SemanticVersion.create(8, 1, 0),
+ StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion8_2() throws Exception {
+ runTest(
+ SemanticVersion.create(8, 2, 0),
+ StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion10() throws Exception {
+ runTest(
+ SemanticVersion.create(10, 0, 0),
+ StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_X.trim()));
+ }
+
+ @Test
+ public void runTestVersion10_5() throws Exception {
+ runTest(
+ SemanticVersion.create(10, 5, 0),
+ StringUtils.lines(
+ EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_E.trim(), EXPECTED_X.trim()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
index ecc00c4..6ff21ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
@@ -7,6 +7,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import com.android.tools.r8.ExtractR8Rules;
+import com.android.tools.r8.ExtractR8RulesCommand;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -137,6 +139,20 @@
assertEquals(
expected,
stripCommentsAndInJars(configuration, providerType == ProviderType.INJARS)));
+
+ // Test the standalone rules extractor (test independent of providerType).
+ if (providerType == ProviderType.API) {
+ StringBuilder resultBuilder = new StringBuilder();
+ ExtractR8RulesCommand.Builder builder = ExtractR8RulesCommand.builder();
+ ExtractR8RulesCommand command =
+ builder
+ .addProgramFiles(library)
+ .setRulesConsumer((s, h) -> resultBuilder.append(s))
+ .setFakeCompilerVersion(compilerVersion)
+ .build();
+ ExtractR8Rules.run(command);
+ assertEquals(expected, resultBuilder.toString());
+ }
}
private static String stripCommentsAndInJars(String configuration, boolean expectOneInJar) {