Pass resource provider/consumer/config to R8 partial configuration
Add test that this works in the simple case where the R class is in R8 compiled code.
In this simple case, the R class field reachable from D8 comiled code
is explicitly kept by the trace reference rules, and hence we keep the
resource.
Add test where this does not work for cases where the R class resides in the D8 part.
Bug: b/388746233
Change-Id: I518d2cffae2cf0a3ed887e1b99b0c8a0d5120cef
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index 985b03a..ded44df 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -230,6 +230,11 @@
options.partialCompilationConfiguration.r8OptionsConsumer.accept(r8Options);
r8Options.mapConsumer = options.mapConsumer;
r8Options.quiet = true; // Don't write the R8 version.
+ if (options.androidResourceProvider != null) {
+ r8Options.androidResourceProvider = options.androidResourceProvider;
+ r8Options.androidResourceConsumer = options.androidResourceConsumer;
+ r8Options.resourceShrinkerConfiguration = options.resourceShrinkerConfiguration;
+ }
R8.runInternal(r8App, r8Options, executor);
if (r8OutputAppConsumer != null) {
r8OutputAppConsumer.accept(r8OutputAppSink.build());
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java
new file mode 100644
index 0000000..9784553
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkingInPartialR8Test.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2025, 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.androidresources;
+
+import com.android.tools.r8.R8PartialTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 ResourceShrinkingInPartialR8Test extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ // TODO(b/388763735): Don't pin to version 7
+ return getTestParameters()
+ .withDexRuntime(Version.V7_0_0)
+ .withApiLevel(AndroidApiLevel.N)
+ .build();
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testPartialWithRClassInR8() throws Exception {
+ getR8PartialTestBuilder(false)
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName(
+ "string", "referencedFromD8Code");
+ resourceTableInspector.assertContainsResourceWithName(
+ "string", "referencedFromR8Code");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ })
+ .run(parameters.getRuntime(), InR8.class)
+ .assertSuccess();
+ }
+
+ @Test
+ public void testPartialWithRClassInD8() throws Exception {
+ getR8PartialTestBuilder(true)
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ // TODO(b/388746233): We should still trace the resources when we have the R class
+ // in D8 code.
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "referencedFromD8Code");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "referencedFromR8Code");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ })
+ .run(parameters.getRuntime(), InR8.class)
+ .assertSuccess();
+ }
+
+ private R8PartialTestBuilder getR8PartialTestBuilder(boolean rClassInD8) throws Exception {
+ return testForR8Partial(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(InR8.class, InD8.class)
+ .addAndroidResources(getTestResources(temp))
+ .setDefaultIncludeAll()
+ .addR8ExcludedClasses(InD8.class)
+ .applyIf(rClassInD8, b -> b.addR8ExcludedClasses(R.string.class))
+ .addKeepMainRule(InR8.class);
+ }
+
+ public static class InR8 {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.string.referencedFromR8Code);
+ InD8.callMe();
+ }
+ }
+ }
+
+ public static class InD8 {
+ public static void callMe() {
+ System.out.println(R.string.referencedFromD8Code);
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+ public static int referencedFromD8Code;
+ public static int referencedFromR8Code;
+ public static int unused_string;
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
index d3320d3..a6bc7fa 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.R8PartialCompilationConfiguration;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -20,6 +22,9 @@
public class R8PartialTestBuilder
extends R8TestBuilder<R8PartialTestCompileResult, R8TestRunResult, R8PartialTestBuilder> {
+ private final ArrayList<Class<?>> includedClasses = new ArrayList<>();
+ private final ArrayList<Class<?>> excludedClasses = new ArrayList<>();
+ private boolean defaultIncludeAll = false;
private R8PartialCompilationConfiguration r8PartialConfiguration =
R8PartialCompilationConfiguration.disabledConfiguration();
@@ -72,14 +77,6 @@
}
public R8PartialTestBuilder setR8PartialConfiguration(
- R8PartialCompilationConfiguration configuration) {
- assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
- : "Overwriting configuration...?";
- r8PartialConfiguration = configuration;
- return self();
- }
-
- public R8PartialTestBuilder setR8PartialConfiguration(
Consumer<R8PartialCompilationConfiguration.Builder> consumer) {
assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
: "Overwriting configuration...?";
@@ -89,6 +86,42 @@
return self();
}
+ public R8PartialTestBuilder setDefaultIncludeAll() {
+ this.defaultIncludeAll = true;
+ return self();
+ }
+
+ public R8PartialTestBuilder addR8IncludedClasses(Class<?>... classes) {
+ assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
+ : "Overwriting configuration...?";
+ Collections.addAll(includedClasses, classes);
+ return self();
+ }
+
+ public R8PartialTestBuilder addR8ExcludedClasses(Class<?>... classes) {
+ assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
+ : "Overwriting configuration...?";
+ Collections.addAll(excludedClasses, classes);
+ return self();
+ }
+
+ private R8PartialCompilationConfiguration getPartialConfiguration() {
+ if (r8PartialConfiguration != R8PartialCompilationConfiguration.disabledConfiguration()) {
+ assert excludedClasses.isEmpty() && includedClasses.isEmpty();
+ return r8PartialConfiguration;
+ }
+ R8PartialCompilationConfiguration.Builder partialBuilder =
+ R8PartialCompilationConfiguration.builder();
+ if (defaultIncludeAll) {
+ assert includedClasses.isEmpty();
+ partialBuilder.includeAll();
+ } else {
+ partialBuilder.includeClasses(includedClasses);
+ }
+ partialBuilder.excludeClasses(excludedClasses);
+ return partialBuilder.build();
+ }
+
@Override
R8PartialTestCompileResult internalCompileR8(
Builder builder,
@@ -105,7 +138,7 @@
Box<AndroidApp> d8OutputAppBox = new Box<>();
Consumer<InternalOptions> configureR8PartialCompilation =
options -> {
- options.partialCompilationConfiguration = r8PartialConfiguration;
+ options.partialCompilationConfiguration = getPartialConfiguration();
options.partialCompilationConfiguration.r8InputAppConsumer = r8InputAppBox::set;
options.partialCompilationConfiguration.d8InputAppConsumer = d8InputAppBox::set;
options.partialCompilationConfiguration.r8OutputAppConsumer = r8OutputAppBox::set;