PoC APIs usage validation

^KT-65540
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 182e387..3bc8a8d 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -3382,6 +3382,12 @@
             <sha256 value="3b33fb52e494a78d803c3836fd4cec45fd22ca1cad62829db83912c6bfa503b9" origin="Generated by Gradle"/>
          </artifact>
       </component>
+      <component group="org.codehaus.mojo" name="animal-sniffer" version="1.23">
+         <artifact name="animal-sniffer-1.23.jar">
+            <md5 value="4d1723b6c0ff4a1b148b231497c1ee2b" origin="Generated by Gradle"/>
+            <sha256 value="a175ba9f939bca4b7066961842507dd5d25ad4236249c13cfbd3407ae436dedd" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
       <component group="org.codehaus.mojo" name="animal-sniffer-annotations" version="1.14">
          <artifact name="animal-sniffer-annotations-1.14.jar">
             <md5 value="9d42e46845c874f1710a9f6a741f6c14" origin="Generated by Gradle"/>
@@ -3394,6 +3400,12 @@
             <sha256 value="e67ec27ceeaf13ab5d54cf5fdbcc544c41b4db8d02d9f006678cca2c7c13ee9d" origin="Generated by Gradle"/>
          </artifact>
       </component>
+      <component group="org.codehaus.mojo" name="animal-sniffer-annotations" version="1.23">
+         <artifact name="animal-sniffer-annotations-1.23.jar">
+            <md5 value="13729ebd1fbdddc25d7feb7694d3028d" origin="Generated by Gradle"/>
+            <sha256 value="9ffe526bf43a6348e9d8b33b9cd6f580a7f5eed0cf055913007eda263de974d0" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
       <component group="org.codehaus.mojo.signature" name="java16" version="1.1">
          <artifact name="java16-1.1.signature">
             <md5 value="f54abe9fb83f358412ad738cc46fc158" origin="Generated by Gradle"/>
diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
index d3867e8..9b2484e 100644
--- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
@@ -305,3 +305,43 @@
         dependsOn("functionalTest")
     }
 }
