diff --git a/CHANGELOG.md b/CHANGELOG.md index 794fe0788..bc0e08388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/android-instrumentation-sample/build.gradle.kts b/examples/android-instrumentation-sample/build.gradle.kts index a6e3821a2..26782e727 100644 --- a/examples/android-instrumentation-sample/build.gradle.kts +++ b/examples/android-instrumentation-sample/build.gradle.kts @@ -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")) diff --git a/examples/multi-module-sample/build.gradle.kts b/examples/multi-module-sample/build.gradle.kts index 650af1771..079fda70e 100644 --- a/examples/multi-module-sample/build.gradle.kts +++ b/examples/multi-module-sample/build.gradle.kts @@ -16,6 +16,6 @@ tasks.withType { sentry { debug.set(true) telemetry.set(false) - includeSourceContext.set(true) + includeSourceContext.set(CI.canAutoUpload()) additionalSourceDirsForSourceContext.set(setOf("testsrc")) } diff --git a/examples/multi-module-sample/spring-boot-in-multi-module-sample/build.gradle.kts b/examples/multi-module-sample/spring-boot-in-multi-module-sample/build.gradle.kts index 8325d806a..79bf5e8c7 100644 --- a/examples/multi-module-sample/spring-boot-in-multi-module-sample/build.gradle.kts +++ b/examples/multi-module-sample/spring-boot-in-multi-module-sample/build.gradle.kts @@ -52,6 +52,6 @@ tasks.withType { sentry { debug.set(true) telemetry.set(false) - includeSourceContext.set(true) + includeSourceContext.set(CI.canAutoUpload()) additionalSourceDirsForSourceContext.set(setOf("testsrc")) } diff --git a/examples/multi-module-sample/spring-boot-in-multi-module-sample2/build.gradle.kts b/examples/multi-module-sample/spring-boot-in-multi-module-sample2/build.gradle.kts index 3a54b0873..12c693631 100644 --- a/examples/multi-module-sample/spring-boot-in-multi-module-sample2/build.gradle.kts +++ b/examples/multi-module-sample/spring-boot-in-multi-module-sample2/build.gradle.kts @@ -51,6 +51,6 @@ tasks.withType { sentry { debug.set(true) - includeSourceContext.set(true) + includeSourceContext.set(CI.canAutoUpload()) additionalSourceDirsForSourceContext.set(setOf("testsrc")) } diff --git a/examples/spring-boot-sample/build.gradle.kts b/examples/spring-boot-sample/build.gradle.kts index 3a54b0873..12c693631 100644 --- a/examples/spring-boot-sample/build.gradle.kts +++ b/examples/spring-boot-sample/build.gradle.kts @@ -51,6 +51,6 @@ tasks.withType { sentry { debug.set(true) - includeSourceContext.set(true) + includeSourceContext.set(CI.canAutoUpload()) additionalSourceDirsForSourceContext.set(setOf("testsrc")) } 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 d7c57bf5d..86b6172d6 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,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 { @@ -30,22 +28,19 @@ abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { @get:Internal override val outputFile: Provider get() = output.file(SENTRY_UUID_OUTPUT) - @get:Input - abstract val proguardMappingFileHash: Property + @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 { @@ -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 } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginConfigurationCacheTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginConfigurationCacheTest.kt index c07994f09..da084bc2c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginConfigurationCacheTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginConfigurationCacheTest.kt @@ -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: // @@ -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", diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextNonAndroidTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextNonAndroidTest.kt index 0746e096f..28dc2f88e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextNonAndroidTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextNonAndroidTest.kt @@ -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 @@ -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( diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextTest.kt index 2cc0185d1..9d5467d6c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginSourceContextTest.kt @@ -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 @@ -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( 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 a3a7d7651..f8eacd30f 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 @@ -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 @@ -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( + """ + + + + + + """.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") @@ -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) } @@ -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( 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 22f656980..32f9003eb 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 @@ -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 @@ -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 = - 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 { diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SkipOnForksRule.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SkipOnForksRule.kt new file mode 100644 index 000000000..fdb7010e3 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SkipOnForksRule.kt @@ -0,0 +1,22 @@ +package io.sentry.android.gradle.util + +import org.hamcrest.CoreMatchers.`is` +import org.junit.Assume.assumeThat +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class SkipOnForksRule : TestRule { + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + assumeThat( + "Sentry Source Context only supported when SENTRY_AUTH_TOKEN is present", + System.getenv("SENTRY_AUTH_TOKEN").isNullOrEmpty(), + `is`(false) + ) + base.evaluate() + } + } + } +}