From 22083d5a7e49db11eba75667e79b14b565bf4ff0 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:40:14 +0000 Subject: [PATCH 01/11] ref(proguard): Detect mappings from produced artifacts Co-Authored-By: Roman Zavarnitsyn --- .../io/sentry/android/gradle/AGP74Compat.kt | 23 +------------------ .../android/gradle/AndroidComponentsConfig.kt | 4 +--- .../tasks/SentryGenerateProguardUuidTask.kt | 8 +++++++ .../tasks/SentryUploadProguardMappingsTask.kt | 16 ++++++------- .../gradle/integration/SentryPluginTest.kt | 11 +++++++-- 5 files changed, 27 insertions(+), 35 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP74Compat.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP74Compat.kt index 5f60b60da..4d435a76b 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP74Compat.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP74Compat.kt @@ -14,7 +14,6 @@ import com.android.build.api.variant.impl.VariantImpl import io.sentry.android.gradle.SentryTasksProvider.getComposeMappingMergeTask import io.sentry.android.gradle.SentryTasksProvider.getMinifyTask import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask -import io.sentry.android.gradle.util.AgpVersions import io.sentry.gradle.common.SentryVariant import io.sentry.gradle.common.filterBuildConfig import org.gradle.api.Project @@ -32,9 +31,7 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant { override val flavorName: String? = variant.flavorName override val buildTypeName: String? = variant.buildType override val productFlavors: List = variant.productFlavors.map { it.second } - override val isMinifyEnabled: Boolean = - (variant as? CanMinifyCode)?.isMinifyEnabled == true || - variant.isApplicationOptimizationEnabled() + override val isMinifyEnabled: Boolean = (variant as? CanMinifyCode)?.isMinifyEnabled == true // TODO: replace this eventually (when targeting AGP 8.3.0) with // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-api/src/main/java/com/android/build/api/variant/Component.kt;l=103-104;bpv=1 @@ -117,24 +114,6 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant { } } -private fun Variant.isApplicationOptimizationEnabled(): Boolean { - if (!AgpVersions.isAGP93(AgpVersions.CURRENT)) { - return false - } - - // AGP 9.3's optimization.enable DSL does not update CanMinifyCode.isMinifyEnabled. The merged - // value is only exposed through an internal creation config, so use reflection to keep this - // plugin binary-compatible with older AGP versions. - return runCatching { - val optimizationCreationConfig = - javaClass.getMethod("getOptimizationCreationConfig").invoke(this) - optimizationCreationConfig.javaClass - .getMethod("getApplicationOptimizationEnabled") - .invoke(optimizationCreationConfig) as Boolean - } - .getOrDefault(false) -} - fun configureInstrumentationFor74( variant: Variant, classVisitorFactoryImplClass: Class>, diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt index 32c7123ca..718b5506a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt @@ -38,7 +38,6 @@ import io.sentry.android.gradle.telemetry.SentryTelemetryService import io.sentry.android.gradle.util.AgpVersions import io.sentry.android.gradle.util.GroovyCompat import io.sentry.android.gradle.util.SentryModules -import io.sentry.android.gradle.util.SentryPluginUtils.isMinificationEnabled import io.sentry.android.gradle.util.SentryPluginUtils.isVariantAllowed import io.sentry.android.gradle.util.collectModules import io.sentry.android.gradle.util.hookWithAssembleTasks @@ -351,9 +350,8 @@ private fun ApplicationVariant.configureProguardMappingsTasks( } val sentryProps = getPropertiesFilePath(project, variant) val dexguardEnabled = extension.dexguardEnabled.get() - val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled) - if (isMinifyEnabled && extension.includeProguardMapping.get()) { + if (extension.includeProguardMapping.get()) { val mappings = getMappingFileProvider(project, variant, dexguardEnabled) val generateUuidTask = SentryGenerateProguardUuidTask.register( diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index 0acd26b83..c65885b5a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -36,6 +36,13 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { // Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact @get:Internal abstract val mappingFile: RegularFileProperty + private fun hasMappingFile(): Boolean = + if (mappingFile.isPresent) { + mappingFile.get().asFile.exists() + } else { + fallbackMappingFiles.files.any { it.exists() } + } + @TaskAction fun generateProperties() { val outputDir = output.get().asFile @@ -83,6 +90,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { task.fallbackMappingFiles.from(proguardMappingFile) } task.outputs.upToDateWhen { false } + task.onlyIf("a ProGuard mapping file was produced") { task.hasMappingFile() } } return generateUuidTask } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt index eb1c88f24..cc002c08d 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt @@ -15,6 +15,7 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskProvider @@ -36,7 +37,10 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { outputs.upToDateWhen { true } } - @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val uuidFile: RegularFileProperty + @get:InputFile + @get:Optional + @get:PathSensitive(PathSensitivity.NONE) + abstract val uuidFile: RegularFileProperty @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) @@ -44,13 +48,6 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { @get:Input abstract val autoUploadProguardMapping: Property - override fun exec() { - if (!mappingsFiles.isPresent || mappingsFiles.get().isEmpty) { - error("[sentry] Mapping files are missing!") - } - super.exec() - } - override fun getArguments(args: MutableList) { val uuid = readUuidFromFile(uuidFile.get().asFile) val firstExistingFile = mappingsFiles.get().files.firstOrNull { it.exists() } @@ -118,6 +115,9 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } task.asSentryCliExec() task.withSentryTelemetry(extension, sentryTelemetryProvider) + task.onlyIf("a ProGuard mapping file was produced") { + task.mappingsFiles.isPresent && task.mappingsFiles.get().files.any { it.exists() } + } } return uploadSentryProguardMappingsTask } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index 7509d9921..d058a8e77 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -374,9 +374,16 @@ class SentryPluginTest : @Test fun `does not include a UUID in the APK`() { - // isMinifyEnabled is disabled by default in debug builds - runner.appendArguments(":app:assembleDebug").build() + val build = runner.appendArguments(":app:assembleDebug").build() + assertEquals( + TaskOutcome.SKIPPED, + build.task(":app:generateSentryProguardUuidDebug")?.outcome, + ) + assertEquals( + TaskOutcome.SKIPPED, + build.task(":app:uploadSentryProguardMappingsDebug")?.outcome, + ) assertThrows(AssertionError::class.java) { verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false) } From 197263b426747289c1810b5212ec0e97d7cd3b72 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:46:44 +0000 Subject: [PATCH 02/11] fix(proguard): Skip absent mapping providers --- .../gradle/tasks/SentryGenerateProguardUuidTask.kt | 13 ++++++++----- .../tasks/SentryUploadProguardMappingsTask.kt | 2 +- .../android/gradle/integration/SentryPluginTest.kt | 10 ++-------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index c65885b5a..170a4aff3 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -37,11 +37,14 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { @get:Internal abstract val mappingFile: RegularFileProperty private fun hasMappingFile(): Boolean = - if (mappingFile.isPresent) { - mappingFile.get().asFile.exists() - } else { - fallbackMappingFiles.files.any { it.exists() } - } + runCatching { + if (mappingFile.isPresent) { + mappingFile.get().asFile.exists() + } else { + fallbackMappingFiles.files.any { it.exists() } + } + } + .getOrDefault(false) @TaskAction fun generateProperties() { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt index cc002c08d..64aa2ab84 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt @@ -116,7 +116,7 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { task.asSentryCliExec() task.withSentryTelemetry(extension, sentryTelemetryProvider) task.onlyIf("a ProGuard mapping file was produced") { - task.mappingsFiles.isPresent && task.mappingsFiles.get().files.any { it.exists() } + task.mappingsFiles.orNull?.files?.any { it.exists() } == true } } return uploadSentryProguardMappingsTask diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index d058a8e77..dc358083b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -376,14 +376,8 @@ class SentryPluginTest : fun `does not include a UUID in the APK`() { val build = runner.appendArguments(":app:assembleDebug").build() - assertEquals( - TaskOutcome.SKIPPED, - build.task(":app:generateSentryProguardUuidDebug")?.outcome, - ) - assertEquals( - TaskOutcome.SKIPPED, - build.task(":app:uploadSentryProguardMappingsDebug")?.outcome, - ) + assertEquals(TaskOutcome.SKIPPED, build.task(":app:generateSentryProguardUuidDebug")?.outcome) + assertEquals(TaskOutcome.SKIPPED, build.task(":app:uploadSentryProguardMappingsDebug")?.outcome) assertThrows(AssertionError::class.java) { verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false) } From 995eb9e23444bed65f03d0f40e700e501f434c24 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:11:56 +0000 Subject: [PATCH 03/11] test(proguard): Expect UUID task to skip without mapping --- .../gradle/integration/SentryPluginTest.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index dc358083b..ca8b7375e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -1120,26 +1120,24 @@ class SentryPluginTest : ) val result1 = runner.appendArguments(":app:testReleaseUnitTest").build() - val uuid1 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) assertFalse { "minifyReleaseWithR8" in result1.output } + assertEquals( + TaskOutcome.SKIPPED, + result1.task(":app:generateSentryProguardUuidRelease")?.outcome, + ) + assertThrows(AssertionError::class.java) { + verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) + } val result2 = runner.build() - val uuid2 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) assertFalse { "minifyReleaseWithR8" in result2.output } - - // UUIDs may differ between runs when minify doesn't run, because there's no mapping file - // to generate a deterministic hash from. The important assertion is that minify doesn't run. - // When minify DOES run (in production builds), the UUID will be deterministic. - assertNotEquals( - "00000000-0000-0000-0000-000000000000", - uuid1.toString(), - "UUID should be generated", - ) - assertNotEquals( - "00000000-0000-0000-0000-000000000000", - uuid2.toString(), - "UUID should be generated", + assertEquals( + TaskOutcome.SKIPPED, + result2.task(":app:generateSentryProguardUuidRelease")?.outcome, ) + assertThrows(AssertionError::class.java) { + verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) + } } @Test From 9cb1d03af2934348a5c9bd84c0a81c3df06a127a Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:56:50 +0000 Subject: [PATCH 04/11] test(proguard): Keep unit test assertion focused Co-Authored-By: Roman Zavarnitsyn --- .../sentry/android/gradle/integration/SentryPluginTest.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index ca8b7375e..18deba0b4 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -1125,9 +1125,6 @@ class SentryPluginTest : TaskOutcome.SKIPPED, result1.task(":app:generateSentryProguardUuidRelease")?.outcome, ) - assertThrows(AssertionError::class.java) { - verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) - } val result2 = runner.build() assertFalse { "minifyReleaseWithR8" in result2.output } @@ -1135,9 +1132,6 @@ class SentryPluginTest : TaskOutcome.SKIPPED, result2.task(":app:generateSentryProguardUuidRelease")?.outcome, ) - assertThrows(AssertionError::class.java) { - verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) - } } @Test From 7948d252a8c3d47457b4ff2700f571d4c711e01f Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:00:53 +0000 Subject: [PATCH 05/11] ref(proguard): Narrow mapping provider lookup Co-Authored-By: Roman Zavarnitsyn --- .../gradle/tasks/SentryGenerateProguardUuidTask.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index 170a4aff3..94f0c865d 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -37,14 +37,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { @get:Internal abstract val mappingFile: RegularFileProperty private fun hasMappingFile(): Boolean = - runCatching { - if (mappingFile.isPresent) { - mappingFile.get().asFile.exists() - } else { - fallbackMappingFiles.files.any { it.exists() } - } - } - .getOrDefault(false) + mappingFile.orNull?.asFile?.exists() ?: fallbackMappingFiles.files.any { it.exists() } @TaskAction fun generateProperties() { From e9c8704cf33c4b416b2b156d28a7bd1e591a6340 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:08:42 +0000 Subject: [PATCH 06/11] fix(proguard): Handle missing mapping provider value --- .../gradle/tasks/SentryGenerateProguardUuidTask.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index 94f0c865d..feebff966 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -36,8 +36,15 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { // Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact @get:Internal abstract val mappingFile: RegularFileProperty - private fun hasMappingFile(): Boolean = - mappingFile.orNull?.asFile?.exists() ?: fallbackMappingFiles.files.any { it.exists() } + private fun hasMappingFile(): Boolean { + val mapping = + try { + mappingFile.orNull + } catch (_: IllegalStateException) { + return false + } + return mapping?.asFile?.exists() ?: fallbackMappingFiles.files.any { it.exists() } + } @TaskAction fun generateProperties() { From c959046f649e6a2278756c2665b4b20408c721c8 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:12:27 +0000 Subject: [PATCH 07/11] fix(proguard): Document missing mapping provider Co-Authored-By: Roman Zavarnitsyn --- .../android/gradle/tasks/SentryGenerateProguardUuidTask.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index feebff966..a001813a7 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -41,6 +41,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { try { mappingFile.orNull } catch (_: IllegalStateException) { + // AGP uses a provider with no value for non-minified variants; treat it as no mapping. return false } return mapping?.asFile?.exists() ?: fallbackMappingFiles.files.any { it.exists() } From ca1cea542d34bed9da95cededead79cc80f383bb Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:20:17 +0000 Subject: [PATCH 08/11] fix(proguard): Reuse safe mapping lookup --- .../tasks/SentryGenerateProguardUuidTask.kt | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index a001813a7..bba249a7e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -5,6 +5,7 @@ import io.sentry.android.gradle.telemetry.SentryTelemetryService import io.sentry.android.gradle.telemetry.withSentryTelemetry import io.sentry.android.gradle.util.contentHash import io.sentry.android.gradle.util.info +import java.io.File import java.util.UUID import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection @@ -36,15 +37,16 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { // Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact @get:Internal abstract val mappingFile: RegularFileProperty - private fun hasMappingFile(): Boolean { + private fun findMappingFile(): File? { val mapping = try { mappingFile.orNull } catch (_: IllegalStateException) { // AGP uses a provider with no value for non-minified variants; treat it as no mapping. - return false + return null } - return mapping?.asFile?.exists() ?: fallbackMappingFiles.files.any { it.exists() } + return mapping?.asFile?.takeIf { it.exists() } + ?: fallbackMappingFiles.files.firstOrNull { it.exists() } } @TaskAction @@ -52,14 +54,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { val outputDir = output.get().asFile outputDir.mkdirs() - // Prefer mappingFile (set via toListenTo on AGP 8.3+) over fallbackMappingFiles - val mappingFile = - if (mappingFile.isPresent) { - mappingFile.get().asFile.takeIf { it.exists() } - } else { - // Fallback for AGP < 8.3: use conventional file paths - fallbackMappingFiles.files.firstOrNull { it.exists() } - } + val mappingFile = findMappingFile() val uuid = mappingFile?.let { UUID.nameUUIDFromBytes(it.contentHash().toByteArray()) } @@ -94,7 +89,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { task.fallbackMappingFiles.from(proguardMappingFile) } task.outputs.upToDateWhen { false } - task.onlyIf("a ProGuard mapping file was produced") { task.hasMappingFile() } + task.onlyIf("a ProGuard mapping file was produced") { task.findMappingFile() != null } } return generateUuidTask } From 0379c4821883e2c7c35bd390984379336ed9e63e Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:26:13 +0000 Subject: [PATCH 09/11] fix(proguard): Handle missing Gradle provider values --- .../android/gradle/tasks/SentryGenerateProguardUuidTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index bba249a7e..0a6a0f693 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -7,6 +7,7 @@ import io.sentry.android.gradle.util.contentHash import io.sentry.android.gradle.util.info import java.io.File import java.util.UUID +import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory @@ -41,7 +42,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { val mapping = try { mappingFile.orNull - } catch (_: IllegalStateException) { + } catch (_: GradleException) { // AGP uses a provider with no value for non-minified variants; treat it as no mapping. return null } From 8710499ebb455a1b4844256c341217168a40c859 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:48:26 +0000 Subject: [PATCH 10/11] fix(proguard): Clean stale mapping metadata Co-Authored-By: Roman Zavarnitsyn --- .../tasks/SentryGenerateProguardUuidTask.kt | 36 ++++++++++--------- .../tasks/SentryUploadProguardMappingsTask.kt | 5 +-- .../gradle/integration/SentryPluginTest.kt | 6 ++-- .../SentryGenerateProguardUuidTaskTest.kt | 28 +++++++++++++++ 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt index 0a6a0f693..70caf0f87 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt @@ -38,17 +38,17 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { // Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact @get:Internal abstract val mappingFile: RegularFileProperty - private fun findMappingFile(): File? { - val mapping = - try { - mappingFile.orNull - } catch (_: GradleException) { - // AGP uses a provider with no value for non-minified variants; treat it as no mapping. - return null - } - return mapping?.asFile?.takeIf { it.exists() } - ?: fallbackMappingFiles.files.firstOrNull { it.exists() } - } + internal fun findMappingFile(): File? = + try { + mappingFile.orNull?.asFile?.takeIf { it.exists() } + ?: fallbackMappingFiles.files.firstOrNull { it.exists() } + } catch (_: GradleException) { + // AGP uses a provider with no value for non-minified variants; treat it as no mapping. + null + } catch (_: IllegalStateException) { + // Gradle may realize a missing artifact provider before wrapping it in a GradleException. + null + } @TaskAction fun generateProperties() { @@ -56,11 +56,16 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { outputDir.mkdirs() val mappingFile = findMappingFile() + val outputFile = outputFile.get().asFile + if (mappingFile == null) { + // The task may have produced a UUID in an earlier minified build. Remove it so a later + // non-minified build cannot inject or upload that stale UUID. + outputFile.delete() + return + } - val uuid = - mappingFile?.let { UUID.nameUUIDFromBytes(it.contentHash().toByteArray()) } - ?: UUID.randomUUID() - outputFile.get().asFile.writer().use { writer -> + val uuid = UUID.nameUUIDFromBytes(mappingFile.contentHash().toByteArray()) + outputFile.writer().use { writer -> writer.appendLine("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=$uuid") } @@ -90,7 +95,6 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { task.fallbackMappingFiles.from(proguardMappingFile) } task.outputs.upToDateWhen { false } - task.onlyIf("a ProGuard mapping file was produced") { task.findMappingFile() != null } } return generateUuidTask } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt index 64aa2ab84..7411d4bb2 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt @@ -115,8 +115,9 @@ abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } task.asSentryCliExec() task.withSentryTelemetry(extension, sentryTelemetryProvider) - task.onlyIf("a ProGuard mapping file was produced") { - task.mappingsFiles.orNull?.files?.any { it.exists() } == true + task.onlyIf("a ProGuard mapping file and matching UUID were produced") { + task.uuidFile.asFile.orNull?.exists() == true && + task.mappingsFiles.orNull?.files?.any { it.exists() } == true } } return uploadSentryProguardMappingsTask diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt index 18deba0b4..9568f4246 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTest.kt @@ -376,7 +376,7 @@ class SentryPluginTest : fun `does not include a UUID in the APK`() { val build = runner.appendArguments(":app:assembleDebug").build() - assertEquals(TaskOutcome.SKIPPED, build.task(":app:generateSentryProguardUuidDebug")?.outcome) + assertEquals(TaskOutcome.SUCCESS, build.task(":app:generateSentryProguardUuidDebug")?.outcome) assertEquals(TaskOutcome.SKIPPED, build.task(":app:uploadSentryProguardMappingsDebug")?.outcome) assertThrows(AssertionError::class.java) { verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false) @@ -1122,14 +1122,14 @@ class SentryPluginTest : val result1 = runner.appendArguments(":app:testReleaseUnitTest").build() assertFalse { "minifyReleaseWithR8" in result1.output } assertEquals( - TaskOutcome.SKIPPED, + TaskOutcome.SUCCESS, result1.task(":app:generateSentryProguardUuidRelease")?.outcome, ) val result2 = runner.build() assertFalse { "minifyReleaseWithR8" in result2.output } assertEquals( - TaskOutcome.SKIPPED, + TaskOutcome.SUCCESS, result2.task(":app:generateSentryProguardUuidRelease")?.outcome, ) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTaskTest.kt index f8e0bcb5a..82d1f9403 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTaskTest.kt @@ -4,6 +4,7 @@ import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask.Companion.S import io.sentry.android.gradle.util.PropertiesUtil import java.io.File import java.util.UUID +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue import org.gradle.api.Project @@ -22,6 +23,12 @@ class SentryGenerateProguardUuidTaskTest { SentryGenerateProguardUuidTask::class.java, ) { it.output.set(project.layout.buildDirectory.dir("dummy/folder/")) + it.fallbackMappingFiles.from( + File(project.buildDir, "mapping.txt").also { file -> + file.parentFile.mkdirs() + file.writeText("mapping") + } + ) } task.get().generateProperties() @@ -35,6 +42,27 @@ class SentryGenerateProguardUuidTaskTest { UUID.fromString(uuid) } + @Test + fun `generate proguard UUID removes stale output when no mapping exists`() { + val project = createProject() + val task = + project.tasks.register( + "testRemoveStaleProguardUuid", + SentryGenerateProguardUuidTask::class.java, + ) { + it.output.set(project.layout.buildDirectory.dir("dummy/folder/")) + } + val staleOutput = + File(project.buildDir, "dummy/folder/sentry-proguard-uuid.properties").also { + it.parentFile.mkdirs() + it.writeText("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=stale") + } + + task.get().generateProperties() + + assertFalse(staleOutput.exists()) + } + private fun createProject(): Project { with(ProjectBuilder.builder().build()) { plugins.apply("io.sentry.android.gradle") From 0c89cd38ebf602a1747c15d95e46371313d935b9 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:55:51 +0000 Subject: [PATCH 11/11] test(proguard): Provide mappings for UUID metadata tests --- .../SentryGenerateDebugMetaPropertiesTaskTest.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTaskTest.kt index a9a9ee3a0..752b39974 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTaskTest.kt @@ -9,6 +9,8 @@ import java.io.File import kotlin.test.assertNotNull import kotlin.test.assertTrue import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider import org.gradle.testfixtures.ProjectBuilder import org.junit.Test @@ -36,7 +38,7 @@ class SentryGenerateDebugMetaPropertiesTaskTest { project.extensions.findByName("sentry") as SentryPluginExtension, null, project.layout.buildDirectory.dir("dummy/folder/"), - null, + createMappingFileProvider(project), "test", ) val idGenerationTasks = listOf(bundleIdTask, proguardIdTask) @@ -81,7 +83,7 @@ class SentryGenerateDebugMetaPropertiesTaskTest { project.extensions.findByName("sentry") as SentryPluginExtension, null, project.layout.buildDirectory.dir("dummy/folder/"), - null, + createMappingFileProvider(project), "test", ) val idGenerationTasks = listOf(bundleIdTask, proguardIdTask) @@ -121,6 +123,16 @@ class SentryGenerateDebugMetaPropertiesTaskTest { assertNotNull(bundleId2) } + private fun createMappingFileProvider(project: Project): Provider = + project.provider { + project.files( + File(project.buildDir, "mapping.txt").also { + it.parentFile.mkdirs() + it.writeText("mapping") + } + ) + } + private fun createProject(): Project { with(ProjectBuilder.builder().build()) { plugins.apply("io.sentry.android.gradle")