+
+fun copyConfigurationOverridingAndroidLibraries(
+    configuration: Configuration,
+    androidVersion: String,
+): Configuration {
+    val copy = configuration.copyRecursive()
+    copy.resolutionStrategy.disableDependencyVerification()
+    copy.resolutionStrategy {
+        force(
+            "com.android.tools.build:gradle-api:$androidVersion",
+            "com.android.tools.build:gradle:$androidVersion",
+            "com.android.tools.build:builder:$androidVersion",
+            "com.android.tools.build:builder-model:$androidVersion",
+        )
+    }
+    return copy
+}
+
+val oldAndroidLibraries = configurations.commonCompileClasspath.map {
+    copyConfigurationOverridingAndroidLibraries(it, "7.1.3")
+}
+val newAndroidLibraries = configurations.commonCompileClasspath.map {
+    val newAndroidLibrariesConf = copyConfigurationOverridingAndroidLibraries(it, "8.2.2")
+    newAndroidLibrariesConf.attributes.attribute(
+        Attribute.of("org.gradle.jvm.version", Integer::class.java),
+        @Suppress("DEPRECATION")
+        Integer(11),
+    )
+    newAndroidLibrariesConf
+}
+val validationTask = validateRuntimeAPIsUsage(
+    oldApis = project.files(oldAndroidLibraries),
+    newApis = project.files(newAndroidLibraries),
+    filesToValidate = project.files(kotlin.target.compilations.getByName("common").compileTaskProvider),
+    sourcesPath = layout.projectDirectory.dir("src/common").asFile,
+    unsafeApisUsageAnnotationFqn = "org.jetbrains.kotlin.gradle.utils.UnsafeAtRuntime",
+)
+tasks.named("check").configure {
+    dependsOn(validationTask)
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/UnsafeAtRuntime.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/UnsafeAtRuntime.kt
new file mode 100644
index 0000000..c463b94
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/UnsafeAtRuntime.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.gradle.utils
+
+@Retention(AnnotationRetention.BINARY)
+annotation class UnsafeAtRuntime
\ No newline at end of file
diff --git a/repo/gradle-build-conventions/buildsrc-compat/build.gradle.kts b/repo/gradle-build-conventions/buildsrc-compat/build.gradle.kts
index 735e8c1..c74bd96 100644
--- a/repo/gradle-build-conventions/buildsrc-compat/build.gradle.kts
+++ b/repo/gradle-build-conventions/buildsrc-compat/build.gradle.kts
@@ -124,6 +124,8 @@
     testImplementation(libs.junit.jupiter.api)
     testRuntimeOnly(libs.junit.platform.launcher)
     testRuntimeOnly(libs.junit.jupiter.engine)
+
+    implementation("org.codehaus.mojo:animal-sniffer:1.23")
 }
 
 tasks.withType<Test>().configureEach {
diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/apisUsageValidation.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/apisUsageValidation.kt
new file mode 100644
index 0000000..f0117fe
--- /dev/null
+++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/apisUsageValidation.kt
@@ -0,0 +1,114 @@
+import org.codehaus.mojo.animal_sniffer.SignatureBuilder
+import org.codehaus.mojo.animal_sniffer.SignatureChecker
+import org.codehaus.mojo.animal_sniffer.logging.PrintWriterLogger
+import org.codehaus.mojo.animal_sniffer.logging.Logger
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.tasks.TaskProvider
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+
+
+fun Project.validateRuntimeAPIsUsage(
+    oldApis: FileCollection,
+    newApis: FileCollection,
+    filesToValidate: FileCollection,
+    sourcesPath: File,
+    unsafeApisUsageAnnotationFqn: String,
+): TaskProvider<*> {
+    return tasks.register("apiUsageValidation") {
+        inputs.files(
+            oldApis,
+            newApis,
+            filesToValidate,
+        )
+        val layout = project.layout
+        doLast {
+            val unrecognizedSelectorsInOldApis = dumpUnrecognizedSignature(
+                createKnownSignatureFile(
+                    layout,
+                    oldApis,
+                    "oldApis",
+                ),
+                filesToValidate,
+                sourcesPath,
+                unsafeApisUsageAnnotationFqn
+            )
+            val unrecognizedSelectorsInNewApis = dumpUnrecognizedSignature(
+                createKnownSignatureFile(
+                    layout,
+                    newApis,
+                    "newApis",
+                ),
+                filesToValidate,
+                sourcesPath,
+                unsafeApisUsageAnnotationFqn
+            )
+
+            val apisFromNewVersionNotPresentInOld = unrecognizedSelectorsInOldApis.subtract(unrecognizedSelectorsInNewApis)
+            val apisFromOldVersionNotPresentInNew = unrecognizedSelectorsInNewApis.subtract(unrecognizedSelectorsInOldApis)
+
+            if (apisFromNewVersionNotPresentInOld.isNotEmpty() || apisFromOldVersionNotPresentInNew.isNotEmpty()) {
+                error(
+                    buildString {
+                        appendLine("Unsafe APIs unmarked by $unsafeApisUsageAnnotationFqn detected.")
+                        if (apisFromNewVersionNotPresentInOld.isNotEmpty()) {
+                            appendLine("These APIs only exist in newer libraries:")
+                            apisFromNewVersionNotPresentInOld.forEach(::appendLine)
+                            appendLine()
+                        }
+                        if (apisFromOldVersionNotPresentInNew.isNotEmpty()) {
+                            appendLine("These APIs only exist in older libraries:")
+                            apisFromOldVersionNotPresentInNew.forEach(::appendLine)
+                        }
+                    }
+                )
+            }
+        }
+    }
+}
+
+private fun createKnownSignatureFile(
+    layout: ProjectLayout,
+    apis: FileCollection,
+    signatureFileName: String,
+): File {
+    val file = layout.buildDirectory.file(signatureFileName).get().asFile
+    FileOutputStream(file).use {
+        val builder = SignatureBuilder(it, PrintWriterLogger(System.out))
+        apis.forEach {
+            builder.process(it)
+        }
+        builder.close()
+    }
+    return file
+}
+
+private fun dumpUnrecognizedSignature(
+    knownSignatures: File,
+    classFiles: FileCollection,
+    sourcesPath: File,
+    unsafeApisUsageAnnotationFqn: String,
+): Set<String> {
+    val messages = mutableSetOf<String>()
+    val checker = SignatureChecker(
+        FileInputStream(knownSignatures), emptySet(),
+        object : Logger by PrintWriterLogger(System.out) {
+            override fun error(message: String?) {
+                message?.let { messages.add(it) }
+            }
+
+            override fun error(message: String?, t: Throwable?) {
+                message?.let { messages.add(it) }
+            }
+        }
+    )
+    checker.setAnnotationTypes(listOf(unsafeApisUsageAnnotationFqn))
+    checker.setSourcePath(listOf(sourcesPath))
+    classFiles.forEach {
+        checker.process(it)
+    }
+    return messages
+}
\ No newline at end of file