Warn when startup classes do not fit in classes.dex

Bug: b/294189439
Change-Id: I279b09ec92dbc5e5d4718c268e92b8fab1b3dfd7
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7e8904d..73b009b 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.errors.StartupClassesOverflowDiagnostic.Factory.createStartupClassesOverflowDiagnostic;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.FeatureSplit;
@@ -1444,6 +1445,9 @@
             virtualFile.commitTransaction();
           }
         }
+
+        options.reporter.warning(
+            createStartupClassesOverflowDiagnostic(cycler.filesForDistribution.size()));
       }
 
       if (options.getStartupOptions().isMinimalStartupDexEnabled()) {
diff --git a/src/main/java/com/android/tools/r8/errors/StartupClassesOverflowDiagnostic.java b/src/main/java/com/android/tools/r8/errors/StartupClassesOverflowDiagnostic.java
new file mode 100644
index 0000000..00290a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/StartupClassesOverflowDiagnostic.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2023, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@Keep
+public class StartupClassesOverflowDiagnostic implements Diagnostic {
+
+  private final int numberOfStartupDexFiles;
+
+  StartupClassesOverflowDiagnostic(int numberOfStartupDexFiles) {
+    this.numberOfStartupDexFiles = numberOfStartupDexFiles;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Unable to include all startup classes in classes.dex. "
+        + "Startup classes were distributed in: classes.dex, "
+        + IntStream.range(2, numberOfStartupDexFiles + 1)
+            .mapToObj(i -> "classes" + i + ".dex")
+            .collect(Collectors.joining(", "))
+        + ".";
+  }
+
+  // Public factory to keep the constructor of the diagnostic out of the public API.
+  public static class Factory {
+
+    public static StartupClassesOverflowDiagnostic createStartupClassesOverflowDiagnostic(
+        int numberOfStartupDexFiles) {
+      return new StartupClassesOverflowDiagnostic(numberOfStartupDexFiles);
+    }
+  }
+}