Add a --startup-profile flag to the D8 CLI

Bug: b/245880650
Change-Id: I5ce88574ecd435b63e459be570252fe47d099da7
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index c85fe0d..0336de9 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.ParseFlagInfoImpl.flag1;
 
+import com.android.tools.r8.experimental.startup.StartupProfileProviderUtils;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -24,6 +25,8 @@
 
 public class D8CommandParser extends BaseCompilerCommandParser<D8Command, D8Command.Builder> {
 
+  static final String STARTUP_PROFILE_FLAG = "--startup-profile";
+
   private static final Set<String> OPTIONS_WITH_PARAMETER =
       ImmutableSet.of(
           "--output",
@@ -37,7 +40,8 @@
           "--main-dex-list-output",
           "--desugared-lib",
           "--desugared-lib-pg-conf-output",
-          THREAD_COUNT_FLAG);
+          THREAD_COUNT_FLAG,
+          STARTUP_PROFILE_FLAG);
 
   public static List<ParseFlagInfo> getFlags() {
     return ImmutableList.<ParseFlagInfo>builder()
@@ -79,6 +83,7 @@
         .add(ParseFlagInfoImpl.getThreadCount())
         .add(ParseFlagInfoImpl.getMapDiagnostics())
         .add(ParseFlagInfoImpl.getAndroidPlatformBuild())
+        .add(ParseFlagInfoImpl.getStartupProfile())
         .add(ParseFlagInfoImpl.getVersion("d8"))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -305,6 +310,10 @@
         builder.setDesugaredLibraryKeepRuleConsumer(consumer);
       } else if (arg.equals("--android-platform-build")) {
         builder.setAndroidPlatformBuild(true);
+      } 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, origin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
index af8d407..595a0b0 100644
--- a/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.BaseCompilerCommandParser.MAP_DIAGNOSTICS;
 import static com.android.tools.r8.BaseCompilerCommandParser.MIN_API_FLAG;
 import static com.android.tools.r8.BaseCompilerCommandParser.THREAD_COUNT_FLAG;
+import static com.android.tools.r8.D8CommandParser.STARTUP_PROFILE_FLAG;
 
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
@@ -163,6 +164,10 @@
         "is assumed to be the version specified by --min-api.");
   }
 
+  public static ParseFlagInfoImpl getStartupProfile() {
+    return flag1(STARTUP_PROFILE_FLAG, "<file>", "Startup profile <file> to use for dex layout.");
+  }
+
   public static ParseFlagInfoImpl flag0(String flag, String... help) {
     return flag(flag, Collections.emptyList(), Arrays.asList(help));
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
index 0eef997..a4b0f5e 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.experimental.startup.profile.StartupItem;
 import com.android.tools.r8.experimental.startup.profile.StartupProfile;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
-import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
-import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
-import com.android.tools.r8.profile.art.ArtProfileRulePredicate;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.profile.art.HumanReadableArtProfileParserBuilder;
 import com.android.tools.r8.startup.StartupProfileBuilder;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
@@ -22,33 +20,23 @@
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Path;
+import java.util.function.Consumer;
 
 public class StartupProfileProviderUtils {
 
   public static StartupProfileProvider createFromHumanReadableArtProfile(Path path) {
+    return createFromHumanReadableArtProfile(path, emptyConsumer());
+  }
+
+  public static StartupProfileProvider createFromHumanReadableArtProfile(
+      Path path, Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
     return new StartupProfileProvider() {
 
       @Override
       public void getStartupProfile(StartupProfileBuilder startupProfileBuilder) {
         try {
           startupProfileBuilder.addHumanReadableArtProfile(
-              new UTF8TextInputStream(path),
-              profileParserBuilder ->
-                  profileParserBuilder.setRulePredicate(
-                      new ArtProfileRulePredicate() {
-                        @Override
-                        public boolean testClassRule(
-                            ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
-                          return false;
-                        }
-
-                        @Override
-                        public boolean testMethodRule(
-                            MethodReference methodReference,
-                            ArtProfileMethodRuleInfo methodRuleInfo) {
-                          return methodRuleInfo.isStartup();
-                        }
-                      }));
+              new UTF8TextInputStream(path), parserBuilderConsumer);
         } catch (IOException e) {
           throw new UncheckedIOException(e);
         }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index e33cb93..a90db2a 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -21,9 +21,15 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.experimental.startup.profile.StartupItem;
+import com.android.tools.r8.experimental.startup.profile.StartupMethod;
+import com.android.tools.r8.experimental.startup.profile.StartupProfile;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.SyntheticToSyntheticContextGeneralization;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.StartupProfileProvider;
+import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
@@ -773,6 +779,60 @@
     assertTrue(parse("--android-platform-build").getAndroidPlatformBuild());
   }
 
+  @Test
+  public void startupProfileFlagAbsentTest() throws Exception {
+    assertTrue(parse().getStartupProfileProviders().isEmpty());
+  }
+
+  @Test
+  public void startupProfileFlagPresentTest() throws Exception {
+    // Create a simple profile.
+    Path profile = temp.newFile("profile.txt").toPath();
+    String profileRule = "Lfoo/bar/Baz;->qux()V";
+    FileUtils.writeTextFile(profile, profileRule);
+
+    // Pass the profile on the command line.
+    List<StartupProfileProvider> startupProfileProviders =
+        parse("--startup-profile", profile.toString()).getStartupProfileProviders();
+    assertEquals(1, startupProfileProviders.size());
+
+    // Construct the internal profile representation using the provider.
+    InternalOptions options = new InternalOptions();
+    MissingStartupProfileItemsDiagnostic.Builder missingStartupProfileItemsDiagnosticBuilder =
+        MissingStartupProfileItemsDiagnostic.Builder.nop();
+    StartupProfileProvider startupProfileProvider = startupProfileProviders.get(0);
+    SyntheticToSyntheticContextGeneralization syntheticToSyntheticContextGeneralization =
+        SyntheticToSyntheticContextGeneralization.createForD8();
+    StartupProfile.Builder startupProfileBuilder =
+        StartupProfile.builder(
+            options,
+            missingStartupProfileItemsDiagnosticBuilder,
+            startupProfileProvider,
+            syntheticToSyntheticContextGeneralization);
+    startupProfileProvider.getStartupProfile(startupProfileBuilder);
+
+    // Verify we found the same rule.
+    StartupProfile startupProfile = startupProfileBuilder.build();
+    Collection<StartupItem> startupItems = startupProfile.getStartupItems();
+    assertEquals(1, startupItems.size());
+    StartupItem startupItem = startupItems.iterator().next();
+    assertTrue(startupItem.isStartupMethod());
+    StartupMethod startupMethod = startupItem.asStartupMethod();
+    assertEquals(profileRule, startupMethod.getReference().toSmaliString());
+  }
+
+  @Test
+  public void startupProfileFlagMissingParameterTest() {
+    String expectedErrorContains = "Missing parameter for --startup-profile.";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains, handler -> parse(handler, "--startup-profile"));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
   @Override
   String[] requiredArgsForTest() {
     return new String[0];