Add a flag for using a custom legacy main dex list generator for android_binary.

RELNOTES: None
PiperOrigin-RevId: 295192505
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index c1919bd..fad73c7 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -1754,75 +1754,122 @@
       @Nullable Artifact mainDexProguardSpec,
       @Nullable Artifact proguardOutputMap)
       throws InterruptedException {
-    // Process the input jar through Proguard into an intermediate, streamlined jar.
-    Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar");
+    AndroidConfiguration config = AndroidCommon.getAndroidConfig(ruleContext);
     AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
-    SpawnAction.Builder streamlinedBuilder =
-        new SpawnAction.Builder()
-            .useDefaultShellEnvironment()
-            .addOutput(strippedJar)
-            .setExecutable(sdk.getProguard())
-            .setProgressMessage("Generating streamlined input jar for main dex classes list")
-            .setMnemonic("MainDexClassesIntermediate")
-            .addInput(jar)
-            .addInput(sdk.getShrinkedAndroidJar());
-    CustomCommandLine.Builder streamlinedCommandLine =
-        CustomCommandLine.builder()
-            .add("-forceprocessing")
-            .addExecPath("-injars", jar)
-            .addExecPath("-libraryjars", sdk.getShrinkedAndroidJar())
-            .addExecPath("-outjars", strippedJar)
-            .add("-dontwarn")
-            .add("-dontnote")
-            .add("-dontoptimize")
-            .add("-dontobfuscate");
-
-    List<Artifact> specs = new ArrayList<>();
-    specs.addAll(
-        ruleContext.getPrerequisiteArtifacts("main_dex_proguard_specs", Mode.TARGET).list());
-    if (specs.isEmpty()) {
-      specs.add(sdk.getMainDexClasses());
-    }
-    if (mainDexProguardSpec != null) {
-      specs.add(mainDexProguardSpec);
-    }
-
-    for (Artifact spec : specs) {
-      streamlinedBuilder.addInput(spec);
-      streamlinedCommandLine.addExecPath("-include", spec);
-    }
-
-    androidSemantics.addMainDexListActionArguments(
-        ruleContext, streamlinedBuilder, streamlinedCommandLine, proguardOutputMap);
-
-    streamlinedBuilder.addCommandLine(streamlinedCommandLine.build());
-    ruleContext.registerAction(streamlinedBuilder.build(ruleContext));
-
     // Create the main dex classes list.
     Artifact mainDexList = AndroidBinary.getDxArtifact(ruleContext, "main_dex_list.txt");
-    SpawnAction.Builder builder =
-        new SpawnAction.Builder()
-            .setMnemonic("MainDexClasses")
-            .setProgressMessage("Generating main dex classes list");
 
-    ruleContext.registerAction(
-        builder
-            .setExecutable(sdk.getMainDexListCreator())
-            .addOutput(mainDexList)
-            .addInput(strippedJar)
-            .addInput(jar)
-            .addCommandLine(
-                CustomCommandLine.builder()
-                    .addExecPath(mainDexList)
-                    .addExecPath(strippedJar)
-                    .addExecPath(jar)
-                    .addAll(
-                        ruleContext
-                            .getExpander()
-                            .withDataLocations()
-                            .tokenized("main_dex_list_opts"))
-                    .build())
-            .build(ruleContext));
+    List<Artifact> proguardSpecs = new ArrayList<>();
+    proguardSpecs.addAll(
+        ruleContext.getPrerequisiteArtifacts("main_dex_proguard_specs", Mode.TARGET).list());
+    if (proguardSpecs.isEmpty()) {
+      proguardSpecs.add(sdk.getMainDexClasses());
+    }
+    if (mainDexProguardSpec != null) {
+      proguardSpecs.add(mainDexProguardSpec);
+    }
+
+    // If --legacy_main_dex_list_generator is not set, use ProGuard and the main dext list creator
+    // specified by the android_sdk rule. If --legacy_main_dex_list_generator is provided, use that
+    // tool instead.
+    // TODO(b/147692286): Remove the old main-dex list generation that relied on ProGuard.
+    if (config.getLegacyMainDexListGenerator() == null) {
+      // Process the input jar through Proguard into an intermediate, streamlined jar.
+      Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar");
+      SpawnAction.Builder streamlinedBuilder =
+          new SpawnAction.Builder()
+              .useDefaultShellEnvironment()
+              .addOutput(strippedJar)
+              .setExecutable(sdk.getProguard())
+              .setProgressMessage("Generating streamlined input jar for main dex classes list")
+              .setMnemonic("MainDexClassesIntermediate")
+              .addInput(jar)
+              .addInput(sdk.getShrinkedAndroidJar());
+      CustomCommandLine.Builder streamlinedCommandLine =
+          CustomCommandLine.builder()
+              .add("-forceprocessing")
+              .addExecPath("-injars", jar)
+              .addExecPath("-libraryjars", sdk.getShrinkedAndroidJar())
+              .addExecPath("-outjars", strippedJar)
+              .add("-dontwarn")
+              .add("-dontnote")
+              .add("-dontoptimize")
+              .add("-dontobfuscate");
+
+      for (Artifact spec : proguardSpecs) {
+        streamlinedBuilder.addInput(spec);
+        streamlinedCommandLine.addExecPath("-include", spec);
+      }
+
+      androidSemantics.addMainDexListActionArguments(
+          ruleContext, streamlinedBuilder, streamlinedCommandLine, proguardOutputMap);
+
+      streamlinedBuilder.addCommandLine(streamlinedCommandLine.build());
+      ruleContext.registerAction(streamlinedBuilder.build(ruleContext));
+
+      SpawnAction.Builder builder =
+          new SpawnAction.Builder()
+              .setMnemonic("MainDexClasses")
+              .setProgressMessage("Generating main dex classes list");
+
+      ruleContext.registerAction(
+          builder
+              .setExecutable(sdk.getMainDexListCreator())
+              .addOutput(mainDexList)
+              .addInput(strippedJar)
+              .addInput(jar)
+              .addCommandLine(
+                  CustomCommandLine.builder()
+                      .addExecPath(mainDexList)
+                      .addExecPath(strippedJar)
+                      .addExecPath(jar)
+                      .addAll(
+                          ruleContext
+                              .getExpander()
+                              .withDataLocations()
+                              .tokenized("main_dex_list_opts"))
+                      .build())
+              .build(ruleContext));
+    } else {
+      FilesToRunProvider legacyMainDexListGenerator =
+          ruleContext.getExecutablePrerequisite(":legacy_main_dex_list_generator", Mode.HOST);
+      // Use the newer legacy multidex main-dex list generation.
+      SpawnAction.Builder actionBuilder =
+          new SpawnAction.Builder()
+              .setMnemonic("MainDexClasses")
+              .setProgressMessage("Generating main dex classes list");
+
+      CustomCommandLine.Builder commandLineBuilder =
+          CustomCommandLine.builder()
+              .addExecPath("--main-dex-list-output", mainDexList)
+              .addExecPath("--lib", sdk.getAndroidJar());
+      if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs()) {
+        NestedSet<Artifact> legacyApis =
+            ruleContext
+                .getPrerequisite("$desugared_java8_legacy_apis", Mode.TARGET)
+                .getProvider(FileProvider.class)
+                .getFilesToBuild();
+        for (Artifact lib : legacyApis.toList()) {
+          actionBuilder.addInput(lib);
+          commandLineBuilder.addExecPath("--lib", lib);
+        }
+      }
+      for (Artifact spec : proguardSpecs) {
+        actionBuilder.addInput(spec);
+        commandLineBuilder.addExecPath("--main-dex-rules", spec);
+      }
+
+      commandLineBuilder.addExecPath(jar);
+
+      ruleContext.registerAction(
+          actionBuilder
+              .setExecutable(legacyMainDexListGenerator)
+              .addOutput(mainDexList)
+              .addInput(jar)
+              .addInput(sdk.getAndroidJar())
+              .addCommandLine(commandLineBuilder.build())
+              .build(ruleContext));
+    }
     return mainDexList;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
