Check desugar_jdk_libs version and flavor

Bug: 289401250
Test: CheckAarMetadataTaskTest
Change-Id: I1938170192a3d95931d7f9eb72c42c74f5ce8e70
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTask.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTask.kt
index 9616755..efc8466 100644
--- a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTask.kt
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTask.kt
@@ -18,6 +18,7 @@
 import com.android.SdkConstants.AAR_FORMAT_VERSION_PROPERTY
 import com.android.SdkConstants.AAR_METADATA_VERSION_PROPERTY
 import com.android.SdkConstants.CORE_LIBRARY_DESUGARING_ENABLED_PROPERTY
+import com.android.SdkConstants.DESUGAR_JDK_LIB_PROPERTY
 import com.android.SdkConstants.FORCE_COMPILE_SDK_PREVIEW_PROPERTY
 import com.android.SdkConstants.MIN_ANDROID_GRADLE_PLUGIN_VERSION_PROPERTY
 import com.android.SdkConstants.MIN_COMPILE_SDK_EXTENSION_PROPERTY
@@ -30,6 +31,9 @@
 import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction
 import com.android.build.gradle.internal.ide.dependencies.getIdString
 import com.android.build.gradle.internal.tasks.AarMetadataTask.Companion.DEFAULT_MIN_COMPILE_SDK_EXTENSION
+import com.android.build.gradle.internal.utils.checkDesugarJdkVariant
+import com.android.build.gradle.internal.utils.checkDesugarJdkVersion
+import com.android.build.gradle.internal.utils.getDesugarLibDependencyGraph
 import com.android.build.gradle.internal.utils.parseTargetHash
 import com.android.build.gradle.internal.utils.setDisallowChanges
 import com.android.build.gradle.options.BooleanOption
@@ -42,7 +46,9 @@
 import org.gradle.api.artifacts.ArtifactCollection
 import org.gradle.api.artifacts.component.LibraryBinaryIdentifier
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
+import org.gradle.api.artifacts.component.ModuleComponentSelector
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
+import org.gradle.api.artifacts.result.ResolvedComponentResult
 import org.gradle.api.file.DirectoryProperty
 import org.gradle.api.file.FileCollection
 import org.gradle.api.provider.ListProperty
@@ -102,6 +108,10 @@
     @get:Input
     abstract val coreLibraryDesugaringEnabled: Property<Boolean>
 
+    @get:Input
+    @get:Optional
+    abstract val desugarJdkLibDependencyGraph: Property<ResolvedComponentResult>
+
     // platformSdkExtension is the actual extension level of the platform, if specified within the
     // SDK directory. This value is used if the extension level is not specified in the
     // compileSdkVersion hash string.
@@ -152,6 +162,12 @@
             )
             it.projectPath.set(projectPath)
             it.disableCompileSdkChecks.set(disableCompileSdkChecks)
+            desugarJdkLibDependencyGraph.orNull?.dependencies?.firstOrNull()?.requested?.let { id ->
+                if (id is ModuleComponentSelector) {
+                    it.desugarJdkVariant.set(id.module)
+                    it.desugarJdkVersion.set(id.version)
+                }
+            }
         }
     }
 
@@ -193,6 +209,11 @@
                     creationConfig.global.compileOptions.isCoreLibraryDesugaringEnabled
                 }
             task.coreLibraryDesugaringEnabled.setDisallowChanges(coreLibraryDesugaringEnabled)
+            if (coreLibraryDesugaringEnabled) {
+                task.desugarJdkLibDependencyGraph.set(
+                    getDesugarLibDependencyGraph(creationConfig.services)
+                )
+            }
             task.maxRecommendedStableCompileSdkVersionForThisAgp.setDisallowChanges(
                 ToolsRevisionUtils.MAX_RECOMMENDED_COMPILE_SDK_VERSION.apiLevel
             )
