Add Android platform build API.

Bug: b/232073181
Change-Id: Ie65bf484b8e2934e90d0b3ca9b9f4427d4bc0193
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b2e0f6d..471988a 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -54,6 +54,7 @@
   private final DumpInputFlags dumpInputFlags;
   private final MapIdProvider mapIdProvider;
   private final SourceFileProvider sourceFileProvider;
+  private final boolean isAndroidPlatformBuild;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -72,6 +73,7 @@
     dumpInputFlags = DumpInputFlags.noDump();
     mapIdProvider = null;
     sourceFileProvider = null;
+    isAndroidPlatformBuild = false;
   }
 
   BaseCompilerCommand(
@@ -90,7 +92,8 @@
       int threadCount,
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
-      SourceFileProvider sourceFileProvider) {
+      SourceFileProvider sourceFileProvider,
+      boolean isAndroidPlatformBuild) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -109,6 +112,7 @@
     this.dumpInputFlags = dumpInputFlags;
     this.mapIdProvider = mapIdProvider;
     this.sourceFileProvider = sourceFileProvider;
+    this.isAndroidPlatformBuild = isAndroidPlatformBuild;
   }
 
   /**
@@ -197,6 +201,10 @@
     return threadCount;
   }
 
+  public boolean getAndroidPlatformBuild() {
+    return isAndroidPlatformBuild;
+  }
+
   DumpInputFlags getDumpInputFlags() {
     return dumpInputFlags;
   }
@@ -237,6 +245,7 @@
     private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
     private MapIdProvider mapIdProvider = null;
     private SourceFileProvider sourceFileProvider = null;
+    private boolean isAndroidPlatformBuild = false;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -635,6 +644,23 @@
       return self();
     }
 
+    /**
+     * Configure the present build as a "Android platform build".
+     *
+     * <p>A platform build, is a build where the runtime "bootclasspath" is known at compile time.
+     * In other words, the specified <i>min-api</i> is also known to be the <i>max-api</i>. In this
+     * mode the compiler will disable various features that provide support for newer runtimes as
+     * well as disable workarounds for older runtimes.
+     */
+    public B setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+      this.isAndroidPlatformBuild = isAndroidPlatformBuild;
+      return self();
+    }
+
+    public boolean getAndroidPlatformBuild() {
+      return isAndroidPlatformBuild;
+    }
+
     B dumpInputToFile(Path file) {
       dumpInputFlags = DumpInputFlags.dumpToFile(file);
       return self();
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 1495071..d6dec04 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -425,6 +425,7 @@
           getMapIdProvider(),
           proguardMapConsumer,
           enableMissingLibraryApiModeling,
+          getAndroidPlatformBuild(),
           factory);
     }
   }
@@ -516,6 +517,7 @@
       MapIdProvider mapIdProvider,
       StringConsumer proguardMapConsumer,
       boolean enableMissingLibraryApiModeling,
+      boolean isAndroidPlatformBuild,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -533,7 +535,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        null);
+        null,
+        isAndroidPlatformBuild);
     this.intermediate = intermediate;
     this.globalSyntheticsConsumer = globalSyntheticsConsumer;
     this.desugarGraphConsumer = desugarGraphConsumer;
@@ -654,6 +657,8 @@
       horizontalClassMergerOptions.disable();
     }
 
+    internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
     internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
 
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 2b50253..dce9604 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -116,7 +116,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        null);
+        null,
+        false);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -302,6 +303,11 @@
     }
 
     @Override
+    public Builder setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+      throw getReporter().fatalError("L8 does not support configuring Android platform builds.");
+    }
+
+    @Override
     void validate() {
       if (isPrintHelp()) {
         return;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d323ab..50b5de8 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -634,7 +634,8 @@
               getDumpInputFlags(),
               getMapIdProvider(),
               getSourceFileProvider(),
-              enableMissingLibraryApiModeling);
+              enableMissingLibraryApiModeling,
+              getAndroidPlatformBuild());
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -821,7 +822,8 @@
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
-      boolean enableMissingLibraryApiModeling) {
+      boolean enableMissingLibraryApiModeling,
+      boolean isAndroidPlatformBuild) {
     super(
         inputApp,
         mode,
@@ -838,7 +840,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        sourceFileProvider);
+        sourceFileProvider,
+        isAndroidPlatformBuild);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -1038,6 +1041,8 @@
         SourceFileRewriter.computeSourceFileProvider(
             getSourceFileProvider(), proguardConfiguration, internal);
 
+    internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
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 f0a8d56..10ef955 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -260,6 +260,16 @@
     horizontalClassMergerOptions.setRestrictToSynthetics();
   }
 
+  public void configureAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+    if (!isAndroidPlatformBuild) {
+      return;
+    }
+    // Configure options according to platform build assumptions.
+    // See go/r8platformflag and b/232073181.
+    minApiLevel = ANDROID_PLATFORM;
+    apiModelingOptions().disableMissingApiModeling();
+  }
+
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
   // To print memory one also have to enable printtimes.
   public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 0dcd986..017c652 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -45,7 +46,8 @@
       ImmutableList.of(
           GlobalSyntheticsTest.ApiTest.class,
           CommandLineParserTest.ApiTest.class,
-          EnableMissingLibraryApiModelingTest.ApiTest.class);
+          EnableMissingLibraryApiModelingTest.ApiTest.class,
+          AndroidPlatformBuildApiTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
new file mode 100644
index 0000000..07d01cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
@@ -0,0 +1,94 @@
+// 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.compilerapi.androidplatformbuild;
+
+import static com.android.tools.r8.MarkerMatcher.markerMinApi;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+public class AndroidPlatformBuildApiTest extends CompilerApiTestRunner {
+
+  public AndroidPlatformBuildApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runD8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runR8);
+  }
+
+  private void runTest(ThrowingConsumer<ProgramConsumer, Exception> test) throws Exception {
+    Path output = temp.newFolder().toPath().resolve("out.jar");
+    test.accept(new DexIndexedConsumer.ArchiveConsumer(output));
+    assertThat(
+        new CodeInspector(output).getMarkers(),
+        CoreMatchers.everyItem(markerMinApi(AndroidApiLevel.ANDROID_PLATFORM)));
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(ProgramConsumer programConsumer) throws Exception {
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setAndroidPlatformBuild(true)
+              .build());
+    }
+
+    public void runR8(ProgramConsumer programConsumer) throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setAndroidPlatformBuild(true)
+              .build());
+    }
+
+    @Test
+    public void testD8() throws Exception {
+      runD8(DexIndexedConsumer.emptyConsumer());
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      runR8(DexIndexedConsumer.emptyConsumer());
+    }
+  }
+}