AGP work
diff --git a/libraries/tools/kotlin-compose-compiler/build.gradle.kts b/libraries/tools/kotlin-compose-compiler/build.gradle.kts
index 80258ff..2824ea5 100644
--- a/libraries/tools/kotlin-compose-compiler/build.gradle.kts
+++ b/libraries/tools/kotlin-compose-compiler/build.gradle.kts
@@ -1,4 +1,5 @@
 import gradle.GradlePluginVariant
+import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
 
 plugins {
     id("gradle-plugin-common-configuration")
@@ -6,10 +7,23 @@
     id("gradle-plugin-api-reference")
 }
 
+project.updateJvmTarget("11")
+
+tasks.withType<KotlinJvmCompile>().configureEach {
+    compilerOptions {
+        freeCompilerArgs.add("-Xskip-metadata-version-check")
+    }
+}
+
 dependencies {
     commonApi(platform(project(":kotlin-gradle-plugins-bom")))
     commonApi(project(":kotlin-gradle-plugin-model"))
     commonApi(project(":kotlin-gradle-plugin"))
+
+    // TODO: figure out AGP dependency story
+    commonCompileOnly("com.android.tools.build:gradle-api:8.8.1") { isTransitive = false }
+    commonCompileOnly("com.android.tools.build:gradle:8.8.1") { isTransitive = false }
+    commonApi(project(":plugins:compose-compiler-plugin:mapping-generator"))
 }
 
 gradlePlugin {
diff --git a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt
index 3a12889..9804536 100644
--- a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt
+++ b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt
@@ -10,6 +10,7 @@
 import org.gradle.api.provider.Provider
 import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
 import org.jetbrains.kotlin.compose.compiler.gradle.internal.ComposeWithAgpConfig
+import org.jetbrains.kotlin.compose.compiler.gradle.internal.configureComposeMappingFile
 import org.jetbrains.kotlin.compose.compiler.gradle.model.builder.ComposeCompilerModelBuilder
 import org.jetbrains.kotlin.gradle.plugin.*
 import javax.inject.Inject
@@ -38,6 +39,7 @@
     override fun apply(target: Project) {
         composeExtension = target.extensions.create("composeCompiler", ComposeCompilerGradlePluginExtension::class.java)
         registry.register(ComposeCompilerModelBuilder())
+        target.configureComposeMappingFile()
     }
 
     override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
diff --git a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/internal/ComposeAgpMappingFile.kt b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/internal/ComposeAgpMappingFile.kt
new file mode 100644
index 0000000..8130d02
--- /dev/null
+++ b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/internal/ComposeAgpMappingFile.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2010-2025 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.compose.compiler.gradle.internal
+
+import androidx.compose.compiler.mapping.ComposeMapping
+import androidx.compose.compiler.mapping.ErrorReporter
+import com.android.build.api.artifact.ScopedArtifact
+import com.android.build.api.artifact.SingleArtifact
+import com.android.build.api.variant.ApplicationAndroidComponentsExtension
+import com.android.build.api.variant.ScopedArtifacts
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.file.RegularFile
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.api.problems.ProblemGroup
+import org.gradle.api.problems.ProblemId
+import org.gradle.api.problems.Problems
+import org.gradle.api.problems.Severity
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.tasks.*
+import org.gradle.internal.extensions.core.get
+import org.gradle.kotlin.dsl.findByType
+import org.gradle.kotlin.dsl.register
+import java.util.Locale.getDefault
+import javax.inject.Inject
+
+internal fun Project.configureComposeMappingFile() {
+    plugins.withId("com.android.application") {
+        project.extensions.findByType<ApplicationAndroidComponentsExtension>()?.onVariants { variant ->
+            if (!variant.isMinifyEnabled) return@onVariants
+
+            val produceTaskName = "produce${variant.name.capitalize()}ComposeMapping"
+            val taskProvider = project.tasks.register<ProduceMappingFileTask>(produceTaskName) {
+                output.set(project.layout.buildDirectory.file("intermediates/compose_mapping/${variant.name}/compose-mapping.txt"))
+            }
+
+            variant.artifacts
+                .forScope(ScopedArtifacts.Scope.ALL)
+                .use(taskProvider)
+                .toGet(
+                    ScopedArtifact.CLASSES,
+                    ProduceMappingFileTask::projectJars,
+                    ProduceMappingFileTask::projectDirectories
+                )
+
+            val mergeTaskName = "merge${variant.name.capitalize()}ComposeMapping"
+            val mergeTaskProvider = project.tasks.register<MergeMappingFileTask>(mergeTaskName) {
+                composeMapping.set(taskProvider.map { it.output.get() })
+            }
+
+            variant.artifacts
+                .use(mergeTaskProvider)
+                .wiredWithFiles(MergeMappingFileTask::originalFile, MergeMappingFileTask::output)
+                .toTransform(uncheckedCast(SingleArtifact.OBFUSCATION_MAPPING_FILE))
+        }
+    }
+}
+
+
+@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
+private inline fun <T> uncheckedCast(value: Any): T = value as T
+
+private fun String.capitalize(): String =
+    replaceFirstChar { if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() }
+
+@CacheableTask
+internal abstract class MergeMappingFileTask : DefaultTask() {
+
+    @get:InputFile
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val originalFile: RegularFileProperty
+
+    @get:InputFile
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val composeMapping: RegularFileProperty
+
+    @get:OutputFile
+    abstract val output: RegularFileProperty
+
+    @TaskAction
+    fun taskAction() {
+        // todo: fix previous mapping file hash
+        val outputFile = output.get().asFile
+        outputFile.parentFile.mkdirs()
+        outputFile.bufferedWriter().use { writer ->
+            originalFile.orNull?.let { writer.write(it.asFile.readText()) }
+            composeMapping.orNull?.let { writer.write(it.asFile.readText()) }
+        }
+    }
+}
+
+@CacheableTask
+internal abstract class ProduceMappingFileTask @Inject constructor(
+    private val problems: Problems
+) : DefaultTask() {
+    @get:OutputFile
+    abstract val output: RegularFileProperty
+
+    @get:InputFiles
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val projectDirectories: ListProperty<Directory>
+
+    @get:InputFiles
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val projectJars: ListProperty<RegularFile>
+
+    private val files by lazy {
+        services.get<FileOperations>()
+    }
+
+    @TaskAction
+    fun taskAction() {
+        val reporter = object : ErrorReporter {
+            override fun reportError(e: Throwable) {
+                problems.reporter.report(MappingGenerationFailedProblemId) { spec ->
+                    spec.withException(e)
+                        .severity(Severity.WARNING)
+                }
+            }
+        }
+
+        val mappings = buildList {
+            projectJars.get().forEach { jar ->
+                val contents = files.zipTree(jar)
+                contents.forEach { file ->
+                    if (file.name.endsWith(".class")) {
+                        val mapping = ComposeMapping.fromBytecode(reporter, file.readBytes())
+                        add(mapping)
+                    }
+                }
+            }
+
+            projectDirectories.get().forEach {
+                val contents = files.fileTree(it)
+                contents.forEach { file ->
+                    if (file.name.endsWith(".class")) {
+                        val mapping = ComposeMapping.fromBytecode(reporter, file.readBytes())
+                        add(mapping)
+                    }
+                }
+            }
+        }
+
+        output.get().asFile.bufferedWriter().use { writer ->
+            writer.write("ComposeStackTrace -> ${"$$"}compose:\n")
+            mappings.forEach {
+                writer.write(it.asProguardMapping())
+            }
+        }
+    }
+}
+
+private val Group = ProblemGroup.create("compose-mapping", "Compose Mapping Generator Group")
+
+private val MappingGenerationFailedProblemId = ProblemId.create(
+    "compose-mapping-fail",
+    "Failed to generate Compose mapping entry.",
+    Group
+)
diff --git a/plugins/compose/mapping-generator/src/main/kotlin/androidx/compose/compiler/mapping/ComposeMapping.kt b/plugins/compose/mapping-generator/src/main/kotlin/androidx/compose/compiler/mapping/ComposeMapping.kt
new file mode 100644
index 0000000..e5d8396
--- /dev/null
+++ b/plugins/compose/mapping-generator/src/main/kotlin/androidx/compose/compiler/mapping/ComposeMapping.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010-2025 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 androidx.compose.compiler.mapping
+
+import androidx.compose.compiler.mapping.group.GroupInfo
+
+class ComposeMapping private constructor(
+    private val entries: List<Entry>
+) {
+    private class Entry(
+        val cls: ClassInfo,
+        val method: MethodInfo,
+        val group: GroupInfo
+    )
+
+    fun asProguardMapping(): String = buildString {
+        entries.forEach { entry ->
+            appendEntry(entry.cls, entry.method, entry.group)
+            appendLine()
+        }
+    }
+
+    private fun StringBuilder.appendEntry(
+        cls: ClassInfo,
+        method: MethodInfo,
+        group: GroupInfo
+    ) {
+        if (group.key == null) return
+
+        append("  ")
+        append("1:1:")
+        append(descriptorToProguardString("${cls.classId.fqName}.${method.id.methodName}", method.id.methodDescriptor))
+        append(":")
+        append(group.line)
+        append(":")
+        append(group.line)
+        append(" -> ")
+        append("m$")
+        append(group.key.toString())
+    }
+
+    private fun descriptorToProguardString(name: String, descriptor: String): String {
+        if (descriptor.isEmpty()) return "$name()"
+
+        fun descriptorToJavaType(d: String): String =
+            when (d) {
+                "V" -> "void"
+                "Z" -> "boolean"
+                "B" -> "byte"
+                "I" -> "int"
+                "J" -> "long"
+                "S" -> "short"
+                "F" -> "float"
+                "D" -> "double"
+                "C" -> "char"
+                else -> {
+                    if (d.startsWith('L')) {
+                        d.substring(1, d.length - 1).replace('/', '.')
+                    } else if (d.startsWith('[')) {
+                        descriptorToJavaType(d.drop(1)) + "[]"
+                    } else {
+                        error("Unknown descriptor $d")
+                    }
+                }
+            }
+
+        val parameterString = descriptor.takeWhile { it != ')' }.dropWhile { it == '(' }
+        val parameters = sequence {
+            var i = 0
+            while (i < parameterString.length) {
+                val start = i
+                var current = parameterString[i]
+                while (current == '[') {
+                    i++
+                    current = parameterString[i]
+                }
+                val end = if (current == 'L') {
+                    parameterString.indexOf(';', i) + 1
+                } else {
+                    i + 1
+                }
+                yield(parameterString.substring(start, end))
+                i = end
+            }
+        }.map {
+            descriptorToJavaType(it)
+        }
+        val returnType = descriptor.takeLastWhile { it != ')' }
+
+        return parameters.joinToString(
+            separator = ",",
+            prefix = "${descriptorToJavaType(returnType)} $name(",
+            postfix = ")",
+        )
+    }
+
+    companion object {
+        fun fromBytecode(reporter: ErrorReporter, bytecode: ByteArray): ComposeMapping {
+            val cls = with(reporter) { ClassInfo(bytecode) }
+            val entries = buildList {
+                cls.methods.forEach { method ->
+                    method.groups.forEach { group ->
+                        if (group.key != null) {
+                            add(Entry(cls, method, group))
+                        }
+                    }
+                }
+            }
+            return ComposeMapping(entries)
+        }
+    }
+}
\ No newline at end of file