@@ -515,6 +536,30 @@
                     """.trimIndent()
                 )
             }
+
+            aarMetadataReader.desugarJdkLibId?.split(":")?.also {
+                check(it.size == 3) { "Unexpected desugarJdkLib ID format from AAR metadata"}
+                val variantFromAar = it[1]
+                val versionFromAar = it[2]
+
+                if (parameters.desugarJdkVariant.isPresent
+                    && parameters.desugarJdkVersion.isPresent) {
+                    checkDesugarJdkVariant(
+                        variantFromAar,
+                        parameters.desugarJdkVariant.get(),
+                        errorMessages,
+                        displayName,
+                        parameters.projectPath.get()
+                    )
+                    checkDesugarJdkVersion(
+                        versionFromAar,
+                        parameters.desugarJdkVersion.get(),
+                        errorMessages,
+                        displayName,
+                        parameters.projectPath.get()
+                    )
+                }
+            }
         }
     }
 
@@ -537,6 +582,12 @@
         // task).
         throw RuntimeException("Unsupported target hash: $sdkVersion")
     }
+
+    enum class DesugarJdkVariant(val size: Int) {
+        MINIMAL(0),
+        BASIC(1),
+        NIO(2);
+    }
 }
 
 /** [WorkParameters] for [CheckAarMetadataWorkAction] */
@@ -546,6 +597,8 @@
     abstract val aarMetadataVersion: Property<String>
     abstract val compileSdkVersion: Property<String>
     abstract val coreLibraryDesugaringEnabled: Property<Boolean>
+    abstract val desugarJdkVariant: Property<String>
+    abstract val desugarJdkVersion: Property<String>
     abstract val platformSdkExtension: Property<Int>
     abstract val platformSdkApiLevel: Property<Int>
     abstract val agpVersion: Property<String>
@@ -563,6 +616,7 @@
     val forceCompileSdkPreview: String?
     val minCompileSdkExtension: String?
     val coreLibraryDesugaringEnabled: String?
+    val desugarJdkLibId: String?
 
     constructor(file: File) : this(file.inputStream())
 
@@ -576,6 +630,7 @@
         forceCompileSdkPreview = properties.getProperty(FORCE_COMPILE_SDK_PREVIEW_PROPERTY)
         minCompileSdkExtension = properties.getProperty(MIN_COMPILE_SDK_EXTENSION_PROPERTY)
         coreLibraryDesugaringEnabled = properties.getProperty(CORE_LIBRARY_DESUGARING_ENABLED_PROPERTY)
+        desugarJdkLibId = properties.getProperty(DESUGAR_JDK_LIB_PROPERTY)
     }
 }
 
diff --git a/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/AarMetadataCheck.kt b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/AarMetadataCheck.kt
new file mode 100644
index 0000000..b9ed26e
--- /dev/null
+++ b/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/AarMetadataCheck.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.utils
+
+import com.android.build.gradle.internal.tasks.CheckAarMetadataWorkAction
+import com.android.repository.Revision
+
+/**
+ * Utils for checking desugar library requirements from AAR metadata
+ */
+
+fun checkDesugarJdkVariant(
+    variantFromAar: String,
+    variantFromConsumer: String,
+    errorMessages: MutableList<String>,
+    dependency: String,
+    projectPath: String,
+) {
+    val parsedVariantFromAar = parseDesugarJdkVariant(variantFromAar)
+    val parsedVariantFromConsumer = parseDesugarJdkVariant(variantFromConsumer)
+    if (parsedVariantFromAar.size > parsedVariantFromConsumer.size) {
+        errorMessages.add(
+            """
+                        Dependency '$dependency' requires desugar_jdk_libs flavor to be at least
+                        $variantFromAar for $projectPath, which is currently $variantFromConsumer
+
+                        See https://d.android.com/r/tools/api-desugaring-flavors
+                        for more details.
+                    """.trimIndent()
+        )
+    }
+}
+
+fun checkDesugarJdkVersion(
+    versionFromAar: String,
+    versionFromConsumer: String,
+    errorMessages: MutableList<String>,
+    dependency: String,
+    projectPath: String,
+) {
+    val parsedVersionFromAar = Revision.parseRevision(versionFromAar)
+    val parsedVersionFromConsumer = Revision.parseRevision(versionFromConsumer)
+    if (parsedVersionFromAar > parsedVersionFromConsumer) {
+        errorMessages.add(
+            """
+                        Dependency '$dependency' requires desugar_jdk_libs version to be
+                        $versionFromAar or above for $projectPath, which is currently $versionFromConsumer
+
+                        See https://d.android.com/studio/build/library-desugaring for more
+                        details.
+                    """.trimIndent()
+        )
+    }
+}
+
+private fun parseDesugarJdkVariant(variant: String): CheckAarMetadataWorkAction.DesugarJdkVariant {
+    return when(variant) {
+        DESUGAR_JDK_LIBS_MINIMAL -> CheckAarMetadataWorkAction.DesugarJdkVariant.MINIMAL
+        DESUGAR_JDK_LIBS -> CheckAarMetadataWorkAction.DesugarJdkVariant.BASIC
+        DESUGAR_JDK_LIBS_NIO -> CheckAarMetadataWorkAction.DesugarJdkVariant.NIO
+        else -> throw RuntimeException("Failed to parse desugar_jdk_libs variant from" +
+                " desugarJdkLib property of AAR metadata: unknown variant")
+    }
+}
+
+const val DESUGAR_JDK_LIBS_MINIMAL = "desugar_jdk_libs_minimal"
+const val DESUGAR_JDK_LIBS = "desugar_jdk_libs"
+const val DESUGAR_JDK_LIBS_NIO = "desugar_jdk_libs_nio"
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/fixtures/FakeModuleComponentSelector.kt b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/fixtures/FakeModuleComponentSelector.kt
new file mode 100644
index 0000000..565b46e
--- /dev/null
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/fixtures/FakeModuleComponentSelector.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.fixtures
+
+import org.gradle.api.artifacts.ModuleIdentifier
+import org.gradle.api.artifacts.VersionConstraint
+import org.gradle.api.artifacts.component.ComponentIdentifier
+import org.gradle.api.artifacts.component.ModuleComponentSelector
+import org.gradle.api.attributes.AttributeContainer
+import org.gradle.api.capabilities.Capability
+
+class FakeModuleComponentSelector(
+    private val group: String,
+    private val module: String,
+    private val version: String,
+) : ModuleComponentSelector {
+
+    override fun getDisplayName(): String {
+        TODO("Not yet implemented")
+    }
+
+    override fun matchesStrictly(identifier: ComponentIdentifier): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun getAttributes(): AttributeContainer {
+        TODO("Not yet implemented")
+    }
+
+    override fun getRequestedCapabilities(): MutableList<Capability> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getGroup(): String {
+        return group
+    }
+
+    override fun getModule(): String {
+        return module
+    }
+
+    override fun getVersion(): String {
+        return version
+    }
+
+    override fun getVersionConstraint(): VersionConstraint {
+        TODO("Not yet implemented")
+    }
+
+    override fun getModuleIdentifier(): ModuleIdentifier {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTaskTest.kt b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTaskTest.kt
index a304603..8ed16d0 100644
--- a/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTaskTest.kt
+++ b/build-system/gradle-core/src/test/java/com/android/build/gradle/internal/tasks/CheckAarMetadataTaskTest.kt
@@ -20,12 +20,18 @@
 import com.android.build.gradle.internal.fixtures.FakeArtifactCollection
 import com.android.build.gradle.internal.fixtures.FakeComponentIdentifier
 import com.android.build.gradle.internal.fixtures.FakeGradleWorkExecutor
+import com.android.build.gradle.internal.fixtures.FakeModuleComponentSelector
 import com.android.build.gradle.internal.fixtures.FakeNoOpAnalyticsService
 import com.android.build.gradle.internal.fixtures.FakeResolvedArtifactResult
+import com.android.build.gradle.internal.fixtures.FakeResolvedComponentResult
+import com.android.build.gradle.internal.fixtures.FakeResolvedDependencyResult
+import com.android.build.gradle.internal.utils.DESUGAR_JDK_LIBS
+import com.android.build.gradle.internal.utils.DESUGAR_JDK_LIBS_MINIMAL
+import com.android.build.gradle.internal.utils.checkDesugarJdkVariant
+import com.android.build.gradle.internal.utils.checkDesugarJdkVersion
 import com.google.common.truth.Truth.assertThat
 import org.gradle.testfixtures.ProjectBuilder
 import org.gradle.workers.WorkerExecutor
-import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -724,4 +730,114 @@
             """.trimIndent())
         }
     }
