Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the Debug UUID a deterministic value based on the proguard mapping file. #750

Merged
merged 10 commits into from
Aug 7, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- Fix plugin for spring-dependency-management 1.1.6 ([#741](https://github.com/getsentry/sentry-android-gradle-plugin/pull/741))
- Make `SentryUploadNativeSymbolsTask` configuration-cache compatible ([#747](https://github.com/getsentry/sentry-android-gradle-plugin/pull/747))
- Fix `permission denied` error when extracting sentry-cli concurrently ([#748](https://github.com/getsentry/sentry-android-gradle-plugin/pull/748))
- Make `SentryGenerateProguardUuidTask` produce deterministic output based on the mapping file contents ([#750](https://github.com/getsentry/sentry-android-gradle-plugin/pull/748))
- This disables caching for the `SentryGenerateProguardUuidTask` task in favour of deterministic UUID generation. The task will always run but will always produce the same UUID for the same mapping file.

### Dependencies

Expand Down
2 changes: 1 addition & 1 deletion examples/android-instrumentation-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ sentry {
debug.set(true)
autoUploadProguardMapping.set(CI.canAutoUpload())

includeSourceContext.set(true)
includeSourceContext.set(CI.canAutoUpload())
autoUploadSourceContext.set(CI.canAutoUpload())
additionalSourceDirsForSourceContext.set(setOf("src/custom/java"))

Expand Down
2 changes: 1 addition & 1 deletion examples/multi-module-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ tasks.withType<KotlinCompile> {
sentry {
debug.set(true)
telemetry.set(false)
includeSourceContext.set(true)
includeSourceContext.set(CI.canAutoUpload())
additionalSourceDirsForSourceContext.set(setOf("testsrc"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ tasks.withType<KotlinCompile> {
sentry {
debug.set(true)
telemetry.set(false)
includeSourceContext.set(true)
includeSourceContext.set(CI.canAutoUpload())
additionalSourceDirsForSourceContext.set(setOf("testsrc"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ tasks.withType<KotlinCompile> {

sentry {
debug.set(true)
includeSourceContext.set(true)
includeSourceContext.set(CI.canAutoUpload())
additionalSourceDirsForSourceContext.set(setOf("testsrc"))
}
2 changes: 1 addition & 1 deletion examples/spring-boot-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ tasks.withType<KotlinCompile> {

sentry {
debug.set(true)
includeSourceContext.set(true)
includeSourceContext.set(CI.canAutoUpload())
additionalSourceDirsForSourceContext.set(setOf("testsrc"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@ 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.util.Properties
import java.util.UUID
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.work.DisableCachingByDefault

@CacheableTask
@DisableCachingByDefault
abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {

init {
Expand All @@ -30,22 +28,19 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
@get:Internal
override val outputFile: Provider<RegularFile> get() = output.file(SENTRY_UUID_OUTPUT)

@get:Input
abstract val proguardMappingFileHash: Property<String>
@get:Internal
abstract val proguardMappingFiles: ConfigurableFileCollection

@TaskAction
fun generateProperties() {
val outputDir = output.get().asFile
outputDir.mkdirs()

val uuid = UUID.randomUUID()

val props = Properties().also {
it.setProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY, uuid.toString())
}

val proguardMappingFileHash = proguardMappingFiles.files
.joinToString { if (it.isFile) it.contentHash() else STATIC_HASH }
val uuid = UUID.nameUUIDFromBytes(proguardMappingFileHash.toByteArray())
outputFile.get().asFile.writer().use { writer ->
props.store(writer, "")
writer.appendLine("$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY=$uuid")
}

logger.info {
Expand All @@ -72,13 +67,10 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() {
) { task ->
output?.let { task.output.set(it) }
task.withSentryTelemetry(extension, sentryTelemetryProvider)
task.proguardMappingFileHash.set(
proguardMappingFile?.map {
it.files.joinToString { file ->
if (file.exists()) file.contentHash() else STATIC_HASH
}
} ?: project.provider { STATIC_HASH }
)
if (proguardMappingFile != null) {
task.proguardMappingFiles.from(proguardMappingFile)
}
task.outputs.upToDateWhen { false }
}
return generateUuidTask
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ class SentryPluginConfigurationCacheTest :

@Test
fun `sentry-cli is recovered when clean is executed before assemble`() {
assumeThat(
"Sentry native symbols upload only supported when SENTRY_AUTH_TOKEN is present",
System.getenv("SENTRY_AUTH_TOKEN").isNullOrEmpty(),
`is`(false)
)
// configuration cache doesn't seem to work well on older Gradle/AGP combinations
// producing the following output:
//
Expand Down Expand Up @@ -261,6 +266,11 @@ class SentryPluginConfigurationCacheTest :

@Test
fun `native symbols upload task respects configuration cache`() {
assumeThat(
"Sentry native symbols upload only supported when SENTRY_AUTH_TOKEN is present",
System.getenv("SENTRY_AUTH_TOKEN").isNullOrEmpty(),
`is`(false)
)
assumeThat(
"SentryUploadNativeSymbolsTask only supports " +
"configuration cache from Gradle 7.5 onwards",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentry.android.gradle.integration

import io.sentry.android.gradle.util.GradleVersions
import io.sentry.android.gradle.util.SkipOnForksRule
import io.sentry.android.gradle.verifySourceBundleContents
import io.sentry.android.gradle.withDummyCustomFile
import io.sentry.android.gradle.withDummyJavaFile
Expand All @@ -11,11 +12,15 @@ import org.gradle.testkit.runner.TaskOutcome.SKIPPED
import org.gradle.util.GradleVersion
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assume.assumeThat
import org.junit.Rule
import org.junit.Test

class SentryPluginSourceContextNonAndroidTest :
BaseSentryNonAndroidPluginTest(GradleVersion.current().version) {

@get:Rule
val skipOnForksRule = SkipOnForksRule()

@Test
fun `skips bundle and upload tasks if no sources`() {
appBuildFile.writeText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.android.gradle.integration
import io.sentry.BuildConfig
import io.sentry.android.gradle.SentryCliProvider
import io.sentry.android.gradle.util.GradleVersions
import io.sentry.android.gradle.util.SkipOnForksRule
import io.sentry.android.gradle.verifySourceBundleContents
import io.sentry.android.gradle.withDummyComposeFile
import io.sentry.android.gradle.withDummyCustomFile
Expand All @@ -18,11 +19,15 @@ import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
import org.gradle.util.GradleVersion
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assume.assumeThat
import org.junit.Rule
import org.junit.Test

class SentryPluginSourceContextTest :
BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) {

@get:Rule
val skipOnForksRule = SkipOnForksRule()

@Test
fun `skips bundle and upload tasks if no sources`() {
appBuildFile.writeText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import org.gradle.testkit.runner.TaskOutcome
import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE
import org.gradle.util.GradleVersion
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThrows
Expand Down Expand Up @@ -46,6 +45,76 @@ class SentryPluginTest :
assertTrue(configuredTasks.isEmpty(), configuredTasks.joinToString("\n"))
}

@Test
fun `generates the same UUID when mapping file stays the same with rerun tasks`() {
runner.appendArguments(":app:assembleRelease")

val sourceDir = File(testProjectDir.root, "app/src/main/java/com/example").apply {
mkdirs()
}

val manifest = File(testProjectDir.root, "app/src/main/AndroidManifest.xml")
manifest.writeText(
"""
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".MainActivity"/>
</application>
</manifest>
""".trimIndent()
)

val sourceFile = File(sourceDir, "MainActivity.java")
sourceFile.createNewFile()
sourceFile.writeText(
"""
package com.example;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hello();
}

public void hello() {
System.out.println("Hello");
}
}
""".trimIndent()
)

runner.appendArguments(":app:assembleRelease").build()
val uuid1 = verifyProguardUuid(testProjectDir.root)

sourceFile.writeText(
"""
package com.example;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hello();
}

public void hello() {
System.out.println("Hello2");
}
}
""".trimIndent()
)

runner.appendArguments(":app:assembleRelease", "--rerun-tasks").build()
val uuid2 = verifyProguardUuid(testProjectDir.root)

assertEquals(uuid1, uuid2)
}

@Test
fun `does not regenerate UUID every build`() {
runner.appendArguments(":app:assembleRelease")
Expand Down Expand Up @@ -198,15 +267,9 @@ class SentryPluginTest :
""".trimIndent()
)

val build = runner.appendArguments(":app:assembleRelease").build()
runner.appendArguments(":app:assembleRelease").build()
val uuid2 = verifyProguardUuid(testProjectDir.root)

assertEquals(
TaskOutcome.UP_TO_DATE,
build.task(":app:generateSentryProguardUuidRelease")?.outcome,
build.output
)

assertEquals(uuid1, uuid2)
}

Expand Down Expand Up @@ -866,39 +929,6 @@ class SentryPluginTest :
assertEquals(uuid1, uuid2)
}

@Test
fun `caches uuid-generating tasks`() {
// first build it so it gets cached
runner.withArguments(":app:assembleRelease", "--build-cache").build()
val uuid1 = verifyProguardUuid(testProjectDir.root)

// this should erase the build folder so the tasks are not up-to-date
runner.withArguments("clean").build()

// this should restore the entry from cache as the mapping file hasn't changed
val build = runner.withArguments("app:assembleRelease", "--build-cache").build()
val uuid2 = verifyProguardUuid(testProjectDir.root)

assertEquals(
FROM_CACHE,
build.task(":app:generateSentryProguardUuidRelease")?.outcome,
build.output
)
assertEquals(
FROM_CACHE,
build.task(":app:generateSentryDebugMetaPropertiesRelease")?.outcome
?: build.task(":app:injectSentryDebugMetaPropertiesIntoAssetsRelease")?.outcome,
build.output
)
assertEquals(
FROM_CACHE,
build.task(":app:releaseSentryGenerateIntegrationListTask")?.outcome,
build.output
)
// should be the same uuid
assertEquals(uuid1, uuid2)
}

@Test
fun `copyFlutterAssetsDebug is wired up`() {
assumeThat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.sentry.android.gradle.tasks
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask.Companion.SENTRY_PROGUARD_MAPPING_UUID_PROPERTY
import io.sentry.android.gradle.util.PropertiesUtil
import java.io.File
import kotlin.test.assertNotEquals
import java.util.UUID
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.gradle.api.Project
Expand Down Expand Up @@ -32,33 +32,7 @@ class SentryGenerateProguardUuidTaskTest {
val props = PropertiesUtil.load(expectedFile)
val uuid = props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY)
assertNotNull(uuid)
}

@Test
fun `generate proguard UUID overrides the UUID on subsequent calls`() {
val project = createProject()
val task: TaskProvider<SentryGenerateProguardUuidTask> =
project.tasks.register(
"testGenerateProguardUuid",
SentryGenerateProguardUuidTask::class.java
) {
it.output.set(project.layout.buildDirectory.dir("dummy/folder/"))
}
val expectedFile = File(project.buildDir, "dummy/folder/sentry-proguard-uuid.properties")

task.get().generateProperties()

val props1 = PropertiesUtil.load(expectedFile)
val uuid1 = props1.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY)
assertNotNull(uuid1)

task.get().generateProperties()

val props2 = PropertiesUtil.load(expectedFile)
val uuid2 = props2.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY)
assertNotNull(uuid2)

assertNotEquals(uuid1, uuid2)
UUID.fromString(uuid)
}

private fun createProject(): Project {
Expand Down
Loading
Loading