Test to build Chrome with startup instrumentation and minimal startup
Change-Id: I19328bddaf33b59267abb041ce1197684d68f911
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 70f752e..21fccf4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -51,6 +51,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
+import com.android.tools.r8.horizontalclassmerging.policies.SameFilePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
import com.android.tools.r8.horizontalclassmerging.policies.SameMainDexGroup;
import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
@@ -273,6 +274,7 @@
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
builder.add(
+ new SameFilePolicy(appView),
new CheckAbstractClasses(appView),
new NoClassAnnotationCollisions(),
new SameFeatureSplit(appView),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFilePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFilePolicy.java
new file mode 100644
index 0000000..5643d8c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFilePolicy.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+
+public class SameFilePolicy extends MultiClassSameReferencePolicy<String> {
+
+ private final HorizontalClassMergerOptions options;
+
+ public SameFilePolicy(AppView<?> appView) {
+ this.options = appView.options().horizontalClassMergerOptions();
+ }
+
+ @Override
+ public String getMergeKey(DexProgramClass clazz) {
+ return clazz.getType().toDescriptorString().replaceAll("^([^$]+)\\$.*", "$1");
+ }
+
+ @Override
+ public String getName() {
+ return "SameFilePolicy";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return !options.isSameFilePolicyEnabled();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 04eb88c..50fb125 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1504,6 +1504,7 @@
private boolean enableInterfaceMerging =
System.getProperty("com.android.tools.r8.enableHorizontalInterfaceMerging") != null;
private boolean enableInterfaceMergingInInitial = false;
+ private boolean enableSameFilePolicy = false;
private boolean enableSyntheticMerging = true;
private boolean ignoreRuntimeTypeChecksForTesting = false;
private boolean restrictToSynthetics = false;
@@ -1559,6 +1560,10 @@
return ignoreRuntimeTypeChecksForTesting;
}
+ public boolean isSameFilePolicyEnabled() {
+ return enableSameFilePolicy;
+ }
+
public boolean isSyntheticMergingEnabled() {
return enableSyntheticMerging;
}
@@ -1594,6 +1599,10 @@
enableInterfaceMergingInInitial = true;
}
+ public void setEnableSameFilePolicy(boolean enableSameFilePolicy) {
+ this.enableSameFilePolicy = enableSameFilePolicy;
+ }
+
public void setIgnoreRuntimeTypeChecksForTesting() {
ignoreRuntimeTypeChecksForTesting = true;
}
diff --git a/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
new file mode 100644
index 0000000..390e702
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/startup/ChromeStartupTest.java
@@ -0,0 +1,266 @@
+// Copyright (c) 2022, 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.internal.startup;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.BeforeClass;
+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 ChromeStartupTest extends TestBase {
+
+ private static AndroidApiLevel apiLevel = AndroidApiLevel.N;
+
+ // Location of dump.zip and startup.txt.
+ private static Path chromeDirectory = Paths.get("build/chrome/startup");
+
+ // Location of test artifacts.
+ private static Path artifactDirectory = Paths.get("build/chrome/startup");
+
+ // Temporary directory where dump.zip is extracted into.
+ private static Path dumpDirectory;
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @BeforeClass
+ public static void setup() throws IOException {
+ assumeTrue(ToolHelper.isLocalDevelopment());
+ dumpDirectory = getStaticTemp().newFolder().toPath();
+ ZipUtils.unzip(chromeDirectory.resolve("dump.zip"), dumpDirectory);
+ }
+
+ // Outputs the instrumented dex in chrome/instrumented.
+ @Test
+ public void buildInstrumentedDex() throws Exception {
+ buildInstrumentedBase();
+ buildInstrumentedChromeSplit();
+ }
+
+ private void buildInstrumentedBase() throws Exception {
+ Files.createDirectories(artifactDirectory.resolve("instrumented/base/dex"));
+ testForD8()
+ .addProgramFiles(dumpDirectory.resolve("program.jar"))
+ .addClasspathFiles(dumpDirectory.resolve("classpath.jar"))
+ .addLibraryFiles(dumpDirectory.resolve("library.jar"))
+ .addOptionsModification(
+ options ->
+ options
+ .getStartupOptions()
+ .setEnableStartupInstrumentation()
+ .setStartupInstrumentationTag("r8"))
+ .setMinApi(apiLevel)
+ .release()
+ .compile()
+ .writeToDirectory(artifactDirectory.resolve("instrumented/base/dex"));
+ }
+
+ private void buildInstrumentedChromeSplit() throws Exception {
+ Files.createDirectories(artifactDirectory.resolve("instrumented/chrome/dex"));
+ testForD8()
+ // The Chrome split is the feature that contains ChromeApplicationImpl.
+ .addProgramFiles(getChromeSplit())
+ .addClasspathFiles(dumpDirectory.resolve("program.jar"))
+ .addClasspathFiles(dumpDirectory.resolve("classpath.jar"))
+ .addLibraryFiles(dumpDirectory.resolve("library.jar"))
+ .addOptionsModification(
+ options ->
+ options
+ .getStartupOptions()
+ .setEnableStartupInstrumentation()
+ .setStartupInstrumentationTag("r8"))
+ .setMinApi(apiLevel)
+ .release()
+ .compile()
+ .inspect(
+ inspector ->
+ assertThat(
+ inspector.clazz("org.chromium.chrome.browser.ChromeApplicationImpl"),
+ isPresent()))
+ .writeToDirectory(artifactDirectory.resolve("instrumented/chrome/dex"));
+ }
+
+ private Path getChromeSplit() {
+ return getFeatureSplit(9);
+ }
+
+ private Path getFeatureSplit(int index) {
+ return dumpDirectory.resolve("feature-" + index + ".jar");
+ }
+
+ // Outputs Chrome built using R8 in chrome/default.
+ @Test
+ public void buildR8Default() throws Exception {
+ buildR8(ThrowableConsumer.empty(), artifactDirectory.resolve("default"));
+ }
+
+ // Outputs Chrome built using R8 with limited class merging in chrome/default-with-patches.
+ @Test
+ public void buildR8DefaultWithPatches() throws Exception {
+ buildR8(
+ testBuilder ->
+ testBuilder.addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableSameFilePolicy(true)),
+ artifactDirectory.resolve("default-with-patches"));
+ }
+
+ // Outputs Chrome built using R8 with minimal startup dex and no boundary optimizations in
+ // chrome/optimized-minimal-nooptimize.
+ @Test
+ public void buildR8MinimalStartupDexWithoutBoundaryOptimizations() throws Exception {
+ boolean enableMinimalStartupDex = true;
+ boolean enableStartupBoundaryOptimizations = false;
+ buildR8Startup(
+ enableMinimalStartupDex,
+ enableStartupBoundaryOptimizations,
+ artifactDirectory.resolve("optimized-minimal-nooptimize"));
+ }
+
+ // Outputs Chrome built using R8 with minimal startup dex and boundary optimizations enabled in
+ // chrome/optimized-minimal-optimize.
+ @Test
+ public void buildR8MinimalStartupDexWithBoundaryOptimizations() throws Exception {
+ boolean enableMinimalStartupDex = true;
+ boolean enableStartupBoundaryOptimizations = true;
+ buildR8Startup(
+ enableMinimalStartupDex,
+ enableStartupBoundaryOptimizations,
+ artifactDirectory.resolve("optimized-minimal-optimize"));
+ }
+
+ // Outputs Chrome built using R8 with startup layout enabled and no boundary optimizations in
+ // chrome/optimized-nominimal-nooptimize.
+ @Test
+ public void buildR8StartupLayoutWithoutBoundaryOptimizations() throws Exception {
+ boolean enableMinimalStartupDex = false;
+ boolean enableStartupBoundaryOptimizations = false;
+ buildR8Startup(
+ enableMinimalStartupDex,
+ enableStartupBoundaryOptimizations,
+ artifactDirectory.resolve("optimized-nominimal-nooptimize"));
+ }
+
+ // Outputs Chrome built using R8 with startup layout enabled and no boundary optimizations in
+ // chrome/optimized-nominimal-optimize.
+ @Test
+ public void buildR8StartupLayoutWithBoundaryOptimizations() throws Exception {
+ boolean enableMinimalStartupDex = false;
+ boolean enableStartupBoundaryOptimizations = true;
+ buildR8Startup(
+ enableMinimalStartupDex,
+ enableStartupBoundaryOptimizations,
+ artifactDirectory.resolve("optimized-nominimal-optimize"));
+ }
+
+ private void buildR8(ThrowableConsumer<R8FullTestBuilder> configuration, Path outDirectory)
+ throws Exception {
+ Files.createDirectories(outDirectory.resolve("base/dex"));
+ Files.createDirectories(outDirectory.resolve("chrome/dex"));
+ testForR8(Backend.DEX)
+ .addProgramFiles(dumpDirectory.resolve("program.jar"))
+ .addClasspathFiles(dumpDirectory.resolve("classpath.jar"))
+ .addLibraryFiles(dumpDirectory.resolve("library.jar"))
+ .addKeepRuleFiles(dumpDirectory.resolve("proguard.config"))
+ .apply(
+ testBuilder -> {
+ int i = 1;
+ boolean seenChromeSplit = false;
+ for (; i <= 12; i++) {
+ Path feature = getFeatureSplit(i);
+ boolean isChromeSplit = feature.equals(getChromeSplit());
+ seenChromeSplit |= isChromeSplit;
+ assertTrue(feature.toFile().exists());
+ testBuilder.addFeatureSplit(
+ featureSplitBuilder ->
+ featureSplitBuilder
+ .addProgramResourceProvider(
+ ArchiveProgramResourceProvider.fromArchive(feature))
+ .setProgramConsumer(
+ isChromeSplit
+ ? new DexIndexedConsumer.DirectoryConsumer(
+ outDirectory.resolve("chrome/dex"))
+ : DexIndexedConsumer.emptyConsumer())
+ .build());
+ }
+ assertFalse(dumpDirectory.resolve("feature-" + i + ".jar").toFile().exists());
+ assertTrue(seenChromeSplit);
+ assertThat(
+ new CodeInspector(getChromeSplit())
+ .clazz("org.chromium.chrome.browser.ChromeApplicationImpl"),
+ isPresent());
+ })
+ .apply(configuration)
+ .apply(this::disableR8StrictMode)
+ .apply(this::disableR8TestingDefaults)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToDirectory(outDirectory.resolve("base/dex"));
+ }
+
+ private void buildR8Startup(
+ boolean enableMinimalStartupDex,
+ boolean enableStartupBoundaryOptimizations,
+ Path outDirectory)
+ throws Exception {
+ Path startupList = chromeDirectory.resolve("startup.txt");
+ buildR8(
+ testBuilder ->
+ testBuilder.addOptionsModification(
+ options ->
+ options
+ .getStartupOptions()
+ .setEnableMinimalStartupDex(enableMinimalStartupDex)
+ .setEnableStartupBoundaryOptimizations(enableStartupBoundaryOptimizations)
+ .setStartupConfiguration(
+ StartupConfiguration.createStartupConfigurationFromFile(
+ options.dexItemFactory(), options.reporter, startupList))),
+ outDirectory);
+ }
+
+ private void disableR8StrictMode(R8FullTestBuilder testBuilder) {
+ testBuilder
+ .allowDiagnosticMessages()
+ .allowUnnecessaryDontWarnWildcards()
+ .allowUnusedDontWarnPatterns()
+ .allowUnusedProguardConfigurationRules()
+ .addOptionsModification(
+ options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces());
+ }
+
+ private void disableR8TestingDefaults(R8FullTestBuilder testBuilder) {
+ testBuilder.addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMerging(false));
+ }
+}