+
+    @Test
+    fun testFailsOnDesugarJdkLibId() {
+        task.aarMetadataArtifacts =
+            FakeArtifactCollection(
+                mutableSetOf(
+                    FakeResolvedArtifactResult(
+                        file = temporaryFolder.newFile().also {
+                            writeAarMetadataFile(
+                                file = it,
+                                aarFormatVersion = AarMetadataTask.AAR_FORMAT_VERSION,
+                                aarMetadataVersion = AarMetadataTask.AAR_METADATA_VERSION,
+                                minCompileSdk = 28,
+                                minCompileSdkExtension = 0,
+                                minAgpVersion = "3.0.0",
+                                coreLibraryDesugaringEnabled = true,
+                                desugarJdkLib = "com.android.tools:desugar_jdk_libs_nio:2.0.2"
+                            )
+                        },
+                        identifier = FakeComponentIdentifier("displayName")
+                    )
+                )
+            )
+        task.aarFormatVersion.set(AarMetadataTask.AAR_FORMAT_VERSION)
+        task.aarMetadataVersion.set(AarMetadataTask.AAR_METADATA_VERSION)
+        task.compileSdkVersion.set("android-28")
+        task.disableCompileSdkChecks.set(false)
+        task.agpVersion.set(Version.ANDROID_GRADLE_PLUGIN_VERSION)
+        task.projectPath.set(":app")
+        task.coreLibraryDesugaringEnabled.set(true)
+        task.desugarJdkLibDependencyGraph.set(
+            FakeResolvedComponentResult(
+                dependencies = mutableSetOf(FakeResolvedDependencyResult(
+                    requested = FakeModuleComponentSelector(
+                        "com.android.tools", "desugar_jdk_libs", "1.1.5")
+                    )
+                )
+            )
+        )
+        try {
+            task.taskAction()
+        } catch (e: RuntimeException) {
+            assertThat(e.message).isEqualTo("""
+                2 issues were found when checking AAR metadata:
+
+                  1.  Dependency 'displayName' requires desugar_jdk_libs flavor to be at least
+                      desugar_jdk_libs_nio for :app, which is currently desugar_jdk_libs
+
+                      See https://d.android.com/r/tools/api-desugaring-flavors
+                      for more details.
+
+                  2.  Dependency 'displayName' requires desugar_jdk_libs version to be
+                      2.0.2 or above for :app, which is currently 1.1.5
+
+                      See https://d.android.com/studio/build/library-desugaring for more
+                      details.
+            """.trimIndent())
+        }
+    }
+
+    @Test
+    fun testCompareDesugarJdkVariant() {
+        val errorMessages = mutableListOf<String>()
+        checkDesugarJdkVariant(
+            variantFromAar = DESUGAR_JDK_LIBS_MINIMAL,
+            variantFromConsumer = DESUGAR_JDK_LIBS,
+            errorMessages = errorMessages,
+            dependency = "",
+            projectPath = ""
+        )
+        assertThat(errorMessages).isEmpty()
+        checkDesugarJdkVariant(
+            variantFromAar = DESUGAR_JDK_LIBS,
+            variantFromConsumer = DESUGAR_JDK_LIBS_MINIMAL,
+            errorMessages = errorMessages,
+            dependency = "",
+            projectPath = ""
+        )
+        assertThat(errorMessages).isNotEmpty()
+    }
+
+    @Test
+    fun testCompareDesugarJdkVersion() {
+        val errorMessages = mutableListOf<String>()
+        checkDesugarJdkVersion(
+            versionFromAar = "1.1.3",
+            versionFromConsumer = "1.2.5",
+            errorMessages = errorMessages,
+            dependency = "",
+            projectPath = ""
+        )
+        assertThat(errorMessages).isEmpty()
+        checkDesugarJdkVersion(
+            versionFromAar = "2.1.3",
+            versionFromConsumer = "1.2.5",
+            errorMessages = errorMessages,
+            dependency = "",
+            projectPath = ""
+        )
+        assertThat(errorMessages).isNotEmpty()
+        errorMessages.clear()
+        checkDesugarJdkVersion(
+            versionFromAar = "2.1.3",
+            versionFromConsumer = "2.2.5",
+            errorMessages = errorMessages,
+            dependency = "",
+            projectPath = ""
+        )
+        assertThat(errorMessages).isEmpty()
+    }
 }
