Skip to content

Commit bcf7f41

Browse files
romtsnrunningcode
andauthored
fix(proguard): Fix compatibility with Kotlin/Compose 2.3.0 (#1054)
* fix(proguard): Generate a new UUID instead of static hash for non-existing mappings * Properly wire generate uuid task with mapping files * Changelog * fix(proguard): Fix compatibility with Kotlin/Compose 2.3.0 * Changelog * Fix test * Fix test did not have tests enabled * Fix test with a specific kotlin version and not rely on AGP pulling it in * Update plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP83Compat.kt Co-authored-by: Nelson Osacky <nelson.osacky@sentry.io> * More explicit task names --------- Co-authored-by: Nelson Osacky <nelson.osacky@sentry.io>
1 parent 02f1374 commit bcf7f41

14 files changed

Lines changed: 314 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This version introduces sentry-cli 3.0.0. Sentry-cli 3.0.0 and above only offici
2828
- Fix `Modifier.sentryTag()` not found warning ([#997](https://github.com/getsentry/sentry-android-gradle-plugin/pull/997))
2929
- Fix reproducible builds by writing `sentry-debug-meta.properties` without timestamps ([#876](https://github.com/getsentry/sentry-android-gradle-plugin/pull/876))
3030
- Skip generating `sentry-debug-meta.properties` when `includeProguardMapping` and `includeSourceContext` are disabled ([#1047](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1047))
31+
- Fix proguard mapping tasks compatibility with Kotlin/Compose 2.3.0 ([#1054](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1054))
3132
- Include root project check in preMerge task ([#1006](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1006))
3233
- Set SENTRY_PIPELINE environment variable for all sentry-cli invocations ([#1036](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1036))
3334
- Stop passing deprecated parameters to sentry-cli ([#1015](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1015))

plugin-build/src/main/kotlin/io/sentry/android/gradle/AGP74Compat.kt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import com.android.build.api.variant.CanMinifyCode
1111
import com.android.build.api.variant.Variant
1212
import com.android.build.api.variant.impl.ApplicationVariantImpl
1313
import com.android.build.api.variant.impl.VariantImpl
14+
import io.sentry.android.gradle.SentryTasksProvider.getComposeMappingMergeTask
15+
import io.sentry.android.gradle.SentryTasksProvider.getMinifyTask
16+
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
1417
import io.sentry.gradle.common.SentryVariant
1518
import io.sentry.gradle.common.filterBuildConfig
1619
import org.gradle.api.Project
@@ -48,9 +51,7 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant {
4851
val artifacts = variant.artifacts
4952

5053
override fun mappingFileProvider(project: Project): Provider<FileCollection> =
51-
project.provider {
52-
project.files(variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE))
53-
}
54+
variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE).map { project.files(it) }
5455

5556
override fun sources(
5657
project: Project,
@@ -77,6 +78,30 @@ data class AndroidVariant74(private val variant: Variant) : SentryVariant {
7778
}
7879
}
7980

81+
override fun wireMappingFileToUuidTask(
82+
project: Project,
83+
task: TaskProvider<out SentryGenerateProguardUuidTask>,
84+
variantName: String,
85+
dexguardEnabled: Boolean,
86+
) {
87+
// we need to wait for project evaluation to have all tasks available, otherwise the new
88+
// AndroidComponentsExtension is configured too early to look up for the tasks
89+
project.afterEvaluate {
90+
val minifyTask = getMinifyTask(project, variantName, dexguardEnabled)
91+
92+
// we just hack ourselves into the Proguard/R8/DexGuard task's doLast.
93+
minifyTask?.configure { it.finalizedBy(task) }
94+
95+
// When Kotlin Compose compiler is enabled, the final mapping file is written by
96+
// mergeStagingReleaseComposeMapping task, not by R8 directly. We need to hook into that task
97+
// as well to ensure the mapping file exists when our UUID task runs.
98+
val composeMappingMergeTask = getComposeMappingMergeTask(project, variantName)
99+
composeMappingMergeTask?.let { composeTask ->
100+
task.configure { generateUuidTask -> generateUuidTask.mustRunAfter(composeTask) }
101+
}
102+
}
103+
}
104+
80105
fun <T : Task> assetsWiredWithDirectories(
81106
task: TaskProvider<T>,
82107
inputDir: (T) -> DirectoryProperty,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package io.sentry.android.gradle
4+
5+
import com.android.build.api.artifact.SingleArtifact
6+
import com.android.build.api.variant.Variant
7+
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
8+
import io.sentry.gradle.common.SentryVariant
9+
import org.gradle.api.Project
10+
import org.gradle.api.tasks.TaskProvider
11+
12+
/**
13+
* Compatibility layer for AGP 8.3+.
14+
*
15+
* This class provides access to the `toListenTo` API, which was introduced in AGP 8.3. It allows
16+
* proper task wiring for artifacts without forcing their production.
17+
*/
18+
data class AndroidVariant83(
19+
private val variant: Variant,
20+
private val delegate: SentryVariant = AndroidVariant74(variant),
21+
) : SentryVariant by delegate {
22+
23+
override val isDebuggable: Boolean = variant.debuggable
24+
25+
/**
26+
* Wires the [SentryGenerateProguardUuidTask] to listen to the obfuscation mapping file artifact.
27+
*
28+
* When minification runs, this ensures the UUID task executes after the mapping file is produced,
29+
* and receives the mapping file location via [SentryGenerateProguardUuidTask.mappingFile].
30+
*
31+
* When minification doesn't run, the UUID task won't be triggered via this wiring (it would only
32+
* run if explicitly depended upon by another task).
33+
*/
34+
override fun wireMappingFileToUuidTask(
35+
project: Project,
36+
task: TaskProvider<out SentryGenerateProguardUuidTask>,
37+
variantName: String,
38+
dexguardEnabled: Boolean,
39+
) {
40+
if (!dexguardEnabled) {
41+
variant.artifacts
42+
.use(task)
43+
.wiredWith(SentryGenerateProguardUuidTask::mappingFile)
44+
.toListenTo(SingleArtifact.OBFUSCATION_MAPPING_FILE)
45+
} else {
46+
// when dexguard is enabled we still want to go the old way, because AGP API does not apply
47+
delegate.wireMappingFileToUuidTask(project, task, variantName, true)
48+
}
49+
}
50+
}

plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ import io.sentry.android.gradle.tasks.SentryUploadProguardMappingsTask
3232
import io.sentry.android.gradle.tasks.configureNativeSymbolsTask
3333
import io.sentry.android.gradle.tasks.dependencies.SentryExternalDependenciesReportTaskV2
3434
import io.sentry.android.gradle.telemetry.SentryTelemetryService
35+
import io.sentry.android.gradle.util.AgpVersions
3536
import io.sentry.android.gradle.util.GroovyCompat
3637
import io.sentry.android.gradle.util.SentryModules
3738
import io.sentry.android.gradle.util.SentryPluginUtils.isMinificationEnabled
3839
import io.sentry.android.gradle.util.SentryPluginUtils.isVariantAllowed
3940
import io.sentry.android.gradle.util.collectModules
4041
import io.sentry.android.gradle.util.hookWithAssembleTasks
41-
import io.sentry.android.gradle.util.hookWithMinifyTasks
4242
import java.io.File
4343
import org.gradle.api.Project
4444
import org.gradle.api.file.Directory
@@ -353,18 +353,24 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
353353
sentryOrg: String?,
354354
sentryProject: String?,
355355
): TaskProvider<SentryGenerateProguardUuidTask>? {
356-
val variant = AndroidVariant74(this)
356+
val variant =
357+
if (AgpVersions.isAGP83(AgpVersions.CURRENT)) {
358+
AndroidVariant83(this)
359+
} else {
360+
AndroidVariant74(this)
361+
}
357362
val sentryProps = getPropertiesFilePath(project, variant)
358363
val dexguardEnabled = extension.dexguardEnabled.get()
359364
val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled)
360365

361366
if (isMinifyEnabled && extension.includeProguardMapping.get()) {
367+
val mappings = getMappingFileProvider(project, variant, dexguardEnabled)
362368
val generateUuidTask =
363369
SentryGenerateProguardUuidTask.register(
364370
project = project,
365371
extension,
366372
sentryTelemetryProvider,
367-
proguardMappingFile = getMappingFileProvider(project, variant, dexguardEnabled),
373+
proguardMappingFile = mappings,
368374
taskSuffix = name.capitalized,
369375
output = paths.proguardUuidDir,
370376
)
@@ -378,7 +384,7 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
378384
cliExecutable = cliExecutable,
379385
generateUuidTask = generateUuidTask,
380386
sentryProperties = sentryProps,
381-
mappingFiles = getMappingFileProvider(project, variant, dexguardEnabled),
387+
mappingFiles = mappings,
382388
autoUploadProguardMapping = extension.autoUploadProguardMapping,
383389
sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org,
384390
sentryProject = sentryProject?.let { project.provider { it } } ?: extension.projectName,
@@ -387,8 +393,9 @@ private fun ApplicationVariant.configureProguardMappingsTasks(
387393
taskSuffix = name.capitalized,
388394
)
389395

390-
generateUuidTask.hookWithMinifyTasks(
396+
variant.wireMappingFileToUuidTask(
391397
project,
398+
generateUuidTask,
392399
name,
393400
dexguardEnabled && GroovyCompat.isDexguardEnabledForVariant(project, name),
394401
)

plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryTasksProvider.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ internal object SentryTasksProvider {
4141
return project.findTask(tasks)
4242
}
4343

44+
/**
45+
* Returns the Compose mapping merge task for the given project and variant. This task is
46+
* responsible for merging Compose mapping data with the R8 mapping file. The final mapping file
47+
* at build/outputs/mapping/<variant>/mapping.txt is written by this task, not by R8 directly.
48+
*
49+
* https://github.com/JetBrains/kotlin/blob/b73fc4e8afb382976646fac728e717fd0b1d1c9c/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/internal/ComposeAgpMappingFile.kt#L84-L87
50+
*
51+
* @return the task or null if the Kotlin Compose plugin is not applied or the task doesn't exist
52+
*/
53+
@JvmStatic
54+
fun getComposeMappingMergeTask(project: Project, variantName: String): TaskProvider<Task>? =
55+
project.findTask(listOf("merge${variantName.capitalized}ComposeMapping"))
56+
4457
/**
4558
* Returns the pre bundle task for the given project and variant.
4659
*

plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.gradle.api.file.ConfigurableFileCollection
1111
import org.gradle.api.file.Directory
1212
import org.gradle.api.file.FileCollection
1313
import org.gradle.api.file.RegularFile
14+
import org.gradle.api.file.RegularFileProperty
1415
import org.gradle.api.provider.Provider
1516
import org.gradle.api.tasks.Internal
1617
import org.gradle.api.tasks.TaskAction
@@ -29,16 +30,29 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
2930
override val outputFile: Provider<RegularFile>
3031
get() = output.file(SENTRY_UUID_OUTPUT)
3132

32-
@get:Internal abstract val proguardMappingFiles: ConfigurableFileCollection
33+
// Used by AGP < 8.3 with conventional file paths
34+
@get:Internal abstract val fallbackMappingFiles: ConfigurableFileCollection
35+
36+
// Used by AGP 8.3+ with toListenTo API - this property is wired to the mapping artifact
37+
@get:Internal abstract val mappingFile: RegularFileProperty
3338

3439
@TaskAction
3540
fun generateProperties() {
3641
val outputDir = output.get().asFile
3742
outputDir.mkdirs()
3843

39-
val proguardMappingFileHash =
40-
proguardMappingFiles.files.joinToString { if (it.isFile) it.contentHash() else STATIC_HASH }
41-
val uuid = UUID.nameUUIDFromBytes(proguardMappingFileHash.toByteArray())
44+
// Prefer mappingFile (set via toListenTo on AGP 8.3+) over fallbackMappingFiles
45+
val mappingFile =
46+
if (mappingFile.isPresent) {
47+
mappingFile.get().asFile.takeIf { it.exists() }
48+
} else {
49+
// Fallback for AGP < 8.3: use conventional file paths
50+
fallbackMappingFiles.files.firstOrNull { it.exists() }
51+
}
52+
53+
val uuid =
54+
mappingFile?.let { UUID.nameUUIDFromBytes(it.contentHash().toByteArray()) }
55+
?: UUID.randomUUID()
4256
outputFile.get().asFile.writer().use { writer ->
4357
writer.appendLine("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=$uuid")
4458
}
@@ -47,7 +61,6 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
4761
}
4862

4963
companion object {
50-
internal const val STATIC_HASH = "<hash>"
5164
internal const val SENTRY_UUID_OUTPUT = "sentry-proguard-uuid.properties"
5265
const val SENTRY_PROGUARD_MAPPING_UUID_PROPERTY = "io.sentry.ProguardUuids"
5366

@@ -67,7 +80,7 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
6780
output?.let { task.output.set(it) }
6881
task.withSentryTelemetry(extension, sentryTelemetryProvider)
6982
if (proguardMappingFile != null) {
70-
task.proguardMappingFiles.from(proguardMappingFile)
83+
task.fallbackMappingFiles.from(proguardMappingFile)
7184
}
7285
task.outputs.upToDateWhen { false }
7386
}

plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import org.gradle.util.GradleVersion
77
internal object AgpVersions {
88
val CURRENT: SemVer = SemVer.parse(Version.ANDROID_GRADLE_PLUGIN_VERSION)
99
val VERSION_7_4_0: SemVer = SemVer.parse("7.4.0-rc01")
10+
val VERSION_8_3_0: SemVer = SemVer.parse("8.3.0")
1011

1112
fun isAGP74(current: SemVer) = current >= VERSION_7_4_0
13+
14+
fun isAGP83(current: SemVer) = current >= VERSION_8_3_0
1215
}
1316

1417
internal object GradleVersions {

plugin-build/src/main/kotlin/io/sentry/android/gradle/util/tasks.kt

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,12 @@ package io.sentry.android.gradle.util
33
import io.sentry.android.gradle.SentryTasksProvider.getAssembleTaskProvider
44
import io.sentry.android.gradle.SentryTasksProvider.getBundleTask
55
import io.sentry.android.gradle.SentryTasksProvider.getInstallTaskProvider
6-
import io.sentry.android.gradle.SentryTasksProvider.getMinifyTask
7-
import io.sentry.android.gradle.SentryTasksProvider.getPackageBundleTask
8-
import io.sentry.android.gradle.SentryTasksProvider.getPackageProvider
9-
import io.sentry.android.gradle.SentryTasksProvider.getPreBundleTask
106
import io.sentry.android.gradle.util.SentryPluginUtils.withLogging
117
import io.sentry.gradle.common.SentryVariant
128
import org.gradle.api.Project
139
import org.gradle.api.Task
1410
import org.gradle.api.tasks.TaskProvider
1511

16-
fun TaskProvider<out Task>.hookWithMinifyTasks(
17-
project: Project,
18-
variantName: String,
19-
dexguardEnabled: Boolean,
20-
) {
21-
// we need to wait for project evaluation to have all tasks available, otherwise the new
22-
// AndroidComponentsExtension is configured too early to look up for the tasks
23-
project.afterEvaluate {
24-
val minifyTask = getMinifyTask(project, variantName, dexguardEnabled)
25-
26-
// we just hack ourselves into the Proguard/R8/DexGuard task's doLast.
27-
minifyTask?.configure { it.finalizedBy(this) }
28-
}
29-
}
30-
31-
fun TaskProvider<out Task>.hookWithPackageTasks(project: Project, variant: SentryVariant) {
32-
val variantName = variant.name
33-
val preBundleTaskProvider =
34-
withLogging(project.logger, "preBundleTask") { getPreBundleTask(project, variantName) }
35-
val packageBundleTaskProvider =
36-
withLogging(project.logger, "packageBundleTask") { getPackageBundleTask(project, variantName) }
37-
38-
// To include proguard uuid file into aab, run before bundle task.
39-
preBundleTaskProvider?.configure { task -> task.dependsOn(this) }
40-
// The package task will only be executed if the generateUuidTask has already been executed.
41-
getPackageProvider(variant)?.configure { task -> task.dependsOn(this) }
42-
43-
// App bundle has different package task
44-
packageBundleTaskProvider?.configure { task -> task.dependsOn(this) }
45-
}
46-
4712
fun TaskProvider<out Task>.hookWithAssembleTasks(project: Project, variant: SentryVariant) {
4813
// we need to wait for project evaluation to have all tasks available, otherwise the new
4914
// AndroidComponentsExtension is configured too early to look up for the tasks

plugin-build/src/main/kotlin/io/sentry/gradle/common/JavaVariant.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.gradle.common
22

3+
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
34
import org.gradle.api.DefaultTask
45
import org.gradle.api.Project
56
import org.gradle.api.Task
@@ -38,4 +39,11 @@ data class JavaVariant(val project: Project, val javaExtension: JavaPluginExtens
3839
(javaDirs + additionalSources.get()).filterBuildConfig().toSet()
3940
}
4041
}
42+
43+
override fun wireMappingFileToUuidTask(
44+
project: Project,
45+
task: TaskProvider<out SentryGenerateProguardUuidTask>,
46+
variantName: String,
47+
dexguardEnabled: Boolean,
48+
) = Unit
4149
}

plugin-build/src/main/kotlin/io/sentry/gradle/common/SentryVariant.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.gradle.common
22

3+
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
34
import org.gradle.api.Project
45
import org.gradle.api.Task
56
import org.gradle.api.file.Directory
@@ -33,6 +34,13 @@ interface SentryVariant {
3334
project: Project,
3435
additionalSources: Provider<out Collection<Directory>>,
3536
): Provider<out Collection<Directory>>
37+
38+
fun wireMappingFileToUuidTask(
39+
project: Project,
40+
task: TaskProvider<out SentryGenerateProguardUuidTask>,
41+
variantName: String,
42+
dexguardEnabled: Boolean,
43+
)
3644
}
3745

3846
fun Collection<Directory>.filterBuildConfig(): Collection<Directory> = filterNot {

0 commit comments

Comments
 (0)