index 8882332..8cc88a9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
@@ -40,6 +40,7 @@
 import com.google.devtools.common.options.OptionEffectTag;
 import com.google.devtools.common.options.OptionMetadataTag;
 import java.util.List;
+import javax.annotation.Nullable;
 
 /** Configuration fragment for Android rules. */
 @Immutable
@@ -883,6 +884,19 @@
         help = "Use R.txt from the merging action, instead of from the validation action.")
     public boolean useRTxtFromMergedResources;
 
+    @Option(
+        name = "legacy_main_dex_list_generator",
+        // TODO(b/147692286): Update this default value to R8's GenerateMainDexList binary after
+        // migrating usage.
+        defaultValue = "null",
+        converter = LabelConverter.class,
+        documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+        effectTags = {OptionEffectTag.UNKNOWN},
+        help =
+            "Specifies a binary to use to generate the list of classes that must be in the main"
+                + " dex when compiling legacy multidex.")
+    public Label legacyMainDexListGenerator;
+
     @Override
     public FragmentOptions getHost() {
       Options host = (Options) super.getHost();
@@ -979,6 +993,7 @@
   private final boolean alwaysFilterDuplicateClassesFromAndroidTest;
   private final boolean filterLibraryJarWithProgramJar;
   private final boolean useRTxtFromMergedResources;
+  private final Label legacyMainDexListGenerator;
 
   private AndroidConfiguration(Options options) throws InvalidConfigurationException {
     this.sdk = options.sdk;
@@ -1032,6 +1047,7 @@
         options.alwaysFilterDuplicateClassesFromAndroidTest;
     this.filterLibraryJarWithProgramJar = options.filterLibraryJarWithProgramJar;
     this.useRTxtFromMergedResources = options.useRTxtFromMergedResources;
+    this.legacyMainDexListGenerator = options.legacyMainDexListGenerator;
 
     if (options.androidAaptVersion != AndroidAaptVersion.AAPT2) {
       throw new InvalidConfigurationException(
@@ -1283,4 +1299,14 @@
   boolean useRTxtFromMergedResources() {
     return useRTxtFromMergedResources;
   }
+
+  /** Returns the label provided with --legacy_main_dex_list_generator, if any. */
+  // TODO(b/147692286): Move R8's main dex list tool into tool repository.
+  @SkylarkConfigurationField(
+      name = "legacy_main_dex_list_generator",
+      doc = "Returns the label provided with --legacy_main_dex_list_generator, if any.")
+  @Nullable
+  public Label getLegacyMainDexListGenerator() {
+    return legacyMainDexListGenerator;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index d022627..433c288 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -222,6 +222,13 @@
   public static final AndroidSplitTransition ANDROID_SPLIT_TRANSITION =
       new AndroidSplitTransition();
 
+  @AutoCodec
+  static final LabelLateBoundDefault<AndroidConfiguration> LEGACY_MAIN_DEX_LIST_GENERATOR =
+      LabelLateBoundDefault.fromTargetConfiguration(
+          AndroidConfiguration.class,
+          null,
+          (rule, attributes, androidConfig) -> androidConfig.getLegacyMainDexListGenerator());
+
   /** Android Split configuration transition for properly handling native dependencies */
   public static final class AndroidSplitTransition
       implements SplitTransition, AndroidSplitTransititionApi {
@@ -909,6 +916,12 @@
                   .undocumented(
                       "Do not use this attribute. It's for the migration of "
                           + "Android resource processing to Starlark only."))
+          // This comes from the --legacy_main_dex_list_generator flag.
+          .add(
+              attr(":legacy_main_dex_list_generator", LABEL)
+                  .cfg(HostTransition.createFactory())
+                  .value(LEGACY_MAIN_DEX_LIST_GENERATOR)
+                  .exec())
           .removeAttribute("data")
           .advertiseSkylarkProvider(SkylarkProviderIdentifier.forKey(ApkInfo.PROVIDER.getKey()))
           .advertiseSkylarkProvider(SkylarkProviderIdentifier.forKey(JavaInfo.PROVIDER.getKey()))
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
index d2bc7c5..f84b16f 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java
@@ -199,6 +199,42 @@
   }
 
   @Test
+  public void testLegacyMainDexListGenerator() throws Exception {
+    scratch.file(
+        "java/a/BUILD",
+        "android_binary(",
+        "    name = 'a',",
+        "    srcs = ['A.java'],",
+        "    manifest = 'AndroidManifest.xml',",
+        "    multidex = 'legacy')");
+    scratch.file(
+        "tools/fake/BUILD",
+        "cc_binary(",
+        "    name = 'generate_main_dex_list',",
+        "    srcs = ['main.cc'])");
+    useConfiguration("--legacy_main_dex_list_generator=//tools/fake:generate_main_dex_list");
+
+    ConfiguredTarget binary = getConfiguredTarget("//java/a:a");
+    Artifact mainDexList =
+        ActionsTestUtil.getFirstArtifactEndingWith(
+            actionsTestUtil().artifactClosureOf(getFilesToBuild(binary)), "main_dex_list.txt");
+    List<String> args = getGeneratingSpawnActionArgs(mainDexList);
+    NestedSet<Artifact> mainDexInputs = getGeneratingAction(mainDexList).getInputs();
+
+    MoreAsserts.assertContainsSublist(args, "--lib", getAndroidJarPath());
+    MoreAsserts.assertContainsSublist(args, "--main-dex-rules", getMainDexClassesPath());
+
+    assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("generate_main_dex_list");
+    assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains("a_deploy.jar");
+    assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs)).contains(getAndroidJarFilename());
+    assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs))
+        .contains(getMainDexClassesFilename());
+    assertThat(ActionsTestUtil.baseArtifactNames(mainDexInputs))
+        .contains("main_dex_a_proguard.cfg");
+    assertThat(getFirstArtifactEndingWith(mainDexInputs, "main_dex_list_creator")).isNull();
+  }
+
+  @Test
   public void testMainDexListObfuscation() throws Exception {
     useConfiguration("--noincremental_dexing");
     scratch.file("/java/a/list.txt");
diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java
index 392bb5f..ebcd8e2 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBuildViewTestCase.java
@@ -337,10 +337,22 @@
     return getAndroidSdk().getAndroidJar().getRootRelativePathString();
   }
 
+  protected String getAndroidJarFilename() throws Exception {
+    return getAndroidSdk().getAndroidJar().getFilename();
+  }
+
   protected Artifact getProguardBinary() throws Exception {
     return getAndroidSdk().getProguard().getExecutable();
   }
 
+  protected String getMainDexClassesPath() throws Exception {
+    return getAndroidSdk().getMainDexClasses().getRootRelativePathString();
+  }
+
+  protected String getMainDexClassesFilename() throws Exception {
+    return getAndroidSdk().getMainDexClasses().getFilename();
+  }
+
   private AndroidSdkProvider getAndroidSdk() throws Exception {
     Label sdk = targetConfig.getFragment(AndroidConfiguration.class).getSdk();
     return getConfiguredTarget(sdk, targetConfig).get(AndroidSdkProvider.PROVIDER);