diff --git a/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/CheckAarMetadataTaskTest.kt b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/CheckAarMetadataTaskTest.kt
index ba3386d..0e647d1 100644
--- a/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/CheckAarMetadataTaskTest.kt
+++ b/build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/application/CheckAarMetadataTaskTest.kt
@@ -24,6 +24,7 @@
 import com.android.SdkConstants.MIN_COMPILE_SDK_EXTENSION_PROPERTY
 import com.android.SdkConstants.MIN_COMPILE_SDK_PROPERTY
 import com.android.build.gradle.integration.common.fixture.DESUGAR_DEPENDENCY_VERSION
+import com.android.build.gradle.integration.common.fixture.DESUGAR_NIO_DEPENDENCY_VERSION
 import com.android.build.gradle.integration.common.fixture.GradleTestProject
 import com.android.build.gradle.integration.common.fixture.GradleTestProject.Companion.compileSdkHash
 import com.android.build.gradle.integration.common.fixture.app.HelloWorldLibraryApp
@@ -579,6 +580,10 @@
                 android.compileOptions.coreLibraryDesugaringEnabled = false
             """.trimIndent()
         )
+        TestFileUtils.appendToFile(
+            project.getSubproject("app").buildFile,
+            "dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:$DESUGAR_DEPENDENCY_VERSION'}"
+        )
         TestFileUtils.searchAndReplace(
             project.getSubproject("app").buildFile,
             "implementation files('libs/library.aar')",
@@ -587,6 +592,32 @@
         project.executor().run(":app:checkAndroidTestAarMetadata")
     }
 
+    @Test
+    fun testCheckingDesugarJdkLib() {
+        addAarWithPossiblyInvalidAarMetadataToAppProject(
+            aarFormatVersion = AarMetadataTask.AAR_FORMAT_VERSION,
+            aarMetadataVersion = AarMetadataTask.AAR_METADATA_VERSION,
+            coreLibraryDesugaringEnabled = "true",
+            minDesugarJdkLib = "com.android.tools:desugar_jdk_libs_nio:$DESUGAR_NIO_DEPENDENCY_VERSION"
+        )
+
+        TestFileUtils.appendToFile(
+            project.getSubproject("app").buildFile,
+            """
+                android.compileOptions.coreLibraryDesugaringEnabled = true
+                android.defaultConfig.multiDexEnabled = true
+            """.trimIndent()
+        )
+        TestFileUtils.appendToFile(
+            project.getSubproject("app").buildFile,
+            "dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:$DESUGAR_DEPENDENCY_VERSION'}"
+        )
+        val result = project.executor().expectFailure().run(":app:checkDebugAarMetadata")
+        ScannerSubject.assertThat(result.stderr).contains(
+            "2 issues were found when checking AAR metadata"
+        )
+    }
+
     private fun addAarWithPossiblyInvalidAarMetadataToAppProject(
         aarFormatVersion: String?,
         aarMetadataVersion: String?,