From d37ca4669ece05edffae42375d3e9f91318309a1 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:05:04 +0200 Subject: [PATCH 01/17] Make SentryUploadNativeSymbolsTask configuration-cache compatible --- plugin-build/build.gradle.kts | 2 +- .../tasks/SentryUploadNativeSymbolsTask.kt | 28 +++++----- .../SentryPluginConfigurationCacheTest.kt | 33 ++++++++++++ .../SentryUploadNativeSymbolsTaskTest.kt | 53 ++++++++++++++----- 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index 978bffd8..e3cffc3e 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -94,7 +94,7 @@ dependencies { testImplementationAar(Libs.SQLITE) testImplementationAar(Libs.SQLITE_FRAMEWORK) testRuntimeOnly(files(androidSdkPath)) - testImplementation(Libs.SENTRY_ANDROID) + testImplementationAar(Libs.SENTRY_ANDROID) testImplementationAar(Libs.SENTRY_ANDROID_OKHTTP) testImplementationAar(Libs.SENTRY_OKHTTP) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 55cbbdf3..4430114c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -11,10 +11,17 @@ import io.sentry.android.gradle.util.info import io.sentry.gradle.common.SentryVariant import java.io.File import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskProvider abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @@ -29,8 +36,8 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @get:Input abstract val includeNativeSources: Property - @get:Internal - abstract val variantName: Property + @get:InputFiles + abstract val nativeLibsDir: ConfigurableFileCollection override fun getArguments(args: MutableList) { args.add("debug-files") @@ -40,16 +47,7 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { args.add("--no-upload") } - val sep = File.separator - - // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variantName} - // where {variantName} could be debug/release... - args.add( - File( - project.buildDir, - "intermediates${sep}merged_native_libs${sep}${variantName.get()}" - ).absolutePath - ) + args.add(nativeLibsDir.asPath) // Only include sources if includeNativeSources is enabled, as this is opt-in feature if (includeNativeSources.get()) { @@ -74,6 +72,10 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { autoUploadNativeSymbols: Property, taskSuffix: String = "", ): TaskProvider { + val nativeLibsDir = File( + project.buildDir, + "intermediates${File.separator}merged_native_libs${File.separator}${variantName}" + ) val uploadSentryNativeSymbolsTask = project.tasks.register( "uploadSentryNativeSymbolsFor$taskSuffix", SentryUploadNativeSymbolsTask::class.java @@ -84,12 +86,12 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { task.cliExecutable.set(cliExecutable) task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.includeNativeSources.set(includeNativeSources) - task.variantName.set(variantName) task.sentryOrganization.set(sentryOrg) task.sentryProject.set(sentryProject) task.sentryAuthToken.set(sentryAuthToken) task.sentryUrl.set(sentryUrl) task.sentryTelemetryService.set(sentryTelemetryProvider) + task.nativeLibsDir.setFrom(nativeLibsDir) task.asSentryCliExec() task.withSentryTelemetry(extension, sentryTelemetryProvider) } 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 876313ff..3bcdc6f9 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 @@ -234,4 +234,37 @@ class SentryPluginConfigurationCacheTest : val run = runner.build() assertTrue(run.output) { "BUILD SUCCESSFUL" in run.output } } + + @Test + fun `native symbols upload task respects configuration cache`() { + appBuildFile.writeText( + // language=Groovy + """ + plugins { + id "com.android.application" + id "io.sentry.android.gradle" + } + + android { + namespace 'com.example' + } + + sentry { + includeNativeSources = true + uploadNativeSymbols = true + autoUploadProguardMapping = false + autoInstallation.enabled = false + telemetry = false + } + """.trimIndent() + ) + runner.appendArguments(":app:assembleRelease") + .appendArguments("--configuration-cache") + + val output = runner.build().output + assertTrue { "Configuration cache entry stored." in output } + + val outputWithConfigCache = runner.build().output + assertTrue { "Configuration cache entry reused." in outputWithConfigCache } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index a92c4a5f..3f0b9fd5 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -14,22 +14,23 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `cli-executable is set correctly`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } val args = task.computeCommandLineArgs() - val sep = File.separator assertTrue("sentry-cli" in args) assertTrue("debug-files" in args) assertTrue("upload" in args) - val path = "${project.buildDir}${sep}intermediates" + - "${sep}merged_native_libs${sep}debug" - assertTrue(path in args) + assertTrue(nativeLibsDir.absolutePath in args) assertFalse("--include-sources" in args) assertFalse("--log-level=debug" in args) } @@ -37,10 +38,14 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--auto-upload is set correctly`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(false) } @@ -52,10 +57,14 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--log-level=debug is set correctly`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(false) it.debug.set(true) } @@ -68,10 +77,14 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--include-sources is set correctly`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(true) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } @@ -106,11 +119,15 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryOrganization adds --org`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryOrganization.set("dummy-org") it.includeNativeSources.set(true) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } @@ -123,11 +140,15 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryProject adds --project`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryProject.set("dummy-proj") it.includeNativeSources.set(true) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } @@ -140,11 +161,15 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryUrl adds --url`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } @@ -157,11 +182,15 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `the --url parameter is placed as the first argument`() { val project = createProject() + val sep = File.separator + val nativeLibsDir = File( + "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" + ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) - it.variantName.set("debug") + it.nativeLibsDir.setFrom(nativeLibsDir) it.autoUploadNativeSymbol.set(true) } From b15315a43fdee206f8cbd24bcd16af37a8e4e420 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:07:07 +0200 Subject: [PATCH 02/17] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce17e2f9..2dd59567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - 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)) ### Dependencies From 615ec6f752400842cac173c2b00ac4737af8245f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:27:43 +0200 Subject: [PATCH 03/17] ktlint --- .../gradle/tasks/SentryUploadNativeSymbolsTask.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 4430114c..c2f22850 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -9,20 +9,14 @@ import io.sentry.android.gradle.util.asSentryCliExec import io.sentry.android.gradle.util.hookWithAssembleTasks import io.sentry.android.gradle.util.info import io.sentry.gradle.common.SentryVariant -import java.io.File import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskProvider +import java.io.File abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @@ -74,7 +68,7 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { ): TaskProvider { val nativeLibsDir = File( project.buildDir, - "intermediates${File.separator}merged_native_libs${File.separator}${variantName}" + "intermediates${File.separator}merged_native_libs${File.separator}$variantName" ) val uploadSentryNativeSymbolsTask = project.tasks.register( "uploadSentryNativeSymbolsFor$taskSuffix", From 19e8e0d802f612573d2c676fba20de06490adf7e Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:38:27 +0200 Subject: [PATCH 04/17] Fix tests --- .../android/gradle/integration/BaseSentryPluginTest.kt | 5 +++-- .../integration/SentryPluginConfigurationCacheTest.kt | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt index b542d19d..ed7c2a23 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt @@ -130,9 +130,10 @@ abstract class BaseSentryPluginTest( .withArguments("--stacktrace") .withPluginClasspath() .withGradleVersion(gradleVersion) + .forwardOutput() // .withDebug(true) - .forwardStdOutput(writer) - .forwardStdError(writer) +// .forwardStdOutput(writer) +// .forwardStdError(writer) if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { // for newer Gradle versions transforms are unlocked at config time instead of a task 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 3bcdc6f9..e4a257b9 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 @@ -237,6 +237,12 @@ class SentryPluginConfigurationCacheTest : @Test fun `native symbols upload task respects configuration cache`() { + assumeThat( + "SentryUploadNativeSymbolsTask only supports " + + "configuration cache from Gradle 7.5 onwards", + GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, + `is`(true) + ) appBuildFile.writeText( // language=Groovy """ @@ -252,6 +258,7 @@ class SentryPluginConfigurationCacheTest : sentry { includeNativeSources = true uploadNativeSymbols = true + includeProguardMapping = false autoUploadProguardMapping = false autoInstallation.enabled = false telemetry = false From 6aca2361cdeba7d30dce0f5c025ccb6124a79bcb Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:40:21 +0200 Subject: [PATCH 05/17] Revert --- .../android/gradle/integration/BaseSentryPluginTest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt index ed7c2a23..b542d19d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt @@ -130,10 +130,9 @@ abstract class BaseSentryPluginTest( .withArguments("--stacktrace") .withPluginClasspath() .withGradleVersion(gradleVersion) - .forwardOutput() // .withDebug(true) -// .forwardStdOutput(writer) -// .forwardStdError(writer) + .forwardStdOutput(writer) + .forwardStdError(writer) if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { // for newer Gradle versions transforms are unlocked at config time instead of a task From b3973f37b8256a7783dee500d24b2892f5955412 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 1 Aug 2024 23:51:13 +0200 Subject: [PATCH 06/17] Formatting --- .../android/gradle/tasks/SentryUploadNativeSymbolsTask.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index c2f22850..b5f5d119 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -9,6 +9,7 @@ import io.sentry.android.gradle.util.asSentryCliExec import io.sentry.android.gradle.util.hookWithAssembleTasks import io.sentry.android.gradle.util.info import io.sentry.gradle.common.SentryVariant +import java.io.File import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.provider.Property @@ -16,7 +17,6 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.TaskProvider -import java.io.File abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { From 1087f692edf057fe91ca9480b321430fce9e8fe0 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:21:52 +0200 Subject: [PATCH 07/17] Fix permission denied error when extracting sentry-cli concurrently --- .../android/gradle/SentryCliProvider.kt | 1 + .../SentryPluginConfigurationCacheTest.kt | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index 7cc6cf5c..361edc0a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -153,6 +153,7 @@ internal object SentryCliProvider { /** * Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ + @Synchronized internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { val cli = File(cliPath) if (!cli.exists()) { 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 e4a257b9..c07994f0 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 @@ -224,11 +224,35 @@ class SentryPluginConfigurationCacheTest : `is`(true) ) + appBuildFile.writeText( + // language=Groovy + """ + plugins { + id "com.android.application" + id "io.sentry.android.gradle" + } + + android { + namespace 'com.example' + } + + sentry { + includeNativeSources = true + uploadNativeSymbols = true + includeProguardMapping = true + autoUploadProguardMapping = true + autoInstallation.enabled = false + telemetry = false + } + """.trimIndent() + ) + val runner = runner.withArguments( "--configuration-cache", "--build-cache", ":app:clean", - ":app:assembleRelease" + ":app:assembleRelease", + "--stacktrace" ) val run = runner.build() From f2cf09368f6ca1bd39edcd07d1b38517968c80a2 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:24:38 +0200 Subject: [PATCH 08/17] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd59567..6129ae4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - 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)) ### Dependencies From 314d4ca8a6d2dc5015ef8a03c46807ce4094f6bd Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:49:36 +0200 Subject: [PATCH 09/17] Revert to an easier fix --- .../tasks/SentryUploadNativeSymbolsTask.kt | 20 +++++-- .../SentryUploadNativeSymbolsTaskTest.kt | 53 +++++-------------- 2 files changed, 28 insertions(+), 45 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index b5f5d119..47d1b5f9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -16,6 +16,7 @@ import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskProvider abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @@ -30,8 +31,10 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { @get:Input abstract val includeNativeSources: Property - @get:InputFiles - abstract val nativeLibsDir: ConfigurableFileCollection + @get:Internal + abstract val variantName: Property + + private val buildDir: Provider = project.layout.buildDirectory.asFile override fun getArguments(args: MutableList) { args.add("debug-files") @@ -41,7 +44,16 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { args.add("--no-upload") } - args.add(nativeLibsDir.asPath) + val sep = File.separator + + // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variantName} + // where {variantName} could be debug/release... + args.add( + File( + buildDir.get(), + "intermediates${sep}merged_native_libs${sep}${variantName.get()}" + ).absolutePath + ) // Only include sources if includeNativeSources is enabled, as this is opt-in feature if (includeNativeSources.get()) { @@ -80,12 +92,12 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { task.cliExecutable.set(cliExecutable) task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) task.includeNativeSources.set(includeNativeSources) + task.variantName.set(variantName) task.sentryOrganization.set(sentryOrg) task.sentryProject.set(sentryProject) task.sentryAuthToken.set(sentryAuthToken) task.sentryUrl.set(sentryUrl) task.sentryTelemetryService.set(sentryTelemetryProvider) - task.nativeLibsDir.setFrom(nativeLibsDir) task.asSentryCliExec() task.withSentryTelemetry(extension, sentryTelemetryProvider) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index 3f0b9fd5..a92c4a5f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -14,23 +14,22 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `cli-executable is set correctly`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } val args = task.computeCommandLineArgs() + val sep = File.separator assertTrue("sentry-cli" in args) assertTrue("debug-files" in args) assertTrue("upload" in args) - assertTrue(nativeLibsDir.absolutePath in args) + val path = "${project.buildDir}${sep}intermediates" + + "${sep}merged_native_libs${sep}debug" + assertTrue(path in args) assertFalse("--include-sources" in args) assertFalse("--log-level=debug" in args) } @@ -38,14 +37,10 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--auto-upload is set correctly`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(false) } @@ -57,14 +52,10 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--log-level=debug is set correctly`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(false) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(false) it.debug.set(true) } @@ -77,14 +68,10 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `--include-sources is set correctly`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.includeNativeSources.set(true) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } @@ -119,15 +106,11 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryOrganization adds --org`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryOrganization.set("dummy-org") it.includeNativeSources.set(true) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } @@ -140,15 +123,11 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryProject adds --project`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryProject.set("dummy-proj") it.includeNativeSources.set(true) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } @@ -161,15 +140,11 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `with sentryUrl adds --url`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } @@ -182,15 +157,11 @@ class SentryUploadNativeSymbolsTaskTest { @Test fun `the --url parameter is placed as the first argument`() { val project = createProject() - val sep = File.separator - val nativeLibsDir = File( - "${project.buildDir}${sep}intermediates${sep}merged_native_libs${sep}debug" - ) val task = createTestTask(project) { it.cliExecutable.set("sentry-cli") it.sentryUrl.set("https://some-host.sentry.io") it.includeNativeSources.set(true) - it.nativeLibsDir.setFrom(nativeLibsDir) + it.variantName.set("debug") it.autoUploadNativeSymbol.set(true) } From e62634a1633160a35ffb7513548d085023e3eed9 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:52:28 +0200 Subject: [PATCH 10/17] Remove redundancy --- .../android/gradle/tasks/SentryUploadNativeSymbolsTask.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 47d1b5f9..e9b346c5 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -11,11 +11,9 @@ import io.sentry.android.gradle.util.info import io.sentry.gradle.common.SentryVariant import java.io.File import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskProvider @@ -78,10 +76,6 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { autoUploadNativeSymbols: Property, taskSuffix: String = "", ): TaskProvider { - val nativeLibsDir = File( - project.buildDir, - "intermediates${File.separator}merged_native_libs${File.separator}$variantName" - ) val uploadSentryNativeSymbolsTask = project.tasks.register( "uploadSentryNativeSymbolsFor$taskSuffix", SentryUploadNativeSymbolsTask::class.java From caa7b373cfafc6b2e7809c1ff718241809f73ee2 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:53:27 +0200 Subject: [PATCH 11/17] Remove redundancy --- .../android/gradle/tasks/SentryUploadNativeSymbolsTask.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 47d1b5f9..2485b5c8 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -78,10 +78,6 @@ abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { autoUploadNativeSymbols: Property, taskSuffix: String = "", ): TaskProvider { - val nativeLibsDir = File( - project.buildDir, - "intermediates${File.separator}merged_native_libs${File.separator}$variantName" - ) val uploadSentryNativeSymbolsTask = project.tasks.register( "uploadSentryNativeSymbolsFor$taskSuffix", SentryUploadNativeSymbolsTask::class.java From 421e3ea7bf4e6c3b04eea6d789fc8db7781e2d52 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 10:53:59 +0200 Subject: [PATCH 12/17] Formatting --- .../android/gradle/tasks/SentryUploadNativeSymbolsTask.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index 2485b5c8..e9b346c5 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -11,11 +11,9 @@ import io.sentry.android.gradle.util.info import io.sentry.gradle.common.SentryVariant import java.io.File import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskProvider From 45f3daa1a158ccef1fc3f93fa8603671f05248ae Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 11:24:45 +0200 Subject: [PATCH 13/17] integrate spotless and ktfmt --- build.gradle.kts | 38 ++++++++++++-------------- buildSrc/src/main/java/Dependencies.kt | 3 +- plugin-build/build.gradle.kts | 20 ++++---------- plugin-build/common/build.gradle.kts | 16 ++++------- 4 files changed, 29 insertions(+), 48 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 449c421d..82f3f0fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { kotlin("jvm") version BuildPluginsVersion.KOTLIN apply false id("com.android.application") version BuildPluginsVersion.AGP apply false - id("org.jlleitschuh.gradle.ktlint") version BuildPluginsVersion.KTLINT + id("com.diffplug.spotless") version BuildPluginsVersion.SPOTLESS } allprojects { @@ -13,28 +13,24 @@ allprojects { subprojects { apply { - plugin("org.jlleitschuh.gradle.ktlint") + plugin("com.diffplug.spotless") } - ktlint { - debug.set(false) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(true) - filter { - exclude("**/generated/**") - include("**/kotlin/**") + if (name != "examples") { + spotless { + kotlin { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**", "**/kotlin/**") + } } } } -tasks.register("clean", Delete::class.java) { - delete(rootProject.buildDir) - dependsOn(gradle.includedBuild("plugin-build").task(":clean")) - dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":clean")) -} +//tasks.register("clean", Delete::class.java) { +// delete(rootProject.buildDir) +// dependsOn(gradle.includedBuild("plugin-build").task(":clean")) +// dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":clean")) +//} tasks.register("integrationTest") { group = "verification" @@ -58,10 +54,10 @@ tasks.register("preMerge") { dependsOn(gradle.includedBuild("plugin-build").task(":check")) } -tasks.getByName("ktlintFormat") { - dependsOn(gradle.includedBuild("plugin-build").task(":ktlintFormat")) +tasks.getByName("spotlessCheck") { + dependsOn(gradle.includedBuild("plugin-build").task(":spotlessCheck")) } -tasks.getByName("ktlintCheck") { - dependsOn(gradle.includedBuild("plugin-build").task(":ktlintCheck")) +tasks.getByName("spotlessApply") { + dependsOn(gradle.includedBuild("plugin-build").task(":spotlessApply")) } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 5800f77f..4a651a6e 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -6,7 +6,8 @@ object BuildPluginsVersion { const val DOKKA = "1.8.10" const val KOTLIN = "1.8.20" const val AAR_2_JAR = "0.6" - const val KTLINT = "10.2.1" + const val SPOTLESS = "7.0.0.BETA1" + const val KTFMT = "0.51" const val SHADOW = "7.1.2" // do not upgrade to 0.18.0, it does not generate the pom-default.xml and module.json under // build/publications/maven diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index e3cffc3e..0ff85320 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -17,7 +17,7 @@ plugins { id("org.jetbrains.dokka") version BuildPluginsVersion.DOKKA id("java-gradle-plugin") id("com.vanniktech.maven.publish") version BuildPluginsVersion.MAVEN_PUBLISH apply false - id("org.jlleitschuh.gradle.ktlint") version BuildPluginsVersion.KTLINT + id("com.diffplug.spotless") version BuildPluginsVersion.SPOTLESS // we need this plugin in order to include .aar dependencies into a pure java project, which the gradle plugin is id("io.sentry.android.gradle.aar2jar") id("com.github.johnrengelman.shadow") version BuildPluginsVersion.SHADOW @@ -222,20 +222,10 @@ artifacts { archives(tasks.named("shadowJar")) } -ktlint { - debug.set(false) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(true) - filter { - exclude("**/generated/**") - include("**/kotlin/**") - // see https://github.com/JLLeitschuh/ktlint-gradle/issues/522#issuecomment-958756817 - exclude { entry -> - entry.file.toString().contains("generated") - } +spotless { + kotlin { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**", "**/kotlin/**") } } diff --git a/plugin-build/common/build.gradle.kts b/plugin-build/common/build.gradle.kts index 6a22ce6e..2ae446c6 100644 --- a/plugin-build/common/build.gradle.kts +++ b/plugin-build/common/build.gradle.kts @@ -1,22 +1,16 @@ plugins { kotlin("jvm") - id("org.jlleitschuh.gradle.ktlint") + id("com.diffplug.spotless") } dependencies { compileOnly(Libs.GRADLE_API) } -ktlint { - debug.set(false) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(true) - filter { - exclude("**/generated/**") - include("**/kotlin/**") +spotless { + kotlin { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**", "**/kotlin/**") } } From 616a1367eb637e4b350e0aa6982a9294632e4715 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 12:06:36 +0200 Subject: [PATCH 14/17] integrate spotless and ktfmt --- Makefile | 2 +- build.gradle.kts | 12 +++++++----- plugin-build/build.gradle.kts | 2 +- plugin-build/common/build.gradle.kts | 2 +- sentry-kotlin-compiler-plugin/build.gradle.kts | 16 +++++----------- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 7194ec91..b44941ed 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: format clean preMerge stop all format: - ./gradlew ktlintFormat + ./gradlew spotlessApply clean: ./gradlew clean diff --git a/build.gradle.kts b/build.gradle.kts index 82f3f0fa..06a7172e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,11 +26,11 @@ subprojects { } } -//tasks.register("clean", Delete::class.java) { -// delete(rootProject.buildDir) -// dependsOn(gradle.includedBuild("plugin-build").task(":clean")) -// dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":clean")) -//} +tasks.withType().configureEach { + delete(rootProject.buildDir) + dependsOn(gradle.includedBuild("plugin-build").task(":clean")) + dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":clean")) +} tasks.register("integrationTest") { group = "verification" @@ -55,9 +55,11 @@ tasks.register("preMerge") { } tasks.getByName("spotlessCheck") { + dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":spotlessCheck")) dependsOn(gradle.includedBuild("plugin-build").task(":spotlessCheck")) } tasks.getByName("spotlessApply") { + dependsOn(gradle.includedBuild("sentry-kotlin-compiler-plugin").task(":spotlessApply")) dependsOn(gradle.includedBuild("plugin-build").task(":spotlessApply")) } diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index 0ff85320..c4141c48 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -225,7 +225,7 @@ artifacts { spotless { kotlin { ktfmt(BuildPluginsVersion.KTFMT).googleStyle() - targetExclude("**/generated/**", "**/kotlin/**") + targetExclude("**/generated/**") } } diff --git a/plugin-build/common/build.gradle.kts b/plugin-build/common/build.gradle.kts index 2ae446c6..d24f3524 100644 --- a/plugin-build/common/build.gradle.kts +++ b/plugin-build/common/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { spotless { kotlin { ktfmt(BuildPluginsVersion.KTFMT).googleStyle() - targetExclude("**/generated/**", "**/kotlin/**") + targetExclude("**/generated/**") } } diff --git a/sentry-kotlin-compiler-plugin/build.gradle.kts b/sentry-kotlin-compiler-plugin/build.gradle.kts index 352671d2..081e5def 100644 --- a/sentry-kotlin-compiler-plugin/build.gradle.kts +++ b/sentry-kotlin-compiler-plugin/build.gradle.kts @@ -3,7 +3,7 @@ plugins { kotlin("kapt") version "1.9.24" id("distribution") id("com.vanniktech.maven.publish") version "0.17.0" - id("org.jlleitschuh.gradle.ktlint") version "10.2.1" + id("com.diffplug.spotless") version "7.0.0.BETA1" } allprojects { @@ -13,16 +13,10 @@ allprojects { } } -ktlint { - debug.set(false) - verbose.set(true) - android.set(true) - outputToConsole.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(true) - filter { - exclude("**/generated/**") - include("**/kotlin/**") +spotless { + kotlin { + ktfmt("0.51").googleStyle() + targetExclude("**/generated/**") } } From 4db2c2ccfdccccd494a8cca3b31ac7e72ae03e96 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 12:06:55 +0200 Subject: [PATCH 15/17] integrate spotless and ktfmt --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 06a7172e..2a9f4e3d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ subprojects { spotless { kotlin { ktfmt(BuildPluginsVersion.KTFMT).googleStyle() - targetExclude("**/generated/**", "**/kotlin/**") + targetExclude("**/generated/**") } } } From c667f602e837ff6ab47365d3a2fe87ebd5a1b12c Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 12:20:34 +0200 Subject: [PATCH 16/17] Format code with ktfmt --- build.gradle.kts | 4 + examples/android-gradle-kts/build.gradle.kts | 38 +- .../build.gradle.kts | 151 +- examples/android-room-lib/build.gradle.kts | 36 +- examples/multi-module-sample/build.gradle.kts | 20 +- .../build.gradle.kts | 68 +- .../build.gradle.kts | 66 +- examples/spring-boot-sample/build.gradle.kts | 66 +- plugin-build/build.gradle.kts | 456 +++--- plugin-build/common/build.gradle.kts | 4 + .../io/sentry/android/gradle/AGP70Compat.kt | 80 +- .../io/sentry/android/gradle/AGP74Compat.kt | 148 +- .../android/gradle/AndroidComponentsConfig.kt | 744 +++++---- .../io/sentry/android/gradle/AppConfig.kt | 552 ++++--- .../sentry/android/gradle/ManifestWriter.kt | 66 +- .../android/gradle/SentryCliProvider.kt | 305 ++-- .../io/sentry/android/gradle/SentryPlugin.kt | 119 +- .../gradle/SentryPropertiesFileProvider.kt | 97 +- .../android/gradle/SentryTasksProvider.kt | 338 ++-- .../autoinstall/AbstractInstallStrategy.kt | 180 ++- .../android/gradle/autoinstall/AutoInstall.kt | 134 +- .../gradle/autoinstall/AutoInstallState.kt | 85 +- .../BuildFinishedListenerService.kt | 39 +- .../autoinstall/InstallStrategyRegistrar.kt | 2 +- .../compose/ComposeInstallStrategy.kt | 39 +- .../fragment/FragmentInstallStrategy.kt | 35 +- .../graphql/GraphqlInstallStrategy.kt | 39 +- .../autoinstall/jdbc/JdbcInstallStrategy.kt | 121 +- .../kotlin/KotlinExtensionsInstallStrategy.kt | 47 +- .../log4j2/Log4j2InstallStrategy.kt | 40 +- .../logback/LogbackInstallStrategy.kt | 40 +- .../okhttp/AndroidOkHttpInstallStrategy.kt | 43 +- .../okhttp/OkHttpInstallStrategy.kt | 40 +- .../override/WarnOnOverrideStrategy.kt | 123 +- .../quartz/QuartzInstallStrategy.kt | 35 +- .../spring/Spring5InstallStrategy.kt | 45 +- .../spring/Spring6InstallStrategy.kt | 40 +- .../spring/SpringBoot2InstallStrategy.kt | 48 +- .../spring/SpringBoot3InstallStrategy.kt | 43 +- .../sqlite/SQLiteInstallStrategy.kt | 40 +- .../timber/TimberInstallStrategy.kt | 40 +- .../gradle/extensions/AppStartExtension.kt | 14 +- .../gradle/extensions/AutoInstallExtension.kt | 24 +- .../gradle/extensions/LogcatExtension.kt | 24 +- .../extensions/SentryPluginExtension.kt | 403 +++-- .../TracingInstrumentationExtension.kt | 175 +- .../AbstractSpanAddingMethodVisitor.kt | 284 ++-- .../instrumentation/ChainedInstrumentable.kt | 61 +- .../instrumentation/CommonClassVisitor.kt | 90 +- .../gradle/instrumentation/Instrumentable.kt | 59 +- .../instrumentation/InstrumentableContext.kt | 12 +- .../gradle/instrumentation/ReturnType.kt | 33 +- .../SpanAddingClassVisitorFactory.kt | 222 ++- .../gradle/instrumentation/SpanOperations.kt | 4 +- .../androidx/compose/ComposeNavigation.kt | 73 +- .../RememberNavControllerMethodVisitor.kt | 50 +- .../androidx/room/AndroidXRoomDao.kt | 135 +- .../androidx/room/RoomMethodType.kt | 8 +- .../room/visitor/AndroidXRoomDaoVisitor.kt | 126 +- .../InstrumentableMethodsCollectingVisitor.kt | 148 +- .../sqlite/AndroidXSQLiteOpenHelper.kt | 76 +- .../sqlite/database/AndroidXSQLiteDatabase.kt | 107 +- .../database/visitor/ExecSqlMethodVisitor.kt | 102 +- .../database/visitor/QueryMethodVisitor.kt | 172 +- .../statement/AndroidXSQLiteStatement.kt | 86 +- .../visitor/ExecuteStatementMethodVisitor.kt | 250 ++- .../visitor/SQLiteOpenHelperMethodVisitor.kt | 83 +- .../instrumentation/appstart/Application.kt | 62 +- .../appstart/ApplicationMethodVisitor.kt | 57 +- .../appstart/ContentProvider.kt | 64 +- .../appstart/ContentProviderMethodVisitor.kt | 89 +- .../gradle/instrumentation/logcat/Logcat.kt | 44 +- .../logcat/LogcatClassVisitor.kt | 93 +- .../instrumentation/logcat/LogcatLevel.kt | 40 +- .../gradle/instrumentation/okhttp/OkHttp.kt | 75 +- .../okhttp/OkHttpEventListener.kt | 72 +- .../OkHttpEventListenerMethodVisitor.kt | 126 +- ...sponseWithInterceptorChainMethodVisitor.kt | 148 +- .../remap/RemappingInstrumentable.kt | 28 +- .../instrumentation/util/AnalyzingVisitor.kt | 12 +- .../util/CatchingMethodVisitor.kt | 37 +- .../util/ConstantPoolHelpers.kt | 119 +- .../gradle/instrumentation/util/FieldUtils.kt | 22 +- .../instrumentation/util/FileLogTextifier.kt | 51 +- .../util/SentryPackageNameUtils.kt | 12 +- .../gradle/instrumentation/util/Types.kt | 35 +- .../instrumentation/wrap/Replacements.kt | 179 +-- .../wrap/WrappingInstrumentable.kt | 124 +- .../wrap/visitor/WrappingVisitor.kt | 316 ++-- .../gradle/services/SentryModulesService.kt | 253 ++- .../gradle/sourcecontext/BundleSourcesTask.kt | 138 +- .../sourcecontext/CollectSourcesTask.kt | 159 +- .../sourcecontext/GenerateBundleIdTask.kt | 97 +- .../gradle/sourcecontext/OutputPaths.kt | 20 +- .../gradle/sourcecontext/SourceContext.kt | 158 +- .../sourcecontext/UploadSourceBundleTask.kt | 129 +- .../gradle/tasks/DirectoryOutputTask.kt | 3 +- ...njectSentryMetaPropertiesIntoAssetsTask.kt | 153 +- .../gradle/tasks/PropertiesFileOutputTask.kt | 3 +- .../android/gradle/tasks/SentryCliExecTask.kt | 178 +-- .../SentryGenerateDebugMetaPropertiesTask.kt | 89 +- .../SentryGenerateIntegrationListTask.kt | 89 +- .../tasks/SentryGenerateProguardUuidTask.kt | 96 +- .../tasks/SentryUploadNativeSymbolsTask.kt | 188 ++- .../tasks/SentryUploadProguardMappingsTask.kt | 209 ++- .../SentryExternalDependenciesReportTask.kt | 119 +- ...ryExternalDependenciesReportTaskFactory.kt | 72 +- .../SentryExternalDependenciesReportTaskV2.kt | 95 +- .../telemetry/SentryTelemetryService.kt | 852 +++++----- .../telemetry/SentryTelemetryTaskWrapper.kt | 31 +- .../transforms/MetaInfStripTransform.kt | 232 ++- .../android/gradle/util/CliFailureReason.kt | 69 +- .../io/sentry/android/gradle/util/Files.kt | 56 +- .../android/gradle/util/PropertiesUtil.kt | 28 +- .../sentry/android/gradle/util/ReleaseInfo.kt | 6 +- .../io/sentry/android/gradle/util/SemVer.kt | 257 +-- .../android/gradle/util/SentryCliExec.kt | 40 +- .../android/gradle/util/SentryLogging.kt | 8 +- .../gradle/util/SentryModulesCollector.kt | 81 +- .../android/gradle/util/SentryPluginUtils.kt | 87 +- .../io/sentry/android/gradle/util/Versions.kt | 162 +- .../sentry/android/gradle/util/artifacts.kt | 15 +- .../android/gradle/util/buildServices.kt | 12 +- .../io/sentry/android/gradle/util/tasks.kt | 91 +- .../io/sentry/jvm/gradle/SentryJvmPlugin.kt | 224 ++- .../SentryKotlinCompilerGradlePlugin.kt | 34 +- .../sqlite/db/SupportSQLiteOpenHelper.kt | 2 +- .../android/gradle/SentryCliProviderTest.kt | 332 ++-- .../android/gradle/SentryPluginMRJarTest.kt | 52 +- .../SentryPropertiesFileProviderTest.kt | 400 +++-- .../android/gradle/SentryTaskProviderTest.kt | 426 +++-- .../io/sentry/android/gradle/TestUtils.kt | 357 ++--- .../AbstractInstallStrategyTest.kt | 55 +- .../compose/ComposeInstallStrategyTest.kt | 102 +- .../fragment/FragmentInstallStrategyTest.kt | 80 +- .../graphql/GraphqlInstallStrategyTest.kt | 86 +- .../jdbc/JdbcInstallStrategyTest.kt | 86 +- .../KotlinExtensionsInstallStrategyTest.kt | 111 +- .../log4j2/Log4j2InstallStrategyTest.kt | 106 +- .../logback/LogbackInstallStrategyTest.kt | 106 +- .../AndroidOkHttpInstallStrategyTest.kt | 129 +- .../okhttp/OkHttpInstallStrategyTest.kt | 126 +- .../override/WarnOnOverrideStrategyTest.kt | 104 +- .../quartz/QuartzInstallStrategyTest.kt | 86 +- .../spring/Spring5InstallStrategyTest.kt | 126 +- .../spring/Spring6InstallStrategyTest.kt | 106 +- .../spring/SpringBoot2InstallStrategyTest.kt | 129 +- .../spring/SpringBoot3InstallStrategyTest.kt | 109 +- .../sqlite/SqliteInstallStrategyTest.kt | 106 +- .../timber/TimberInstallStrategyTest.kt | 102 +- .../ChainedInstrumentableTest.kt | 169 +- .../instrumentation/CommonClassVisitorTest.kt | 175 +- .../gradle/instrumentation/VisitorTest.kt | 421 ++--- ...trumentableMethodsCollectingVisitorTest.kt | 211 +-- .../instrumentation/classloader/Common.kt | 32 +- .../instrumentation/classloader/Compiler.kt | 41 +- .../GeneratingMissingClassesClassLoader.kt | 52 +- .../classloader/mapping/DeletionDaoMapping.kt | 60 +- .../mapping/InsertionDaoMapping.kt | 20 +- .../classloader/mapping/OkHttpMapping.kt | 15 +- .../mapping/SQLiteCopyOpenHelperMapping.kt | 10 +- .../classloader/mapping/SelectDaoMapping.kt | 30 +- .../classloader/mapping/UpdateDaoMapping.kt | 60 +- .../instrumentation/fakes/BaseTestLogger.kt | 144 +- .../fakes/CapturingTestLogger.kt | 44 +- .../fakes/MethodNamePrintingVisitor.kt | 30 +- .../instrumentation/fakes/TestClassContext.kt | 22 +- .../fakes/TestSpanAddingParameters.kt | 30 +- .../instrumentation/logcat/LogcatLevelTest.kt | 20 +- .../util/CatchingMethodVisitorTest.kt | 97 +- .../util/FileLogTextifierTest.kt | 122 +- .../util/MinifiedClassDetectionTest.kt | 103 +- .../util/SentryPackageNameUtilsTest.kt | 25 +- .../wrap/visitor/WrappingVisitorTest.kt | 761 ++++----- .../BaseSentryNonAndroidPluginTest.kt | 151 +- .../integration/BaseSentryPluginTest.kt | 126 +- .../SentryPluginAutoInstallNonAndroidTest.kt | 638 ++++---- .../SentryPluginAutoInstallTest.kt | 301 ++-- .../SentryPluginCheckAndroidSdkTest.kt | 112 +- .../SentryPluginConfigurationCacheTest.kt | 386 ++--- .../SentryPluginIntegrationTest.kt | 212 ++- .../SentryPluginKotlinCompilerTest.kt | 34 +- .../integration/SentryPluginNonAndroidTest.kt | 55 +- ...SentryPluginSourceContextNonAndroidTest.kt | 228 ++- .../SentryPluginSourceContextTest.kt | 403 ++--- .../integration/SentryPluginTelemetryTest.kt | 60 +- .../gradle/integration/SentryPluginTest.kt | 1407 ++++++++--------- .../integration/SentryPluginVariantTest.kt | 132 +- ...nWithDependencyCollectorsNonAndroidTest.kt | 32 +- ...entryPluginWithDependencyCollectorsTest.kt | 32 +- .../SentryPluginWithFirebaseTest.kt | 32 +- .../SentryPluginWithMinifiedLibsTest.kt | 27 +- .../gradle/tasks/BundleSourcesTaskTest.kt | 495 +++--- .../gradle/tasks/CollectSourcesTaskTest.kt | 64 +- .../gradle/tasks/GenerateBundleIdTaskTest.kt | 98 +- .../gradle/tasks/SentryCliExecTaskTest.kt | 339 ++-- ...ntryGenerateDebugMetaPropertiesTaskTest.kt | 202 ++- .../SentryGenerateProguardUuidTaskTest.kt | 86 +- .../SentryUploadNativeSymbolsTaskTest.kt | 350 ++-- .../SentryUploadProguardMappingTaskTest.kt | 673 ++++---- .../tasks/UploadSourceBundleTaskTest.kt | 395 +++-- ...entryExternalDependenciesReportTaskTest.kt | 238 +-- .../telemetry/SentryTelemetryServiceTest.kt | 37 +- .../gradle/testutil/ProjectTestUtil.kt | 68 +- .../transforms/MetaInfStripTransformTest.kt | 338 ++-- .../transforms/fakes/FakeTransformOutputs.kt | 53 +- .../android/gradle/util/AgpVersionsTest.kt | 14 +- .../gradle/util/CliFailureReasonTest.kt | 42 +- .../util/PrintBuildOutputOnFailureRule.kt | 24 +- .../gradle/util/SentryModulesCollectorTest.kt | 443 +++--- .../gradle/util/SentryPluginUtilsTest.kt | 148 +- .../android/gradle/util/SystemPropertyRule.kt | 38 +- .../android/gradle/util/WithSystemProperty.kt | 9 +- .../android/roomsample/data/Entities.kt | 38 +- .../src/test/kotlin/okhttp3/Interceptor.kt | 2 +- .../build.gradle.kts | 91 +- .../io/sentry/SentryKotlinCompilerPlugin.kt | 20 +- ...otlinCompilerPluginCommandLineProcessor.kt | 4 +- .../JetpackComposeTracingIrExtension.kt | 408 +++-- .../JetpackComposeInstrumentationTest.kt | 349 ++-- 220 files changed, 14255 insertions(+), 15112 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2a9f4e3d..08539b3e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,10 @@ subprojects { ktfmt(BuildPluginsVersion.KTFMT).googleStyle() targetExclude("**/generated/**") } + kotlinGradle { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**") + } } } } diff --git a/examples/android-gradle-kts/build.gradle.kts b/examples/android-gradle-kts/build.gradle.kts index 2add7991..50952825 100644 --- a/examples/android-gradle-kts/build.gradle.kts +++ b/examples/android-gradle-kts/build.gradle.kts @@ -1,30 +1,28 @@ plugins { - id("com.android.application") - id("io.sentry.android.gradle") + id("com.android.application") + id("io.sentry.android.gradle") } android { - compileSdk = LibsVersion.SDK_VERSION - defaultConfig { - minSdk = LibsVersion.MIN_SDK_VERSION - targetSdk = LibsVersion.SDK_VERSION - versionCode = 1 - versionName = "1.0" + compileSdk = LibsVersion.SDK_VERSION + defaultConfig { + minSdk = LibsVersion.MIN_SDK_VERSION + targetSdk = LibsVersion.SDK_VERSION + versionCode = 1 + versionName = "1.0" + } + buildTypes { + getByName("release") { + isMinifyEnabled = true + proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) } - buildTypes { - getByName("release") { - isMinifyEnabled = true - proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) - } - } - namespace = "com.example.sampleapp" + } + namespace = "com.example.sampleapp" } sentry { - autoUploadProguardMapping.set(CI.canAutoUpload()) + autoUploadProguardMapping.set(CI.canAutoUpload()) - telemetryDsn.set(CI.SENTRY_SDKS_DSN) - tracingInstrumentation { - enabled.set(false) - } + telemetryDsn.set(CI.SENTRY_SDKS_DSN) + tracingInstrumentation { enabled.set(false) } } diff --git a/examples/android-instrumentation-sample/build.gradle.kts b/examples/android-instrumentation-sample/build.gradle.kts index a6e3821a..52ae5df6 100644 --- a/examples/android-instrumentation-sample/build.gradle.kts +++ b/examples/android-instrumentation-sample/build.gradle.kts @@ -1,12 +1,13 @@ plugins { - id("com.android.application") - kotlin("android") - kotlin("kapt") - id("io.sentry.android.gradle") + id("com.android.application") + kotlin("android") + kotlin("kapt") + id("io.sentry.android.gradle") } // useful for local debugging of the androidx.sqlite lib -// make sure to checkout the lib from https://github.com/androidx/androidx/tree/androidx-main/sqlite/sqlite-framework +// make sure to checkout the lib from +// https://github.com/androidx/androidx/tree/androidx-main/sqlite/sqlite-framework // configurations.all { // resolutionStrategy.dependencySubstitution { // substitute(module("androidx.sqlite:sqlite-framework")).using(project(":sqlite-framework")) @@ -14,62 +15,54 @@ plugins { // } android { - compileSdk = LibsVersion.SDK_VERSION - defaultConfig { - minSdk = LibsVersion.MIN_SDK_VERSION - targetSdk = LibsVersion.SDK_VERSION - versionCode = 1 - versionName = "1.0" + compileSdk = LibsVersion.SDK_VERSION + defaultConfig { + minSdk = LibsVersion.MIN_SDK_VERSION + targetSdk = LibsVersion.SDK_VERSION + versionCode = 1 + versionName = "1.0" + } + buildTypes { + getByName("release") { + isMinifyEnabled = true + proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) + signingConfig = signingConfigs.getByName("debug") } - buildTypes { - getByName("release") { - isMinifyEnabled = true - proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt")) - signingConfig = signingConfigs.getByName("debug") - } + } + + flavorDimensions += "environment" + // just a random product flavor for compatibility testing against AGP + productFlavors { + create("staging") { + dimension = "environment" + versionNameSuffix = "-staging" } - - flavorDimensions += "environment" - // just a random product flavor for compatibility testing against AGP - productFlavors { - create("staging") { - dimension = "environment" - versionNameSuffix = "-staging" - } + } + + signingConfigs { + getByName("debug") { + storeFile = file("debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" } + } - signingConfigs { - getByName("debug") { - storeFile = file("debug.keystore") - storePassword = "android" - keyAlias = "androiddebugkey" - keyPassword = "android" - } - } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } + namespace = "io.sentry.samples.instrumentation" - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } - namespace = "io.sentry.samples.instrumentation" + buildFeatures { compose = true } - buildFeatures { - compose = true - } + composeOptions { kotlinCompilerExtensionVersion = "1.4.6" } - composeOptions { - kotlinCompilerExtensionVersion = "1.4.6" - } - - testOptions.unitTests.isIncludeAndroidResources = true + testOptions.unitTests.isIncludeAndroidResources = true } -kotlin { - jvmToolchain(11) -} +kotlin { jvmToolchain(11) } // useful, when we want to modify room-generated classes, and then compile them into .class files // so room does not re-generate and overwrite our changes @@ -78,43 +71,41 @@ kotlin { // } dependencies { - implementation(Samples.AndroidX.recyclerView) - implementation(Samples.AndroidX.lifecycle) - implementation(Samples.AndroidX.appcompat) + implementation(Samples.AndroidX.recyclerView) + implementation(Samples.AndroidX.lifecycle) + implementation(Samples.AndroidX.appcompat) - implementation(Samples.AndroidX.composeRuntime) - implementation(Samples.AndroidX.composeActivity) - implementation(Samples.AndroidX.composeFoundation) - implementation(Samples.AndroidX.composeFoundationLayout) - implementation(Samples.AndroidX.composeNavigation) + implementation(Samples.AndroidX.composeRuntime) + implementation(Samples.AndroidX.composeActivity) + implementation(Samples.AndroidX.composeFoundation) + implementation(Samples.AndroidX.composeFoundationLayout) + implementation(Samples.AndroidX.composeNavigation) - implementation(Samples.Coroutines.core) - implementation(Samples.Coroutines.android) + implementation(Samples.Coroutines.core) + implementation(Samples.Coroutines.android) - implementation(Samples.Room.runtime) - implementation(Samples.Room.ktx) - implementation(Samples.Room.rxjava) + implementation(Samples.Room.runtime) + implementation(Samples.Room.ktx) + implementation(Samples.Room.rxjava) - implementation(Samples.Timber.timber) - implementation(Samples.Fragment.fragmentKtx) - implementation(project(":examples:android-room-lib")) + implementation(Samples.Timber.timber) + implementation(Samples.Fragment.fragmentKtx) + implementation(project(":examples:android-room-lib")) - kapt(Samples.Room.compiler) + kapt(Samples.Room.compiler) } sentry { - debug.set(true) - autoUploadProguardMapping.set(CI.canAutoUpload()) + debug.set(true) + autoUploadProguardMapping.set(CI.canAutoUpload()) - includeSourceContext.set(true) - autoUploadSourceContext.set(CI.canAutoUpload()) - additionalSourceDirsForSourceContext.set(setOf("src/custom/java")) + includeSourceContext.set(true) + autoUploadSourceContext.set(CI.canAutoUpload()) + additionalSourceDirsForSourceContext.set(setOf("src/custom/java")) - org.set("sentry-sdks") - projectName.set("sentry-android") - telemetryDsn.set(CI.SENTRY_SDKS_DSN) + org.set("sentry-sdks") + projectName.set("sentry-android") + telemetryDsn.set(CI.SENTRY_SDKS_DSN) - tracingInstrumentation { - forceInstrumentDependencies.set(true) - } + tracingInstrumentation { forceInstrumentDependencies.set(true) } } diff --git a/examples/android-room-lib/build.gradle.kts b/examples/android-room-lib/build.gradle.kts index 3e456983..d079f5ad 100644 --- a/examples/android-room-lib/build.gradle.kts +++ b/examples/android-room-lib/build.gradle.kts @@ -1,30 +1,28 @@ plugins { - id("com.android.library") - kotlin("android") + id("com.android.library") + kotlin("android") } android { - compileSdk = LibsVersion.SDK_VERSION - defaultConfig { - minSdk = LibsVersion.MIN_SDK_VERSION - targetSdk = LibsVersion.SDK_VERSION - } + compileSdk = LibsVersion.SDK_VERSION + defaultConfig { + minSdk = LibsVersion.MIN_SDK_VERSION + targetSdk = LibsVersion.SDK_VERSION + } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - namespace = "io.sentry.android.instrumentation.lib" + kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } + namespace = "io.sentry.android.instrumentation.lib" } dependencies { - implementation(Samples.Coroutines.core) - implementation(Samples.Coroutines.android) + implementation(Samples.Coroutines.core) + implementation(Samples.Coroutines.android) - implementation(Samples.Room.runtime) - implementation(Samples.Room.ktx) + implementation(Samples.Room.runtime) + implementation(Samples.Room.ktx) - // this is here for test purposes, to ensure that transitive dependencies are also recognized - // by our auto-installation - api(Samples.Retrofit.retrofit) - api(Samples.Retrofit.retrofitGson) + // this is here for test purposes, to ensure that transitive dependencies are also recognized + // by our auto-installation + api(Samples.Retrofit.retrofit) + api(Samples.Retrofit.retrofitGson) } diff --git a/examples/multi-module-sample/build.gradle.kts b/examples/multi-module-sample/build.gradle.kts index 650af177..a03d47cc 100644 --- a/examples/multi-module-sample/build.gradle.kts +++ b/examples/multi-module-sample/build.gradle.kts @@ -2,20 +2,20 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") - id("io.sentry.jvm.gradle") + kotlin("jvm") + id("io.sentry.jvm.gradle") } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_1_8.toString() - } + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } sentry { - debug.set(true) - telemetry.set(false) - includeSourceContext.set(true) - additionalSourceDirsForSourceContext.set(setOf("testsrc")) + debug.set(true) + telemetry.set(false) + includeSourceContext.set(true) + 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 8325d806..dca7bb96 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 @@ -3,55 +3,53 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id(Samples.SpringBoot.springBoot) version - BuildPluginsVersion.SPRING_BOOT - id(Samples.SpringBoot.springDependencyManagement) version - BuildPluginsVersion.SPRING_DEP_MANAGEMENT - kotlin("jvm") - kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN - id("io.sentry.jvm.gradle") + id(Samples.SpringBoot.springBoot) version BuildPluginsVersion.SPRING_BOOT + id(Samples.SpringBoot.springDependencyManagement) version + BuildPluginsVersion.SPRING_DEP_MANAGEMENT + kotlin("jvm") + kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN + id("io.sentry.jvm.gradle") } group = "io.sentry.samples.spring-boot" + version = "0.0.1-SNAPSHOT" + java.sourceCompatibility = JavaVersion.VERSION_1_8 + java.targetCompatibility = JavaVersion.VERSION_1_8 -repositories { - mavenCentral() -} +repositories { mavenCentral() } dependencies { - implementation(Samples.SpringBoot.springBootStarterSecurity) - implementation(Samples.SpringBoot.springBootStarterWeb) - implementation(Samples.SpringBoot.springBootStarterWebflux) - implementation(Samples.SpringBoot.springBootStarterAop) - implementation(Samples.SpringBoot.aspectj) - implementation(Samples.SpringBoot.springBootStarter) - implementation(Samples.SpringBoot.kotlinReflect) - implementation(Samples.SpringBoot.springBootStarterJdbc) - implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) - - runtimeOnly(Samples.SpringBoot.hsqldb) - testImplementation(Samples.SpringBoot.springBootStarterTest) { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } + implementation(Samples.SpringBoot.springBootStarterSecurity) + implementation(Samples.SpringBoot.springBootStarterWeb) + implementation(Samples.SpringBoot.springBootStarterWebflux) + implementation(Samples.SpringBoot.springBootStarterAop) + implementation(Samples.SpringBoot.aspectj) + implementation(Samples.SpringBoot.springBootStarter) + implementation(Samples.SpringBoot.kotlinReflect) + implementation(Samples.SpringBoot.springBootStarterJdbc) + implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) + + runtimeOnly(Samples.SpringBoot.hsqldb) + testImplementation(Samples.SpringBoot.springBootStarterTest) { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } } -tasks.withType { - useJUnitPlatform() -} +tasks.withType { useJUnitPlatform() } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_1_8.toString() - } + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } sentry { - debug.set(true) - telemetry.set(false) - includeSourceContext.set(true) - additionalSourceDirsForSourceContext.set(setOf("testsrc")) + debug.set(true) + telemetry.set(false) + includeSourceContext.set(true) + 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 3a54b087..b2e19359 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 @@ -3,54 +3,52 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id(Samples.SpringBoot.springBoot) version - BuildPluginsVersion.SPRING_BOOT - id(Samples.SpringBoot.springDependencyManagement) version - BuildPluginsVersion.SPRING_DEP_MANAGEMENT - kotlin("jvm") - kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN - id("io.sentry.jvm.gradle") + id(Samples.SpringBoot.springBoot) version BuildPluginsVersion.SPRING_BOOT + id(Samples.SpringBoot.springDependencyManagement) version + BuildPluginsVersion.SPRING_DEP_MANAGEMENT + kotlin("jvm") + kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN + id("io.sentry.jvm.gradle") } group = "io.sentry.samples.spring-boot" + version = "0.0.1-SNAPSHOT" + java.sourceCompatibility = JavaVersion.VERSION_1_8 + java.targetCompatibility = JavaVersion.VERSION_1_8 -repositories { - mavenCentral() -} +repositories { mavenCentral() } dependencies { - implementation(Samples.SpringBoot.springBootStarterSecurity) - implementation(Samples.SpringBoot.springBootStarterWeb) - implementation(Samples.SpringBoot.springBootStarterWebflux) - implementation(Samples.SpringBoot.springBootStarterAop) - implementation(Samples.SpringBoot.aspectj) - implementation(Samples.SpringBoot.springBootStarter) - implementation(Samples.SpringBoot.kotlinReflect) - implementation(Samples.SpringBoot.springBootStarterJdbc) - implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) - - runtimeOnly(Samples.SpringBoot.hsqldb) - testImplementation(Samples.SpringBoot.springBootStarterTest) { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } + implementation(Samples.SpringBoot.springBootStarterSecurity) + implementation(Samples.SpringBoot.springBootStarterWeb) + implementation(Samples.SpringBoot.springBootStarterWebflux) + implementation(Samples.SpringBoot.springBootStarterAop) + implementation(Samples.SpringBoot.aspectj) + implementation(Samples.SpringBoot.springBootStarter) + implementation(Samples.SpringBoot.kotlinReflect) + implementation(Samples.SpringBoot.springBootStarterJdbc) + implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) + + runtimeOnly(Samples.SpringBoot.hsqldb) + testImplementation(Samples.SpringBoot.springBootStarterTest) { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } } -tasks.withType { - useJUnitPlatform() -} +tasks.withType { useJUnitPlatform() } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_1_8.toString() - } + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } sentry { - debug.set(true) - includeSourceContext.set(true) - additionalSourceDirsForSourceContext.set(setOf("testsrc")) + debug.set(true) + includeSourceContext.set(true) + additionalSourceDirsForSourceContext.set(setOf("testsrc")) } diff --git a/examples/spring-boot-sample/build.gradle.kts b/examples/spring-boot-sample/build.gradle.kts index 3a54b087..b2e19359 100644 --- a/examples/spring-boot-sample/build.gradle.kts +++ b/examples/spring-boot-sample/build.gradle.kts @@ -3,54 +3,52 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id(Samples.SpringBoot.springBoot) version - BuildPluginsVersion.SPRING_BOOT - id(Samples.SpringBoot.springDependencyManagement) version - BuildPluginsVersion.SPRING_DEP_MANAGEMENT - kotlin("jvm") - kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN - id("io.sentry.jvm.gradle") + id(Samples.SpringBoot.springBoot) version BuildPluginsVersion.SPRING_BOOT + id(Samples.SpringBoot.springDependencyManagement) version + BuildPluginsVersion.SPRING_DEP_MANAGEMENT + kotlin("jvm") + kotlin("plugin.spring") version BuildPluginsVersion.KOTLIN + id("io.sentry.jvm.gradle") } group = "io.sentry.samples.spring-boot" + version = "0.0.1-SNAPSHOT" + java.sourceCompatibility = JavaVersion.VERSION_1_8 + java.targetCompatibility = JavaVersion.VERSION_1_8 -repositories { - mavenCentral() -} +repositories { mavenCentral() } dependencies { - implementation(Samples.SpringBoot.springBootStarterSecurity) - implementation(Samples.SpringBoot.springBootStarterWeb) - implementation(Samples.SpringBoot.springBootStarterWebflux) - implementation(Samples.SpringBoot.springBootStarterAop) - implementation(Samples.SpringBoot.aspectj) - implementation(Samples.SpringBoot.springBootStarter) - implementation(Samples.SpringBoot.kotlinReflect) - implementation(Samples.SpringBoot.springBootStarterJdbc) - implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) - - runtimeOnly(Samples.SpringBoot.hsqldb) - testImplementation(Samples.SpringBoot.springBootStarterTest) { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } + implementation(Samples.SpringBoot.springBootStarterSecurity) + implementation(Samples.SpringBoot.springBootStarterWeb) + implementation(Samples.SpringBoot.springBootStarterWebflux) + implementation(Samples.SpringBoot.springBootStarterAop) + implementation(Samples.SpringBoot.aspectj) + implementation(Samples.SpringBoot.springBootStarter) + implementation(Samples.SpringBoot.kotlinReflect) + implementation(Samples.SpringBoot.springBootStarterJdbc) + implementation(kotlin(Samples.SpringBoot.kotlinStdLib, KotlinCompilerVersion.VERSION)) + + runtimeOnly(Samples.SpringBoot.hsqldb) + testImplementation(Samples.SpringBoot.springBootStarterTest) { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } } -tasks.withType { - useJUnitPlatform() -} +tasks.withType { useJUnitPlatform() } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_1_8.toString() - } + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } sentry { - debug.set(true) - includeSourceContext.set(true) - additionalSourceDirsForSourceContext.set(setOf("testsrc")) + debug.set(true) + includeSourceContext.set(true) + additionalSourceDirsForSourceContext.set(setOf("testsrc")) } diff --git a/plugin-build/build.gradle.kts b/plugin-build/build.gradle.kts index c4141c48..88ed8855 100644 --- a/plugin-build/build.gradle.kts +++ b/plugin-build/build.gradle.kts @@ -11,25 +11,26 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("dev.gradleplugins.groovy-gradle-plugin") version BuildPluginsVersion.GROOVY_REDISTRIBUTED - kotlin("jvm") version BuildPluginsVersion.KOTLIN - id("distribution") - id("org.jetbrains.dokka") version BuildPluginsVersion.DOKKA - id("java-gradle-plugin") - id("com.vanniktech.maven.publish") version BuildPluginsVersion.MAVEN_PUBLISH apply false - id("com.diffplug.spotless") version BuildPluginsVersion.SPOTLESS - // we need this plugin in order to include .aar dependencies into a pure java project, which the gradle plugin is - id("io.sentry.android.gradle.aar2jar") - id("com.github.johnrengelman.shadow") version BuildPluginsVersion.SHADOW - id("com.github.gmazzo.buildconfig") version BuildPluginsVersion.BUILDCONFIG + id("dev.gradleplugins.groovy-gradle-plugin") version BuildPluginsVersion.GROOVY_REDISTRIBUTED + kotlin("jvm") version BuildPluginsVersion.KOTLIN + id("distribution") + id("org.jetbrains.dokka") version BuildPluginsVersion.DOKKA + id("java-gradle-plugin") + id("com.vanniktech.maven.publish") version BuildPluginsVersion.MAVEN_PUBLISH apply false + id("com.diffplug.spotless") version BuildPluginsVersion.SPOTLESS + // we need this plugin in order to include .aar dependencies into a pure java project, which the + // gradle plugin is + id("io.sentry.android.gradle.aar2jar") + id("com.github.johnrengelman.shadow") version BuildPluginsVersion.SHADOW + id("com.github.gmazzo.buildconfig") version BuildPluginsVersion.BUILDCONFIG } allprojects { - repositories { - mavenLocal() - mavenCentral() - google() - } + repositories { + mavenLocal() + mavenCentral() + google() + } } BootstrapAndroidSdk.locateAndroidSdk(project, extra) @@ -40,315 +41,294 @@ val testImplementationAar by configurations.getting // this converts .aar into . val agp70: SourceSet by sourceSets.creating val agp74: SourceSet by sourceSets.creating -val shade: Configuration by configurations.creating { +val shade: Configuration by + configurations.creating { isCanBeConsumed = false isCanBeResolved = true -} + } val fixtureClasspath: Configuration by configurations.creating dependencies { - agp70.compileOnlyConfigurationName(Libs.GRADLE_API) - agp70.compileOnlyConfigurationName(Libs.agp("7.0.4")) - agp70.compileOnlyConfigurationName(project(":common")) - - agp74.compileOnlyConfigurationName(Libs.GRADLE_API) - agp74.compileOnlyConfigurationName(Libs.agp("7.4.0")) - agp74.compileOnlyConfigurationName(project(":common")) - - compileOnly(Libs.GRADLE_API) - compileOnly(Libs.AGP) - compileOnly(agp70.output) - compileOnly(agp74.output) - compileOnly(Libs.PROGUARD) - - implementation(Libs.ASM) - implementation(Libs.ASM_COMMONS) - - compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${KotlinCompilerVersion.VERSION}") - - implementation(Libs.SENTRY) - - // compileOnly since we'll be shading the common dependency into the final jar - // but we still need to be able to compile it (this also excludes it from .pom) - compileOnly(project(":common")) - shade(project(":common")) - - testImplementation(gradleTestKit()) - testImplementation(kotlin("test")) - testImplementation(Libs.AGP) - testImplementation(agp70.output) - testImplementation(agp74.output) - testImplementation(project(":common")) - fixtureClasspath(agp70.output) - fixtureClasspath(agp74.output) - fixtureClasspath(project(":common")) - testImplementation(Libs.PROGUARD) - testImplementation(Libs.JUNIT) - testImplementation(Libs.MOCKITO_KOTLIN) - - testImplementation(Libs.ASM) - testImplementation(Libs.ASM_COMMONS) - - // we need these dependencies for tests, because the bytecode verifier also analyzes superclasses - testImplementationAar(Libs.SQLITE) - testImplementationAar(Libs.SQLITE_FRAMEWORK) - testRuntimeOnly(files(androidSdkPath)) - testImplementationAar(Libs.SENTRY_ANDROID) - testImplementationAar(Libs.SENTRY_ANDROID_OKHTTP) - testImplementationAar(Libs.SENTRY_OKHTTP) - - // Needed to read contents from APK/Source Bundles - testImplementation(Libs.ARSC_LIB) - testImplementation(Libs.ZIP4J) - - testRuntimeOnly( - files( - serviceOf().getModule("gradle-tooling-api-builders") - .classpath.asFiles.first() - ) + agp70.compileOnlyConfigurationName(Libs.GRADLE_API) + agp70.compileOnlyConfigurationName(Libs.agp("7.0.4")) + agp70.compileOnlyConfigurationName(project(":common")) + + agp74.compileOnlyConfigurationName(Libs.GRADLE_API) + agp74.compileOnlyConfigurationName(Libs.agp("7.4.0")) + agp74.compileOnlyConfigurationName(project(":common")) + + compileOnly(Libs.GRADLE_API) + compileOnly(Libs.AGP) + compileOnly(agp70.output) + compileOnly(agp74.output) + compileOnly(Libs.PROGUARD) + + implementation(Libs.ASM) + implementation(Libs.ASM_COMMONS) + + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${KotlinCompilerVersion.VERSION}") + + implementation(Libs.SENTRY) + + // compileOnly since we'll be shading the common dependency into the final jar + // but we still need to be able to compile it (this also excludes it from .pom) + compileOnly(project(":common")) + shade(project(":common")) + + testImplementation(gradleTestKit()) + testImplementation(kotlin("test")) + testImplementation(Libs.AGP) + testImplementation(agp70.output) + testImplementation(agp74.output) + testImplementation(project(":common")) + fixtureClasspath(agp70.output) + fixtureClasspath(agp74.output) + fixtureClasspath(project(":common")) + testImplementation(Libs.PROGUARD) + testImplementation(Libs.JUNIT) + testImplementation(Libs.MOCKITO_KOTLIN) + + testImplementation(Libs.ASM) + testImplementation(Libs.ASM_COMMONS) + + // we need these dependencies for tests, because the bytecode verifier also analyzes superclasses + testImplementationAar(Libs.SQLITE) + testImplementationAar(Libs.SQLITE_FRAMEWORK) + testRuntimeOnly(files(androidSdkPath)) + testImplementationAar(Libs.SENTRY_ANDROID) + testImplementationAar(Libs.SENTRY_ANDROID_OKHTTP) + testImplementationAar(Libs.SENTRY_OKHTTP) + + // Needed to read contents from APK/Source Bundles + testImplementation(Libs.ARSC_LIB) + testImplementation(Libs.ZIP4J) + + testRuntimeOnly( + files( + serviceOf().getModule("gradle-tooling-api-builders").classpath.asFiles.first() ) + ) } configure { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } // We need to compile Groovy first and let Kotlin depend on it. // See https://docs.gradle.org/6.1-rc-1/release-notes.html#compilation-order tasks.withType().configureEach { - sourceCompatibility = JavaVersion.VERSION_11.toString() - targetCompatibility = JavaVersion.VERSION_11.toString() - - // we don't need the groovy compile task for compatibility source sets - val ignoreTask = name.contains("agp", ignoreCase = true) - isEnabled = !ignoreTask - if (!ignoreTask) { - classpath = sourceSets["main"].compileClasspath - } + sourceCompatibility = JavaVersion.VERSION_11.toString() + targetCompatibility = JavaVersion.VERSION_11.toString() + + // we don't need the groovy compile task for compatibility source sets + val ignoreTask = name.contains("agp", ignoreCase = true) + isEnabled = !ignoreTask + if (!ignoreTask) { + classpath = sourceSets["main"].compileClasspath + } } tasks.withType().configureEach { - if (!name.contains("agp", ignoreCase = true)) { - libraries.from.addAll(files(sourceSets["main"].groovy.classesDirectory)) - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=enable") - languageVersion = "1.4" - apiVersion = "1.4" - } + if (!name.contains("agp", ignoreCase = true)) { + libraries.from.addAll(files(sourceSets["main"].groovy.classesDirectory)) + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=enable") + languageVersion = "1.4" + apiVersion = "1.4" + } } // Append any extra dependencies to the test fixtures via a custom configuration classpath. This // allows us to apply additional plugins in a fixture while still leveraging dependency resolution // and de-duplication semantics. tasks.named("pluginUnderTestMetadata").configure { - (this as PluginUnderTestMetadata).pluginClasspath.from(fixtureClasspath) + (this as PluginUnderTestMetadata).pluginClasspath.from(fixtureClasspath) } tasks.named("test").configure { - require(this is Test) - maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 + require(this is Test) + maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 - // Cap JVM args per test - minHeapSize = "128m" - maxHeapSize = "1g" + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" - filter { - excludeTestsMatching("io.sentry.android.gradle.integration.*") - } + filter { excludeTestsMatching("io.sentry.android.gradle.integration.*") } } tasks.register("integrationTest").configure { - group = "verification" - description = "Runs the integration tests" - - maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 - - // Cap JVM args per test - minHeapSize = "128m" - maxHeapSize = "1g" - - jvmArgs = listOf( - "--add-opens=java.base/java.lang=ALL-UNNAMED", - "--add-opens=java.base/java.io=ALL-UNNAMED", - "--add-opens=java.base/java.util=ALL-UNNAMED", - "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED", - "--add-opens=java.base/java.net=ALL-UNNAMED", - "--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED" + group = "verification" + description = "Runs the integration tests" + + maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 + + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" + + jvmArgs = + listOf( + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.io=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED", + "--add-opens=java.base/java.net=ALL-UNNAMED", + "--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED", ) - filter { - includeTestsMatching("io.sentry.android.gradle.integration.*") - } + filter { includeTestsMatching("io.sentry.android.gradle.integration.*") } } gradlePlugin { - plugins { - register("sentryPlugin") { - id = "io.sentry.android.gradle" - implementationClass = "io.sentry.android.gradle.SentryPlugin" - } - register("kotlinCompilerPlugin") { - id = "io.sentry.kotlin.compiler.gradle" - implementationClass = "io.sentry.kotlin.gradle.SentryKotlinCompilerGradlePlugin" - } - register("sentryJvmPlugin") { - id = "io.sentry.jvm.gradle" - implementationClass = "io.sentry.jvm.gradle.SentryJvmPlugin" - } + plugins { + register("sentryPlugin") { + id = "io.sentry.android.gradle" + implementationClass = "io.sentry.android.gradle.SentryPlugin" + } + register("kotlinCompilerPlugin") { + id = "io.sentry.kotlin.compiler.gradle" + implementationClass = "io.sentry.kotlin.gradle.SentryKotlinCompilerGradlePlugin" } + register("sentryJvmPlugin") { + id = "io.sentry.jvm.gradle" + implementationClass = "io.sentry.jvm.gradle.SentryJvmPlugin" + } + } } tasks.withType { - from(agp70.output) - from(agp74.output) + from(agp70.output) + from(agp74.output) } tasks.withType { - archiveClassifier.set("") - configurations = listOf(project.configurations.getByName("shade")) + archiveClassifier.set("") + configurations = listOf(project.configurations.getByName("shade")) - exclude("/kotlin/**") - exclude("/groovy**") - exclude("/org/**") + exclude("/kotlin/**") + exclude("/groovy**") + exclude("/org/**") } artifacts { - runtimeOnly(tasks.named("shadowJar")) - archives(tasks.named("shadowJar")) + runtimeOnly(tasks.named("shadowJar")) + archives(tasks.named("shadowJar")) } spotless { - kotlin { - ktfmt(BuildPluginsVersion.KTFMT).googleStyle() - targetExclude("**/generated/**") - } + kotlin { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**") + } + kotlinGradle { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**") + } } val sep = File.separator distributions { - main { - contents { - from("build${sep}libs") - from("build${sep}publications${sep}maven") - } - } - create("sentryPluginMarker") { - contents { - from("build${sep}publications${sep}sentryPluginPluginMarkerMaven") - } - } - create("sentryKotlinCompilerPluginMarker") { - contents { - from("build${sep}publications${sep}kotlinCompilerPluginPluginMarkerMaven") - } - } - create("sentryJvmPluginMarker") { - contents { - from("build${sep}publications${sep}sentryJvmPluginPluginMarkerMaven") - } + main { + contents { + from("build${sep}libs") + from("build${sep}publications${sep}maven") } + } + create("sentryPluginMarker") { + contents { from("build${sep}publications${sep}sentryPluginPluginMarkerMaven") } + } + create("sentryKotlinCompilerPluginMarker") { + contents { from("build${sep}publications${sep}kotlinCompilerPluginPluginMarkerMaven") } + } + create("sentryJvmPluginMarker") { + contents { from("build${sep}publications${sep}sentryJvmPluginPluginMarkerMaven") } + } } -apply { - plugin("com.vanniktech.maven.publish") -} +apply { plugin("com.vanniktech.maven.publish") } val publish = extensions.getByType(MavenPublishPluginExtension::class.java) + // signing is done when uploading files to MC // via gpg:sign-and-deploy-file (release.kts) publish.releaseSigningEnabled = false tasks.named("distZip") { - dependsOn("publishToMavenLocal") - onlyIf { - inputs.sourceFiles.isEmpty.not().also { - require(it) { "No distribution to zip." } - } - } + dependsOn("publishToMavenLocal") + onlyIf { inputs.sourceFiles.isEmpty.not().also { require(it) { "No distribution to zip." } } } } tasks.withType { - testLogging { - events = setOf( - TestLogEvent.SKIPPED, - TestLogEvent.PASSED, - TestLogEvent.FAILED - ) - showStandardStreams = true - } + testLogging { + events = setOf(TestLogEvent.SKIPPED, TestLogEvent.PASSED, TestLogEvent.FAILED) + showStandardStreams = true + } } -val downloadSentryCLI = tasks.register("downloadSentryCLI") { - onlyIf { - shouldDownloadSentryCli() - } - doFirst { - logger.lifecycle("Downloading Sentry CLI...") - } +val downloadSentryCLI = + tasks.register("downloadSentryCLI") { + onlyIf { shouldDownloadSentryCli() } + doFirst { logger.lifecycle("Downloading Sentry CLI...") } executable("sh") workingDir("../plugin-build") args("-c", "./download-sentry-cli.sh") -} + } -tasks.named("processResources").configure { - dependsOn(downloadSentryCLI) -} +tasks.named("processResources").configure { dependsOn(downloadSentryCLI) } /** - * Checks whether the sentry-cli.properties matches the copy in `./src/main/resources/bin/`. - * If it doesn't, the CLI should be re-downloaded. + * Checks whether the sentry-cli.properties matches the copy in `./src/main/resources/bin/`. If it + * doesn't, the CLI should be re-downloaded. */ fun shouldDownloadSentryCli(): Boolean { - val cliDir: Array = File( - "$projectDir/src/main/resources/bin/" - ).listFiles() ?: emptyArray() - val expectedSpec = File("$projectDir/sentry-cli.properties") - val actualSpec = File("$projectDir/src/main/resources/bin/sentry-cli.properties") - return when { - cliDir.size <= 2 -> { - logger.lifecycle("Sentry CLI is missing") - true - } - - !actualSpec.exists() -> { - logger.lifecycle("Sentry CLI version specification is missing") - true - } - - expectedSpec.readText() != actualSpec.readText() -> { - logger.lifecycle("Downloaded Sentry CLI version specification doesn't match") - true - } - - else -> false + val cliDir: Array = File("$projectDir/src/main/resources/bin/").listFiles() ?: emptyArray() + val expectedSpec = File("$projectDir/sentry-cli.properties") + val actualSpec = File("$projectDir/src/main/resources/bin/sentry-cli.properties") + return when { + cliDir.size <= 2 -> { + logger.lifecycle("Sentry CLI is missing") + true + } + + !actualSpec.exists() -> { + logger.lifecycle("Sentry CLI version specification is missing") + true } + + expectedSpec.readText() != actualSpec.readText() -> { + logger.lifecycle("Downloaded Sentry CLI version specification doesn't match") + true + } + + else -> false + } } buildConfig { - useKotlinOutput() - packageName("io.sentry") - className("BuildConfig") - - buildConfigField("String", "Version", provider { "\"${project.version}\"" }) - buildConfigField("String", "SdkVersion", provider { "\"${project.property("sdk_version")}\"" }) - buildConfigField("String", "AgpVersion", provider { "\"${BuildPluginsVersion.AGP}\"" }) - buildConfigField( - "String", - "CliVersion", - provider { - "\"${Properties().apply { + useKotlinOutput() + packageName("io.sentry") + className("BuildConfig") + + buildConfigField("String", "Version", provider { "\"${project.version}\"" }) + buildConfigField("String", "SdkVersion", provider { "\"${project.property("sdk_version")}\"" }) + buildConfigField("String", "AgpVersion", provider { "\"${BuildPluginsVersion.AGP}\"" }) + buildConfigField( + "String", + "CliVersion", + provider { + "\"${Properties().apply { load( FileInputStream(File("$projectDir/sentry-cli.properties")) ) }.getProperty("version")}\"" - } - ) + }, + ) } tasks.register("asmify") diff --git a/plugin-build/common/build.gradle.kts b/plugin-build/common/build.gradle.kts index d24f3524..5e1c3f7e 100644 --- a/plugin-build/common/build.gradle.kts +++ b/plugin-build/common/build.gradle.kts @@ -12,6 +12,10 @@ spotless { ktfmt(BuildPluginsVersion.KTFMT).googleStyle() targetExclude("**/generated/**") } + kotlinGradle { + ktfmt(BuildPluginsVersion.KTFMT).googleStyle() + targetExclude("**/generated/**") + } } configure { diff --git a/plugin-build/src/agp70/kotlin/io/sentry/android/gradle/AGP70Compat.kt b/plugin-build/src/agp70/kotlin/io/sentry/android/gradle/AGP70Compat.kt index 0548062c..63167839 100644 --- a/plugin-build/src/agp70/kotlin/io/sentry/android/gradle/AGP70Compat.kt +++ b/plugin-build/src/agp70/kotlin/io/sentry/android/gradle/AGP70Compat.kt @@ -18,55 +18,53 @@ import org.gradle.api.file.FileCollection import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider -data class AndroidVariant70( - private val variant: ApplicationVariant -) : SentryVariant { - override val name: String = variant.name - override val flavorName: String? = variant.flavorName - override val buildTypeName: String = variant.buildType.name - override val productFlavors: List = variant.productFlavors.map { it.name } - override val isMinifyEnabled: Boolean = variant.buildType.isMinifyEnabled - override val isDebuggable: Boolean = variant.buildType.isDebuggable - override val packageProvider: TaskProvider? = variant.packageApplicationProvider - override val assembleProvider: TaskProvider? = variant.assembleProvider - override val installProvider: TaskProvider? = variant.installProvider - override fun mappingFileProvider(project: Project): Provider = - variant.mappingFileProvider - override fun sources( - project: Project, - additionalSources: Provider> - ): Provider> { - val projectDir = project.layout.projectDirectory - return project.provider { - val javaDirs = variant.sourceSets.flatMap { - it.javaDirectories.map { javaDir -> projectDir.dir(javaDir.absolutePath) } - } - val kotlinDirs = variant.sourceSets.flatMap { - it.kotlinDirectories.map { kotlinDir -> projectDir.dir(kotlinDir.absolutePath) } - } - (kotlinDirs + javaDirs + additionalSources.get()).filterBuildConfig().toSet() +data class AndroidVariant70(private val variant: ApplicationVariant) : SentryVariant { + override val name: String = variant.name + override val flavorName: String? = variant.flavorName + override val buildTypeName: String = variant.buildType.name + override val productFlavors: List = variant.productFlavors.map { it.name } + override val isMinifyEnabled: Boolean = variant.buildType.isMinifyEnabled + override val isDebuggable: Boolean = variant.buildType.isDebuggable + override val packageProvider: TaskProvider? = variant.packageApplicationProvider + override val assembleProvider: TaskProvider? = variant.assembleProvider + override val installProvider: TaskProvider? = variant.installProvider + + override fun mappingFileProvider(project: Project): Provider = + variant.mappingFileProvider + + override fun sources( + project: Project, + additionalSources: Provider>, + ): Provider> { + val projectDir = project.layout.projectDirectory + return project.provider { + val javaDirs = + variant.sourceSets.flatMap { + it.javaDirectories.map { javaDir -> projectDir.dir(javaDir.absolutePath) } + } + val kotlinDirs = + variant.sourceSets.flatMap { + it.kotlinDirectories.map { kotlinDir -> projectDir.dir(kotlinDir.absolutePath) } } + (kotlinDirs + javaDirs + additionalSources.get()).filterBuildConfig().toSet() } + } } fun configureInstrumentationFor70( - variant: Variant, - classVisitorFactoryImplClass: Class>, - scope: InstrumentationScope, - mode: FramesComputationMode, - instrumentationParamsConfig: (T) -> Unit + variant: Variant, + classVisitorFactoryImplClass: Class>, + scope: InstrumentationScope, + mode: FramesComputationMode, + instrumentationParamsConfig: (T) -> Unit, ) { - variant.transformClassesWith( - classVisitorFactoryImplClass, - scope, - instrumentationParamsConfig - ) - variant.setAsmFramesComputationMode(mode) + variant.transformClassesWith(classVisitorFactoryImplClass, scope, instrumentationParamsConfig) + variant.setAsmFramesComputationMode(mode) } fun onVariants70( - androidComponentsExt: AndroidComponentsExtension<*, *, *>, - callback: (Variant) -> Unit + androidComponentsExt: AndroidComponentsExtension<*, *, *>, + callback: (Variant) -> Unit, ) { - androidComponentsExt.onVariants(callback = callback) + androidComponentsExt.onVariants(callback = callback) } diff --git a/plugin-build/src/agp74/kotlin/io/sentry/android/gradle/AGP74Compat.kt b/plugin-build/src/agp74/kotlin/io/sentry/android/gradle/AGP74Compat.kt index 996dd6d1..82d03f34 100644 --- a/plugin-build/src/agp74/kotlin/io/sentry/android/gradle/AGP74Compat.kt +++ b/plugin-build/src/agp74/kotlin/io/sentry/android/gradle/AGP74Compat.kt @@ -23,96 +23,96 @@ import org.gradle.api.provider.Provider import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.TaskProvider -data class AndroidVariant74( - private val variant: Variant -) : SentryVariant { - override val name: String = variant.name - 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 +data class AndroidVariant74(private val variant: Variant) : SentryVariant { + override val name: String = variant.name + 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 - // 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 - override val isDebuggable: Boolean = (variant as? ApplicationVariantImpl)?.debuggable == 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 + override val isDebuggable: Boolean = (variant as? ApplicationVariantImpl)?.debuggable == true - // internal APIs are a bit dirty, but our plugin would need a lot of rework to make proper - // dependencies via artifacts API. - override val assembleProvider: TaskProvider? - get() = (variant as? VariantImpl<*>)?.taskContainer?.assembleTask - override val installProvider: TaskProvider? - get() = (variant as? VariantImpl<*>)?.taskContainer?.installTask - override fun mappingFileProvider(project: Project): Provider = - project.provider { - project.files(variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE)) - } - override fun sources( - project: Project, - additionalSources: Provider> - ): Provider> { - val javaProvider = variant.sources.java?.all - val kotlinProvider = variant.sources.kotlin?.all + // internal APIs are a bit dirty, but our plugin would need a lot of rework to make proper + // dependencies via artifacts API. + override val assembleProvider: TaskProvider? + get() = (variant as? VariantImpl<*>)?.taskContainer?.assembleTask - // we cannot use .zip to combine the sources, because of possibly variation of this bug: - // https://github.com/gradle/gradle/issues/23014, but using .map works just fine, so we just - // call .get() inside the .map, and the providers will be lazily evaluated this way. - return when { - javaProvider == null && kotlinProvider == null -> additionalSources - javaProvider == null -> kotlinProvider!!.map { kotlin -> - (kotlin + additionalSources.get()).filterBuildConfig().toSet() - } - kotlinProvider == null -> javaProvider.map { java -> - (java + additionalSources.get()).filterBuildConfig().toSet() - } - else -> - javaProvider.map { java -> - (java + kotlinProvider.get() + additionalSources.get()) - .filterBuildConfig() - .toSet() - } - } + override val installProvider: TaskProvider? + get() = (variant as? VariantImpl<*>)?.taskContainer?.installTask + + override fun mappingFileProvider(project: Project): Provider = + project.provider { + project.files(variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE)) } - fun assetsWiredWithDirectories( - task: TaskProvider, - inputDir: (T) -> DirectoryProperty, - outputDir: (T) -> DirectoryProperty - ) { - variant.artifacts.use(task).wiredWithDirectories( - inputDir, - outputDir - ).toTransform(SingleArtifact.ASSETS) + override fun sources( + project: Project, + additionalSources: Provider>, + ): Provider> { + val javaProvider = variant.sources.java?.all + val kotlinProvider = variant.sources.kotlin?.all + + // we cannot use .zip to combine the sources, because of possibly variation of this bug: + // https://github.com/gradle/gradle/issues/23014, but using .map works just fine, so we just + // call .get() inside the .map, and the providers will be lazily evaluated this way. + return when { + javaProvider == null && kotlinProvider == null -> additionalSources + javaProvider == null -> + kotlinProvider!!.map { kotlin -> + (kotlin + additionalSources.get()).filterBuildConfig().toSet() + } + kotlinProvider == null -> + javaProvider.map { java -> (java + additionalSources.get()).filterBuildConfig().toSet() } + else -> + javaProvider.map { java -> + (java + kotlinProvider.get() + additionalSources.get()).filterBuildConfig().toSet() + } } + } + + fun assetsWiredWithDirectories( + task: TaskProvider, + inputDir: (T) -> DirectoryProperty, + outputDir: (T) -> DirectoryProperty, + ) { + variant.artifacts + .use(task) + .wiredWithDirectories(inputDir, outputDir) + .toTransform(SingleArtifact.ASSETS) + } } fun configureGeneratedSourcesFor74( - variant: Variant, - vararg tasks: Pair, (T) -> DirectoryProperty> + variant: Variant, + vararg tasks: Pair, (T) -> DirectoryProperty>, ) { - tasks.forEach { (task, output) -> - variant.sources.assets?.addGeneratedSourceDirectory(task, output) - } + tasks.forEach { (task, output) -> + variant.sources.assets?.addGeneratedSourceDirectory(task, output) + } } fun configureInstrumentationFor74( - variant: Variant, - classVisitorFactoryImplClass: Class>, - scope: InstrumentationScope, - mode: FramesComputationMode, - excludes: SetProperty, - instrumentationParamsConfig: (T) -> Unit + variant: Variant, + classVisitorFactoryImplClass: Class>, + scope: InstrumentationScope, + mode: FramesComputationMode, + excludes: SetProperty, + instrumentationParamsConfig: (T) -> Unit, ) { - variant.instrumentation.transformClassesWith( - classVisitorFactoryImplClass, - scope, - instrumentationParamsConfig - ) - variant.instrumentation.setAsmFramesComputationMode(mode) - variant.instrumentation.excludes.set(excludes) + variant.instrumentation.transformClassesWith( + classVisitorFactoryImplClass, + scope, + instrumentationParamsConfig, + ) + variant.instrumentation.setAsmFramesComputationMode(mode) + variant.instrumentation.excludes.set(excludes) } fun onVariants74( - androidComponentsExt: AndroidComponentsExtension<*, *, *>, - callback: (Variant) -> Unit + androidComponentsExt: AndroidComponentsExtension<*, *, *>, + callback: (Variant) -> Unit, ) { - androidComponentsExt.onVariants(callback = callback) + androidComponentsExt.onVariants(callback = callback) } 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 b5a357ce..4e283490 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 @@ -50,404 +50,395 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.internal.build.event.BuildEventListenerRegistryInternal fun AndroidComponentsExtension<*, *, *>.configure( - project: Project, - extension: SentryPluginExtension, - buildEvents: BuildEventListenerRegistryInternal, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String?, + project: Project, + extension: SentryPluginExtension, + buildEvents: BuildEventListenerRegistryInternal, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ) { - // temp folder for sentry-related stuff - val tmpDir = File("${project.buildDir}${sep}tmp${sep}sentry") - tmpDir.mkdirs() - - configureVariants { variant -> - if (isVariantAllowed(extension, variant.name, variant.flavorName, variant.buildType)) { - val paths = OutputPaths(project, variant.name) - - val sentryTelemetryProvider = variant.configureTelemetry( - project, - extension, - cliExecutable, - sentryOrg, - buildEvents - ) + // temp folder for sentry-related stuff + val tmpDir = File("${project.buildDir}${sep}tmp${sep}sentry") + tmpDir.mkdirs() - variant.configureDependenciesTask(project, extension, sentryTelemetryProvider) + configureVariants { variant -> + if (isVariantAllowed(extension, variant.name, variant.flavorName, variant.buildType)) { + val paths = OutputPaths(project, variant.name) - // TODO: do this only once, and all other tasks should be SentryVariant.configureSomething - val sentryVariant = if (isAGP74) AndroidVariant74(variant) else null + val sentryTelemetryProvider = + variant.configureTelemetry(project, extension, cliExecutable, sentryOrg, buildEvents) - val additionalSourcesProvider = project.provider { - extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()) - .map { project.layout.projectDirectory.dir(it) } - } - val sourceFiles = sentryVariant?.sources( - project, - additionalSourcesProvider - ) + variant.configureDependenciesTask(project, extension, sentryTelemetryProvider) - val tasksGeneratingProperties = - mutableListOf>() - val sourceContextTasks = variant.configureSourceBundleTasks( - project, - extension, - sentryTelemetryProvider, - paths, - sourceFiles, - cliExecutable, - sentryOrg, - sentryProject - ) - sourceContextTasks?.let { tasksGeneratingProperties.add(it.generateBundleIdTask) } - - val generateProguardUuidTask = variant.configureProguardMappingsTasks( - project, - extension, - sentryTelemetryProvider, - paths, - cliExecutable, - sentryOrg, - sentryProject - ) - generateProguardUuidTask?.let { tasksGeneratingProperties.add(it) } - - sentryVariant?.configureNativeSymbolsTask( - project, - extension, - sentryTelemetryProvider, - cliExecutable, - sentryOrg, - sentryProject - ) + // TODO: do this only once, and all other tasks should be SentryVariant.configureSomething + val sentryVariant = if (isAGP74) AndroidVariant74(variant) else null + + val additionalSourcesProvider = + project.provider { + extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()).map { + project.layout.projectDirectory.dir(it) + } + } + val sourceFiles = sentryVariant?.sources(project, additionalSourcesProvider) + + val tasksGeneratingProperties = mutableListOf>() + val sourceContextTasks = + variant.configureSourceBundleTasks( + project, + extension, + sentryTelemetryProvider, + paths, + sourceFiles, + cliExecutable, + sentryOrg, + sentryProject, + ) + sourceContextTasks?.let { tasksGeneratingProperties.add(it.generateBundleIdTask) } + + val generateProguardUuidTask = + variant.configureProguardMappingsTasks( + project, + extension, + sentryTelemetryProvider, + paths, + cliExecutable, + sentryOrg, + sentryProject, + ) + generateProguardUuidTask?.let { tasksGeneratingProperties.add(it) } + + sentryVariant?.configureNativeSymbolsTask( + project, + extension, + sentryTelemetryProvider, + cliExecutable, + sentryOrg, + sentryProject, + ) + + // we can't hook into asset generation, nor manifest merging, as all those tasks + // are dependencies of the compilation / minification task + // and as our ProGuard UUID depends on minification itself; creating a + // circular dependency + // instead, we transform all assets and inject the properties file + sentryVariant?.apply { + val injectAssetsTask = + InjectSentryMetaPropertiesIntoAssetsTask.register( + project, + extension, + sentryTelemetryProvider, + tasksGeneratingProperties, + variant.name.capitalized, + ) + + assetsWiredWithDirectories( + injectAssetsTask, + InjectSentryMetaPropertiesIntoAssetsTask::inputDir, + InjectSentryMetaPropertiesIntoAssetsTask::outputDir, + ) + + // flutter doesn't use the transform API + // and manually wires up task dependencies, + // which causes errors like this: + // Task ':app:injectSentryDebugMetaPropertiesIntoAssetsDebug' uses this output of task + // ':app:copyFlutterAssetsDebug' without declaring an explicit or implicit dependency + // thus we have to manually add the task dependency + project.afterEvaluate { + // https://github.com/flutter/flutter/blob/6ce591f7ea3ba827d9340ce03f7d8e3a37ebb03a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy#L1295-L1298 + project.tasks.findByName("copyFlutterAssets${variant.name.capitalized}")?.let { + flutterAssetsTask -> + injectAssetsTask.configure { injectTask -> injectTask.dependsOn(flutterAssetsTask) } + } + } + } + + if (extension.tracingInstrumentation.enabled.get()) { + /** + * We detect sentry-android SDK version using configurations.incoming.afterResolve. This is + * guaranteed to be executed BEFORE any of the build tasks/transforms are started. + * + * After detecting the sdk state, we use Gradle's shared build service to persist the state + * between builds and also during a single build, because transforms are run in parallel. + */ + val sentryModulesService = + SentryModulesService.register( + project, + extension.tracingInstrumentation.features, + extension.tracingInstrumentation.logcat.enabled, + extension.includeSourceContext, + extension.dexguardEnabled, + extension.tracingInstrumentation.appStart.enabled, + ) + /** + * We have to register SentryModulesService as a build event listener, so it will not be + * discarded after the configuration phase (where we store the collected dependencies), and + * will be passed down to the InstrumentationFactory + */ + buildEvents.onTaskCompletion(sentryModulesService) + + project.collectModules( + "${variant.name}RuntimeClasspath", + variant.name, + sentryModulesService, + ) + + variant.configureInstrumentation( + SpanAddingClassVisitorFactory::class.java, + InstrumentationScope.ALL, + FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS, + extension.tracingInstrumentation.excludes, + ) { params -> + if (extension.tracingInstrumentation.forceInstrumentDependencies.get()) { + params.invalidate.setDisallowChanges(System.currentTimeMillis()) + } + params.debug.setDisallowChanges(extension.tracingInstrumentation.debug.get()) + params.logcatMinLevel.setDisallowChanges(extension.tracingInstrumentation.logcat.minLevel) + params.sentryModulesService.setDisallowChanges(sentryModulesService) + params.tmpDir.set(tmpDir) + } - // we can't hook into asset generation, nor manifest merging, as all those tasks - // are dependencies of the compilation / minification task - // and as our ProGuard UUID depends on minification itself; creating a - // circular dependency - // instead, we transform all assets and inject the properties file - sentryVariant?.apply { - val injectAssetsTask = InjectSentryMetaPropertiesIntoAssetsTask.register( - project, - extension, - sentryTelemetryProvider, - tasksGeneratingProperties, - variant.name.capitalized - ) - - assetsWiredWithDirectories( - injectAssetsTask, - InjectSentryMetaPropertiesIntoAssetsTask::inputDir, - InjectSentryMetaPropertiesIntoAssetsTask::outputDir - ) - - // flutter doesn't use the transform API - // and manually wires up task dependencies, - // which causes errors like this: - // Task ':app:injectSentryDebugMetaPropertiesIntoAssetsDebug' uses this output of task ':app:copyFlutterAssetsDebug' without declaring an explicit or implicit dependency - // thus we have to manually add the task dependency - project.afterEvaluate { - // https://github.com/flutter/flutter/blob/6ce591f7ea3ba827d9340ce03f7d8e3a37ebb03a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy#L1295-L1298 - project.tasks - .findByName("copyFlutterAssets${variant.name.capitalized}") - ?.let { flutterAssetsTask -> - injectAssetsTask.configure { injectTask -> - injectTask.dependsOn(flutterAssetsTask) - } - } - } - } - - if (extension.tracingInstrumentation.enabled.get()) { - /** - * We detect sentry-android SDK version using configurations.incoming.afterResolve. - * This is guaranteed to be executed BEFORE any of the build tasks/transforms are started. - * - * After detecting the sdk state, we use Gradle's shared build service to persist - * the state between builds and also during a single build, because transforms - * are run in parallel. - */ - val sentryModulesService = SentryModulesService.register( - project, - extension.tracingInstrumentation.features, - extension.tracingInstrumentation.logcat.enabled, - extension.includeSourceContext, - extension.dexguardEnabled, - extension.tracingInstrumentation.appStart.enabled - ) - /** - * We have to register SentryModulesService as a build event listener, so it will - * not be discarded after the configuration phase (where we store the collected - * dependencies), and will be passed down to the InstrumentationFactory - */ - buildEvents.onTaskCompletion(sentryModulesService) - - project.collectModules( - "${variant.name}RuntimeClasspath", - variant.name, - sentryModulesService - ) - - variant.configureInstrumentation( - SpanAddingClassVisitorFactory::class.java, - InstrumentationScope.ALL, - FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS, - extension.tracingInstrumentation.excludes - ) { params -> - if (extension.tracingInstrumentation.forceInstrumentDependencies.get()) { - params.invalidate.setDisallowChanges(System.currentTimeMillis()) - } - params.debug.setDisallowChanges( - extension.tracingInstrumentation.debug.get() - ) - params.logcatMinLevel.setDisallowChanges( - extension.tracingInstrumentation.logcat.minLevel - ) - params.sentryModulesService.setDisallowChanges(sentryModulesService) - params.tmpDir.set(tmpDir) - } - - val manifestUpdater = project.tasks.register( - "${variant.name}SentryGenerateIntegrationListTask", - SentryGenerateIntegrationListTask::class.java - ) { - it.integrations.set( - sentryModulesService.map { service -> - service.retrieveEnabledInstrumentationFeatures() - } - ) - it.usesService(sentryModulesService) - it.withSentryTelemetry(extension, sentryTelemetryProvider) - } - - variant.artifacts.use(manifestUpdater).wiredWithFiles( - SentryGenerateIntegrationListTask::mergedManifest, - SentryGenerateIntegrationListTask::updatedManifest - ).toTransform(SingleArtifact.MERGED_MANIFEST) - - /** - * This necessary to address the issue when target app uses a multi-release jar - * (MR-JAR) as a dependency. https://github.com/getsentry/sentry-android-gradle-plugin/issues/256 - * - * We register a transform (https://docs.gradle.org/current/userguide/artifact_transforms.html) - * that will strip-out unnecessary files from the MR-JAR, so the AGP transforms - * will consume corrected artifacts. We only do this when auto-instrumentation is - * enabled (otherwise there's no need in this fix) AND when AGP version - * is below 7.1.2, where this issue has been fixed. - * (https://androidstudio.googleblog.com/2022/02/android-studio-bumblebee-202111-patch-2.html) - */ - if (AgpVersions.CURRENT < AgpVersions.VERSION_7_1_2) { - // we are only interested in runtime configuration (as ASM transform is - // also run just for the runtime configuration) - project.configurations.named("${variant.name}RuntimeClasspath") - .configure { - it.attributes.attribute(MetaInfStripTransform.metaInfStripped, true) - } - MetaInfStripTransform.register( - project.dependencies, - extension.tracingInstrumentation.forceInstrumentDependencies.get() - ) - } - } + val manifestUpdater = + project.tasks.register( + "${variant.name}SentryGenerateIntegrationListTask", + SentryGenerateIntegrationListTask::class.java, + ) { + it.integrations.set( + sentryModulesService.map { service -> + service.retrieveEnabledInstrumentationFeatures() + } + ) + it.usesService(sentryModulesService) + it.withSentryTelemetry(extension, sentryTelemetryProvider) + } + + variant.artifacts + .use(manifestUpdater) + .wiredWithFiles( + SentryGenerateIntegrationListTask::mergedManifest, + SentryGenerateIntegrationListTask::updatedManifest, + ) + .toTransform(SingleArtifact.MERGED_MANIFEST) + + /** + * This necessary to address the issue when target app uses a multi-release jar (MR-JAR) as + * a dependency. https://github.com/getsentry/sentry-android-gradle-plugin/issues/256 + * + * We register a transform + * (https://docs.gradle.org/current/userguide/artifact_transforms.html) that will strip-out + * unnecessary files from the MR-JAR, so the AGP transforms will consume corrected + * artifacts. We only do this when auto-instrumentation is enabled (otherwise there's no + * need in this fix) AND when AGP version is below 7.1.2, where this issue has been fixed. + * (https://androidstudio.googleblog.com/2022/02/android-studio-bumblebee-202111-patch-2.html) + */ + if (AgpVersions.CURRENT < AgpVersions.VERSION_7_1_2) { + // we are only interested in runtime configuration (as ASM transform is + // also run just for the runtime configuration) + project.configurations.named("${variant.name}RuntimeClasspath").configure { + it.attributes.attribute(MetaInfStripTransform.metaInfStripped, true) + } + MetaInfStripTransform.register( + project.dependencies, + extension.tracingInstrumentation.forceInstrumentDependencies.get(), + ) } + } } + } } private fun Variant.configureTelemetry( - project: Project, - extension: SentryPluginExtension, - cliExecutable: Provider, - sentryOrg: String?, - buildEvents: BuildEventListenerRegistryInternal + project: Project, + extension: SentryPluginExtension, + cliExecutable: Provider, + sentryOrg: String?, + buildEvents: BuildEventListenerRegistryInternal, ): Provider { - val variant = if (isAGP74) AndroidVariant74(this) else null - val sentryTelemetryProvider = SentryTelemetryService.register(project) - project.gradle.taskGraph.whenReady { - sentryTelemetryProvider.get().start { - SentryTelemetryService.createParameters( - project, - variant, - extension, - cliExecutable, - sentryOrg, - "Android" - ) - } - buildEvents.onOperationCompletion(sentryTelemetryProvider) + val variant = if (isAGP74) AndroidVariant74(this) else null + val sentryTelemetryProvider = SentryTelemetryService.register(project) + project.gradle.taskGraph.whenReady { + sentryTelemetryProvider.get().start { + SentryTelemetryService.createParameters( + project, + variant, + extension, + cliExecutable, + sentryOrg, + "Android", + ) } - return sentryTelemetryProvider + buildEvents.onOperationCompletion(sentryTelemetryProvider) + } + return sentryTelemetryProvider } private fun Variant.configureSourceBundleTasks( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - paths: OutputPaths, - sourceFiles: Provider>?, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String? + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + paths: OutputPaths, + sourceFiles: Provider>?, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ): SourceContext.SourceContextTasks? { - if (extension.includeSourceContext.get()) { - if (isAGP74) { - val taskSuffix = name.capitalized - val variant = AndroidVariant74(this) - - val sourceContextTasks = SourceContext.register( - project, - extension, - sentryTelemetryProvider, - variant, - paths, - sourceFiles, - cliExecutable, - sentryOrg, - sentryProject, - taskSuffix - ) + if (extension.includeSourceContext.get()) { + if (isAGP74) { + val taskSuffix = name.capitalized + val variant = AndroidVariant74(this) + + val sourceContextTasks = + SourceContext.register( + project, + extension, + sentryTelemetryProvider, + variant, + paths, + sourceFiles, + cliExecutable, + sentryOrg, + sentryProject, + taskSuffix, + ) - if (variant.buildTypeName == "release") { - sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, variant) - } - - return sourceContextTasks - } else { - project.logger.info { - "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it " + - "does not have new addGeneratedSourceDirectory API" - } - return null - } + if (variant.buildTypeName == "release") { + sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, variant) + } + + return sourceContextTasks } else { - return null + project.logger.info { + "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it " + + "does not have new addGeneratedSourceDirectory API" + } + return null } + } else { + return null + } } private fun Variant.configureDependenciesTask( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, ) { - if (isAGP74) { - if (extension.includeDependenciesReport.get()) { - val reportDependenciesTask = - SentryExternalDependenciesReportTaskFactory.register( - project = project, - extension, - sentryTelemetryProvider, - configurationName = "${name}RuntimeClasspath", - attributeValueJar = "android-classes", - includeReport = extension.includeDependenciesReport, - taskSuffix = name.capitalized - ) - configureGeneratedSourcesFor74( - variant = this, - reportDependenciesTask to DirectoryOutputTask::output - ) - } - } else { - project.logger.info { - "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it does" + - "not have new addGeneratedSourceDirectory API" - } + if (isAGP74) { + if (extension.includeDependenciesReport.get()) { + val reportDependenciesTask = + SentryExternalDependenciesReportTaskFactory.register( + project = project, + extension, + sentryTelemetryProvider, + configurationName = "${name}RuntimeClasspath", + attributeValueJar = "android-classes", + includeReport = extension.includeDependenciesReport, + taskSuffix = name.capitalized, + ) + configureGeneratedSourcesFor74( + variant = this, + reportDependenciesTask to DirectoryOutputTask::output, + ) + } + } else { + project.logger.info { + "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it does" + + "not have new addGeneratedSourceDirectory API" } + } } private fun Variant.configureProguardMappingsTasks( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - paths: OutputPaths, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String? + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + paths: OutputPaths, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ): TaskProvider? { - if (isAGP74) { - val variant = AndroidVariant74(this) - val sentryProps = getPropertiesFilePath(project, variant) - val dexguardEnabled = extension.dexguardEnabled.get() - val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled) - - if (isMinifyEnabled && extension.includeProguardMapping.get()) { - val generateUuidTask = - SentryGenerateProguardUuidTask.register( - project = project, - extension, - sentryTelemetryProvider, - proguardMappingFile = getMappingFileProvider(project, variant, dexguardEnabled), - taskSuffix = name.capitalized, - output = paths.proguardUuidDir - ) - - val releaseInfo = getReleaseInfo(project, this) - val uploadMappingsTask = SentryUploadProguardMappingsTask.register( - project = project, - extension, - sentryTelemetryProvider, - debug = extension.debug, - cliExecutable = cliExecutable, - generateUuidTask = generateUuidTask, - sentryProperties = sentryProps, - mappingFiles = getMappingFileProvider(project, variant, dexguardEnabled), - autoUploadProguardMapping = extension.autoUploadProguardMapping, - sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, - sentryProject = sentryProject?.let { project.provider { it } } - ?: extension.projectName, - sentryAuthToken = extension.authToken, - sentryUrl = extension.url, - taskSuffix = name.capitalized, - releaseInfo = releaseInfo - ) + if (isAGP74) { + val variant = AndroidVariant74(this) + val sentryProps = getPropertiesFilePath(project, variant) + val dexguardEnabled = extension.dexguardEnabled.get() + val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled) + + if (isMinifyEnabled && extension.includeProguardMapping.get()) { + val generateUuidTask = + SentryGenerateProguardUuidTask.register( + project = project, + extension, + sentryTelemetryProvider, + proguardMappingFile = getMappingFileProvider(project, variant, dexguardEnabled), + taskSuffix = name.capitalized, + output = paths.proguardUuidDir, + ) - generateUuidTask.hookWithMinifyTasks( - project, - name, - dexguardEnabled && GroovyCompat.isDexguardEnabledForVariant(project, name) - ) + val releaseInfo = getReleaseInfo(project, this) + val uploadMappingsTask = + SentryUploadProguardMappingsTask.register( + project = project, + extension, + sentryTelemetryProvider, + debug = extension.debug, + cliExecutable = cliExecutable, + generateUuidTask = generateUuidTask, + sentryProperties = sentryProps, + mappingFiles = getMappingFileProvider(project, variant, dexguardEnabled), + autoUploadProguardMapping = extension.autoUploadProguardMapping, + sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, + sentryProject = sentryProject?.let { project.provider { it } } ?: extension.projectName, + sentryAuthToken = extension.authToken, + sentryUrl = extension.url, + taskSuffix = name.capitalized, + releaseInfo = releaseInfo, + ) - uploadMappingsTask.hookWithAssembleTasks( - project, - variant - ) + generateUuidTask.hookWithMinifyTasks( + project, + name, + dexguardEnabled && GroovyCompat.isDexguardEnabledForVariant(project, name), + ) - return generateUuidTask - } else { - return null - } + uploadMappingsTask.hookWithAssembleTasks(project, variant) + + return generateUuidTask } else { - project.logger.info { - "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it does" + - "not have new addGeneratedSourceDirectory API" - } - return null + return null } + } else { + project.logger.info { + "Not configuring AndroidComponentsExtension for ${AgpVersions.CURRENT}, since it does" + + "not have new addGeneratedSourceDirectory API" + } + return null + } } private fun Variant.configureInstrumentation( - classVisitorFactoryImplClass: Class>, - scope: InstrumentationScope, - mode: FramesComputationMode, - excludes: SetProperty, - instrumentationParamsConfig: (T) -> Unit, + classVisitorFactoryImplClass: Class>, + scope: InstrumentationScope, + mode: FramesComputationMode, + excludes: SetProperty, + instrumentationParamsConfig: (T) -> Unit, ) { - if (isAGP74) { - configureInstrumentationFor74( - variant = this, - classVisitorFactoryImplClass, - scope, - mode, - excludes, - instrumentationParamsConfig - ) - } else { - configureInstrumentationFor70( - variant = this, - classVisitorFactoryImplClass, - scope, - mode, - instrumentationParamsConfig - ) - } + if (isAGP74) { + configureInstrumentationFor74( + variant = this, + classVisitorFactoryImplClass, + scope, + mode, + excludes, + instrumentationParamsConfig, + ) + } else { + configureInstrumentationFor70( + variant = this, + classVisitorFactoryImplClass, + scope, + mode, + instrumentationParamsConfig, + ) + } } /** @@ -455,24 +446,23 @@ private fun Variant.configureInstrumentation( * have to distinguish here, although the compatibility sources would look exactly the same. */ private fun AndroidComponentsExtension<*, *, *>.configureVariants(callback: (Variant) -> Unit) { - if (isAGP74) { - onVariants74(this, callback) - } else { - onVariants70(this, callback) - } + if (isAGP74) { + onVariants74(this, callback) + } else { + onVariants70(this, callback) + } } private fun getReleaseInfo(project: Project, variant: Variant): ReleaseInfo { - val appExtension = project.extensions.getByType(AppExtension::class.java) - var applicationId = - appExtension.defaultConfig.applicationId ?: appExtension.namespace.toString() - var versionName = appExtension.defaultConfig.versionName ?: "undefined" - var versionCode = appExtension.defaultConfig.versionCode - val flavor = appExtension.productFlavors.find { it.name == variant.flavorName } - flavor?.applicationId?.let { applicationId = it } - flavor?.versionName?.let { versionName = it } - flavor?.versionCode?.let { versionCode = it } - flavor?.applicationIdSuffix?.let { applicationId += it } - flavor?.versionNameSuffix?.let { versionName += it } - return ReleaseInfo(applicationId, versionName, versionCode) + val appExtension = project.extensions.getByType(AppExtension::class.java) + var applicationId = appExtension.defaultConfig.applicationId ?: appExtension.namespace.toString() + var versionName = appExtension.defaultConfig.versionName ?: "undefined" + var versionCode = appExtension.defaultConfig.versionCode + val flavor = appExtension.productFlavors.find { it.name == variant.flavorName } + flavor?.applicationId?.let { applicationId = it } + flavor?.versionName?.let { versionName = it } + flavor?.versionCode?.let { versionCode = it } + flavor?.applicationIdSuffix?.let { applicationId += it } + flavor?.versionNameSuffix?.let { versionName += it } + return ReleaseInfo(applicationId, versionName, versionCode) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AppConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AppConfig.kt index e40cd9ea..24e9ad9b 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AppConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AppConfig.kt @@ -38,332 +38,330 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.internal.build.event.BuildEventListenerRegistryInternal fun AppExtension.configure( - project: Project, - extension: SentryPluginExtension, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String?, - buildEvents: BuildEventListenerRegistryInternal + project: Project, + extension: SentryPluginExtension, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, + buildEvents: BuildEventListenerRegistryInternal, ) { - applicationVariants.matching { - isVariantAllowed(extension, it.name, it.flavorName, it.buildType.name) - }.configureEach { variant -> - val mergeAssetsDependants = setOf( - getMergeAssetsProvider(variant), - // lint vital tasks scan the entire "build" folder; since we're writing our - // generated stuff in there, we put explicit dependency on them to avoid - // warnings about implicit dependency - withLogging(project.logger, "lintVitalAnalyzeTask") { - getLintVitalAnalyzeProvider(project, variant.name) - }, - withLogging(project.logger, "lintVitalReportTask") { - getLintVitalReportProvider(project, variant.name) - } + applicationVariants + .matching { isVariantAllowed(extension, it.name, it.flavorName, it.buildType.name) } + .configureEach { variant -> + val mergeAssetsDependants = + setOf( + getMergeAssetsProvider(variant), + // lint vital tasks scan the entire "build" folder; since we're writing our + // generated stuff in there, we put explicit dependency on them to avoid + // warnings about implicit dependency + withLogging(project.logger, "lintVitalAnalyzeTask") { + getLintVitalAnalyzeProvider(project, variant.name) + }, + withLogging(project.logger, "lintVitalReportTask") { + getLintVitalReportProvider(project, variant.name) + }, ) - val sentryTelemetryProvider = variant.configureTelemetry( - project, - extension, - cliExecutable, - sentryOrg, - sentryProject, - buildEvents + val sentryTelemetryProvider = + variant.configureTelemetry( + project, + extension, + cliExecutable, + sentryOrg, + sentryProject, + buildEvents, ) - // TODO: do this only once, and all other tasks should be SentryVariant.configureSomething - val sentryVariant = if (isAGP74) null else AndroidVariant70(variant) - sentryVariant?.configureNativeSymbolsTask( - project, - extension, - sentryTelemetryProvider, - cliExecutable, - sentryOrg, - sentryProject - ) + // TODO: do this only once, and all other tasks should be SentryVariant.configureSomething + val sentryVariant = if (isAGP74) null else AndroidVariant70(variant) + sentryVariant?.configureNativeSymbolsTask( + project, + extension, + sentryTelemetryProvider, + cliExecutable, + sentryOrg, + sentryProject, + ) - val additionalSourcesProvider = project.provider { - extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()) - .map { project.layout.projectDirectory.dir(it) } + val additionalSourcesProvider = + project.provider { + extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()).map { + project.layout.projectDirectory.dir(it) + } } - val sourceFiles = sentryVariant?.sources( - project, - additionalSourcesProvider - ) + val sourceFiles = sentryVariant?.sources(project, additionalSourcesProvider) - val tasksGeneratingProperties = mutableListOf>() - val sourceContextTasks = variant.configureSourceBundleTasks( - project, - extension, - sentryTelemetryProvider, - sourceFiles, - cliExecutable, - sentryOrg, - sentryProject + val tasksGeneratingProperties = mutableListOf>() + val sourceContextTasks = + variant.configureSourceBundleTasks( + project, + extension, + sentryTelemetryProvider, + sourceFiles, + cliExecutable, + sentryOrg, + sentryProject, ) - sourceContextTasks?.let { tasksGeneratingProperties.add(it.generateBundleIdTask) } + sourceContextTasks?.let { tasksGeneratingProperties.add(it.generateBundleIdTask) } - variant.configureDependenciesTask( - project, - extension, - sentryTelemetryProvider, - this, - mergeAssetsDependants - ) + variant.configureDependenciesTask( + project, + extension, + sentryTelemetryProvider, + this, + mergeAssetsDependants, + ) - val generateProguardUuidTask = variant.configureProguardMappingsTasks( - project, - extension, - sentryTelemetryProvider, - cliExecutable, - sentryOrg, - sentryProject + val generateProguardUuidTask = + variant.configureProguardMappingsTasks( + project, + extension, + sentryTelemetryProvider, + cliExecutable, + sentryOrg, + sentryProject, ) - generateProguardUuidTask?.let { tasksGeneratingProperties.add(it) } + generateProguardUuidTask?.let { tasksGeneratingProperties.add(it) } - variant.configureDebugMetaPropertiesTask( - project, - extension, - this, - sentryTelemetryProvider, - mergeAssetsDependants, - tasksGeneratingProperties - ) + variant.configureDebugMetaPropertiesTask( + project, + extension, + this, + sentryTelemetryProvider, + mergeAssetsDependants, + tasksGeneratingProperties, + ) } } private fun ApplicationVariant.configureTelemetry( - project: Project, - extension: SentryPluginExtension, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String?, - buildEvents: BuildEventListenerRegistryInternal + project: Project, + extension: SentryPluginExtension, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, + buildEvents: BuildEventListenerRegistryInternal, ): Provider { - val variant = if (isAGP74) null else AndroidVariant70(this) - val sentryTelemetryProvider = SentryTelemetryService.register(project) + val variant = if (isAGP74) null else AndroidVariant70(this) + val sentryTelemetryProvider = SentryTelemetryService.register(project) - project.gradle.taskGraph.whenReady { - sentryTelemetryProvider.get().start { - SentryTelemetryService.createParameters( - project, - variant, - extension, - cliExecutable, - sentryOrg, - "Android" - ) - } - buildEvents.onOperationCompletion(sentryTelemetryProvider) + project.gradle.taskGraph.whenReady { + sentryTelemetryProvider.get().start { + SentryTelemetryService.createParameters( + project, + variant, + extension, + cliExecutable, + sentryOrg, + "Android", + ) } + buildEvents.onOperationCompletion(sentryTelemetryProvider) + } - return sentryTelemetryProvider + return sentryTelemetryProvider } private fun ApplicationVariant.configureDebugMetaPropertiesTask( - project: Project, - extension: SentryPluginExtension, - appExtension: AppExtension, - sentryTelemetryProvider: Provider, - dependants: Set?>, - tasksGeneratingProperties: List> + project: Project, + extension: SentryPluginExtension, + appExtension: AppExtension, + sentryTelemetryProvider: Provider, + dependants: Set?>, + tasksGeneratingProperties: List>, ) { - if (isAGP74) { - project.logger.info { - "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + - "new AppComponentsExtension will be configured" - } - } else { - val variant = AndroidVariant70(this) - val taskSuffix = name.capitalized - val outputDir = project.layout.buildDirectory.dir( - "generated${sep}assets${sep}sentry${sep}debug-meta-properties${sep}$name" - ) - val generateDebugMetaPropertiesTask = SentryGenerateDebugMetaPropertiesTask.register( - project, - extension, - sentryTelemetryProvider, - tasksGeneratingProperties, - outputDir, - taskSuffix - ) - - generateDebugMetaPropertiesTask.setupMergeAssetsDependencies(dependants) - generateDebugMetaPropertiesTask.hookWithPackageTasks(project, variant) - appExtension.sourceSets.getByName(name).assets.srcDir( - generateDebugMetaPropertiesTask.flatMap { it.output } - ) + if (isAGP74) { + project.logger.info { + "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + + "new AppComponentsExtension will be configured" } + } else { + val variant = AndroidVariant70(this) + val taskSuffix = name.capitalized + val outputDir = + project.layout.buildDirectory.dir( + "generated${sep}assets${sep}sentry${sep}debug-meta-properties${sep}$name" + ) + val generateDebugMetaPropertiesTask = + SentryGenerateDebugMetaPropertiesTask.register( + project, + extension, + sentryTelemetryProvider, + tasksGeneratingProperties, + outputDir, + taskSuffix, + ) + + generateDebugMetaPropertiesTask.setupMergeAssetsDependencies(dependants) + generateDebugMetaPropertiesTask.hookWithPackageTasks(project, variant) + appExtension.sourceSets + .getByName(name) + .assets + .srcDir(generateDebugMetaPropertiesTask.flatMap { it.output }) + } } private fun ApplicationVariant.configureSourceBundleTasks( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - sourceFiles: Provider>?, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String? + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + sourceFiles: Provider>?, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ): SourceContext.SourceContextTasks? { - if (isAGP74) { - project.logger.info { - "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + - "new AppComponentsExtension will be configured" - } - return null - } else if (extension.includeSourceContext.get()) { - val paths = OutputPaths(project, name) - val variant = AndroidVariant70(this) - val taskSuffix = name.capitalized - - val sourceContextTasks = SourceContext.register( - project, - extension, - sentryTelemetryProvider, - variant, - paths, - sourceFiles, - cliExecutable, - sentryOrg, - sentryProject, - taskSuffix - ) + if (isAGP74) { + project.logger.info { + "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + + "new AppComponentsExtension will be configured" + } + return null + } else if (extension.includeSourceContext.get()) { + val paths = OutputPaths(project, name) + val variant = AndroidVariant70(this) + val taskSuffix = name.capitalized - if (variant.buildTypeName == "release") { - sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, variant) - } + val sourceContextTasks = + SourceContext.register( + project, + extension, + sentryTelemetryProvider, + variant, + paths, + sourceFiles, + cliExecutable, + sentryOrg, + sentryProject, + taskSuffix, + ) - return sourceContextTasks - } else { - return null + if (variant.buildTypeName == "release") { + sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, variant) } + + return sourceContextTasks + } else { + return null + } } private fun BaseVariant.configureDependenciesTask( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - appExtension: AppExtension, - dependants: Set?> + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + appExtension: AppExtension, + dependants: Set?>, ) { - if (isAGP74) { - project.logger.info { - "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + - "new AppComponentsExtension will be configured" - } - } else if (extension.includeDependenciesReport.get()) { - val outputDir = project.layout.buildDirectory.dir( - "generated${sep}assets${sep}sentry${sep}dependencies${sep}$name" - ) - - val reportDependenciesTask = - SentryExternalDependenciesReportTaskFactory.register( - project = project, - extension, - sentryTelemetryProvider, - configurationName = "${name}RuntimeClasspath", - attributeValueJar = "android-classes", - includeReport = extension.includeDependenciesReport, - output = outputDir, - taskSuffix = name.capitalized - ) - reportDependenciesTask.setupMergeAssetsDependencies(dependants) - appExtension.sourceSets.getByName(name).assets.srcDir( - reportDependenciesTask.flatMap { it.output } - ) + if (isAGP74) { + project.logger.info { + "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + + "new AppComponentsExtension will be configured" } + } else if (extension.includeDependenciesReport.get()) { + val outputDir = + project.layout.buildDirectory.dir( + "generated${sep}assets${sep}sentry${sep}dependencies${sep}$name" + ) + + val reportDependenciesTask = + SentryExternalDependenciesReportTaskFactory.register( + project = project, + extension, + sentryTelemetryProvider, + configurationName = "${name}RuntimeClasspath", + attributeValueJar = "android-classes", + includeReport = extension.includeDependenciesReport, + output = outputDir, + taskSuffix = name.capitalized, + ) + reportDependenciesTask.setupMergeAssetsDependencies(dependants) + appExtension.sourceSets + .getByName(name) + .assets + .srcDir(reportDependenciesTask.flatMap { it.output }) + } } private fun ApplicationVariant.configureProguardMappingsTasks( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String? + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ): TaskProvider? { - if (isAGP74) { - project.logger.info { - "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + - "new AppComponentsExtension will be configured" - } - return null - } else { - val variant = AndroidVariant70(this) - val sentryProps = getPropertiesFilePath(project, variant) - val dexguardEnabled = extension.dexguardEnabled.get() - val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled) - val outputDir = project.layout.buildDirectory.dir( - "generated${sep}assets${sep}sentry${sep}proguard${sep}$name" - ) - - if (isMinifyEnabled && extension.includeProguardMapping.get()) { - val generateUuidTask = - SentryGenerateProguardUuidTask.register( - project = project, - extension, - sentryTelemetryProvider, - output = outputDir, - proguardMappingFile = SentryTasksProvider.getMappingFileProvider( - project, - variant, - dexguardEnabled - ), - taskSuffix = name.capitalized - ) - - val versionName = versionName ?: "undefined" - val versionCode = if (versionCode == -1) { - null - } else { - versionCode - } - val releaseInfo = ReleaseInfo(applicationId, versionName, versionCode) - val uploadMappingsTask = SentryUploadProguardMappingsTask.register( - project = project, - extension, - sentryTelemetryProvider, - debug = extension.debug, - cliExecutable = cliExecutable, - generateUuidTask = generateUuidTask, - sentryProperties = sentryProps, - mappingFiles = SentryTasksProvider.getMappingFileProvider( - project, - variant, - dexguardEnabled - ), - autoUploadProguardMapping = extension.autoUploadProguardMapping, - sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, - sentryProject = sentryProject?.let { project.provider { it } } - ?: extension.projectName, - sentryAuthToken = extension.authToken, - taskSuffix = name.capitalized, - releaseInfo = releaseInfo, - sentryUrl = extension.url - ) - generateUuidTask.hookWithMinifyTasks( - project, - name, - dexguardEnabled && GroovyCompat.isDexguardEnabledForVariant(project, name) - ) + if (isAGP74) { + project.logger.info { + "Not configuring deprecated AppExtension for ${AgpVersions.CURRENT}, " + + "new AppComponentsExtension will be configured" + } + return null + } else { + val variant = AndroidVariant70(this) + val sentryProps = getPropertiesFilePath(project, variant) + val dexguardEnabled = extension.dexguardEnabled.get() + val isMinifyEnabled = isMinificationEnabled(project, variant, dexguardEnabled) + val outputDir = + project.layout.buildDirectory.dir( + "generated${sep}assets${sep}sentry${sep}proguard${sep}$name" + ) - uploadMappingsTask.hookWithAssembleTasks( - project, - variant - ) + if (isMinifyEnabled && extension.includeProguardMapping.get()) { + val generateUuidTask = + SentryGenerateProguardUuidTask.register( + project = project, + extension, + sentryTelemetryProvider, + output = outputDir, + proguardMappingFile = + SentryTasksProvider.getMappingFileProvider(project, variant, dexguardEnabled), + taskSuffix = name.capitalized, + ) - return generateUuidTask + val versionName = versionName ?: "undefined" + val versionCode = + if (versionCode == -1) { + null } else { - return null + versionCode } + val releaseInfo = ReleaseInfo(applicationId, versionName, versionCode) + val uploadMappingsTask = + SentryUploadProguardMappingsTask.register( + project = project, + extension, + sentryTelemetryProvider, + debug = extension.debug, + cliExecutable = cliExecutable, + generateUuidTask = generateUuidTask, + sentryProperties = sentryProps, + mappingFiles = + SentryTasksProvider.getMappingFileProvider(project, variant, dexguardEnabled), + autoUploadProguardMapping = extension.autoUploadProguardMapping, + sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, + sentryProject = sentryProject?.let { project.provider { it } } ?: extension.projectName, + sentryAuthToken = extension.authToken, + taskSuffix = name.capitalized, + releaseInfo = releaseInfo, + sentryUrl = extension.url, + ) + generateUuidTask.hookWithMinifyTasks( + project, + name, + dexguardEnabled && GroovyCompat.isDexguardEnabledForVariant(project, name), + ) + + uploadMappingsTask.hookWithAssembleTasks(project, variant) + + return generateUuidTask + } else { + return null } + } } private fun TaskProvider.setupMergeAssetsDependencies( - dependants: Set?> + dependants: Set?> ) { - dependants.forEach { - it?.configure { task -> - task.dependsOn(this) - } - } + dependants.forEach { it?.configure { task -> task.dependsOn(this) } } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/ManifestWriter.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/ManifestWriter.kt index c950b27b..b0732008 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/ManifestWriter.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/ManifestWriter.kt @@ -10,45 +10,41 @@ import org.w3c.dom.Document internal class ManifestWriter { - companion object { - private const val TAG_APPLICATION = "application" - private const val TAG_META_DATA = "meta-data" - private const val ATTR_NAME = "android:name" - private const val ATTR_VALUE = "android:value" - } + companion object { + private const val TAG_APPLICATION = "application" + private const val TAG_META_DATA = "meta-data" + private const val ATTR_NAME = "android:name" + private const val ATTR_VALUE = "android:value" + } - fun writeMetaData( - manifestSource: File, - manifestTarget: File, - name: String, - value: String - ) { - openAndroidManifestXMLDocument(manifestSource) { document -> - val application = document.getElementsByTagName(TAG_APPLICATION).item(0) - val metadata = document.createElement(TAG_META_DATA) - metadata.setAttribute(ATTR_NAME, name) - metadata.setAttribute(ATTR_VALUE, value) - application.appendChild(metadata) + fun writeMetaData(manifestSource: File, manifestTarget: File, name: String, value: String) { + openAndroidManifestXMLDocument(manifestSource) { document -> + val application = document.getElementsByTagName(TAG_APPLICATION).item(0) + val metadata = document.createElement(TAG_META_DATA) + metadata.setAttribute(ATTR_NAME, name) + metadata.setAttribute(ATTR_VALUE, value) + application.appendChild(metadata) - val factory = TransformerFactory.newInstance() - val transformer = factory.newTransformer().apply { - setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") - setOutputProperty(OutputKeys.INDENT, "yes") - setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") - } - val source = DOMSource(document) - val output = StreamResult(manifestTarget) - transformer.transform(source, output) + val factory = TransformerFactory.newInstance() + val transformer = + factory.newTransformer().apply { + setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") + setOutputProperty(OutputKeys.INDENT, "yes") + setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") } + val source = DOMSource(document) + val output = StreamResult(manifestTarget) + transformer.transform(source, output) } + } - private fun openAndroidManifestXMLDocument(manifest: File, action: (doc: Document) -> Unit) { - manifest.inputStream().buffered().use { stream -> - runCatching { - val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() - val document = builder.parse(stream) - action(document) - } - } + private fun openAndroidManifestXMLDocument(manifest: File, action: (doc: Document) -> Unit) { + manifest.inputStream().buffered().use { stream -> + runCatching { + val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val document = builder.parse(stream) + action(document) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt index 361edc0a..585c69bc 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryCliProvider.kt @@ -22,193 +22,176 @@ import org.gradle.api.tasks.Input internal object SentryCliProvider { - @field:Volatile - private var memoizedCliPath: String? = null - - /** - * Return the correct sentry-cli executable path to use for the given project. This - * will look for a sentry-cli executable in a local node_modules in case it was put - * there by sentry-react-native or others before falling back to the global installation. - * In case there's no global installation, and a matching cli is packaged in the resources - * it will provide a temporary path, without actually extracting it. - */ - @JvmStatic - @Synchronized - fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { - val cliPath = memoizedCliPath - if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { - logger.info { "Using memoized cli path: $cliPath" } - return cliPath - } - // If a path is provided explicitly use that first. - logger.info { "Searching cli from sentry.properties file..." } - - searchCliInPropertiesFile(projectDir, rootDir)?.let { - logger.info { "cli Found: $it" } - memoizedCliPath = it - return@getSentryCliPath it - } ?: logger.info { "sentry-cli not found in sentry.properties file" } - - // next up try a packaged version of sentry-cli - val cliResLocation = getCliLocationInResources() - if (!cliResLocation.isNullOrBlank()) { - logger.info { "cli present in resources: $cliResLocation" } - // just provide the target extraction path - // actual extraction will be done prior to task execution - val extractedResourcePath = getCliResourcesExtractionPath(projectBuildDir) - .absolutePath - memoizedCliPath = extractedResourcePath - return extractedResourcePath - } - - logger.error { "Falling back to invoking `sentry-cli` from shell" } - return "sentry-cli".also { memoizedCliPath = it } + @field:Volatile private var memoizedCliPath: String? = null + + /** + * Return the correct sentry-cli executable path to use for the given project. This will look for + * a sentry-cli executable in a local node_modules in case it was put there by sentry-react-native + * or others before falling back to the global installation. In case there's no global + * installation, and a matching cli is packaged in the resources it will provide a temporary path, + * without actually extracting it. + */ + @JvmStatic + @Synchronized + fun getSentryCliPath(projectDir: File, projectBuildDir: File, rootDir: File): String { + val cliPath = memoizedCliPath + if (!cliPath.isNullOrEmpty() && File(cliPath).exists()) { + logger.info { "Using memoized cli path: $cliPath" } + return cliPath + } + // If a path is provided explicitly use that first. + logger.info { "Searching cli from sentry.properties file..." } + + searchCliInPropertiesFile(projectDir, rootDir)?.let { + logger.info { "cli Found: $it" } + memoizedCliPath = it + return@getSentryCliPath it + } ?: logger.info { "sentry-cli not found in sentry.properties file" } + + // next up try a packaged version of sentry-cli + val cliResLocation = getCliLocationInResources() + if (!cliResLocation.isNullOrBlank()) { + logger.info { "cli present in resources: $cliResLocation" } + // just provide the target extraction path + // actual extraction will be done prior to task execution + val extractedResourcePath = getCliResourcesExtractionPath(projectBuildDir).absolutePath + memoizedCliPath = extractedResourcePath + return extractedResourcePath } - private fun getCliLocationInResources(): String? { - val cliSuffix = getCliSuffix() - logger.info { "cliSuffix is $cliSuffix" } + logger.error { "Falling back to invoking `sentry-cli` from shell" } + return "sentry-cli".also { memoizedCliPath = it } + } - if (!cliSuffix.isNullOrBlank()) { - val resourcePath = "/bin/sentry-cli-$cliSuffix" + private fun getCliLocationInResources(): String? { + val cliSuffix = getCliSuffix() + logger.info { "cliSuffix is $cliSuffix" } - // if we are not in a jar, we can use the file directly - logger.info { "Searching for $resourcePath in resources folder..." } + if (!cliSuffix.isNullOrBlank()) { + val resourcePath = "/bin/sentry-cli-$cliSuffix" - getResourceUrl(resourcePath)?.let { - logger.info { "cli found in resources: $it" } + // if we are not in a jar, we can use the file directly + logger.info { "Searching for $resourcePath in resources folder..." } - // still return the resource path, as it's the one we can use for extraction later - return resourcePath - } ?: logger.info { "Failed to load sentry-cli from resource folder" } - } + getResourceUrl(resourcePath)?.let { + logger.info { "cli found in resources: $it" } - return null + // still return the resource path, as it's the one we can use for extraction later + return resourcePath + } ?: logger.info { "Failed to load sentry-cli from resource folder" } } - internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = - listOf( - File(projectDir, "sentry.properties"), - File(rootDir, "sentry.properties") - ).firstOrNull(File::exists)?.path - - internal fun searchCliInPropertiesFile(projectDir: File, rootDir: File): String? { - return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> - runCatching { - Properties() - .apply { load(FileInputStream(propertiesFile)) } - .getProperty("cli.executable") - }.getOrNull() + return null + } + + internal fun getSentryPropertiesPath(projectDir: File, rootDir: File): String? = + listOf(File(projectDir, "sentry.properties"), File(rootDir, "sentry.properties")) + .firstOrNull(File::exists) + ?.path + + internal fun searchCliInPropertiesFile(projectDir: File, rootDir: File): String? { + return getSentryPropertiesPath(projectDir, rootDir)?.let { propertiesFile -> + runCatching { + Properties().apply { load(FileInputStream(propertiesFile)) }.getProperty("cli.executable") } + .getOrNull() } + } - internal fun getResourceUrl(resourcePath: String): String? = - javaClass.getResource(resourcePath)?.toString() + internal fun getResourceUrl(resourcePath: String): String? = + javaClass.getResource(resourcePath)?.toString() - internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { - // usually /build/tmp/ - return File( - File(projectBuildDir, "tmp"), - "sentry-cli-${BuildConfig.CliVersion}.exe" - ) - } + internal fun getCliResourcesExtractionPath(projectBuildDir: File): File { + // usually /build/tmp/ + return File(File(projectBuildDir, "tmp"), "sentry-cli-${BuildConfig.CliVersion}.exe") + } - internal fun extractCliFromResources(resourcePath: String, outputPath: File): String? { - val resourceStream = javaClass.getResourceAsStream(resourcePath) - return if (resourceStream != null) { - val baseFolder = outputPath.parentFile - logger.info { "sentry-cli base folder: ${baseFolder.absolutePath}" } - - if (!baseFolder.exists() && !baseFolder.mkdirs()) { - logger.error { "sentry-cli base folder could not be created!" } - return null - } - - FileOutputStream(outputPath).use { output -> - resourceStream.use { input -> - input.copyTo(output) - } - } - outputPath.setExecutable(true) - outputPath.deleteOnExit() - - outputPath.absolutePath - } else { - return null - } - } + internal fun extractCliFromResources(resourcePath: String, outputPath: File): String? { + val resourceStream = javaClass.getResourceAsStream(resourcePath) + return if (resourceStream != null) { + val baseFolder = outputPath.parentFile + logger.info { "sentry-cli base folder: ${baseFolder.absolutePath}" } - internal fun getCliSuffix(): String? { - // TODO: change to .lowercase(Locale.ROOT) when using Kotlin 1.6 - val osName = System.getProperty("os.name").toLowerCase(Locale.ROOT) - val osArch = System.getProperty("os.arch") - return when { - "mac" in osName -> "Darwin-universal" - "linux" in osName -> if (osArch == "amd64") "Linux-x86_64" else "Linux-$osArch" - "win" in osName -> "Windows-i686.exe" - else -> null - } - } + if (!baseFolder.exists() && !baseFolder.mkdirs()) { + logger.error { "sentry-cli base folder could not be created!" } + return null + } + + FileOutputStream(outputPath).use { output -> + resourceStream.use { input -> input.copyTo(output) } + } + outputPath.setExecutable(true) + outputPath.deleteOnExit() - /** - * Tries to extract the sentry-cli from resources if the computedCliPath does not exist. - */ - @Synchronized - internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { - val cli = File(cliPath) - if (!cli.exists()) { - // we only want to auto-extract if the path matches the pre-computed one - if (File(cliPath).absolutePath.equals( - getCliResourcesExtractionPath(buildDir).absolutePath - ) - ) { - val cliResPath = getCliLocationInResources() - if (!cliResPath.isNullOrBlank()) { - return extractCliFromResources(cliResPath, cli) ?: cliPath - } - } + outputPath.absolutePath + } else { + return null + } + } + + internal fun getCliSuffix(): String? { + // TODO: change to .lowercase(Locale.ROOT) when using Kotlin 1.6 + val osName = System.getProperty("os.name").toLowerCase(Locale.ROOT) + val osArch = System.getProperty("os.arch") + return when { + "mac" in osName -> "Darwin-universal" + "linux" in osName -> if (osArch == "amd64") "Linux-x86_64" else "Linux-$osArch" + "win" in osName -> "Windows-i686.exe" + else -> null + } + } + + /** Tries to extract the sentry-cli from resources if the computedCliPath does not exist. */ + @Synchronized + internal fun maybeExtractFromResources(buildDir: File, cliPath: String): String { + val cli = File(cliPath) + if (!cli.exists()) { + // we only want to auto-extract if the path matches the pre-computed one + if (File(cliPath).absolutePath.equals(getCliResourcesExtractionPath(buildDir).absolutePath)) { + val cliResPath = getCliLocationInResources() + if (!cliResPath.isNullOrBlank()) { + return extractCliFromResources(cliResPath, cli) ?: cliPath } - return cliPath + } } + return cliPath + } } abstract class SentryCliValueSource : ValueSource { - interface Params : ValueSourceParameters { - @get:Input - val projectDir: Property + interface Params : ValueSourceParameters { + @get:Input val projectDir: Property - @get:Input - val projectBuildDir: Property + @get:Input val projectBuildDir: Property - @get:Input - val rootProjDir: Property - } + @get:Input val rootProjDir: Property + } - override fun obtain(): String? { - return SentryCliProvider.getSentryCliPath( - parameters.projectDir.get(), - parameters.projectBuildDir.get(), - parameters.rootProjDir.get() - ) - } + override fun obtain(): String? { + return SentryCliProvider.getSentryCliPath( + parameters.projectDir.get(), + parameters.projectBuildDir.get(), + parameters.rootProjDir.get(), + ) + } } fun Project.cliExecutableProvider(): Provider { - return if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_5) { - // config-cache compatible way to retrieve the cli path, it properly gets invalidated when - // e.g. switching branches - providers.of(SentryCliValueSource::class.java) { - it.parameters.projectDir.set(project.projectDir) - it.parameters.projectBuildDir.set(project.layout.buildDirectory.asFile.get()) - it.parameters.rootProjDir.set(project.rootDir) - } - } else { - return provider { - SentryCliProvider.getSentryCliPath( - project.projectDir, - project.layout.buildDirectory.asFile.get(), - project.rootDir - ) - } + return if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_5) { + // config-cache compatible way to retrieve the cli path, it properly gets invalidated when + // e.g. switching branches + providers.of(SentryCliValueSource::class.java) { + it.parameters.projectDir.set(project.projectDir) + it.parameters.projectBuildDir.set(project.layout.buildDirectory.asFile.get()) + it.parameters.rootProjDir.set(project.rootDir) + } + } else { + return provider { + SentryCliProvider.getSentryCliPath( + project.projectDir, + project.layout.buildDirectory.asFile.get(), + project.rootDir, + ) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt index 1424ff22..f49aa02a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt @@ -16,87 +16,80 @@ import org.gradle.internal.build.event.BuildEventListenerRegistryInternal import org.slf4j.LoggerFactory @Suppress("UnstableApiUsage") -abstract class SentryPlugin @Inject constructor( - private val buildEvents: BuildEventListenerRegistryInternal -) : Plugin { +abstract class SentryPlugin +@Inject +constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugin { - override fun apply(project: Project) { - if (AgpVersions.CURRENT < AgpVersions.VERSION_7_0_0) { - throw StopExecutionException( - """ + override fun apply(project: Project) { + if (AgpVersions.CURRENT < AgpVersions.VERSION_7_0_0) { + throw StopExecutionException( + """ Using io.sentry.android.gradle:3+ with Android Gradle Plugin < 7 is not supported. Either upgrade the AGP version to 7+, or use an earlier version of the Sentry Android Gradle Plugin. For more information check our migration guide https://docs.sentry.io/platforms/android/migration/#migrating-from-iosentrysentry-android-gradle-plugin-2x-to-iosentrysentry-android-gradle-plugin-300 - """.trimIndent() - ) - } - if (!project.plugins.hasPlugin("com.android.application")) { - project.logger.warn( """ + .trimIndent() + ) + } + if (!project.plugins.hasPlugin("com.android.application")) { + project.logger.warn( + """ WARNING: Using 'io.sentry.android.gradle' is only supported for the app module. Please make sure that you apply the Sentry gradle plugin alongside 'com.android.application' on the _module_ level, and not on the root project level. https://docs.sentry.io/platforms/android/configuration/gradle/ - """.trimIndent() - ) - } + """ + .trimIndent() + ) + } - val extension = project.extensions.create( - "sentry", - SentryPluginExtension::class.java, - project - ) + val extension = project.extensions.create("sentry", SentryPluginExtension::class.java, project) - project.pluginManager.withPlugin("com.android.application") { - val oldAGPExtension = project.extensions.getByType(AppExtension::class.java) - val androidComponentsExt = - project.extensions.getByType(AndroidComponentsExtension::class.java) - val cliExecutable = project.cliExecutableProvider() + project.pluginManager.withPlugin("com.android.application") { + val oldAGPExtension = project.extensions.getByType(AppExtension::class.java) + val androidComponentsExt = + project.extensions.getByType(AndroidComponentsExtension::class.java) + val cliExecutable = project.cliExecutableProvider() - val extraProperties = project.extensions.getByName("ext") - as ExtraPropertiesExtension + val extraProperties = project.extensions.getByName("ext") as ExtraPropertiesExtension - val sentryOrgParameter = runCatching { - extraProperties.get(SENTRY_ORG_PARAMETER).toString() - }.getOrNull() - val sentryProjectParameter = runCatching { - extraProperties.get(SENTRY_PROJECT_PARAMETER).toString() - }.getOrNull() + val sentryOrgParameter = + runCatching { extraProperties.get(SENTRY_ORG_PARAMETER).toString() }.getOrNull() + val sentryProjectParameter = + runCatching { extraProperties.get(SENTRY_PROJECT_PARAMETER).toString() }.getOrNull() - // new API configuration - androidComponentsExt.configure( - project, - extension, - buildEvents, - cliExecutable, - sentryOrgParameter, - sentryProjectParameter, - ) + // new API configuration + androidComponentsExt.configure( + project, + extension, + buildEvents, + cliExecutable, + sentryOrgParameter, + sentryProjectParameter, + ) - // old API configuration - oldAGPExtension.configure( - project, - extension, - cliExecutable, - sentryOrgParameter, - sentryProjectParameter, - buildEvents - ) + // old API configuration + oldAGPExtension.configure( + project, + extension, + cliExecutable, + sentryOrgParameter, + sentryProjectParameter, + buildEvents, + ) - project.installDependencies(extension, true) - } + project.installDependencies(extension, true) } + } - companion object { - const val SENTRY_ORG_PARAMETER = "sentryOrg" - const val SENTRY_PROJECT_PARAMETER = "sentryProject" - internal const val SENTRY_SDK_VERSION = BuildConfig.SdkVersion + companion object { + const val SENTRY_ORG_PARAMETER = "sentryOrg" + const val SENTRY_PROJECT_PARAMETER = "sentryProject" + internal const val SENTRY_SDK_VERSION = BuildConfig.SdkVersion - internal val sep = File.separator + internal val sep = File.separator - // a single unified logger used by instrumentation - internal val logger by lazy { - LoggerFactory.getLogger(SentryPlugin::class.java) - } - } + // a single unified logger used by instrumentation + internal val logger by lazy { LoggerFactory.getLogger(SentryPlugin::class.java) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPropertiesFileProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPropertiesFileProvider.kt index aff4a56b..57eabea6 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPropertiesFileProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPropertiesFileProvider.kt @@ -7,60 +7,59 @@ import org.gradle.api.Project internal object SentryPropertiesFileProvider { - private const val FILENAME = "sentry.properties" + private const val FILENAME = "sentry.properties" - /** - * Find sentry.properties and returns the path to the file. - * @param project the given project - * @param variant the given variant - * @return A [String] for the path if sentry.properties is found or null otherwise - */ - @JvmStatic - fun getPropertiesFilePath(project: Project, variant: SentryVariant): String? { - val flavorName = variant.flavorName.orEmpty() - val buildTypeName = variant.buildTypeName.orEmpty() + /** + * Find sentry.properties and returns the path to the file. + * + * @param project the given project + * @param variant the given variant + * @return A [String] for the path if sentry.properties is found or null otherwise + */ + @JvmStatic + fun getPropertiesFilePath(project: Project, variant: SentryVariant): String? { + val flavorName = variant.flavorName.orEmpty() + val buildTypeName = variant.buildTypeName.orEmpty() - val projDir = project.projectDir - val rootDir = project.rootDir + val projDir = project.projectDir + val rootDir = project.rootDir - val sep = File.separator + val sep = File.separator - // Local Project dirs - val possibleFiles = mutableListOf( - "${projDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME" - ) - if (flavorName.isNotBlank()) { - possibleFiles.add( - "${projDir}${sep}src${sep}${buildTypeName}${sep}$flavorName${sep}$FILENAME" - ) - possibleFiles.add( - "${projDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME" - ) - possibleFiles.add("${projDir}${sep}src${sep}${flavorName}${sep}$FILENAME") - } - possibleFiles.add("${projDir}${sep}$FILENAME") - - // Other flavors dirs - possibleFiles.addAll( - variant.productFlavors.map { "${projDir}${sep}src${sep}${it}${sep}$FILENAME" } - ) + // Local Project dirs + val possibleFiles = mutableListOf("${projDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME") + if (flavorName.isNotBlank()) { + possibleFiles.add("${projDir}${sep}src${sep}${buildTypeName}${sep}$flavorName${sep}$FILENAME") + possibleFiles.add( + "${projDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME" + ) + possibleFiles.add("${projDir}${sep}src${sep}${flavorName}${sep}$FILENAME") + } + possibleFiles.add("${projDir}${sep}$FILENAME") - // Root project dirs - possibleFiles.add("${rootDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME") - if (flavorName.isNotBlank()) { - possibleFiles.add("${rootDir}${sep}src${sep}${flavorName}${sep}$FILENAME") - possibleFiles.add( - "${rootDir}${sep}src${sep}${buildTypeName}${sep}${flavorName}${sep}$FILENAME" - ) - possibleFiles.add( - "${rootDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME" - ) - } - possibleFiles.add("${rootDir}${sep}$FILENAME") + // Other flavors dirs + possibleFiles.addAll( + variant.productFlavors.map { "${projDir}${sep}src${sep}${it}${sep}$FILENAME" } + ) - return possibleFiles.distinct().asSequence() - .onEach { project.logger.info { "Looking for $FILENAME at: $it" } } - .firstOrNull { File(it).exists() } - ?.also { project.logger.info { "Found $FILENAME at: $it" } } + // Root project dirs + possibleFiles.add("${rootDir}${sep}src${sep}${buildTypeName}${sep}$FILENAME") + if (flavorName.isNotBlank()) { + possibleFiles.add("${rootDir}${sep}src${sep}${flavorName}${sep}$FILENAME") + possibleFiles.add( + "${rootDir}${sep}src${sep}${buildTypeName}${sep}${flavorName}${sep}$FILENAME" + ) + possibleFiles.add( + "${rootDir}${sep}src${sep}${flavorName}${sep}${buildTypeName}${sep}$FILENAME" + ) } + possibleFiles.add("${rootDir}${sep}$FILENAME") + + return possibleFiles + .distinct() + .asSequence() + .onEach { project.logger.info { "Looking for $FILENAME at: $it" } } + .firstOrNull { File(it).exists() } + ?.also { project.logger.info { "Found $FILENAME at: $it" } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryTasksProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryTasksProvider.kt index c9d660b4..26b6f5b2 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryTasksProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryTasksProvider.kt @@ -15,179 +15,175 @@ import org.gradle.api.tasks.TaskProvider internal object SentryTasksProvider { - /** - * Returns the minify task for the given project and variant. - * It could be either ProGuard, R8 or DexGuard. - * - * @return the task or null otherwise - */ - @JvmStatic - fun getMinifyTask( - project: Project, - variantName: String, - dexguardEnabled: Boolean = false - ): TaskProvider? { - val tasks = if (dexguardEnabled) { - // We prioritize the Guardsquare's Proguard task towards the AGP ones. - listOf( - "transformClassesAndResourcesWithProguardTransformFor${variantName.capitalized}", - "mergeDex${variantName.capitalized}" + /** + * Returns the minify task for the given project and variant. It could be either ProGuard, R8 or + * DexGuard. + * + * @return the task or null otherwise + */ + @JvmStatic + fun getMinifyTask( + project: Project, + variantName: String, + dexguardEnabled: Boolean = false, + ): TaskProvider? { + val tasks = + if (dexguardEnabled) { + // We prioritize the Guardsquare's Proguard task towards the AGP ones. + listOf( + "transformClassesAndResourcesWithProguardTransformFor${variantName.capitalized}", + "mergeDex${variantName.capitalized}", + ) + } else { + listOf( + "minify${variantName.capitalized}WithR8", + "minify${variantName.capitalized}WithProguard", + ) + } + return project.findTask(tasks) + } + + /** + * Returns the pre bundle task for the given project and variant. + * + * @return the task or null otherwise + */ + @JvmStatic + fun getPreBundleTask(project: Project, variantName: String): TaskProvider? = + project.findTask(listOf("build${variantName.capitalized}PreBundle")) + + /** + * Returns the pre bundle task for the given project and variant. + * + * @return the task or null otherwise + */ + @JvmStatic + fun getBundleTask(project: Project, variantName: String): TaskProvider? = + project.findTask(listOf("bundle${variantName.capitalized}")) + + /** + * Returns the package bundle task (App Bundle only) + * + * @return the package task or null if not found + */ + @JvmStatic + fun getPackageBundleTask(project: Project, variantName: String): TaskProvider? = + // for APK it uses getPackageProvider + project.findTask(listOf("package${variantName.capitalized}Bundle")) + + /** + * Returns the assemble task provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getAssembleTaskProvider(project: Project, variant: SentryVariant): TaskProvider? = + variant.assembleProvider ?: project.findTask(listOf("assemble${variant.name.capitalized}")) + + /** + * Returns the install task provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getInstallTaskProvider(project: Project, variant: SentryVariant): TaskProvider? = + variant.installProvider ?: project.findTask(listOf("install${variant.name.capitalized}")) + + /** + * Returns the merge asset provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getMergeAssetsProvider(variant: ApplicationVariant): TaskProvider? = + variant.mergeAssetsProvider + + /** + * Returns the mapping file provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getMappingFileProvider( + project: Project, + variant: SentryVariant, + dexguardEnabled: Boolean = false, + ): Provider { + if (dexguardEnabled) { + val sep = File.separator + if (project.plugins.hasPlugin("com.guardsquare.proguard")) { + val fileCollection = + project.files( + File( + project.buildDir, + "outputs${sep}proguard${sep}${variant.name}${sep}mapping${sep}mapping.txt", ) - } else { - listOf( - "minify${variantName.capitalized}WithR8", - "minify${variantName.capitalized}WithProguard" - ) - } - return project.findTask(tasks) + ) + return project.provider { fileCollection } + } + if (isDexguardAvailable(project)) { + // For DexGuard the mapping file can either be inside the /apk or the /bundle folder + // (depends on the task that generated it). + val mappingDir = "outputs${sep}dexguard${sep}mapping$sep" + val fileCollection = + project.files( + File(project.buildDir, "${mappingDir}apk${sep}${variant.name}${sep}mapping.txt"), + File(project.buildDir, "${mappingDir}bundle${sep}${variant.name}${sep}mapping.txt"), + ) + return project.provider { fileCollection } + } } - - /** - * Returns the pre bundle task for the given project and variant. - * - * @return the task or null otherwise - */ - @JvmStatic - fun getPreBundleTask(project: Project, variantName: String): TaskProvider? = - project.findTask(listOf("build${variantName.capitalized}PreBundle")) - - /** - * Returns the pre bundle task for the given project and variant. - * - * @return the task or null otherwise - */ - @JvmStatic - fun getBundleTask(project: Project, variantName: String): TaskProvider? = - project.findTask(listOf("bundle${variantName.capitalized}")) - - /** - * Returns the package bundle task (App Bundle only) - * - * @return the package task or null if not found - */ - @JvmStatic - fun getPackageBundleTask(project: Project, variantName: String): TaskProvider? = - // for APK it uses getPackageProvider - project.findTask(listOf("package${variantName.capitalized}Bundle")) - - /** - * Returns the assemble task provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getAssembleTaskProvider(project: Project, variant: SentryVariant): TaskProvider? = - variant.assembleProvider ?: project.findTask(listOf("assemble${variant.name.capitalized}")) - - /** - * Returns the install task provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getInstallTaskProvider(project: Project, variant: SentryVariant): TaskProvider? = - variant.installProvider ?: project.findTask(listOf("install${variant.name.capitalized}")) - - /** - * Returns the merge asset provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getMergeAssetsProvider(variant: ApplicationVariant): TaskProvider? = - variant.mergeAssetsProvider - - /** - * Returns the mapping file provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getMappingFileProvider( - project: Project, - variant: SentryVariant, - dexguardEnabled: Boolean = false - ): Provider { - if (dexguardEnabled) { - val sep = File.separator - if (project.plugins.hasPlugin("com.guardsquare.proguard")) { - val fileCollection = project.files( - File( - project.buildDir, - "outputs${sep}proguard${sep}${variant.name}${sep}mapping${sep}mapping.txt" - ) - ) - return project.provider { fileCollection } - } - if (isDexguardAvailable(project)) { - // For DexGuard the mapping file can either be inside the /apk or the /bundle folder - // (depends on the task that generated it). - val mappingDir = "outputs${sep}dexguard${sep}mapping$sep" - val fileCollection = project.files( - File( - project.buildDir, - "${mappingDir}apk${sep}${variant.name}${sep}mapping.txt" - ), - File( - project.buildDir, - "${mappingDir}bundle${sep}${variant.name}${sep}mapping.txt" - ) - ) - return project.provider { fileCollection } - } + return variant.mappingFileProvider(project) + } + + /** + * Returns the package provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getPackageProvider(variant: SentryVariant): TaskProvider? = + // for App Bundle it uses getPackageBundleTask + variant.packageProvider + + /** + * Returns the lintVitalAnalyze task provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getLintVitalAnalyzeProvider(project: Project, variantName: String) = + project.findTask(listOf("lintVitalAnalyze${variantName.capitalized}")) + + /** + * Returns the lintVitalReport task provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getLintVitalReportProvider(project: Project, variantName: String) = + project.findTask(listOf("lintVitalReport${variantName.capitalized}")) + + /** + * Returns the processResources task provider + * + * @return the provider if found or null otherwise + */ + @JvmStatic + fun getProcessResourcesProvider(project: Project) = project.findTask(listOf("processResources")) + + /** @return the first task found in the list or null */ + private fun Project.findTask(taskName: List): TaskProvider? = + taskName + .mapNotNull { + try { + project.tasks.named(it) + } catch (e: UnknownTaskException) { + null } - return variant.mappingFileProvider(project) - } + } + .firstOrNull() - /** - * Returns the package provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getPackageProvider(variant: SentryVariant): TaskProvider? = - // for App Bundle it uses getPackageBundleTask - variant.packageProvider - - /** - * Returns the lintVitalAnalyze task provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getLintVitalAnalyzeProvider(project: Project, variantName: String) = - project.findTask(listOf("lintVitalAnalyze${variantName.capitalized}")) - - /** - * Returns the lintVitalReport task provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getLintVitalReportProvider(project: Project, variantName: String) = - project.findTask(listOf("lintVitalReport${variantName.capitalized}")) - - /** - * Returns the processResources task provider - * - * @return the provider if found or null otherwise - */ - @JvmStatic - fun getProcessResourcesProvider(project: Project) = project.findTask(listOf("processResources")) - - /** - * @return the first task found in the list or null - */ - private fun Project.findTask(taskName: List): TaskProvider? = - taskName - .mapNotNull { - try { - project.tasks.named(it) - } catch (e: UnknownTaskException) { - null - } - } - .firstOrNull() - - internal val String.capitalized: String get() = this.capitalizeUS() + internal val String.capitalized: String + get() = this.capitalizeUS() } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategy.kt index 2fa31ea9..1aa1fd66 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategy.kt @@ -10,114 +10,110 @@ import org.slf4j.Logger abstract class AbstractInstallStrategy : ComponentMetadataRule { - protected lateinit var logger: Logger + protected lateinit var logger: Logger - protected abstract val sentryModuleId: String + protected abstract val sentryModuleId: String - protected open val minSupportedThirdPartyVersion: SemVer? = null + protected open val minSupportedThirdPartyVersion: SemVer? = null - protected open val maxSupportedThirdPartyVersion: SemVer? = null + protected open val maxSupportedThirdPartyVersion: SemVer? = null - protected open val minSupportedSentryVersion: SemVer = SemVer(0, 0, 0) + protected open val minSupportedSentryVersion: SemVer = SemVer(0, 0, 0) - protected open val maxSupportedSentryVersion: SemVer = SemVer(0, 0, 0) + protected open val maxSupportedSentryVersion: SemVer = SemVer(0, 0, 0) - override fun execute(context: ComponentMetadataContext) { - val autoInstallState = AutoInstallState.getInstance() - if (!autoInstallState.enabled) { - logger.info { - "$sentryModuleId won't be installed because autoInstallation is disabled" - } - return - } - minSupportedThirdPartyVersion?.let { - parseVersion(context.details.id.version)?.let { thirdPartySemVersion -> - if (thirdPartySemVersion < it) { - logger.info { - "$sentryModuleId won't be installed because the current version is " + - "lower than the minimum supported version ($it)" - } - return - } - } ?: return + override fun execute(context: ComponentMetadataContext) { + val autoInstallState = AutoInstallState.getInstance() + if (!autoInstallState.enabled) { + logger.info { "$sentryModuleId won't be installed because autoInstallation is disabled" } + return + } + minSupportedThirdPartyVersion?.let { + parseVersion(context.details.id.version)?.let { thirdPartySemVersion -> + if (thirdPartySemVersion < it) { + logger.info { + "$sentryModuleId won't be installed because the current version is " + + "lower than the minimum supported version ($it)" + } + return } - maxSupportedThirdPartyVersion?.let { - parseVersion(context.details.id.version)?.let { thirdPartySemVersion -> - if (thirdPartySemVersion > it) { - logger.info { - "$sentryModuleId won't be installed because the current version is " + - "higher than the maximum supported version ($it)" - } - return - } - } ?: return + } ?: return + } + maxSupportedThirdPartyVersion?.let { + parseVersion(context.details.id.version)?.let { thirdPartySemVersion -> + if (thirdPartySemVersion > it) { + logger.info { + "$sentryModuleId won't be installed because the current version is " + + "higher than the maximum supported version ($it)" + } + return } + } ?: return + } - if (minSupportedSentryVersion.major > 0) { - try { - val sentrySemVersion = SemVer.parse(autoInstallState.sentryVersion) - if (sentrySemVersion < minSupportedSentryVersion) { - logger.warn { - "$sentryModuleId won't be installed because the current sentry version " + - "is lower than the minimum supported sentry version " + - "($minSupportedSentryVersion)" - } - return - } - } catch (ex: IllegalArgumentException) { - logger.warn { - "$sentryModuleId won't be installed because the provided " + - "sentry version(${autoInstallState.sentryVersion}) could not be " + - "processed as a semantic version." - } - return - } + if (minSupportedSentryVersion.major > 0) { + try { + val sentrySemVersion = SemVer.parse(autoInstallState.sentryVersion) + if (sentrySemVersion < minSupportedSentryVersion) { + logger.warn { + "$sentryModuleId won't be installed because the current sentry version " + + "is lower than the minimum supported sentry version " + + "($minSupportedSentryVersion)" + } + return } - - if (maxSupportedSentryVersion.major > 0) { - try { - val sentrySemVersion = SemVer.parse(autoInstallState.sentryVersion) - if (sentrySemVersion > maxSupportedSentryVersion) { - logger.debug { - "$sentryModuleId won't be installed because the current sentry version " + - "is higher than the maximum supported sentry version " + - "($maxSupportedSentryVersion)" - } - return - } - } catch (ex: IllegalArgumentException) { - logger.warn { - "$sentryModuleId won't be installed because the provided " + - "sentry version(${autoInstallState.sentryVersion}) could not be " + - "processed as a semantic version." - } - return - } + } catch (ex: IllegalArgumentException) { + logger.warn { + "$sentryModuleId won't be installed because the provided " + + "sentry version(${autoInstallState.sentryVersion}) could not be " + + "processed as a semantic version." } + return + } + } - context.details.allVariants { metadata -> - metadata.withDependencies { dependencies -> - val sentryVersion = autoInstallState.sentryVersion - dependencies.add("$SENTRY_GROUP:$sentryModuleId:$sentryVersion") - - logger.info { - "$sentryModuleId was successfully installed with version: $sentryVersion" - } - } + if (maxSupportedSentryVersion.major > 0) { + try { + val sentrySemVersion = SemVer.parse(autoInstallState.sentryVersion) + if (sentrySemVersion > maxSupportedSentryVersion) { + logger.debug { + "$sentryModuleId won't be installed because the current sentry version " + + "is higher than the maximum supported sentry version " + + "($maxSupportedSentryVersion)" + } + return + } + } catch (ex: IllegalArgumentException) { + logger.warn { + "$sentryModuleId won't be installed because the provided " + + "sentry version(${autoInstallState.sentryVersion}) could not be " + + "processed as a semantic version." } + return + } } - private fun parseVersion(version: String): SemVer? { - // older Spring versions ended in .RELEASE - return parseVersionSafely(version.removeSuffix(".RELEASE")) + context.details.allVariants { metadata -> + metadata.withDependencies { dependencies -> + val sentryVersion = autoInstallState.sentryVersion + dependencies.add("$SENTRY_GROUP:$sentryModuleId:$sentryVersion") + + logger.info { "$sentryModuleId was successfully installed with version: $sentryVersion" } + } } + } - private fun parseVersionSafely(version: String): SemVer? { - try { - return SemVer.parse(version) - } catch (t: Throwable) { - logger.warn { "Unable to parse version $version as a semantic version." } - return null - } + private fun parseVersion(version: String): SemVer? { + // older Spring versions ended in .RELEASE + return parseVersionSafely(version.removeSuffix(".RELEASE")) + } + + private fun parseVersionSafely(version: String): SemVer? { + try { + return SemVer.parse(version) + } catch (t: Throwable) { + logger.warn { "Unable to parse version $version as a semantic version." } + return null } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstall.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstall.kt index 54bd176d..bddcc1ab 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstall.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstall.kt @@ -25,7 +25,8 @@ import org.gradle.api.artifacts.DependencySet internal const val SENTRY_GROUP = "io.sentry" -private val strategies = listOf( +private val strategies = + listOf( AndroidOkHttpInstallStrategy.Registrar, OkHttpInstallStrategy.Registrar, SQLiteInstallStrategy.Registrar, @@ -38,91 +39,82 @@ private val strategies = listOf( GraphqlInstallStrategy.Registrar, QuartzInstallStrategy.Registrar, KotlinExtensionsInstallStrategy.Registrar, - WarnOnOverrideStrategy.Registrar -) + WarnOnOverrideStrategy.Registrar, + ) -private val delayedStrategies = listOf( +private val delayedStrategies = + listOf( Spring5InstallStrategy.Registrar, Spring6InstallStrategy.Registrar, SpringBoot2InstallStrategy.Registrar, SpringBoot3InstallStrategy.Registrar, -) + ) fun Project.installDependencies(extension: SentryPluginExtension, isAndroid: Boolean) { - configurations.named("implementation").configure { configuration -> - configuration.withDependencies { dependencies -> + configurations.named("implementation").configure { configuration -> + configuration.withDependencies { dependencies -> + project.dependencies.components { component -> + delayedStrategies.forEach { it.register(component) } + } - project.dependencies.components { component -> - delayedStrategies.forEach { it.register(component) } - } - - // if autoInstallation is disabled, the autoInstallState will contain initial values - // which all default to false, hence, the integrations won't be installed as well - if (extension.autoInstallation.enabled.get()) { - val sentryVersion = dependencies.findSentryVersion(isAndroid) - with(AutoInstallState.getInstance(gradle)) { - val sentryArtifactId = if (isAndroid) { - SentryModules.SENTRY_ANDROID.name - } else { - SentryModules.SENTRY.name - } - this.sentryVersion = installSentrySdk( - sentryVersion, - dependencies, - sentryArtifactId, - extension - ) - this.enabled = true - } + // if autoInstallation is disabled, the autoInstallState will contain initial values + // which all default to false, hence, the integrations won't be installed as well + if (extension.autoInstallation.enabled.get()) { + val sentryVersion = dependencies.findSentryVersion(isAndroid) + with(AutoInstallState.getInstance(gradle)) { + val sentryArtifactId = + if (isAndroid) { + SentryModules.SENTRY_ANDROID.name + } else { + SentryModules.SENTRY.name } + this.sentryVersion = + installSentrySdk(sentryVersion, dependencies, sentryArtifactId, extension) + this.enabled = true } + } } - project.dependencies.components { component -> - strategies.forEach { it.register(component) } - } + } + project.dependencies.components { component -> strategies.forEach { it.register(component) } } } private fun Project.installSentrySdk( - sentryVersion: String?, - dependencies: DependencySet, - sentryArtifactId: String, - extension: SentryPluginExtension + sentryVersion: String?, + dependencies: DependencySet, + sentryArtifactId: String, + extension: SentryPluginExtension, ): String { - return if (sentryVersion == null) { - val userDefinedVersion = extension.autoInstallation.sentryVersion.get() - val sentryAndroidDep = - this.dependencies.create("$SENTRY_GROUP:$sentryArtifactId:$userDefinedVersion") - dependencies.add(sentryAndroidDep) - logger.info { - "$sentryArtifactId was successfully installed with version: $userDefinedVersion" - } - userDefinedVersion - } else { - logger.info { - "$sentryArtifactId won't be installed because it was already installed directly" - } - sentryVersion - } + return if (sentryVersion == null) { + val userDefinedVersion = extension.autoInstallation.sentryVersion.get() + val sentryAndroidDep = + this.dependencies.create("$SENTRY_GROUP:$sentryArtifactId:$userDefinedVersion") + dependencies.add(sentryAndroidDep) + logger.info { "$sentryArtifactId was successfully installed with version: $userDefinedVersion" } + userDefinedVersion + } else { + logger.info { "$sentryArtifactId won't be installed because it was already installed directly" } + sentryVersion + } } private fun DependencySet.findSentryVersion(isAndroid: Boolean): String? = - if (isAndroid) { - find { - it.group == SENTRY_GROUP && - ( - it.name == SentryModules.SENTRY_ANDROID_CORE.name || - it.name == SentryModules.SENTRY_ANDROID.name || - it.name == SentryModules.SENTRY_BOM.name - ) && it.version != null - }?.version - } else { - find { - it.group == SENTRY_GROUP && - ( - it.name == SentryModules.SENTRY.name || - it.name == SentryModules.SENTRY_SPRING_BOOT2.name || - it.name == SentryModules.SENTRY_SPRING_BOOT3.name || - it.name == SentryModules.SENTRY_BOM.name - ) && it.version != null - }?.version - } + if (isAndroid) { + find { + it.group == SENTRY_GROUP && + (it.name == SentryModules.SENTRY_ANDROID_CORE.name || + it.name == SentryModules.SENTRY_ANDROID.name || + it.name == SentryModules.SENTRY_BOM.name) && + it.version != null + } + ?.version + } else { + find { + it.group == SENTRY_GROUP && + (it.name == SentryModules.SENTRY.name || + it.name == SentryModules.SENTRY_SPRING_BOOT2.name || + it.name == SentryModules.SENTRY_SPRING_BOOT3.name || + it.name == SentryModules.SENTRY_BOM.name) && + it.version != null + } + ?.version + } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstallState.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstallState.kt index 3f3a09b8..dd67bdab 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstallState.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/AutoInstallState.kt @@ -7,62 +7,57 @@ import org.jetbrains.annotations.TestOnly class AutoInstallState private constructor() : Serializable { - @get:Synchronized - @set:Synchronized - var sentryVersion: String = SENTRY_SDK_VERSION + @get:Synchronized @set:Synchronized var sentryVersion: String = SENTRY_SDK_VERSION - @get:Synchronized - @set:Synchronized - var enabled: Boolean = false + @get:Synchronized @set:Synchronized var enabled: Boolean = false - override fun toString(): String { - return "AutoInstallState(sentryVersion='$sentryVersion', enabled=$enabled)" - } + override fun toString(): String { + return "AutoInstallState(sentryVersion='$sentryVersion', enabled=$enabled)" + } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - other as AutoInstallState + other as AutoInstallState - if (sentryVersion != other.sentryVersion) return false - return enabled == other.enabled - } + if (sentryVersion != other.sentryVersion) return false + return enabled == other.enabled + } - override fun hashCode(): Int { - var result = sentryVersion.hashCode() - result = 31 * result + enabled.hashCode() - return result - } + override fun hashCode(): Int { + var result = sentryVersion.hashCode() + result = 31 * result + enabled.hashCode() + return result + } - // We can't use Kotlin object because we need new instance on each Gradle rebuild - // But if we're inside Gradle daemon, Kotlin object will be shared between builds - companion object { - @field:Volatile - private var instance: AutoInstallState? = null + // We can't use Kotlin object because we need new instance on each Gradle rebuild + // But if we're inside Gradle daemon, Kotlin object will be shared between builds + companion object { + @field:Volatile private var instance: AutoInstallState? = null - @JvmStatic - @Synchronized - fun getInstance(gradle: Gradle? = null): AutoInstallState { - if (instance != null) { - return instance!! - } + @JvmStatic + @Synchronized + fun getInstance(gradle: Gradle? = null): AutoInstallState { + if (instance != null) { + return instance!! + } - val state = AutoInstallState() - instance = state + val state = AutoInstallState() + instance = state - if (gradle != null) { - BuildFinishedListenerService.getInstance(gradle).onClose { instance = null } - } + if (gradle != null) { + BuildFinishedListenerService.getInstance(gradle).onClose { instance = null } + } - return state - } + return state + } - @JvmStatic - @Synchronized - @TestOnly - fun clearReference() { - instance = null - } + @JvmStatic + @Synchronized + @TestOnly + fun clearReference() { + instance = null } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/BuildFinishedListenerService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/BuildFinishedListenerService.kt index a2031829..f33ed4ed 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/BuildFinishedListenerService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/BuildFinishedListenerService.kt @@ -26,28 +26,29 @@ import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters abstract class BuildFinishedListenerService : - BuildService, - AutoCloseable { + BuildService, AutoCloseable { - private val actionsOnClose = mutableListOf<() -> Unit>() + private val actionsOnClose = mutableListOf<() -> Unit>() - fun onClose(action: () -> Unit) { - actionsOnClose.add(action) - } + fun onClose(action: () -> Unit) { + actionsOnClose.add(action) + } - override fun close() { - for (action in actionsOnClose) { - action() - } - actionsOnClose.clear() + override fun close() { + for (action in actionsOnClose) { + action() } - - companion object { - fun getInstance(gradle: Gradle): BuildFinishedListenerService { - return gradle.sharedServices.registerIfAbsent( - getBuildServiceName(BuildFinishedListenerService::class.java), - BuildFinishedListenerService::class.java - ) {}.get() - } + actionsOnClose.clear() + } + + companion object { + fun getInstance(gradle: Gradle): BuildFinishedListenerService { + return gradle.sharedServices + .registerIfAbsent( + getBuildServiceName(BuildFinishedListenerService::class.java), + BuildFinishedListenerService::class.java, + ) {} + .get() } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/InstallStrategyRegistrar.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/InstallStrategyRegistrar.kt index 63863322..d8198863 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/InstallStrategyRegistrar.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/InstallStrategyRegistrar.kt @@ -3,5 +3,5 @@ package io.sentry.android.gradle.autoinstall import org.gradle.api.artifacts.dsl.ComponentMetadataHandler interface InstallStrategyRegistrar { - fun register(component: ComponentMetadataHandler) + fun register(component: ComponentMetadataHandler) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategy.kt index b42117d8..28638969 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategy.kt @@ -10,31 +10,32 @@ import org.slf4j.Logger abstract class ComposeInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_COMPOSE_ID + override val sentryModuleId: String + get() = SENTRY_COMPOSE_ID - override val minSupportedSentryVersion: SemVer - get() = SemVer(6, 7, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 7, 0) - override val minSupportedThirdPartyVersion: SemVer - get() = SemVer(1, 0, 0) + override val minSupportedThirdPartyVersion: SemVer + get() = SemVer(1, 0, 0) - companion object Registrar : InstallStrategyRegistrar { + companion object Registrar : InstallStrategyRegistrar { - internal const val SENTRY_COMPOSE_ID = "sentry-compose-android" + internal const val SENTRY_COMPOSE_ID = "sentry-compose-android" - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "androidx.compose.runtime:runtime", - ComposeInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule( + "androidx.compose.runtime:runtime", + ComposeInstallStrategy::class.java, + ) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategy.kt index 414e531b..25babf78 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategy.kt @@ -11,28 +11,27 @@ import org.slf4j.Logger // @CacheableRule // TODO: make it cacheable somehow (probably depends on parameters) abstract class FragmentInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_FRAGMENT_ID + override val sentryModuleId: String + get() = SENTRY_FRAGMENT_ID - override val minSupportedSentryVersion: SemVer get() = SemVer(5, 1, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(5, 1, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val FRAGMENT_GROUP = "androidx.fragment" - private const val FRAGMENT_ID = "fragment" - internal const val SENTRY_FRAGMENT_ID = "sentry-android-fragment" + companion object Registrar : InstallStrategyRegistrar { + private const val FRAGMENT_GROUP = "androidx.fragment" + private const val FRAGMENT_ID = "fragment" + internal const val SENTRY_FRAGMENT_ID = "sentry-android-fragment" - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$FRAGMENT_GROUP:$FRAGMENT_ID", - FragmentInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$FRAGMENT_GROUP:$FRAGMENT_ID", FragmentInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategy.kt index 203ab300..21c8f1c0 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategy.kt @@ -11,30 +11,29 @@ import org.slf4j.Logger // @CacheableRule abstract class GraphqlInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_GRAPHQL_ID + override val sentryModuleId: String + get() = SENTRY_GRAPHQL_ID - // prior versions could cause circular dependencies - // due to having graphql as implementation dependency - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 25, 2) + // prior versions could cause circular dependencies + // due to having graphql as implementation dependency + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 25, 2) - companion object Registrar : InstallStrategyRegistrar { - private const val GRAPHQL_GROUP = "com.graphql-java" - private const val GRAPHQL_ID = "graphql-java" - internal const val SENTRY_GRAPHQL_ID = "sentry-graphql" + companion object Registrar : InstallStrategyRegistrar { + private const val GRAPHQL_GROUP = "com.graphql-java" + private const val GRAPHQL_ID = "graphql-java" + internal const val SENTRY_GRAPHQL_ID = "sentry-graphql" - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$GRAPHQL_GROUP:$GRAPHQL_ID", - GraphqlInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$GRAPHQL_GROUP:$GRAPHQL_ID", GraphqlInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategy.kt index cdfac9ef..ac8bb724 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategy.kt @@ -11,73 +11,60 @@ import org.slf4j.Logger // @CacheableRule abstract class JdbcInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } + + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) + + override val sentryModuleId: String + get() = SENTRY_JDBC_ID + + override val minSupportedSentryVersion: SemVer + get() = SemVer(5, 3, 0) + + companion object Registrar : InstallStrategyRegistrar { + private const val SPRING_JDBC_GROUP = "org.springframework" + private const val SPRING_JDBC_ID = "spring-jdbc" + + private const val HSQL_GROUP = "org.hsqldb" + private const val HSQL_ID = "hsqldb" + + private const val MYSQL_GROUP = "mysql" + private const val MYSQL_ID = "mysql-connector-java" + + private const val MARIADB_GROUP = "org.mariadb.jdbc" + private const val MARIADB_ID = "mariadb-java-client" + + private const val POSTGRES_GROUP = "org.postgresql" + private const val POSTGRES_ID = "postgresql" + + private const val ORACLE_GROUP = "com.oracle.jdbc" + private const val ORACLE_DATABASE_GROUP = "com.oracle.database.jdbc" + private const val ORACLE_OJDBC_ID_PREFIX = "ojdbc" + + internal const val SENTRY_JDBC_ID = "sentry-jdbc" + + override fun register(component: ComponentMetadataHandler) { + component.withModule("$SPRING_JDBC_GROUP:$SPRING_JDBC_ID", JdbcInstallStrategy::class.java) {} + component.withModule("$HSQL_GROUP:$HSQL_ID", JdbcInstallStrategy::class.java) {} + component.withModule("$MYSQL_GROUP:$MYSQL_ID", JdbcInstallStrategy::class.java) {} + component.withModule("$MARIADB_GROUP:$MARIADB_ID", JdbcInstallStrategy::class.java) {} + component.withModule("$POSTGRES_GROUP:$POSTGRES_ID", JdbcInstallStrategy::class.java) {} + + (5..14).forEach { + component.withModule( + "$ORACLE_GROUP:$ORACLE_OJDBC_ID_PREFIX$it", + JdbcInstallStrategy::class.java, + ) {} - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) - - override val sentryModuleId: String get() = SENTRY_JDBC_ID - - override val minSupportedSentryVersion: SemVer get() = SemVer(5, 3, 0) - - companion object Registrar : InstallStrategyRegistrar { - private const val SPRING_JDBC_GROUP = "org.springframework" - private const val SPRING_JDBC_ID = "spring-jdbc" - - private const val HSQL_GROUP = "org.hsqldb" - private const val HSQL_ID = "hsqldb" - - private const val MYSQL_GROUP = "mysql" - private const val MYSQL_ID = "mysql-connector-java" - - private const val MARIADB_GROUP = "org.mariadb.jdbc" - private const val MARIADB_ID = "mariadb-java-client" - - private const val POSTGRES_GROUP = "org.postgresql" - private const val POSTGRES_ID = "postgresql" - - private const val ORACLE_GROUP = "com.oracle.jdbc" - private const val ORACLE_DATABASE_GROUP = "com.oracle.database.jdbc" - private const val ORACLE_OJDBC_ID_PREFIX = "ojdbc" - - internal const val SENTRY_JDBC_ID = "sentry-jdbc" - - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SPRING_JDBC_GROUP:$SPRING_JDBC_ID", - JdbcInstallStrategy::class.java - ) {} - component.withModule( - "$HSQL_GROUP:$HSQL_ID", - JdbcInstallStrategy::class.java - ) {} - component.withModule( - "$MYSQL_GROUP:$MYSQL_ID", - JdbcInstallStrategy::class.java - ) {} - component.withModule( - "$MARIADB_GROUP:$MARIADB_ID", - JdbcInstallStrategy::class.java - ) {} - component.withModule( - "$POSTGRES_GROUP:$POSTGRES_ID", - JdbcInstallStrategy::class.java - ) {} - - (5..14).forEach { - component.withModule( - "$ORACLE_GROUP:$ORACLE_OJDBC_ID_PREFIX$it", - JdbcInstallStrategy::class.java - ) {} - - component.withModule( - "$ORACLE_DATABASE_GROUP:$ORACLE_OJDBC_ID_PREFIX$it", - JdbcInstallStrategy::class.java - ) {} - } - } + component.withModule( + "$ORACLE_DATABASE_GROUP:$ORACLE_OJDBC_ID_PREFIX$it", + JdbcInstallStrategy::class.java, + ) {} + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategy.kt index 36c17a01..00129911 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategy.kt @@ -11,34 +11,37 @@ import org.slf4j.Logger // @CacheableRule abstract class KotlinExtensionsInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_KOTLIN_EXTENSIONS_ID + override val sentryModuleId: String + get() = SENTRY_KOTLIN_EXTENSIONS_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - // prior versions would cause circular dependencies - // due to having kotlin coroutines as implementation dependency - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 25, 2) + // prior versions would cause circular dependencies + // due to having kotlin coroutines as implementation dependency + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 25, 2) - companion object Registrar : InstallStrategyRegistrar { - private const val KOTLINX_GROUP = "org.jetbrains.kotlinx" - private const val KOTLIN_COROUTINES_ID = "kotlinx-coroutines-core" - internal const val SENTRY_KOTLIN_EXTENSIONS_ID = "sentry-kotlin-extensions" + companion object Registrar : InstallStrategyRegistrar { + private const val KOTLINX_GROUP = "org.jetbrains.kotlinx" + private const val KOTLIN_COROUTINES_ID = "kotlinx-coroutines-core" + internal const val SENTRY_KOTLIN_EXTENSIONS_ID = "sentry-kotlin-extensions" - private val MIN_SUPPORTED_VERSION = SemVer(1, 6, 1) + private val MIN_SUPPORTED_VERSION = SemVer(1, 6, 1) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$KOTLINX_GROUP:$KOTLIN_COROUTINES_ID", - KotlinExtensionsInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule( + "$KOTLINX_GROUP:$KOTLIN_COROUTINES_ID", + KotlinExtensionsInstallStrategy::class.java, + ) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategy.kt index db7e3955..0dbce011 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategy.kt @@ -11,32 +11,32 @@ import org.slf4j.Logger // @CacheableRule abstract class Log4j2InstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_LOG4J2_ID + override val sentryModuleId: String + get() = SENTRY_LOG4J2_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 25, 2) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 25, 2) - companion object Registrar : InstallStrategyRegistrar { - private const val LOG4J2_GROUP = "org.apache.logging.log4j" - private const val LOG4J2_ID = "log4j-api" - internal const val SENTRY_LOG4J2_ID = "sentry-log4j2" + companion object Registrar : InstallStrategyRegistrar { + private const val LOG4J2_GROUP = "org.apache.logging.log4j" + private const val LOG4J2_ID = "log4j-api" + internal const val SENTRY_LOG4J2_ID = "sentry-log4j2" - private val MIN_SUPPORTED_VERSION = SemVer(2, 0, 0) + private val MIN_SUPPORTED_VERSION = SemVer(2, 0, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$LOG4J2_GROUP:$LOG4J2_ID", - Log4j2InstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$LOG4J2_GROUP:$LOG4J2_ID", Log4j2InstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategy.kt index 21800639..9b5a7f30 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategy.kt @@ -11,32 +11,32 @@ import org.slf4j.Logger // @CacheableRule abstract class LogbackInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_LOGBACK_ID + override val sentryModuleId: String + get() = SENTRY_LOGBACK_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 25, 2) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 25, 2) - companion object Registrar : InstallStrategyRegistrar { - private const val LOGBACK_GROUP = "ch.qos.logback" - private const val LOGBACK_ID = "logback-classic" - internal const val SENTRY_LOGBACK_ID = "sentry-logback" + companion object Registrar : InstallStrategyRegistrar { + private const val LOGBACK_GROUP = "ch.qos.logback" + private const val LOGBACK_ID = "logback-classic" + internal const val SENTRY_LOGBACK_ID = "sentry-logback" - private val MIN_SUPPORTED_VERSION = SemVer(1, 0, 0) + private val MIN_SUPPORTED_VERSION = SemVer(1, 0, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$LOGBACK_GROUP:$LOGBACK_ID", - LogbackInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$LOGBACK_GROUP:$LOGBACK_ID", LogbackInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategy.kt index d85b6349..88fdde3c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategy.kt @@ -10,34 +10,35 @@ import org.slf4j.Logger abstract class AndroidOkHttpInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_ANDROID_OKHTTP_ID + override val sentryModuleId: String + get() = SENTRY_ANDROID_OKHTTP_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(4, 4, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(4, 4, 0) - override val maxSupportedSentryVersion: SemVer get() = SemVer(6, 9999, 9999) + override val maxSupportedSentryVersion: SemVer + get() = SemVer(6, 9999, 9999) - companion object Registrar : InstallStrategyRegistrar { - private const val OKHTTP_GROUP = "com.squareup.okhttp3" - private const val OKHTTP_ID = "okhttp" - internal const val SENTRY_ANDROID_OKHTTP_ID = "sentry-android-okhttp" + companion object Registrar : InstallStrategyRegistrar { + private const val OKHTTP_GROUP = "com.squareup.okhttp3" + private const val OKHTTP_ID = "okhttp" + internal const val SENTRY_ANDROID_OKHTTP_ID = "sentry-android-okhttp" - private val MIN_SUPPORTED_VERSION = SemVer(3, 13, 0) + private val MIN_SUPPORTED_VERSION = SemVer(3, 13, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$OKHTTP_GROUP:$OKHTTP_ID", - AndroidOkHttpInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$OKHTTP_GROUP:$OKHTTP_ID", AndroidOkHttpInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategy.kt index f0fdd235..d1dd8d6d 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategy.kt @@ -11,32 +11,32 @@ import org.slf4j.Logger // @CacheableRule abstract class OkHttpInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_OKHTTP_ID + override val sentryModuleId: String + get() = SENTRY_OKHTTP_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(7, 0, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(7, 0, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val OKHTTP_GROUP = "com.squareup.okhttp3" - private const val OKHTTP_ID = "okhttp" - internal const val SENTRY_OKHTTP_ID = "sentry-okhttp" + companion object Registrar : InstallStrategyRegistrar { + private const val OKHTTP_GROUP = "com.squareup.okhttp3" + private const val OKHTTP_ID = "okhttp" + internal const val SENTRY_OKHTTP_ID = "sentry-okhttp" - private val MIN_SUPPORTED_VERSION = SemVer(3, 13, 0) + private val MIN_SUPPORTED_VERSION = SemVer(3, 13, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$OKHTTP_GROUP:$OKHTTP_ID", - OkHttpInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$OKHTTP_GROUP:$OKHTTP_ID", OkHttpInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategy.kt index dac8f11f..38d0ac49 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategy.kt @@ -14,79 +14,76 @@ import org.slf4j.Logger abstract class WarnOnOverrideStrategy : ComponentMetadataRule { - private var logger: Logger + private var logger: Logger - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override fun execute(context: ComponentMetadataContext) { - val autoInstallState = AutoInstallState.getInstance() + override fun execute(context: ComponentMetadataContext) { + val autoInstallState = AutoInstallState.getInstance() - if (!autoInstallState.enabled) { - return - } + if (!autoInstallState.enabled) { + return + } - val providedVersion = - parseVersionSafely(autoInstallState.sentryVersion) ?: return - val userVersion = parseVersionSafely(context.details.id.version) ?: return + val providedVersion = parseVersionSafely(autoInstallState.sentryVersion) ?: return + val userVersion = parseVersionSafely(context.details.id.version) ?: return - if (userVersion < providedVersion) { - logger.warn( - "WARNING: Version of '${context.details.id.module}' was overridden " + - "from '$userVersion' to '$providedVersion' by the Sentry Gradle plugin. " + - "If you want to use the older version, you can add " + - "`autoInstallation.sentryVersion.set(\"$userVersion\")` in the `sentry {}` " + - "plugin configuration block" - ) - } + if (userVersion < providedVersion) { + logger.warn( + "WARNING: Version of '${context.details.id.module}' was overridden " + + "from '$userVersion' to '$providedVersion' by the Sentry Gradle plugin. " + + "If you want to use the older version, you can add " + + "`autoInstallation.sentryVersion.set(\"$userVersion\")` in the `sentry {}` " + + "plugin configuration block" + ) } + } - private fun parseVersionSafely(version: String): SemVer? { - return try { - SemVer.parse(version) - } catch (t: Throwable) { - logger.info { "Unable to parse version $version as a semantic version." } - null - } + private fun parseVersionSafely(version: String): SemVer? { + return try { + SemVer.parse(version) + } catch (t: Throwable) { + logger.info { "Unable to parse version $version as a semantic version." } + null } + } - companion object Registrar : InstallStrategyRegistrar { - private val sentryModules = setOf( - SentryModules.SENTRY, - SentryModules.SENTRY_ANDROID, - SentryModules.SENTRY_ANDROID_CORE, - SentryModules.SENTRY_ANDROID_NDK, - SentryModules.SENTRY_ANDROID_OKHTTP, - SentryModules.SENTRY_ANDROID_SQLITE, - SentryModules.SENTRY_ANDROID_COMPOSE, - SentryModules.SENTRY_ANDROID_FRAGMENT, - SentryModules.SENTRY_ANDROID_NAVIGATION, - SentryModules.SENTRY_ANDROID_TIMBER, - SentryModules.SENTRY_OKHTTP, - SentryModules.SENTRY_KOTLIN_EXTENSIONS, - SentryModules.SENTRY_GRAPHQL, - SentryModules.SENTRY_JDBC, - SentryModules.SENTRY_LOG4J2, - SentryModules.SENTRY_LOGBACK, - SentryModules.SENTRY_QUARTZ, - SentryModules.SENTRY_SPRING5, - SentryModules.SENTRY_SPRING6, - SentryModules.SENTRY_SPRING_BOOT2, - SentryModules.SENTRY_SPRING_BOOT3 - ) + companion object Registrar : InstallStrategyRegistrar { + private val sentryModules = + setOf( + SentryModules.SENTRY, + SentryModules.SENTRY_ANDROID, + SentryModules.SENTRY_ANDROID_CORE, + SentryModules.SENTRY_ANDROID_NDK, + SentryModules.SENTRY_ANDROID_OKHTTP, + SentryModules.SENTRY_ANDROID_SQLITE, + SentryModules.SENTRY_ANDROID_COMPOSE, + SentryModules.SENTRY_ANDROID_FRAGMENT, + SentryModules.SENTRY_ANDROID_NAVIGATION, + SentryModules.SENTRY_ANDROID_TIMBER, + SentryModules.SENTRY_OKHTTP, + SentryModules.SENTRY_KOTLIN_EXTENSIONS, + SentryModules.SENTRY_GRAPHQL, + SentryModules.SENTRY_JDBC, + SentryModules.SENTRY_LOG4J2, + SentryModules.SENTRY_LOGBACK, + SentryModules.SENTRY_QUARTZ, + SentryModules.SENTRY_SPRING5, + SentryModules.SENTRY_SPRING6, + SentryModules.SENTRY_SPRING_BOOT2, + SentryModules.SENTRY_SPRING_BOOT3, + ) - override fun register(component: ComponentMetadataHandler) { - sentryModules.forEach { module -> - component.withModule( - module.toString(), - WarnOnOverrideStrategy::class.java - ) {} - } - } + override fun register(component: ComponentMetadataHandler) { + sentryModules.forEach { module -> + component.withModule(module.toString(), WarnOnOverrideStrategy::class.java) {} + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategy.kt index 380682dd..461efdd8 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategy.kt @@ -11,28 +11,27 @@ import org.slf4j.Logger // @CacheableRule abstract class QuartzInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_QUARTZ_ID + override val sentryModuleId: String + get() = SENTRY_QUARTZ_ID - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 30, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 30, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val QUARTZ_GROUP = "org.quartz-scheduler" - private const val QUARTZ_ID = "quartz" - internal const val SENTRY_QUARTZ_ID = "sentry-quartz" + companion object Registrar : InstallStrategyRegistrar { + private const val QUARTZ_GROUP = "org.quartz-scheduler" + private const val QUARTZ_ID = "quartz" + internal const val SENTRY_QUARTZ_ID = "sentry-quartz" - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$QUARTZ_GROUP:$QUARTZ_ID", - QuartzInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$QUARTZ_GROUP:$QUARTZ_ID", QuartzInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategy.kt index c5c85922..c69e89b2 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategy.kt @@ -11,35 +11,36 @@ import org.slf4j.Logger // @CacheableRule abstract class Spring5InstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_SPRING_5_ID + override val sentryModuleId: String + get() = SENTRY_SPRING_5_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val maxSupportedThirdPartyVersion: SemVer get() = MAX_SUPPORTED_VERSION + override val maxSupportedThirdPartyVersion: SemVer + get() = MAX_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(4, 1, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(4, 1, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val SPRING_GROUP = "org.springframework" - private const val SPRING_5_ID = "spring-core" - internal const val SENTRY_SPRING_5_ID = "sentry-spring" + companion object Registrar : InstallStrategyRegistrar { + private const val SPRING_GROUP = "org.springframework" + private const val SPRING_5_ID = "spring-core" + internal const val SENTRY_SPRING_5_ID = "sentry-spring" - private val MIN_SUPPORTED_VERSION = SemVer(5, 1, 2) - private val MAX_SUPPORTED_VERSION = SemVer(5, 9999, 9999) + private val MIN_SUPPORTED_VERSION = SemVer(5, 1, 2) + private val MAX_SUPPORTED_VERSION = SemVer(5, 9999, 9999) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SPRING_GROUP:$SPRING_5_ID", - Spring5InstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$SPRING_GROUP:$SPRING_5_ID", Spring5InstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategy.kt index 4e5626a6..42566e33 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategy.kt @@ -11,32 +11,32 @@ import org.slf4j.Logger // @CacheableRule abstract class Spring6InstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_SPRING_6_ID + override val sentryModuleId: String + get() = SENTRY_SPRING_6_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 7, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 7, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val SPRING_GROUP = "org.springframework" - private const val SPRING_6_ID = "spring-core" - internal const val SENTRY_SPRING_6_ID = "sentry-spring-jakarta" + companion object Registrar : InstallStrategyRegistrar { + private const val SPRING_GROUP = "org.springframework" + private const val SPRING_6_ID = "spring-core" + internal const val SENTRY_SPRING_6_ID = "sentry-spring-jakarta" - private val MIN_SUPPORTED_VERSION = SemVer(6, 0, 0) + private val MIN_SUPPORTED_VERSION = SemVer(6, 0, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SPRING_GROUP:$SPRING_6_ID", - Spring6InstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$SPRING_GROUP:$SPRING_6_ID", Spring6InstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategy.kt index e46dd24b..eccf0609 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategy.kt @@ -11,35 +11,39 @@ import org.slf4j.Logger // @CacheableRule abstract class SpringBoot2InstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_SPRING_BOOT_2_ID + override val sentryModuleId: String + get() = SENTRY_SPRING_BOOT_2_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val maxSupportedThirdPartyVersion: SemVer get() = MAX_SUPPORTED_VERSION + override val maxSupportedThirdPartyVersion: SemVer + get() = MAX_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 28, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 28, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val SPRING_GROUP = "org.springframework.boot" - private const val SPRING_BOOT_2_ID = "spring-boot" - internal const val SENTRY_SPRING_BOOT_2_ID = "sentry-spring-boot" + companion object Registrar : InstallStrategyRegistrar { + private const val SPRING_GROUP = "org.springframework.boot" + private const val SPRING_BOOT_2_ID = "spring-boot" + internal const val SENTRY_SPRING_BOOT_2_ID = "sentry-spring-boot" - private val MIN_SUPPORTED_VERSION = SemVer(2, 1, 0) - private val MAX_SUPPORTED_VERSION = SemVer(2, 9999, 9999) + private val MIN_SUPPORTED_VERSION = SemVer(2, 1, 0) + private val MAX_SUPPORTED_VERSION = SemVer(2, 9999, 9999) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SPRING_GROUP:$SPRING_BOOT_2_ID", - SpringBoot2InstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule( + "$SPRING_GROUP:$SPRING_BOOT_2_ID", + SpringBoot2InstallStrategy::class.java, + ) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategy.kt index 50a2c0e9..edd8e56e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategy.kt @@ -11,32 +11,35 @@ import org.slf4j.Logger // @CacheableRule abstract class SpringBoot3InstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_SPRING_BOOT_3_ID + override val sentryModuleId: String + get() = SENTRY_SPRING_BOOT_3_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 28, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 28, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val SPRING_GROUP = "org.springframework.boot" - private const val SPRING_BOOT_3_ID = "spring-boot" - internal const val SENTRY_SPRING_BOOT_3_ID = "sentry-spring-boot-jakarta" + companion object Registrar : InstallStrategyRegistrar { + private const val SPRING_GROUP = "org.springframework.boot" + private const val SPRING_BOOT_3_ID = "spring-boot" + internal const val SENTRY_SPRING_BOOT_3_ID = "sentry-spring-boot-jakarta" - private val MIN_SUPPORTED_VERSION = SemVer(3, 0, 0) + private val MIN_SUPPORTED_VERSION = SemVer(3, 0, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SPRING_GROUP:$SPRING_BOOT_3_ID", - SpringBoot3InstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule( + "$SPRING_GROUP:$SPRING_BOOT_3_ID", + SpringBoot3InstallStrategy::class.java, + ) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SQLiteInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SQLiteInstallStrategy.kt index 5c0adfbf..771dd317 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SQLiteInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SQLiteInstallStrategy.kt @@ -10,32 +10,32 @@ import org.slf4j.Logger abstract class SQLiteInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_SQLITE_ID + override val sentryModuleId: String + get() = SENTRY_SQLITE_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(6, 21, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(6, 21, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val SQLITE_GROUP = "androidx.sqlite" - private const val SQLITE_ID = "sqlite" - internal const val SENTRY_SQLITE_ID = "sentry-android-sqlite" + companion object Registrar : InstallStrategyRegistrar { + private const val SQLITE_GROUP = "androidx.sqlite" + private const val SQLITE_ID = "sqlite" + internal const val SENTRY_SQLITE_ID = "sentry-android-sqlite" - private val MIN_SUPPORTED_VERSION = SemVer(2, 0, 0) + private val MIN_SUPPORTED_VERSION = SemVer(2, 0, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$SQLITE_GROUP:$SQLITE_ID", - SQLiteInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$SQLITE_GROUP:$SQLITE_ID", SQLiteInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategy.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategy.kt index 562df150..ff0b3a4f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategy.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategy.kt @@ -11,31 +11,31 @@ import org.slf4j.Logger // @CacheableRule abstract class TimberInstallStrategy : AbstractInstallStrategy { - constructor(logger: Logger) : super() { - this.logger = logger - } + constructor(logger: Logger) : super() { + this.logger = logger + } - @Suppress("unused") // used by Gradle - @Inject // inject is needed to avoid Gradle error - constructor() : this(SentryPlugin.logger) + @Suppress("unused") // used by Gradle + @Inject // inject is needed to avoid Gradle error + constructor() : this(SentryPlugin.logger) - override val sentryModuleId: String get() = SENTRY_TIMBER_ID + override val sentryModuleId: String + get() = SENTRY_TIMBER_ID - override val minSupportedThirdPartyVersion: SemVer get() = MIN_SUPPORTED_VERSION + override val minSupportedThirdPartyVersion: SemVer + get() = MIN_SUPPORTED_VERSION - override val minSupportedSentryVersion: SemVer get() = SemVer(3, 0, 0) + override val minSupportedSentryVersion: SemVer + get() = SemVer(3, 0, 0) - companion object Registrar : InstallStrategyRegistrar { - private const val TIMBER_GROUP = "com.jakewharton.timber" - private const val TIMBER_ID = "timber" - internal const val SENTRY_TIMBER_ID = "sentry-android-timber" - private val MIN_SUPPORTED_VERSION = SemVer(4, 6, 0) + companion object Registrar : InstallStrategyRegistrar { + private const val TIMBER_GROUP = "com.jakewharton.timber" + private const val TIMBER_ID = "timber" + internal const val SENTRY_TIMBER_ID = "sentry-android-timber" + private val MIN_SUPPORTED_VERSION = SemVer(4, 6, 0) - override fun register(component: ComponentMetadataHandler) { - component.withModule( - "$TIMBER_GROUP:$TIMBER_ID", - TimberInstallStrategy::class.java - ) {} - } + override fun register(component: ComponentMetadataHandler) { + component.withModule("$TIMBER_GROUP:$TIMBER_ID", TimberInstallStrategy::class.java) {} } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AppStartExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AppStartExtension.kt index f76564c0..4257bd14 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AppStartExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AppStartExtension.kt @@ -5,11 +5,11 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property open class AppStartExtension @Inject constructor(objects: ObjectFactory) { - /** - * Enables or disables the app start instrumentation feature. - * When enabled, all ContentProviders and the Application onCreate methods will be instrumented. - * - * Defaults to true. - */ - val enabled: Property = objects.property(Boolean::class.java).convention(true) + /** + * Enables or disables the app start instrumentation feature. When enabled, all ContentProviders + * and the Application onCreate methods will be instrumented. + * + * Defaults to true. + */ + val enabled: Property = objects.property(Boolean::class.java).convention(true) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AutoInstallExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AutoInstallExtension.kt index d6f12540..aa96e8b3 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AutoInstallExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/AutoInstallExtension.kt @@ -6,18 +6,16 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property open class AutoInstallExtension @Inject constructor(objects: ObjectFactory) { - /** - * Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber, - * fragment and sqlite integrations). - * Defaults to true. - */ - val enabled: Property = objects.property(Boolean::class.java) - .convention(true) + /** + * Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber, fragment + * and sqlite integrations). Defaults to true. + */ + val enabled: Property = objects.property(Boolean::class.java).convention(true) - /** - * Overrides default (bundled with plugin) or inherited (from user's buildscript) sentry version. - * Defaults to the latest published sentry version. - */ - val sentryVersion: Property = objects.property(String::class.java) - .convention(SENTRY_SDK_VERSION) + /** + * Overrides default (bundled with plugin) or inherited (from user's buildscript) sentry version. + * Defaults to the latest published sentry version. + */ + val sentryVersion: Property = + objects.property(String::class.java).convention(SENTRY_SDK_VERSION) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/LogcatExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/LogcatExtension.kt index dd7bd151..a5c0532c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/LogcatExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/LogcatExtension.kt @@ -6,19 +6,15 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property open class LogcatExtension @Inject constructor(objects: ObjectFactory) { - /** - * Enables or disables the Logcat feature. - * When enabled and the Log call meets the minimum log level, - * it will replace Logcat calls with SentryLogcatAdapter calls and add breadcrumbs. - * - * Defaults to true. - */ - val enabled: Property = objects.property(Boolean::class.java).convention(true) + /** + * Enables or disables the Logcat feature. When enabled and the Log call meets the minimum log + * level, it will replace Logcat calls with SentryLogcatAdapter calls and add breadcrumbs. + * + * Defaults to true. + */ + val enabled: Property = objects.property(Boolean::class.java).convention(true) - /** - * The minimum log level to capture. - * Defaults to Level.WARNING. - */ - val minLevel: Property = - objects.property(LogcatLevel::class.java).convention(LogcatLevel.WARNING) + /** The minimum log level to capture. Defaults to Level.WARNING. */ + val minLevel: Property = + objects.property(LogcatLevel::class.java).convention(LogcatLevel.WARNING) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/SentryPluginExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/SentryPluginExtension.kt index 17ea100e..4a0cd24a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/SentryPluginExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/SentryPluginExtension.kt @@ -9,222 +9,189 @@ import org.gradle.api.provider.SetProperty abstract class SentryPluginExtension @Inject constructor(project: Project) { - private val objects = project.objects - - /** - * Disables or enables the handling of Proguard mapping for Sentry. - * If enabled the plugin will generate a UUID and will take care of - * uploading the mapping to Sentry. If disabled, all the logic - * related to proguard mapping will be excluded. - * Default is enabled. - * - * @see [autoUpload] - * @see [autoUploadProguardMapping] - */ - val includeProguardMapping: Property = objects - .property(Boolean::class.java).convention(true) - - /** - * Whether the plugin should attempt to auto-upload the mapping file to Sentry or not. - * If disabled the plugin will run a dry-run. - * Default is enabled. - */ - val autoUploadProguardMapping: Property = objects - .property(Boolean::class.java).convention(true) - - /** - * Whether the plugin should attempt to auto-upload the mapping file to Sentry or not. - * If disabled the plugin will run a dry-run. - * Default is enabled. - */ - @Deprecated( - "Use autoUploadProguardMapping instead", - replaceWith = ReplaceWith("autoUploadProguardMapping") - ) - val autoUpload: Property = autoUploadProguardMapping - - /** - * Disables or enables the automatic configuration of Native Symbols - * for Sentry. This executes sentry-cli automatically so - * you don't need to do it manually. - * Default is disabled. - * - * @see [autoUploadNativeSymbols] - */ - val uploadNativeSymbols: Property = objects.property(Boolean::class.java).convention( - false - ) - - /** - * Whether the plugin should attempt to auto-upload the native debug symbols to Sentry or not. - * If disabled the plugin will run a dry-run. - * Default is enabled. - */ - val autoUploadNativeSymbols: Property = - objects.property(Boolean::class.java).convention(true) - - /** - * Includes or not the source code of native code when uploading native symbols for Sentry. - * This executes sentry-cli with the --include-sources param. automatically so - * you don't need to do it manually. - * - * This only works with [uploadNativeSymbols] enabled. - * @see [uploadNativeSymbols] - * - * Default is disabled. - */ - val includeNativeSources: Property = objects.property(Boolean::class.java).convention( - false - ) - - /** List of Android build variants that should be ignored by the Sentry plugin. */ - val ignoredVariants: SetProperty = objects.setProperty(String::class.java) - .convention(emptySet()) - - /** List of Android build types that should be ignored by the Sentry plugin. */ - val ignoredBuildTypes: SetProperty = objects.setProperty(String::class.java) - .convention(emptySet()) - - /** List of Android build flavors that should be ignored by the Sentry plugin. */ - val ignoredFlavors: SetProperty = objects.setProperty(String::class.java) - .convention(emptySet()) - - val tracingInstrumentation: TracingInstrumentationExtension = objects.newInstance( - TracingInstrumentationExtension::class.java - ) - - /** - * Turn on support for GuardSquare's tools integration (Dexguard and External Proguard). - * If enabled, the plugin will try to consume and upload the mapping file - * produced by Dexguard and External Proguard. - * Default is disabled. - */ - val dexguardEnabled: Property = objects - .property(Boolean::class.java).convention(false) - - /** - * Configure the tracing instrumentation. - * Default configuration is enabled. - */ - fun tracingInstrumentation( - tracingInstrumentationAction: Action - ) { - tracingInstrumentationAction.execute(tracingInstrumentation) - } - - val autoInstallation: AutoInstallExtension = objects.newInstance( - AutoInstallExtension::class.java - ) - - /** - * Configure the auto installation feature. - */ - fun autoInstallation( - autoInstallationAction: Action - ) { - autoInstallationAction.execute(autoInstallation) - } - - /** - * Disables or enables the reporting of dependencies metadata for Sentry. - * If enabled the plugin will collect external dependencies and will take care of - * uploading them to Sentry as part of events. If disabled, all the logic - * related to dependencies metadata report will be excluded. - * - * Default is enabled. - */ - val includeDependenciesReport: Property = objects.property(Boolean::class.java) - .convention(true) - - /** - * Disables or enables the handling of source context for Sentry. - * If enabled the plugin will generate a UUID and will take care of - * uploading the source context to Sentry. If disabled, all the logic - * related to source context will be excluded. - * Default is disabled. - * - * @see [autoUploadSourceContext] - */ - val includeSourceContext: Property = objects - .property(Boolean::class.java).convention(false) - - /** - * Whether the plugin should attempt to auto-upload the source context to Sentry or not. - * If disabled the plugin will run a dry-run. - * Default is enabled. - */ - val autoUploadSourceContext: Property = objects - .property(Boolean::class.java).convention(true) - - /** - * Configure additional directories to be included in the source bundle which is used for - * source context. The directories should be specified relative to the Gradle module/project's - * root. For example, if you have a custom source set alongside 'main', the parameter would be - * 'src/custom/java'. - */ - val additionalSourceDirsForSourceContext: SetProperty = objects.setProperty( - String::class.java - ).convention( - emptySet() - ) - - /** - * Disables or enables debug log output, e.g. for for sentry-cli. - * - * Default is disabled. - */ - val debug: Property = objects.property(Boolean::class.java) - .convention(false) - - /** - * The slug of the Sentry organization to use for uploading proguard mappings/source contexts. - * - * Default is null. - */ - val org: Property = objects.property(String::class.java) - .convention(null as String?) - - /** - * The slug of the Sentry project to use for uploading proguard mappings/source contexts. - * - * Default is null. - */ - val projectName: Property = objects.property(String::class.java) - .convention(null as String?) - - /** - * The authentication token to use for uploading proguard mappings/source contexts. - * WARNING: Do not expose this token in your build.gradle files, but rather set an environment - * variable and read it into this property. - * - * Default is null. - */ - val authToken: Property = objects.property(String::class.java) - .convention(null as String?) - - /** - * The url of your Sentry instance. If you're using SAAS (not self hosting) you do not have to - * set this. If you are self hosting you can set your URL here. - * - * Default is null meaning Sentry SAAS. - */ - val url: Property = objects.property(String::class.java) - .convention(null as String?) - - /** - * Whether the plugin should send telemetry data to Sentry. - * If disabled the plugin will not send telemetry data. - * This is auto disabled if running against a self hosted instance of Sentry. - * Default is enabled. - */ - val telemetry: Property = objects - .property(Boolean::class.java).convention(true) - - /** - * The DSN (Sentry URL) telemetry data is sent to. - * - * Default is Sentry SAAS. - */ - val telemetryDsn: Property = objects.property(String::class.java) - .convention(SENTRY_SAAS_DSN) + private val objects = project.objects + + /** + * Disables or enables the handling of Proguard mapping for Sentry. If enabled the plugin will + * generate a UUID and will take care of uploading the mapping to Sentry. If disabled, all the + * logic related to proguard mapping will be excluded. Default is enabled. + * + * @see [autoUpload] + * @see [autoUploadProguardMapping] + */ + val includeProguardMapping: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Whether the plugin should attempt to auto-upload the mapping file to Sentry or not. If disabled + * the plugin will run a dry-run. Default is enabled. + */ + val autoUploadProguardMapping: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Whether the plugin should attempt to auto-upload the mapping file to Sentry or not. If disabled + * the plugin will run a dry-run. Default is enabled. + */ + @Deprecated( + "Use autoUploadProguardMapping instead", + replaceWith = ReplaceWith("autoUploadProguardMapping"), + ) + val autoUpload: Property = autoUploadProguardMapping + + /** + * Disables or enables the automatic configuration of Native Symbols for Sentry. This executes + * sentry-cli automatically so you don't need to do it manually. Default is disabled. + * + * @see [autoUploadNativeSymbols] + */ + val uploadNativeSymbols: Property = + objects.property(Boolean::class.java).convention(false) + + /** + * Whether the plugin should attempt to auto-upload the native debug symbols to Sentry or not. If + * disabled the plugin will run a dry-run. Default is enabled. + */ + val autoUploadNativeSymbols: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Includes or not the source code of native code when uploading native symbols for Sentry. This + * executes sentry-cli with the --include-sources param. automatically so you don't need to do it + * manually. + * + * This only works with [uploadNativeSymbols] enabled. + * + * @see [uploadNativeSymbols] + * + * Default is disabled. + */ + val includeNativeSources: Property = + objects.property(Boolean::class.java).convention(false) + + /** List of Android build variants that should be ignored by the Sentry plugin. */ + val ignoredVariants: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** List of Android build types that should be ignored by the Sentry plugin. */ + val ignoredBuildTypes: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** List of Android build flavors that should be ignored by the Sentry plugin. */ + val ignoredFlavors: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + val tracingInstrumentation: TracingInstrumentationExtension = + objects.newInstance(TracingInstrumentationExtension::class.java) + + /** + * Turn on support for GuardSquare's tools integration (Dexguard and External Proguard). If + * enabled, the plugin will try to consume and upload the mapping file produced by Dexguard and + * External Proguard. Default is disabled. + */ + val dexguardEnabled: Property = objects.property(Boolean::class.java).convention(false) + + /** Configure the tracing instrumentation. Default configuration is enabled. */ + fun tracingInstrumentation( + tracingInstrumentationAction: Action + ) { + tracingInstrumentationAction.execute(tracingInstrumentation) + } + + val autoInstallation: AutoInstallExtension = objects.newInstance(AutoInstallExtension::class.java) + + /** Configure the auto installation feature. */ + fun autoInstallation(autoInstallationAction: Action) { + autoInstallationAction.execute(autoInstallation) + } + + /** + * Disables or enables the reporting of dependencies metadata for Sentry. If enabled the plugin + * will collect external dependencies and will take care of uploading them to Sentry as part of + * events. If disabled, all the logic related to dependencies metadata report will be excluded. + * + * Default is enabled. + */ + val includeDependenciesReport: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Disables or enables the handling of source context for Sentry. If enabled the plugin will + * generate a UUID and will take care of uploading the source context to Sentry. If disabled, all + * the logic related to source context will be excluded. Default is disabled. + * + * @see [autoUploadSourceContext] + */ + val includeSourceContext: Property = + objects.property(Boolean::class.java).convention(false) + + /** + * Whether the plugin should attempt to auto-upload the source context to Sentry or not. If + * disabled the plugin will run a dry-run. Default is enabled. + */ + val autoUploadSourceContext: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Configure additional directories to be included in the source bundle which is used for source + * context. The directories should be specified relative to the Gradle module/project's root. For + * example, if you have a custom source set alongside 'main', the parameter would be + * 'src/custom/java'. + */ + val additionalSourceDirsForSourceContext: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** + * Disables or enables debug log output, e.g. for for sentry-cli. + * + * Default is disabled. + */ + val debug: Property = objects.property(Boolean::class.java).convention(false) + + /** + * The slug of the Sentry organization to use for uploading proguard mappings/source contexts. + * + * Default is null. + */ + val org: Property = objects.property(String::class.java).convention(null as String?) + + /** + * The slug of the Sentry project to use for uploading proguard mappings/source contexts. + * + * Default is null. + */ + val projectName: Property = + objects.property(String::class.java).convention(null as String?) + + /** + * The authentication token to use for uploading proguard mappings/source contexts. WARNING: Do + * not expose this token in your build.gradle files, but rather set an environment variable and + * read it into this property. + * + * Default is null. + */ + val authToken: Property = objects.property(String::class.java).convention(null as String?) + + /** + * The url of your Sentry instance. If you're using SAAS (not self hosting) you do not have to set + * this. If you are self hosting you can set your URL here. + * + * Default is null meaning Sentry SAAS. + */ + val url: Property = objects.property(String::class.java).convention(null as String?) + + /** + * Whether the plugin should send telemetry data to Sentry. If disabled the plugin will not send + * telemetry data. This is auto disabled if running against a self hosted instance of Sentry. + * Default is enabled. + */ + val telemetry: Property = objects.property(Boolean::class.java).convention(true) + + /** + * The DSN (Sentry URL) telemetry data is sent to. + * + * Default is Sentry SAAS. + */ + val telemetryDsn: Property = + objects.property(String::class.java).convention(SENTRY_SAAS_DSN) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt index ed6fe1e9..d51ca26f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt @@ -7,114 +7,99 @@ import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty open class TracingInstrumentationExtension @Inject constructor(objects: ObjectFactory) { - /** - * Enable the tracing instrumentation. - * Does bytecode manipulation for specified [features]. - * Defaults to true. - */ - val enabled: Property = objects.property(Boolean::class.java) - .convention(true) + /** + * Enable the tracing instrumentation. Does bytecode manipulation for specified [features]. + * Defaults to true. + */ + val enabled: Property = objects.property(Boolean::class.java).convention(true) - /** - * Enabled debug output of the plugin. Useful when there are issues with code instrumentation, - * shows the modified bytecode. - * Defaults to false. - */ - val debug: Property = objects.property(Boolean::class.java).convention( - false - ) + /** + * Enabled debug output of the plugin. Useful when there are issues with code instrumentation, + * shows the modified bytecode. Defaults to false. + */ + val debug: Property = objects.property(Boolean::class.java).convention(false) - /** - * Forces dependencies instrumentation, even if they were already instrumented. - * Useful when there are issues with code instrumentation, e.g. the dependencies are - * partially instrumented. - * Defaults to false. - */ - val forceInstrumentDependencies: Property = objects.property(Boolean::class.java) - .convention(false) + /** + * Forces dependencies instrumentation, even if they were already instrumented. Useful when there + * are issues with code instrumentation, e.g. the dependencies are partially instrumented. + * Defaults to false. + */ + val forceInstrumentDependencies: Property = + objects.property(Boolean::class.java).convention(false) - /** - * Specifies a set of [InstrumentationFeature] features that are eligible for bytecode - * manipulation. - * Defaults to all available features of [InstrumentationFeature]. - */ - val features: SetProperty = - objects.setProperty(InstrumentationFeature::class.java).convention( - setOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.OKHTTP, - InstrumentationFeature.COMPOSE, - ) + /** + * Specifies a set of [InstrumentationFeature] features that are eligible for bytecode + * manipulation. Defaults to all available features of [InstrumentationFeature]. + */ + val features: SetProperty = + objects + .setProperty(InstrumentationFeature::class.java) + .convention( + setOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, + InstrumentationFeature.OKHTTP, + InstrumentationFeature.COMPOSE, ) + ) - /** - * The set of glob patterns to exclude from instrumentation. Classes matching any of these - * patterns in the project sources and dependencies jars do not get instrumented by the Sentry - * Gradle plugin. - * - * Do not add the file extension to the end as the filtration is done on compiled classes and - * the .class suffix is not included in the pattern matching. - * - * Example usage: - * ``` - * excludes.set(setOf("com/example/donotinstrument/**", "**/*Test")) - * ``` - * - * Only supported when using Android Gradle plugin (AGP) version 7.4.0 and above. - */ - val excludes: SetProperty = objects.setProperty(String::class.java) - .convention(emptySet()) + /** + * The set of glob patterns to exclude from instrumentation. Classes matching any of these + * patterns in the project sources and dependencies jars do not get instrumented by the Sentry + * Gradle plugin. + * + * Do not add the file extension to the end as the filtration is done on compiled classes and the + * .class suffix is not included in the pattern matching. + * + * Example usage: + * ``` + * excludes.set(setOf("com/example/donotinstrument/**", "**/*Test")) + * ``` + * + * Only supported when using Android Gradle plugin (AGP) version 7.4.0 and above. + */ + val excludes: SetProperty = objects.setProperty(String::class.java).convention(emptySet()) - val logcat: LogcatExtension = objects.newInstance( - LogcatExtension::class.java - ) + val logcat: LogcatExtension = objects.newInstance(LogcatExtension::class.java) - fun logcat( - logcatAction: Action - ) { - logcatAction.execute(logcat) - } + fun logcat(logcatAction: Action) { + logcatAction.execute(logcat) + } - val appStart: AppStartExtension = objects.newInstance( - AppStartExtension::class.java - ) + val appStart: AppStartExtension = objects.newInstance(AppStartExtension::class.java) - fun appStart( - appStartExtensionAction: Action - ) { - appStartExtensionAction.execute(appStart) - } + fun appStart(appStartExtensionAction: Action) { + appStartExtensionAction.execute(appStart) + } } enum class InstrumentationFeature(val integrationName: String) { - /** - * When enabled the SDK will create spans for any CRUD operation performed by - * 'androidx.sqlite.db.SupportSQLiteOpenHelper' and 'androidx.room'. This feature uses bytecode manipulation. - */ - DATABASE("DatabaseInstrumentation"), + /** + * When enabled the SDK will create spans for any CRUD operation performed by + * 'androidx.sqlite.db.SupportSQLiteOpenHelper' and 'androidx.room'. This feature uses bytecode + * manipulation. + */ + DATABASE("DatabaseInstrumentation"), - /** - * When enabled the SDK will create spans for [java.io.FileInputStream], - * [java.io.FileOutputStream], [java.io.FileReader], [java.io.FileWriter]. - * This feature uses bytecode manipulation and replaces the above - * mentioned classes with Sentry-specific implementations. - */ - FILE_IO("FileIOInstrumentation"), + /** + * When enabled the SDK will create spans for [java.io.FileInputStream], + * [java.io.FileOutputStream], [java.io.FileReader], [java.io.FileWriter]. This feature uses + * bytecode manipulation and replaces the above mentioned classes with Sentry-specific + * implementations. + */ + FILE_IO("FileIOInstrumentation"), - /** - * When enabled the SDK will create spans for outgoing network requests and attach - * sentry-trace-header for distributed tracing. - * This feature uses bytecode manipulation and attaches SentryOkHttpInterceptor to all OkHttp - * clients in the project. - */ - OKHTTP("OkHttpInstrumentation"), + /** + * When enabled the SDK will create spans for outgoing network requests and attach + * sentry-trace-header for distributed tracing. This feature uses bytecode manipulation and + * attaches SentryOkHttpInterceptor to all OkHttp clients in the project. + */ + OKHTTP("OkHttpInstrumentation"), - /** - * When enabled the SDK will create breadcrumbs when navigating - * using [androidx.navigation.NavController]. - * This feature uses bytecode manipulation and adds an OnDestinationChangedListener to all - * navigation controllers used in Jetpack Compose. - */ - COMPOSE("ComposeInstrumentation") + /** + * When enabled the SDK will create breadcrumbs when navigating using + * [androidx.navigation.NavController]. This feature uses bytecode manipulation and adds an + * OnDestinationChangedListener to all navigation controllers used in Jetpack Compose. + */ + COMPOSE("ComposeInstrumentation"), } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/AbstractSpanAddingMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/AbstractSpanAddingMethodVisitor.kt index 11fcdeee..a91d8f27 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/AbstractSpanAddingMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/AbstractSpanAddingMethodVisitor.kt @@ -13,167 +13,157 @@ import org.objectweb.asm.commons.LocalVariablesSorter * Base class for all [MethodVisitor] that inject spans bytecode into existing methods. Abstracts * away most of the logic like creating a span, setting span status, finishing a span, etc. * - * Since it inherits from [LocalVariablesSorter] for introducing new local variables, all child classes - * **must** use [originalVisitor] for all visiting operations, otherwise all variables visits will be always remapped, - * even those that we don't want to. + * Since it inherits from [LocalVariablesSorter] for introducing new local variables, all child + * classes **must** use [originalVisitor] for all visiting operations, otherwise all variables + * visits will be always remapped, even those that we don't want to. */ abstract class AbstractSpanAddingMethodVisitor( - api: Int, - originalVisitor: MethodVisitor, - access: Int, - descriptor: String? + api: Int, + originalVisitor: MethodVisitor, + access: Int, + descriptor: String?, ) : LocalVariablesSorter(api, access, descriptor, originalVisitor) { - protected val instrumenting = AtomicBoolean(false) - protected var childIndex by Delegates.notNull() + protected val instrumenting = AtomicBoolean(false) + protected var childIndex by Delegates.notNull() - protected val label0 = Label() - protected val label1 = Label() - protected val label2 = Label() - protected val label3 = Label() - protected val label4 = Label() + protected val label0 = Label() + protected val label1 = Label() + protected val label2 = Label() + protected val label3 = Label() + protected val label4 = Label() - // bytecode preparations for try-catch blocks - protected open fun MethodVisitor.visitTryCatchBlocks(expectedException: String) { - visitTryCatchBlock(label0, label1, label2, expectedException) - visitTryCatchBlock(label0, label1, label3, null) - visitTryCatchBlock(label2, label4, label3, null) - } + // bytecode preparations for try-catch blocks + protected open fun MethodVisitor.visitTryCatchBlocks(expectedException: String) { + visitTryCatchBlock(label0, label1, label2, expectedException) + visitTryCatchBlock(label0, label1, label3, null) + visitTryCatchBlock(label2, label4, label3, null) + } - /* - ISpan span = Sentry.getSpan() - ISpan child = null; - if (span != null) { - child = span.startChild("db", ); - } - */ - protected fun MethodVisitor.visitStartSpan( - gotoIfNull: Label, - descriptionVisitor: MethodVisitor.() -> Unit - ) { - visitMethodInsn( - Opcodes.INVOKESTATIC, - "io/sentry/Sentry", - "getSpan", - "()Lio/sentry/ISpan;", - /* isInterface = */ false - ) - val spanIndex = newLocal(Types.SPAN) - childIndex = newLocal(Types.SPAN) - visitVarInsn(Opcodes.ASTORE, spanIndex) // span - visitInsn(Opcodes.ACONST_NULL) - visitVarInsn(Opcodes.ASTORE, childIndex) // child - visitVarInsn(Opcodes.ALOAD, spanIndex) // span - visitJumpInsn(Opcodes.IFNULL, gotoIfNull) - visitVarInsn(Opcodes.ALOAD, spanIndex) // span - descriptionVisitor() - visitMethodInsn( - Opcodes.INVOKEINTERFACE, - "io/sentry/ISpan", - "startChild", - "(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ISpan;", - /* isInterface = */ true - ) - visitVarInsn(Opcodes.ASTORE, childIndex) // child = ... - } + /* + ISpan span = Sentry.getSpan() + ISpan child = null; + if (span != null) { + child = span.startChild("db", ); + } + */ + protected fun MethodVisitor.visitStartSpan( + gotoIfNull: Label, + descriptionVisitor: MethodVisitor.() -> Unit, + ) { + visitMethodInsn( + Opcodes.INVOKESTATIC, + "io/sentry/Sentry", + "getSpan", + "()Lio/sentry/ISpan;", + /* isInterface = */ false, + ) + val spanIndex = newLocal(Types.SPAN) + childIndex = newLocal(Types.SPAN) + visitVarInsn(Opcodes.ASTORE, spanIndex) // span + visitInsn(Opcodes.ACONST_NULL) + visitVarInsn(Opcodes.ASTORE, childIndex) // child + visitVarInsn(Opcodes.ALOAD, spanIndex) // span + visitJumpInsn(Opcodes.IFNULL, gotoIfNull) + visitVarInsn(Opcodes.ALOAD, spanIndex) // span + descriptionVisitor() + visitMethodInsn( + Opcodes.INVOKEINTERFACE, + "io/sentry/ISpan", + "startChild", + "(Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ISpan;", + /* isInterface = */ true, + ) + visitVarInsn(Opcodes.ASTORE, childIndex) // child = ... + } - /* - if (child != null) { - child.setStatus(SpanStatus.OK|SpanStatus.INTERNAL_ERROR); - } - */ - protected fun MethodVisitor.visitSetStatus(status: String, gotoIfNull: Label) { - visitVarInsn(Opcodes.ALOAD, childIndex) // child - visitJumpInsn(Opcodes.IFNULL, gotoIfNull) - visitVarInsn(Opcodes.ALOAD, childIndex) - visitFieldInsn( - Opcodes.GETSTATIC, - "io/sentry/SpanStatus", - status, - "Lio/sentry/SpanStatus;" - ) - visitMethodInsn( - Opcodes.INVOKEINTERFACE, - "io/sentry/ISpan", - "setStatus", - "(Lio/sentry/SpanStatus;)V", - /* isInterface = */true - ) - } + /* + if (child != null) { + child.setStatus(SpanStatus.OK|SpanStatus.INTERNAL_ERROR); + } + */ + protected fun MethodVisitor.visitSetStatus(status: String, gotoIfNull: Label) { + visitVarInsn(Opcodes.ALOAD, childIndex) // child + visitJumpInsn(Opcodes.IFNULL, gotoIfNull) + visitVarInsn(Opcodes.ALOAD, childIndex) + visitFieldInsn(Opcodes.GETSTATIC, "io/sentry/SpanStatus", status, "Lio/sentry/SpanStatus;") + visitMethodInsn( + Opcodes.INVOKEINTERFACE, + "io/sentry/ISpan", + "setStatus", + "(Lio/sentry/SpanStatus;)V", + /* isInterface = */ true, + ) + } - /* - child.setThrowable(e); - */ - private fun MethodVisitor.visitSetThrowable(varToLoad: Int) { - visitVarInsn(Opcodes.ALOAD, childIndex) // child + /* + child.setThrowable(e); + */ + private fun MethodVisitor.visitSetThrowable(varToLoad: Int) { + visitVarInsn(Opcodes.ALOAD, childIndex) // child - visitVarInsn(Opcodes.ALOAD, varToLoad) // Exception e - visitMethodInsn( - Opcodes.INVOKEINTERFACE, - "io/sentry/ISpan", - "setThrowable", - "(Ljava/lang/Throwable;)V", - /* isInterface = */true - ) - } + visitVarInsn(Opcodes.ALOAD, varToLoad) // Exception e + visitMethodInsn( + Opcodes.INVOKEINTERFACE, + "io/sentry/ISpan", + "setThrowable", + "(Ljava/lang/Throwable;)V", + /* isInterface = */ true, + ) + } - /* - finally { - if (child != null) { - child.finish(); - } + /* + finally { + if (child != null) { + child.finish(); } - */ - protected fun MethodVisitor.visitFinallyBlock(gotoIfNull: Label, status: String? = null) { - visitVarInsn(Opcodes.ALOAD, childIndex) - visitJumpInsn(Opcodes.IFNULL, gotoIfNull) - visitVarInsn(Opcodes.ALOAD, childIndex) - if (status != null) { - visitFieldInsn( - Opcodes.GETSTATIC, - "io/sentry/SpanStatus", - status, - "Lio/sentry/SpanStatus;" - ) - } - visitMethodInsn( - Opcodes.INVOKEINTERFACE, - "io/sentry/ISpan", - "finish", - if (status == null) "()V" else "(Lio/sentry/SpanStatus;)V", - /* isInterface = */true - ) + } + */ + protected fun MethodVisitor.visitFinallyBlock(gotoIfNull: Label, status: String? = null) { + visitVarInsn(Opcodes.ALOAD, childIndex) + visitJumpInsn(Opcodes.IFNULL, gotoIfNull) + visitVarInsn(Opcodes.ALOAD, childIndex) + if (status != null) { + visitFieldInsn(Opcodes.GETSTATIC, "io/sentry/SpanStatus", status, "Lio/sentry/SpanStatus;") } + visitMethodInsn( + Opcodes.INVOKEINTERFACE, + "io/sentry/ISpan", + "finish", + if (status == null) "()V" else "(Lio/sentry/SpanStatus;)V", + /* isInterface = */ true, + ) + } - /* - catch (Exception e) { - if (child != null) { - child.setStatus(SpanStatus.INTERNAL_ERROR); - child.setThrowable(e); - } - throw e; - } - */ - protected fun MethodVisitor.visitCatchBlock( - catchLabel: Label, - throwLabel: Label, - exceptionType: Type = Types.EXCEPTION - ) { - val exceptionIndex = newLocal(exceptionType) - visitLabel(catchLabel) - visitVarInsn(Opcodes.ASTORE, exceptionIndex) // Exception e - visitSetStatus(status = "INTERNAL_ERROR", gotoIfNull = throwLabel) - visitSetThrowable(varToLoad = exceptionIndex) - - visitLabel(throwLabel) - visitThrow(varToLoad = exceptionIndex) + /* + catch (Exception e) { + if (child != null) { + child.setStatus(SpanStatus.INTERNAL_ERROR); + child.setThrowable(e); } - - /* throw e; - */ - protected fun MethodVisitor.visitThrow(varToLoad: Int) { - visitVarInsn(Opcodes.ALOAD, varToLoad) // Exception e - visitInsn(Opcodes.ATHROW) - } + } + */ + protected fun MethodVisitor.visitCatchBlock( + catchLabel: Label, + throwLabel: Label, + exceptionType: Type = Types.EXCEPTION, + ) { + val exceptionIndex = newLocal(exceptionType) + visitLabel(catchLabel) + visitVarInsn(Opcodes.ASTORE, exceptionIndex) // Exception e + visitSetStatus(status = "INTERNAL_ERROR", gotoIfNull = throwLabel) + visitSetThrowable(varToLoad = exceptionIndex) + + visitLabel(throwLabel) + visitThrow(varToLoad = exceptionIndex) + } + + /* + throw e; + */ + protected fun MethodVisitor.visitThrow(varToLoad: Int) { + visitVarInsn(Opcodes.ALOAD, varToLoad) // Exception e + visitInsn(Opcodes.ATHROW) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentable.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentable.kt index 5c921e47..3288eb35 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentable.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentable.kt @@ -6,43 +6,38 @@ import com.android.build.api.instrumentation.ClassContext import java.util.LinkedList import org.objectweb.asm.ClassVisitor -class ChainedInstrumentable( - private val instrumentables: List = emptyList() -) : ClassInstrumentable { +class ChainedInstrumentable(private val instrumentables: List = emptyList()) : + ClassInstrumentable { - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - // build a chain of visitors in order they are provided - val queue = LinkedList(instrumentables) - var prevVisitor = originalVisitor - var visitor: ClassVisitor? = null - while (queue.isNotEmpty()) { - val instrumentable = queue.poll() + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + // build a chain of visitors in order they are provided + val queue = LinkedList(instrumentables) + var prevVisitor = originalVisitor + var visitor: ClassVisitor? = null + while (queue.isNotEmpty()) { + val instrumentable = queue.poll() - visitor = if (instrumentable.isInstrumentable(instrumentableContext)) { - instrumentable.getVisitor( - instrumentableContext, - apiVersion, - prevVisitor, - parameters - ) - } else { - prevVisitor - } - prevVisitor = visitor + visitor = + if (instrumentable.isInstrumentable(instrumentableContext)) { + instrumentable.getVisitor(instrumentableContext, apiVersion, prevVisitor, parameters) + } else { + prevVisitor } - return visitor ?: originalVisitor + prevVisitor = visitor } + return visitor ?: originalVisitor + } - override fun isInstrumentable(data: ClassContext): Boolean = - instrumentables.any { it.isInstrumentable(data) } + override fun isInstrumentable(data: ClassContext): Boolean = + instrumentables.any { it.isInstrumentable(data) } - override fun toString(): String { - return "ChainedInstrumentable(instrumentables=" + - "${instrumentables.joinToString(", ") { it.javaClass.simpleName }})" - } + override fun toString(): String { + return "ChainedInstrumentable(instrumentables=" + + "${instrumentables.joinToString(", ") { it.javaClass.simpleName }})" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitor.kt index 32632760..97bc9e99 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitor.kt @@ -10,60 +10,54 @@ import org.objectweb.asm.util.TraceMethodVisitor @Suppress("UnstableApiUsage") class CommonClassVisitor( - apiVersion: Int, - classVisitor: ClassVisitor, - private val className: String, - private val methodInstrumentables: List, - private val parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + apiVersion: Int, + classVisitor: ClassVisitor, + private val className: String, + private val methodInstrumentables: List, + private val parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, ) : ClassVisitor(apiVersion, classVisitor) { - private lateinit var log: File + private lateinit var log: File - init { - // to avoid file creation in case the debug mode is not set - if (parameters.debug.get()) { + init { + // to avoid file creation in case the debug mode is not set + if (parameters.debug.get()) { - // create log dir. - val logDir = parameters.tmpDir.get() - logDir.mkdirs() + // create log dir. + val logDir = parameters.tmpDir.get() + logDir.mkdirs() - // delete and recreate file - log = File(parameters.tmpDir.get(), "$className-instrumentation.log") - if (log.exists()) { - log.delete() - } - log.createNewFile() - } + // delete and recreate file + log = File(parameters.tmpDir.get(), "$className-instrumentation.log") + if (log.exists()) { + log.delete() + } + log.createNewFile() + } + } + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor { + var mv = super.visitMethod(access, name, descriptor, signature, exceptions) + val methodContext = MethodContext(access, name, descriptor, signature, exceptions?.toList()) + val instrumentable = methodInstrumentables.find { it.isInstrumentable(methodContext) } + + var textifier: ExceptionHandler? = null + if (parameters.debug.get() && instrumentable != null) { + textifier = FileLogTextifier(api, log, name, descriptor) + mv = TraceMethodVisitor(mv, textifier) } - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor { - var mv = super.visitMethod(access, name, descriptor, signature, exceptions) - val methodContext = MethodContext(access, name, descriptor, signature, exceptions?.toList()) - val instrumentable = methodInstrumentables.find { it.isInstrumentable(methodContext) } - - var textifier: ExceptionHandler? = null - if (parameters.debug.get() && instrumentable != null) { - textifier = FileLogTextifier(api, log, name, descriptor) - mv = TraceMethodVisitor(mv, textifier) - } - - val instrumentableVisitor = instrumentable?.getVisitor(methodContext, api, mv, parameters) - return if (instrumentableVisitor != null) { - CatchingMethodVisitor( - api, - instrumentableVisitor, - className, - methodContext, - textifier - ) - } else { - mv - } + val instrumentableVisitor = instrumentable?.getVisitor(methodContext, api, mv, parameters) + return if (instrumentableVisitor != null) { + CatchingMethodVisitor(api, instrumentableVisitor, className, methodContext, textifier) + } else { + mv } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/Instrumentable.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/Instrumentable.kt index 273a3f00..a7cde655 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/Instrumentable.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/Instrumentable.kt @@ -9,42 +9,41 @@ import org.objectweb.asm.MethodVisitor interface Instrumentable : Serializable { - /** - * Fully-qualified name of the instrumentable. Examples: - * Class: androidx.sqlite.db.framework.FrameworkSQLiteDatabase - * Method: query - */ - val fqName: String get() = "" - - /** - * Provides a visitor for this instrumentable. A visitor can be one of the visitors defined - * in [ASM](https://asm.ow2.io/javadoc/org/objectweb/asm/package-summary.html) - * - * @param instrumentableContext A context of the instrumentable. - * @param apiVersion Defines the ASM api version, usually provided from the parent - * @param originalVisitor The original visitor that ASM provides us with before visiting code - * @param parameters Parameters that are configured by users and passed via the Sentry gradle plugin - */ - fun getVisitor( - instrumentableContext: InstrumentableContext, - apiVersion: Int, - originalVisitor: Visitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): Visitor - - /** - * Defines whether this object is instrumentable or not based on [data] - */ - fun isInstrumentable(data: InstrumentableContext): Boolean + /** + * Fully-qualified name of the instrumentable. Examples: Class: + * androidx.sqlite.db.framework.FrameworkSQLiteDatabase Method: query + */ + val fqName: String + get() = "" + + /** + * Provides a visitor for this instrumentable. A visitor can be one of the visitors defined in + * [ASM](https://asm.ow2.io/javadoc/org/objectweb/asm/package-summary.html) + * + * @param instrumentableContext A context of the instrumentable. + * @param apiVersion Defines the ASM api version, usually provided from the parent + * @param originalVisitor The original visitor that ASM provides us with before visiting code + * @param parameters Parameters that are configured by users and passed via the Sentry gradle + * plugin + */ + fun getVisitor( + instrumentableContext: InstrumentableContext, + apiVersion: Int, + originalVisitor: Visitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): Visitor + + /** Defines whether this object is instrumentable or not based on [data] */ + fun isInstrumentable(data: InstrumentableContext): Boolean } interface ClassInstrumentable : Instrumentable { - override fun isInstrumentable(data: ClassContext): Boolean = - fqName == data.currentClassData.className + override fun isInstrumentable(data: ClassContext): Boolean = + fqName == data.currentClassData.className } interface MethodInstrumentable : Instrumentable { - override fun isInstrumentable(data: MethodContext): Boolean = fqName == data.name + override fun isInstrumentable(data: MethodContext): Boolean = fqName == data.name } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/InstrumentableContext.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/InstrumentableContext.kt index 458cd363..99358673 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/InstrumentableContext.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/InstrumentableContext.kt @@ -8,12 +8,12 @@ import com.android.build.gradle.internal.instrumentation.ClassesDataCache import com.android.build.gradle.internal.instrumentation.ClassesHierarchyResolver data class MethodContext( - val access: Int, - val name: String?, - val descriptor: String?, - val signature: String?, - val exceptions: List? + val access: Int, + val name: String?, + val descriptor: String?, + val signature: String?, + val exceptions: List?, ) fun ClassData.toClassContext() = - ClassContextImpl(this, ClassesHierarchyResolver.Builder(ClassesDataCache()).build()) + ClassContextImpl(this, ClassesHierarchyResolver.Builder(ClassesDataCache()).build()) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ReturnType.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ReturnType.kt index f498399f..acd4e8e6 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ReturnType.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/ReturnType.kt @@ -5,23 +5,24 @@ import org.objectweb.asm.Opcodes import org.objectweb.asm.Type enum class ReturnType(val loadInsn: Int, val storeInsn: Int, val returnInsn: Int) { - INTEGER(Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.IRETURN), - FLOAT(Opcodes.FLOAD, Opcodes.FSTORE, Opcodes.FRETURN), - DOUBLE(Opcodes.DLOAD, Opcodes.DSTORE, Opcodes.DRETURN), - LONG(Opcodes.LLOAD, Opcodes.LSTORE, Opcodes.LRETURN), - OBJECT(Opcodes.ALOAD, Opcodes.ASTORE, Opcodes.ARETURN), - VOID(Opcodes.NOP, Opcodes.NOP, Opcodes.RETURN); + INTEGER(Opcodes.ILOAD, Opcodes.ISTORE, Opcodes.IRETURN), + FLOAT(Opcodes.FLOAD, Opcodes.FSTORE, Opcodes.FRETURN), + DOUBLE(Opcodes.DLOAD, Opcodes.DSTORE, Opcodes.DRETURN), + LONG(Opcodes.LLOAD, Opcodes.LSTORE, Opcodes.LRETURN), + OBJECT(Opcodes.ALOAD, Opcodes.ASTORE, Opcodes.ARETURN), + VOID(Opcodes.NOP, Opcodes.NOP, Opcodes.RETURN); - fun toType(): Type = when (this) { - INTEGER -> Type.INT_TYPE - FLOAT -> Type.FLOAT_TYPE - DOUBLE -> Type.DOUBLE_TYPE - LONG -> Type.LONG_TYPE - VOID -> Type.VOID_TYPE - OBJECT -> Types.OBJECT + fun toType(): Type = + when (this) { + INTEGER -> Type.INT_TYPE + FLOAT -> Type.FLOAT_TYPE + DOUBLE -> Type.DOUBLE_TYPE + LONG -> Type.LONG_TYPE + VOID -> Type.VOID_TYPE + OBJECT -> Types.OBJECT } - companion object { - fun returnCodes(): List = values().map { it.returnInsn } - } + companion object { + fun returnCodes(): List = values().map { it.returnInsn } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt index 3734eb8b..83832cc6 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt @@ -36,137 +36,101 @@ import org.objectweb.asm.ClassVisitor @Suppress("UnstableApiUsage") abstract class SpanAddingClassVisitorFactory : - AsmClassVisitorFactory { - - interface SpanAddingParameters : InstrumentationParameters { - - /** - * AGP will re-instrument dependencies, when the [InstrumentationParameters] changed - * https://issuetracker.google.com/issues/190082518#comment4. This is just a dummy parameter - * that is used solely for that purpose. - */ - @get:Input - @get:Optional - val invalidate: Property - - @get:Input - val debug: Property - - @get:Input - val logcatMinLevel: Property - - @get:Internal - val sentryModulesService: Property - - @get:Internal - val tmpDir: Property - - @get:Internal - var _instrumentable: ClassInstrumentable? + AsmClassVisitorFactory { + + interface SpanAddingParameters : InstrumentationParameters { + + /** + * AGP will re-instrument dependencies, when the [InstrumentationParameters] changed + * https://issuetracker.google.com/issues/190082518#comment4. This is just a dummy parameter + * that is used solely for that purpose. + */ + @get:Input @get:Optional val invalidate: Property + + @get:Input val debug: Property + + @get:Input val logcatMinLevel: Property + + @get:Internal val sentryModulesService: Property + + @get:Internal val tmpDir: Property + + @get:Internal var _instrumentable: ClassInstrumentable? + } + + private val instrumentable: ClassInstrumentable + get() { + val memoized = parameters.get()._instrumentable + if (memoized != null) { + return memoized + } + + val sentryModules = parameters.get().sentryModulesService.get().sentryModules + val externalModules = parameters.get().sentryModulesService.get().externalModules + val androidXSqliteFrameWorkModule = + DefaultModuleIdentifier.newId("androidx.sqlite", "sqlite-framework") + val androidXSqliteFrameWorkVersion = + externalModules.getOrDefault(androidXSqliteFrameWorkModule, SemVer()) + val okHttpModule = DefaultModuleIdentifier.newId("com.squareup.okhttp3", "okhttp") + val okHttpVersion = externalModules.getOrDefault(okHttpModule, SemVer()) + val sentryOkhttpVersion = sentryModules.getOrDefault(SentryModules.SENTRY_OKHTTP, SemVer()) + val useSentryAndroidOkHttp = sentryOkhttpVersion < SentryVersions.VERSION_OKHTTP + + SentryPlugin.logger.info { "Read sentry modules: $sentryModules" } + + val sentryModulesService = parameters.get().sentryModulesService.get() + val instrumentable = + ChainedInstrumentable( + listOfNotNull( + AndroidXSQLiteOpenHelper().takeIf { sentryModulesService.isNewDatabaseInstrEnabled() }, + AndroidXSQLiteDatabase().takeIf { sentryModulesService.isOldDatabaseInstrEnabled() }, + AndroidXSQLiteStatement(androidXSqliteFrameWorkVersion).takeIf { + sentryModulesService.isOldDatabaseInstrEnabled() + }, + AndroidXRoomDao().takeIf { + sentryModulesService.isNewDatabaseInstrEnabled() || + sentryModulesService.isOldDatabaseInstrEnabled() + }, + OkHttpEventListener(useSentryAndroidOkHttp, okHttpVersion).takeIf { + sentryModulesService.isOkHttpListenerInstrEnabled() + }, + OkHttp(useSentryAndroidOkHttp).takeIf { sentryModulesService.isOkHttpInstrEnabled() }, + WrappingInstrumentable().takeIf { sentryModulesService.isFileIOInstrEnabled() }, + RemappingInstrumentable().takeIf { sentryModulesService.isFileIOInstrEnabled() }, + ComposeNavigation().takeIf { sentryModulesService.isComposeInstrEnabled() }, + Logcat().takeIf { sentryModulesService.isLogcatInstrEnabled() }, + Application().takeIf { sentryModulesService.isAppStartInstrEnabled() }, + ContentProvider().takeIf { sentryModulesService.isAppStartInstrEnabled() }, + ) + ) + SentryPlugin.logger.info { "Instrumentable: $instrumentable" } + parameters.get()._instrumentable = instrumentable + return instrumentable } - private val instrumentable: ClassInstrumentable - get() { - val memoized = parameters.get()._instrumentable - if (memoized != null) { - return memoized - } - - val sentryModules = parameters.get().sentryModulesService.get().sentryModules - val externalModules = parameters.get().sentryModulesService.get().externalModules - val androidXSqliteFrameWorkModule = DefaultModuleIdentifier.newId( - "androidx.sqlite", - "sqlite-framework" - ) - val androidXSqliteFrameWorkVersion = externalModules.getOrDefault( - androidXSqliteFrameWorkModule, - SemVer() - ) - val okHttpModule = DefaultModuleIdentifier.newId( - "com.squareup.okhttp3", - "okhttp" - ) - val okHttpVersion = externalModules.getOrDefault(okHttpModule, SemVer()) - val sentryOkhttpVersion = sentryModules.getOrDefault( - SentryModules.SENTRY_OKHTTP, - SemVer() - ) - val useSentryAndroidOkHttp = sentryOkhttpVersion < SentryVersions.VERSION_OKHTTP - - SentryPlugin.logger.info { "Read sentry modules: $sentryModules" } - - val sentryModulesService = parameters.get().sentryModulesService.get() - val instrumentable = ChainedInstrumentable( - listOfNotNull( - AndroidXSQLiteOpenHelper().takeIf { - sentryModulesService.isNewDatabaseInstrEnabled() - }, - AndroidXSQLiteDatabase().takeIf { - sentryModulesService.isOldDatabaseInstrEnabled() - }, - AndroidXSQLiteStatement(androidXSqliteFrameWorkVersion).takeIf { - sentryModulesService.isOldDatabaseInstrEnabled() - }, - AndroidXRoomDao().takeIf { - sentryModulesService.isNewDatabaseInstrEnabled() || - sentryModulesService.isOldDatabaseInstrEnabled() - }, - OkHttpEventListener(useSentryAndroidOkHttp, okHttpVersion).takeIf { - sentryModulesService.isOkHttpListenerInstrEnabled() - }, - OkHttp(useSentryAndroidOkHttp).takeIf { - sentryModulesService.isOkHttpInstrEnabled() - }, - WrappingInstrumentable().takeIf { - sentryModulesService.isFileIOInstrEnabled() - }, - RemappingInstrumentable().takeIf { - sentryModulesService.isFileIOInstrEnabled() - }, - ComposeNavigation().takeIf { - sentryModulesService.isComposeInstrEnabled() - }, - Logcat().takeIf { - sentryModulesService.isLogcatInstrEnabled() - }, - Application().takeIf { - sentryModulesService.isAppStartInstrEnabled() - }, - ContentProvider().takeIf { - sentryModulesService.isAppStartInstrEnabled() - } - ) - ) - SentryPlugin.logger.info { - "Instrumentable: $instrumentable" - } - parameters.get()._instrumentable = instrumentable - return instrumentable - } - - override fun createClassVisitor( - classContext: ClassContext, - nextClassVisitor: ClassVisitor - ): ClassVisitor { - val className = classContext.currentClassData.className - - val classReader = nextClassVisitor.findClassWriter()?.findClassReader() - val isMinifiedClass = classReader?.isMinifiedClass() ?: false - if (isMinifiedClass) { - SentryPlugin.logger.info { - "$className skipped from instrumentation because it's a minified class." - } - return nextClassVisitor - } - - return instrumentable.getVisitor( - classContext, - instrumentationContext.apiVersion.get(), - nextClassVisitor, - parameters = parameters.get() - ) + override fun createClassVisitor( + classContext: ClassContext, + nextClassVisitor: ClassVisitor, + ): ClassVisitor { + val className = classContext.currentClassData.className + + val classReader = nextClassVisitor.findClassWriter()?.findClassReader() + val isMinifiedClass = classReader?.isMinifiedClass() ?: false + if (isMinifiedClass) { + SentryPlugin.logger.info { + "$className skipped from instrumentation because it's a minified class." + } + return nextClassVisitor } - override fun isInstrumentable(classData: ClassData): Boolean = - instrumentable.isInstrumentable(classData.toClassContext()) + return instrumentable.getVisitor( + classContext, + instrumentationContext.apiVersion.get(), + nextClassVisitor, + parameters = parameters.get(), + ) + } + + override fun isInstrumentable(classData: ClassData): Boolean = + instrumentable.isInstrumentable(classData.toClassContext()) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanOperations.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanOperations.kt index 3e81d58b..fb1acce3 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanOperations.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanOperations.kt @@ -1,6 +1,6 @@ package io.sentry.android.gradle.instrumentation object SpanOperations { - const val DB_SQL_ROOM = "db.sql.room" - const val DB_SQL_QUERY = "db.sql.query" + const val DB_SQL_ROOM = "db.sql.room" + const val DB_SQL_QUERY = "db.sql.query" } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/ComposeNavigation.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/ComposeNavigation.kt index 3302d123..800303d5 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/ComposeNavigation.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/ComposeNavigation.kt @@ -12,43 +12,46 @@ import org.objectweb.asm.MethodVisitor open class ComposeNavigation : ClassInstrumentable { - companion object { - private const val NAV_HOST_CONTROLLER_CLASSNAME = - "androidx.navigation.compose.NavHostControllerKt" - } + companion object { + private const val NAV_HOST_CONTROLLER_CLASSNAME = + "androidx.navigation.compose.NavHostControllerKt" + } - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - return CommonClassVisitor( - apiVersion, - originalVisitor, - NAV_HOST_CONTROLLER_CLASSNAME, - listOf(object : MethodInstrumentable { + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + return CommonClassVisitor( + apiVersion, + originalVisitor, + NAV_HOST_CONTROLLER_CLASSNAME, + listOf( + object : MethodInstrumentable { - override val fqName: String get() = "rememberNavController" + override val fqName: String + get() = "rememberNavController" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor { - return RememberNavControllerMethodVisitor( - apiVersion, - originalVisitor, - instrumentableContext - ) - } - }), - parameters - ) - } + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor { + return RememberNavControllerMethodVisitor( + apiVersion, + originalVisitor, + instrumentableContext, + ) + } + } + ), + parameters, + ) + } - override fun isInstrumentable(data: ClassContext): Boolean { - return data.currentClassData.className == NAV_HOST_CONTROLLER_CLASSNAME - } + override fun isInstrumentable(data: ClassContext): Boolean { + return data.currentClassData.className == NAV_HOST_CONTROLLER_CLASSNAME + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt index 701d5a23..fc00684a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt @@ -8,38 +8,38 @@ import org.objectweb.asm.commons.AdviceAdapter import org.objectweb.asm.commons.Method class RememberNavControllerMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { - /* ktlint-disable max-line-length */ - private val replacement = Replacement( - "Lio/sentry/compose/SentryNavigationIntegrationKt;", - "withSentryObservableEffect", - "(Landroidx/navigation/NavHostController;Landroidx/compose/runtime/Composer;I)Landroidx/navigation/NavHostController;" + instrumentableContext.descriptor, + ) { + /* ktlint-disable max-line-length */ + private val replacement = + Replacement( + "Lio/sentry/compose/SentryNavigationIntegrationKt;", + "withSentryObservableEffect", + "(Landroidx/navigation/NavHostController;Landroidx/compose/runtime/Composer;I)Landroidx/navigation/NavHostController;", ) - /* ktlint-enable max-line-length */ - override fun onMethodExit(opcode: Int) { - // NavHostController is the return value; - // thus it's already on top of stack + /* ktlint-enable max-line-length */ - // Composer $composer - loadArg(1) + override fun onMethodExit(opcode: Int) { + // NavHostController is the return value; + // thus it's already on top of stack - // int $changed - loadArg(2) + // Composer $composer + loadArg(1) - invokeStatic( - Type.getType(replacement.owner), - Method(replacement.name, replacement.descriptor) - ) - super.onMethodExit(opcode) - } + // int $changed + loadArg(2) + + invokeStatic(Type.getType(replacement.owner), Method(replacement.name, replacement.descriptor)) + super.onMethodExit(opcode) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/AndroidXRoomDao.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/AndroidXRoomDao.kt index b414e72c..a8030345 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/AndroidXRoomDao.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/AndroidXRoomDao.kt @@ -19,83 +19,88 @@ import org.objectweb.asm.tree.MethodNode class AndroidXRoomDao : ClassInstrumentable { - override val fqName: String get() = "androidx.room.Dao" + override val fqName: String + get() = "androidx.room.Dao" - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - val currentClassName = instrumentableContext.currentClassData.className - val originalClassName = currentClassName.substringBefore(IMPL_SUFFIX) - val originalClass = instrumentableContext.loadClassData(originalClassName) - return if (originalClass != null && fqName in originalClass.classAnnotations) { - InstrumentableMethodsCollectingVisitor( - apiVersion, - nextVisitorInitializer = { methodsToInstrument -> - if (methodsToInstrument.isEmpty()) { - originalVisitor - } else { - CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = currentClassName.substringAfterLast('.'), - methodInstrumentables = methodsToInstrument.map { (methodNode, type) -> - RoomMethod(originalClassName, methodNode, type) - }, - parameters = parameters - ) - } - } - ) - } else { - if (parameters.debug.get() && originalClass == null) { - SentryPlugin.logger.info { - "Expected $originalClassName in the classpath, but failed to discover" - } - } + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + val currentClassName = instrumentableContext.currentClassData.className + val originalClassName = currentClassName.substringBefore(IMPL_SUFFIX) + val originalClass = instrumentableContext.loadClassData(originalClassName) + return if (originalClass != null && fqName in originalClass.classAnnotations) { + InstrumentableMethodsCollectingVisitor( + apiVersion, + nextVisitorInitializer = { methodsToInstrument -> + if (methodsToInstrument.isEmpty()) { originalVisitor + } else { + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = currentClassName.substringAfterLast('.'), + methodInstrumentables = + methodsToInstrument.map { (methodNode, type) -> + RoomMethod(originalClassName, methodNode, type) + }, + parameters = parameters, + ) + } + }, + ) + } else { + if (parameters.debug.get() && originalClass == null) { + SentryPlugin.logger.info { + "Expected $originalClassName in the classpath, but failed to discover" } + } + originalVisitor } + } - override fun isInstrumentable(data: ClassContext): Boolean = - // this is not very deterministic, but we filter out false positives in [getVisitor] - // based on the androidx.room.Dao annotation - IMPL_SUFFIX in data.currentClassData.className + override fun isInstrumentable(data: ClassContext): Boolean = + // this is not very deterministic, but we filter out false positives in [getVisitor] + // based on the androidx.room.Dao annotation + IMPL_SUFFIX in data.currentClassData.className - companion object { - private const val IMPL_SUFFIX = "_Impl" - } + companion object { + private const val IMPL_SUFFIX = "_Impl" + } } class RoomMethod( - private val className: String, - private val methodNode: MethodNode, - private val type: RoomMethodType + private val className: String, + private val methodNode: MethodNode, + private val type: RoomMethodType, ) : MethodInstrumentable { - override val fqName: String = methodNode.name + override val fqName: String = methodNode.name - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = AndroidXRoomDaoVisitor( - className, - apiVersion, - originalVisitor, - instrumentableContext.access, - instrumentableContext.descriptor, - type + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + AndroidXRoomDaoVisitor( + className, + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.descriptor, + type, ) - override fun isInstrumentable(data: MethodContext): Boolean { - return data.name == fqName && data.descriptor == methodNode.desc && - // we only care about public Dao methods - (data.access and Opcodes.ACC_PUBLIC) != 0 && - // there's a synthetic bridge method generated by java compiler with the same name which we don't want to instrument - (data.access and Opcodes.ACC_BRIDGE) == 0 - } + override fun isInstrumentable(data: MethodContext): Boolean { + return data.name == fqName && + data.descriptor == methodNode.desc && + // we only care about public Dao methods + (data.access and Opcodes.ACC_PUBLIC) != 0 && + // there's a synthetic bridge method generated by java compiler with the same name which we + // don't want to instrument + (data.access and Opcodes.ACC_BRIDGE) == 0 + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/RoomMethodType.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/RoomMethodType.kt index 8857c114..ae4ae5a9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/RoomMethodType.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/RoomMethodType.kt @@ -1,9 +1,9 @@ package io.sentry.android.gradle.instrumentation.androidx.room enum class RoomMethodType { - TRANSACTION, - QUERY, - QUERY_WITH_TRANSACTION; + TRANSACTION, + QUERY, + QUERY_WITH_TRANSACTION; - fun isTransaction() = this == TRANSACTION || this == QUERY_WITH_TRANSACTION + fun isTransaction() = this == TRANSACTION || this == QUERY_WITH_TRANSACTION } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/AndroidXRoomDaoVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/AndroidXRoomDaoVisitor.kt index d2d97062..df3a3f3e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/AndroidXRoomDaoVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/AndroidXRoomDaoVisitor.kt @@ -8,77 +8,81 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes class AndroidXRoomDaoVisitor( - private val className: String, - api: Int, - private val originalVisitor: MethodVisitor, - access: Int, - descriptor: String?, - private val type: RoomMethodType -) : AbstractSpanAddingMethodVisitor( + private val className: String, + api: Int, + private val originalVisitor: MethodVisitor, + access: Int, + descriptor: String?, + private val type: RoomMethodType, +) : + AbstractSpanAddingMethodVisitor( api = api, originalVisitor = originalVisitor, access = access, - descriptor = descriptor -) { - private val childIfNullStatusOk = Label() - private val childIfNullFinallyPositive = Label() - private val childIfNullFinallyNegative = Label() - private val startSpanIfNull = Label() - private var finallyVisitCount = 0 + descriptor = descriptor, + ) { + private val childIfNullStatusOk = Label() + private val childIfNullFinallyPositive = Label() + private val childIfNullFinallyNegative = Label() + private val startSpanIfNull = Label() + private var finallyVisitCount = 0 - override fun visitCode() { - super.visitCode() - originalVisitor.visitStartSpan(startSpanIfNull) { - visitLdcInsn(SpanOperations.DB_SQL_ROOM) - visitLdcInsn(className) - } - originalVisitor.visitLabel(startSpanIfNull) + override fun visitCode() { + super.visitCode() + originalVisitor.visitStartSpan(startSpanIfNull) { + visitLdcInsn(SpanOperations.DB_SQL_ROOM) + visitLdcInsn(className) } + originalVisitor.visitLabel(startSpanIfNull) + } - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean - ) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - // The backend binds the exception to the current span, via tracing without performance, so we don't need any special try/catch management. - when { - instrumentTransactionSuccessful(name, opcode) -> { - // the original method wants to return, but we intervene here to set status - originalVisitor.visitSetStatus(status = "OK", gotoIfNull = childIfNullStatusOk) - originalVisitor.visitLabel(childIfNullStatusOk) - } - instrumentEndTransaction(name, opcode) || instrumentQuery(name, opcode) -> { - // room's finally block ends here, we add our code to finish the span + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean, + ) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + // The backend binds the exception to the current span, via tracing without performance, so we + // don't need any special try/catch management. + when { + instrumentTransactionSuccessful(name, opcode) -> { + // the original method wants to return, but we intervene here to set status + originalVisitor.visitSetStatus(status = "OK", gotoIfNull = childIfNullStatusOk) + originalVisitor.visitLabel(childIfNullStatusOk) + } + instrumentEndTransaction(name, opcode) || instrumentQuery(name, opcode) -> { + // room's finally block ends here, we add our code to finish the span - // we visit finally block 2 times - one for the positive path in control flow (try) one for negative (catch) - // hence we need to use different labels - val visitCount = ++finallyVisitCount - val label = if (visitCount == 1) { - childIfNullFinallyPositive - } else { - childIfNullFinallyNegative - } - originalVisitor.visitFinallyBlock(gotoIfNull = label) - originalVisitor.visitLabel(label) - } - } + // we visit finally block 2 times - one for the positive path in control flow (try) one for + // negative (catch) + // hence we need to use different labels + val visitCount = ++finallyVisitCount + val label = + if (visitCount == 1) { + childIfNullFinallyPositive + } else { + childIfNullFinallyNegative + } + originalVisitor.visitFinallyBlock(gotoIfNull = label) + originalVisitor.visitLabel(label) + } } + } - private fun instrumentTransactionSuccessful(name: String?, op: Int) = - type.isTransaction() && name == SET_TRANSACTION_SUCCESSFUL && op == Opcodes.INVOKEVIRTUAL + private fun instrumentTransactionSuccessful(name: String?, op: Int) = + type.isTransaction() && name == SET_TRANSACTION_SUCCESSFUL && op == Opcodes.INVOKEVIRTUAL - private fun instrumentQuery(name: String?, op: Int) = - type == RoomMethodType.QUERY && name == CLOSE && op == Opcodes.INVOKEINTERFACE + private fun instrumentQuery(name: String?, op: Int) = + type == RoomMethodType.QUERY && name == CLOSE && op == Opcodes.INVOKEINTERFACE - private fun instrumentEndTransaction(name: String?, op: Int) = - type.isTransaction() && name == END_TRANSACTION && op == Opcodes.INVOKEVIRTUAL + private fun instrumentEndTransaction(name: String?, op: Int) = + type.isTransaction() && name == END_TRANSACTION && op == Opcodes.INVOKEVIRTUAL - companion object { - private const val CLOSE = "close" - private const val END_TRANSACTION = "endTransaction" - private const val SET_TRANSACTION_SUCCESSFUL = "setTransactionSuccessful" - } + companion object { + private const val CLOSE = "close" + private const val END_TRANSACTION = "endTransaction" + private const val SET_TRANSACTION_SUCCESSFUL = "setTransactionSuccessful" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt index c022f975..83ab7423 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt @@ -12,91 +12,87 @@ import org.slf4j.Logger typealias NextVisitorInitializer = (List>) -> ClassVisitor class InstrumentableMethodsCollectingVisitor( - private val apiVersion: Int, - private val nextVisitorInitializer: NextVisitorInitializer, - private val logger: Logger = SentryPlugin.logger + private val apiVersion: Int, + private val nextVisitorInitializer: NextVisitorInitializer, + private val logger: Logger = SentryPlugin.logger, ) : ClassNode(apiVersion) { - private val methodsToInstrument = mutableMapOf() + private val methodsToInstrument = mutableMapOf() - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor { - val methodNode = super.visitMethod( - access, - name, - descriptor, - signature, - exceptions - ) as MethodNode - - return object : MethodVisitor(apiVersion, methodNode) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor { + val methodNode = + super.visitMethod(access, name, descriptor, signature, exceptions) as MethodNode - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean - ) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - val pair = owner to name - if (pair in lookup) { - var type: RoomMethodType? = if (pair == lookup.first()) { - RoomMethodType.TRANSACTION - } else { - RoomMethodType.QUERY - } + return object : MethodVisitor(apiVersion, methodNode) { - // if this methodNode has already been added to the instrumentable list - // this means that either it's a SELECT query wrapped into a transaction - // or some unknown to us usecase for instrumentation and we rather skip it - if (methodNode in methodsToInstrument) { - /* ktlint-disable max-line-length */ - val prevType = methodsToInstrument[methodNode] - type = when { - prevType == RoomMethodType.QUERY && - type == RoomMethodType.TRANSACTION -> - RoomMethodType.QUERY_WITH_TRANSACTION - prevType == RoomMethodType.TRANSACTION && - type == RoomMethodType.QUERY -> - RoomMethodType.QUERY_WITH_TRANSACTION - prevType == RoomMethodType.QUERY_WITH_TRANSACTION -> - RoomMethodType.QUERY_WITH_TRANSACTION - else -> { - logger.warn { - "Unable to identify RoomMethodType, skipping ${methodNode.name} from instrumentation" - } - null - } - } - /* ktlint-enable max-line-length */ - } + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean, + ) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + val pair = owner to name + if (pair in lookup) { + var type: RoomMethodType? = + if (pair == lookup.first()) { + RoomMethodType.TRANSACTION + } else { + RoomMethodType.QUERY + } - if (type != null) { - methodsToInstrument[methodNode] = type - } else { - methodsToInstrument.remove(methodNode) - } + // if this methodNode has already been added to the instrumentable list + // this means that either it's a SELECT query wrapped into a transaction + // or some unknown to us usecase for instrumentation and we rather skip it + if (methodNode in methodsToInstrument) { + /* ktlint-disable max-line-length */ + val prevType = methodsToInstrument[methodNode] + type = + when { + prevType == RoomMethodType.QUERY && type == RoomMethodType.TRANSACTION -> + RoomMethodType.QUERY_WITH_TRANSACTION + prevType == RoomMethodType.TRANSACTION && type == RoomMethodType.QUERY -> + RoomMethodType.QUERY_WITH_TRANSACTION + prevType == RoomMethodType.QUERY_WITH_TRANSACTION -> + RoomMethodType.QUERY_WITH_TRANSACTION + else -> { + logger.warn { + "Unable to identify RoomMethodType, skipping ${methodNode.name} from instrumentation" + } + null } - } + } + /* ktlint-enable max-line-length */ + } + + if (type != null) { + methodsToInstrument[methodNode] = type + } else { + methodsToInstrument.remove(methodNode) + } } + } } + } - override fun visitEnd() { - super.visitEnd() - val nextVisitor = nextVisitorInitializer(methodsToInstrument.toList()) - accept(nextVisitor) - } + override fun visitEnd() { + super.visitEnd() + val nextVisitor = nextVisitorInitializer(methodsToInstrument.toList()) + accept(nextVisitor) + } - companion object { - private val lookup = listOf( - "androidx/room/RoomDatabase" to "beginTransaction", - "androidx/room/util/DBUtil" to "query" - ) - } + companion object { + private val lookup = + listOf( + "androidx/room/RoomDatabase" to "beginTransaction", + "androidx/room/util/DBUtil" to "query", + ) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/AndroidXSQLiteOpenHelper.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/AndroidXSQLiteOpenHelper.kt index efbc1a7b..f4cfbf41 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/AndroidXSQLiteOpenHelper.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/AndroidXSQLiteOpenHelper.kt @@ -12,51 +12,45 @@ import org.objectweb.asm.MethodVisitor class AndroidXSQLiteOpenHelper : ClassInstrumentable { - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - val currentClassName = instrumentableContext.currentClassData.className - val sqLiteMethodList: List = listOf( - SQLiteOpenHelperMethodInstrumentable() - ) - return CommonClassVisitor( - apiVersion, - originalVisitor, - currentClassName.substringAfterLast('.'), - sqLiteMethodList, - parameters - ) - } + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + val currentClassName = instrumentableContext.currentClassData.className + val sqLiteMethodList: List = + listOf(SQLiteOpenHelperMethodInstrumentable()) + return CommonClassVisitor( + apiVersion, + originalVisitor, + currentClassName.substringAfterLast('.'), + sqLiteMethodList, + parameters, + ) + } - // Instrument any class implementing androidx.sqlite.db.SupportSQLiteOpenHelper$Factory - override fun isInstrumentable(data: ClassContext) = - data.currentClassData.interfaces.contains( - "androidx.sqlite.db.SupportSQLiteOpenHelper\$Factory" - ) + // Instrument any class implementing androidx.sqlite.db.SupportSQLiteOpenHelper$Factory + override fun isInstrumentable(data: ClassContext) = + data.currentClassData.interfaces.contains("androidx.sqlite.db.SupportSQLiteOpenHelper\$Factory") } class SQLiteOpenHelperMethodInstrumentable : MethodInstrumentable { - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor { - return SQLiteOpenHelperMethodVisitor( - apiVersion, - originalVisitor, - instrumentableContext - ) - } + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor { + return SQLiteOpenHelperMethodVisitor(apiVersion, originalVisitor, instrumentableContext) + } - // We want to instrument only the SupportSQLiteOpenHelper.Factory method - // fun create(config: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper {...} - override fun isInstrumentable(data: MethodContext) = - data.name == "create" && - data.descriptor == "(Landroidx/sqlite/db/SupportSQLiteOpenHelper\$Configuration;)" + - "Landroidx/sqlite/db/SupportSQLiteOpenHelper;" + // We want to instrument only the SupportSQLiteOpenHelper.Factory method + // fun create(config: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper {...} + override fun isInstrumentable(data: MethodContext) = + data.name == "create" && + data.descriptor == + "(Landroidx/sqlite/db/SupportSQLiteOpenHelper\$Configuration;)" + + "Landroidx/sqlite/db/SupportSQLiteOpenHelper;" } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/AndroidXSQLiteDatabase.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/AndroidXSQLiteDatabase.kt index 808e79e8..3ae826a1 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/AndroidXSQLiteDatabase.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/AndroidXSQLiteDatabase.kt @@ -14,66 +14,73 @@ import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor class AndroidXSQLiteDatabase : ClassInstrumentable { - override val fqName: String get() = "androidx.sqlite.db.framework.FrameworkSQLiteDatabase" + override val fqName: String + get() = "androidx.sqlite.db.framework.FrameworkSQLiteDatabase" - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = fqName.substringAfterLast('.'), - methodInstrumentables = listOf(Query(), ExecSql()), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = fqName.substringAfterLast('.'), + methodInstrumentables = listOf(Query(), ExecSql()), + parameters = parameters, ) } class Query : MethodInstrumentable { - override val fqName: String get() = "query" + override val fqName: String + get() = "query" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = - if (instrumentableContext.descriptor == QUERY_METHOD_DESCRIPTOR || - instrumentableContext.descriptor == QUERY_METHOD_WITH_CANCELLATION_DESCRIPTOR - ) { - // only instrument certain method overloads, as the other ones are calling these 2, otherwise we'd be creating 2 spans for one method - QueryMethodVisitor( - api = apiVersion, - originalVisitor = originalVisitor, - access = instrumentableContext.access, - descriptor = instrumentableContext.descriptor - ) - } else { - originalVisitor - } - - companion object { - private const val QUERY_METHOD_DESCRIPTOR = - "(Landroidx/sqlite/db/SupportSQLiteQuery;)Landroid/database/Cursor;" - private const val QUERY_METHOD_WITH_CANCELLATION_DESCRIPTOR = - "(Landroidx/sqlite/db/SupportSQLiteQuery;Landroid/os/CancellationSignal;)" + - "Landroid/database/Cursor;" + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + if ( + instrumentableContext.descriptor == QUERY_METHOD_DESCRIPTOR || + instrumentableContext.descriptor == QUERY_METHOD_WITH_CANCELLATION_DESCRIPTOR + ) { + // only instrument certain method overloads, as the other ones are calling these 2, otherwise + // we'd be creating 2 spans for one method + QueryMethodVisitor( + api = apiVersion, + originalVisitor = originalVisitor, + access = instrumentableContext.access, + descriptor = instrumentableContext.descriptor, + ) + } else { + originalVisitor } + + companion object { + private const val QUERY_METHOD_DESCRIPTOR = + "(Landroidx/sqlite/db/SupportSQLiteQuery;)Landroid/database/Cursor;" + private const val QUERY_METHOD_WITH_CANCELLATION_DESCRIPTOR = + "(Landroidx/sqlite/db/SupportSQLiteQuery;Landroid/os/CancellationSignal;)" + + "Landroid/database/Cursor;" + } } class ExecSql : MethodInstrumentable { - override val fqName: String get() = "execSQL" + override val fqName: String + get() = "execSQL" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ExecSqlMethodVisitor( - api = apiVersion, - originalVisitor = originalVisitor, - access = instrumentableContext.access, - descriptor = instrumentableContext.descriptor + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ExecSqlMethodVisitor( + api = apiVersion, + originalVisitor = originalVisitor, + access = instrumentableContext.access, + descriptor = instrumentableContext.descriptor, ) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/ExecSqlMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/ExecSqlMethodVisitor.kt index f990647f..be50b487 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/ExecSqlMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/ExecSqlMethodVisitor.kt @@ -12,71 +12,67 @@ import org.objectweb.asm.Opcodes.GOTO import org.objectweb.asm.Opcodes.RETURN class ExecSqlMethodVisitor( - api: Int, - private val originalVisitor: MethodVisitor, - access: Int, - descriptor: String? + api: Int, + private val originalVisitor: MethodVisitor, + access: Int, + descriptor: String?, ) : AbstractSpanAddingMethodVisitor(api, originalVisitor, access, descriptor) { - private val label5 = Label() - private val label6 = Label() - private val label7 = Label() + private val label5 = Label() + private val label6 = Label() + private val label7 = Label() - override fun visitCode() { - super.visitCode() - // start visiting method - originalVisitor.visitTryCatchBlocks(expectedException = "android/database/SQLException") + override fun visitCode() { + super.visitCode() + // start visiting method + originalVisitor.visitTryCatchBlocks(expectedException = "android/database/SQLException") - originalVisitor.visitStartSpan(gotoIfNull = label0) { - visitLdcInsn(SpanOperations.DB_SQL_QUERY) - visitVarInsn(ALOAD, 1) - } - - // we delegate to the original method visitor to keep the original method's bytecode - // in theory, we could rewrite the original bytecode as well, but it would mean keeping track - // of its changes and maintaining it - originalVisitor.visitLabel(label0) + originalVisitor.visitStartSpan(gotoIfNull = label0) { + visitLdcInsn(SpanOperations.DB_SQL_QUERY) + visitVarInsn(ALOAD, 1) } - override fun visitInsn(opcode: Int) { - // if the original method wants to return, we prevent it from doing so - // and inject our logic - if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { - originalVisitor.finalizeSpan() - instrumenting.set(false) - return - } - super.visitInsn(opcode) + // we delegate to the original method visitor to keep the original method's bytecode + // in theory, we could rewrite the original bytecode as well, but it would mean keeping track + // of its changes and maintaining it + originalVisitor.visitLabel(label0) + } + + override fun visitInsn(opcode: Int) { + // if the original method wants to return, we prevent it from doing so + // and inject our logic + if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { + originalVisitor.finalizeSpan() + instrumenting.set(false) + return } + super.visitInsn(opcode) + } - private fun MethodVisitor.finalizeSpan() { - // set status to OK after the successful query - visitSetStatus(status = "OK", gotoIfNull = label1) + private fun MethodVisitor.finalizeSpan() { + // set status to OK after the successful query + visitSetStatus(status = "OK", gotoIfNull = label1) - // visit finally block for the positive path in the control flow (return from try-block) - visitLabel(label1) - visitFinallyBlock(gotoIfNull = label5) - visitJumpInsn(GOTO, label5) + // visit finally block for the positive path in the control flow (return from try-block) + visitLabel(label1) + visitFinallyBlock(gotoIfNull = label5) + visitJumpInsn(GOTO, label5) - visitCatchBlock( - catchLabel = label2, - throwLabel = label6, - exceptionType = Types.SQL_EXCEPTION - ) + visitCatchBlock(catchLabel = label2, throwLabel = label6, exceptionType = Types.SQL_EXCEPTION) - val exceptionIndex = newLocal(Types.SQL_EXCEPTION) - // store exception - visitLabel(label3) - visitVarInsn(ASTORE, exceptionIndex) // Exception e; + val exceptionIndex = newLocal(Types.SQL_EXCEPTION) + // store exception + visitLabel(label3) + visitVarInsn(ASTORE, exceptionIndex) // Exception e; - // visit finally block for the negative path in the control flow (throw from catch-block) - visitLabel(label4) - visitFinallyBlock(gotoIfNull = label7) + // visit finally block for the negative path in the control flow (throw from catch-block) + visitLabel(label4) + visitFinallyBlock(gotoIfNull = label7) - visitLabel(label7) - visitThrow(varToLoad = exceptionIndex) + visitLabel(label7) + visitThrow(varToLoad = exceptionIndex) - visitLabel(label5) - visitInsn(RETURN) - } + visitLabel(label5) + visitInsn(RETURN) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/QueryMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/QueryMethodVisitor.kt index 870d8f88..c9d91c57 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/QueryMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/database/visitor/QueryMethodVisitor.kt @@ -13,95 +13,95 @@ import org.objectweb.asm.Opcodes.ASTORE import org.objectweb.asm.Opcodes.INVOKEINTERFACE class QueryMethodVisitor( - api: Int, - private val originalVisitor: MethodVisitor, - access: Int, - descriptor: String? + api: Int, + private val originalVisitor: MethodVisitor, + access: Int, + descriptor: String?, ) : AbstractSpanAddingMethodVisitor(api, originalVisitor, access, descriptor) { - private val label5 = Label() - private val label6 = Label() - private val label7 = Label() - private val label8 = Label() - - private var cursorIndex by Delegates.notNull() - - override fun visitCode() { - super.visitCode() - // start visiting method - originalVisitor.visitTryCatchBlocks(expectedException = "java/lang/Exception") - - originalVisitor.visitStartSpan(gotoIfNull = label0) { - visitLdcInsn(SpanOperations.DB_SQL_QUERY) - visitVarInsn(ALOAD, 1) - visitMethodInsn( - INVOKEINTERFACE, - "androidx/sqlite/db/SupportSQLiteQuery", - "getSql", - "()Ljava/lang/String;", - /* isInterface = */true - ) - } - - // we delegate to the original method visitor to keep the original method's bytecode - // in theory, we could rewrite the original bytecode as well, but it would mean keeping track - // of its changes and maintaining it - originalVisitor.visitLabel(label0) + private val label5 = Label() + private val label6 = Label() + private val label7 = Label() + private val label8 = Label() + + private var cursorIndex by Delegates.notNull() + + override fun visitCode() { + super.visitCode() + // start visiting method + originalVisitor.visitTryCatchBlocks(expectedException = "java/lang/Exception") + + originalVisitor.visitStartSpan(gotoIfNull = label0) { + visitLdcInsn(SpanOperations.DB_SQL_QUERY) + visitVarInsn(ALOAD, 1) + visitMethodInsn( + INVOKEINTERFACE, + "androidx/sqlite/db/SupportSQLiteQuery", + "getSql", + "()Ljava/lang/String;", + /* isInterface = */ true, + ) } - override fun visitInsn(opcode: Int) { - // if the original method wants to return, we prevent it from doing so - // and inject our logic - if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { - originalVisitor.visitFinalizeSpan() - instrumenting.set(false) - return - } - super.visitInsn(opcode) - } - - private fun MethodVisitor.visitFinalizeSpan() { - cursorIndex = newLocal(Types.CURSOR) - visitVarInsn(ASTORE, cursorIndex) // Cursor cursor = ... - - // set status to OK after the successful query - visitSetStatus(status = "OK", gotoIfNull = label5) - - visitStoreCursor() - // visit finally block for the positive path in the control flow (return from try-block) - visitLabel(label1) - visitFinallyBlock(gotoIfNull = label6) - visitReturn() - - visitCatchBlock(catchLabel = label2, throwLabel = label7) - - val exceptionIndex = newLocal(Types.EXCEPTION) - // store exception - visitLabel(label3) - visitVarInsn(ASTORE, exceptionIndex) // Exception e; - - // visit finally block for the negative path in the control flow (throw from catch-block) - visitLabel(label4) - visitFinallyBlock(gotoIfNull = label8) - visitLabel(label8) - visitThrow(varToLoad = exceptionIndex) - } - - private fun MethodVisitor.visitStoreCursor() { - visitLabel(label5) - - visitVarInsn(ALOAD, cursorIndex) - cursorIndex = newLocal(Types.CURSOR) - visitVarInsn(ASTORE, cursorIndex) - } - - /* - return cursor; - */ - private fun MethodVisitor.visitReturn() { - visitLabel(label6) - - visitVarInsn(ALOAD, cursorIndex) - visitInsn(ARETURN) + // we delegate to the original method visitor to keep the original method's bytecode + // in theory, we could rewrite the original bytecode as well, but it would mean keeping track + // of its changes and maintaining it + originalVisitor.visitLabel(label0) + } + + override fun visitInsn(opcode: Int) { + // if the original method wants to return, we prevent it from doing so + // and inject our logic + if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { + originalVisitor.visitFinalizeSpan() + instrumenting.set(false) + return } + super.visitInsn(opcode) + } + + private fun MethodVisitor.visitFinalizeSpan() { + cursorIndex = newLocal(Types.CURSOR) + visitVarInsn(ASTORE, cursorIndex) // Cursor cursor = ... + + // set status to OK after the successful query + visitSetStatus(status = "OK", gotoIfNull = label5) + + visitStoreCursor() + // visit finally block for the positive path in the control flow (return from try-block) + visitLabel(label1) + visitFinallyBlock(gotoIfNull = label6) + visitReturn() + + visitCatchBlock(catchLabel = label2, throwLabel = label7) + + val exceptionIndex = newLocal(Types.EXCEPTION) + // store exception + visitLabel(label3) + visitVarInsn(ASTORE, exceptionIndex) // Exception e; + + // visit finally block for the negative path in the control flow (throw from catch-block) + visitLabel(label4) + visitFinallyBlock(gotoIfNull = label8) + visitLabel(label8) + visitThrow(varToLoad = exceptionIndex) + } + + private fun MethodVisitor.visitStoreCursor() { + visitLabel(label5) + + visitVarInsn(ALOAD, cursorIndex) + cursorIndex = newLocal(Types.CURSOR) + visitVarInsn(ASTORE, cursorIndex) + } + + /* + return cursor; + */ + private fun MethodVisitor.visitReturn() { + visitLabel(label6) + + visitVarInsn(ALOAD, cursorIndex) + visitInsn(ARETURN) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/AndroidXSQLiteStatement.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/AndroidXSQLiteStatement.kt index e007d038..10a5013a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/AndroidXSQLiteStatement.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/AndroidXSQLiteStatement.kt @@ -16,57 +16,61 @@ import org.objectweb.asm.MethodVisitor class AndroidXSQLiteStatement(private val androidXSqliteVersion: SemVer) : ClassInstrumentable { - override val fqName: String get() = "androidx.sqlite.db.framework.FrameworkSQLiteStatement" + override val fqName: String + get() = "androidx.sqlite.db.framework.FrameworkSQLiteStatement" - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = fqName.substringAfterLast('.'), - methodInstrumentables = listOf( - ExecuteInsert(androidXSqliteVersion), - ExecuteUpdateDelete(androidXSqliteVersion) - ), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = fqName.substringAfterLast('.'), + methodInstrumentables = + listOf(ExecuteInsert(androidXSqliteVersion), ExecuteUpdateDelete(androidXSqliteVersion)), + parameters = parameters, ) } class ExecuteInsert(private val androidXSqliteVersion: SemVer) : MethodInstrumentable { - override val fqName: String get() = "executeInsert" + override val fqName: String + get() = "executeInsert" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ExecuteStatementMethodVisitor( - ReturnType.LONG, - apiVersion, - originalVisitor, - instrumentableContext.access, - instrumentableContext.descriptor, - androidXSqliteVersion + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ExecuteStatementMethodVisitor( + ReturnType.LONG, + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.descriptor, + androidXSqliteVersion, ) } class ExecuteUpdateDelete(private val androidXSqliteVersion: SemVer) : MethodInstrumentable { - override val fqName: String get() = "executeUpdateDelete" + override val fqName: String + get() = "executeUpdateDelete" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ExecuteStatementMethodVisitor( - ReturnType.INTEGER, - apiVersion, - originalVisitor, - instrumentableContext.access, - instrumentableContext.descriptor, - androidXSqliteVersion + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ExecuteStatementMethodVisitor( + ReturnType.INTEGER, + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.descriptor, + androidXSqliteVersion, ) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/visitor/ExecuteStatementMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/visitor/ExecuteStatementMethodVisitor.kt index 59cd1340..db7bd62a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/visitor/ExecuteStatementMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/statement/visitor/ExecuteStatementMethodVisitor.kt @@ -19,144 +19,136 @@ import org.objectweb.asm.Opcodes.INVOKEVIRTUAL import org.objectweb.asm.Opcodes.ISTORE class ExecuteStatementMethodVisitor( - private val returnType: ReturnType, - api: Int, - private val originalVisitor: MethodVisitor, - access: Int, - descriptor: String?, - private val androidXSqliteVersion: SemVer -) : AbstractSpanAddingMethodVisitor( + private val returnType: ReturnType, + api: Int, + private val originalVisitor: MethodVisitor, + access: Int, + descriptor: String?, + private val androidXSqliteVersion: SemVer, +) : + AbstractSpanAddingMethodVisitor( api = api, originalVisitor = originalVisitor, access = access, - descriptor = descriptor -) { + descriptor = descriptor, + ) { - private val label5 = Label() - private val label6 = Label() - private val label7 = Label() - private val label8 = Label() + private val label5 = Label() + private val label6 = Label() + private val label7 = Label() + private val label8 = Label() - private var descriptionIndex by Delegates.notNull() - private var resultIndex by Delegates.notNull() + private var descriptionIndex by Delegates.notNull() + private var resultIndex by Delegates.notNull() - override fun visitCode() { - super.visitCode() - // start visiting method - originalVisitor.visitTryCatchBlocks("java/lang/Exception") + override fun visitCode() { + super.visitCode() + // start visiting method + originalVisitor.visitTryCatchBlocks("java/lang/Exception") - // extract query description from the toString() method - originalVisitor.visitExtractDescription() + // extract query description from the toString() method + originalVisitor.visitExtractDescription() - originalVisitor.visitStartSpan(gotoIfNull = label0) { - visitLdcInsn(SpanOperations.DB_SQL_QUERY) - visitVarInsn(ALOAD, descriptionIndex) // description - } - - // delegate to the original method visitor to keep the original method's bytecode - originalVisitor.visitLabel(label0) - } - - override fun visitInsn(opcode: Int) { - // if the original method wants to return, we prevent it from doing so - // and inject our logic - if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { - originalVisitor.finalizeSpan() - instrumenting.set(false) - return - } - super.visitInsn(opcode) + originalVisitor.visitStartSpan(gotoIfNull = label0) { + visitLdcInsn(SpanOperations.DB_SQL_QUERY) + visitVarInsn(ALOAD, descriptionIndex) // description } - private fun MethodVisitor.finalizeSpan() { - resultIndex = newLocal(returnType.toType()) - visitVarInsn( - returnType.storeInsn, - resultIndex - ) // result of the original query - - // set status to OK after the successful query - visitSetStatus(status = "OK", gotoIfNull = label5) - - visitStoreResult() - // visit finally block for the positive path in the control flow (return from try-block) - visitLabel(label1) - visitFinallyBlock(gotoIfNull = label6) - visitReturn() - - visitCatchBlock(catchLabel = label2, throwLabel = label7) - - val exceptionIndex = newLocal(Types.EXCEPTION) - // store exception - visitLabel(label3) - visitVarInsn(ASTORE, exceptionIndex) - - // visit finally block for the negative path in the control flow (throw from catch-block) - visitLabel(label4) - visitFinallyBlock(gotoIfNull = label8) - visitLabel(label8) - visitThrow(varToLoad = exceptionIndex) - } - - private fun MethodVisitor.visitStoreResult() { - visitLabel(label5) - - visitVarInsn(returnType.loadInsn, resultIndex) - resultIndex = newLocal(returnType.toType()) - visitVarInsn(returnType.storeInsn, resultIndex) - } - - /* - String description = delegate.toString(); - int index = description.indexOf(':'); - description = description.substring(index + 2); - */ - private fun MethodVisitor.visitExtractDescription() { - visitVarInsn(ALOAD, 0) // this - - // androidx.sqlite changed the name of the variable in version 2.3.0 - val name = if (androidXSqliteVersion >= SemVer(2, 3, 0)) "delegate" else "mDelegate" - visitFieldInsn( - GETFIELD, - "androidx/sqlite/db/framework/FrameworkSQLiteStatement", - name, - "Landroid/database/sqlite/SQLiteStatement;" - ) - visitMethodInsn( - INVOKEVIRTUAL, - "android/database/sqlite/SQLiteStatement", - "toString", - "()Ljava/lang/String;", - false - ) - descriptionIndex = newLocal(Types.STRING) - val indexIndex = newLocal(Types.INT) - visitVarInsn(ASTORE, descriptionIndex) // description - visitVarInsn(ALOAD, descriptionIndex) - visitIntInsn(BIPUSH, 58) // ':' - visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf", "(I)I", false) - visitVarInsn(ISTORE, indexIndex) // index - visitVarInsn(ALOAD, descriptionIndex) // description - visitVarInsn(ILOAD, indexIndex) // index - visitInsn(ICONST_2) // 2 - visitInsn(IADD) // index + 2 - visitMethodInsn( - INVOKEVIRTUAL, - "java/lang/String", - "substring", - "(I)Ljava/lang/String;", - false - ) - visitVarInsn(ASTORE, descriptionIndex) // description - } - - /* - return result; - */ - private fun MethodVisitor.visitReturn() { - visitLabel(label6) - - visitVarInsn(returnType.loadInsn, resultIndex) - visitInsn(returnType.returnInsn) + // delegate to the original method visitor to keep the original method's bytecode + originalVisitor.visitLabel(label0) + } + + override fun visitInsn(opcode: Int) { + // if the original method wants to return, we prevent it from doing so + // and inject our logic + if (opcode in ReturnType.returnCodes() && !instrumenting.getAndSet(true)) { + originalVisitor.finalizeSpan() + instrumenting.set(false) + return } + super.visitInsn(opcode) + } + + private fun MethodVisitor.finalizeSpan() { + resultIndex = newLocal(returnType.toType()) + visitVarInsn(returnType.storeInsn, resultIndex) // result of the original query + + // set status to OK after the successful query + visitSetStatus(status = "OK", gotoIfNull = label5) + + visitStoreResult() + // visit finally block for the positive path in the control flow (return from try-block) + visitLabel(label1) + visitFinallyBlock(gotoIfNull = label6) + visitReturn() + + visitCatchBlock(catchLabel = label2, throwLabel = label7) + + val exceptionIndex = newLocal(Types.EXCEPTION) + // store exception + visitLabel(label3) + visitVarInsn(ASTORE, exceptionIndex) + + // visit finally block for the negative path in the control flow (throw from catch-block) + visitLabel(label4) + visitFinallyBlock(gotoIfNull = label8) + visitLabel(label8) + visitThrow(varToLoad = exceptionIndex) + } + + private fun MethodVisitor.visitStoreResult() { + visitLabel(label5) + + visitVarInsn(returnType.loadInsn, resultIndex) + resultIndex = newLocal(returnType.toType()) + visitVarInsn(returnType.storeInsn, resultIndex) + } + + /* + String description = delegate.toString(); + int index = description.indexOf(':'); + description = description.substring(index + 2); + */ + private fun MethodVisitor.visitExtractDescription() { + visitVarInsn(ALOAD, 0) // this + + // androidx.sqlite changed the name of the variable in version 2.3.0 + val name = if (androidXSqliteVersion >= SemVer(2, 3, 0)) "delegate" else "mDelegate" + visitFieldInsn( + GETFIELD, + "androidx/sqlite/db/framework/FrameworkSQLiteStatement", + name, + "Landroid/database/sqlite/SQLiteStatement;", + ) + visitMethodInsn( + INVOKEVIRTUAL, + "android/database/sqlite/SQLiteStatement", + "toString", + "()Ljava/lang/String;", + false, + ) + descriptionIndex = newLocal(Types.STRING) + val indexIndex = newLocal(Types.INT) + visitVarInsn(ASTORE, descriptionIndex) // description + visitVarInsn(ALOAD, descriptionIndex) + visitIntInsn(BIPUSH, 58) // ':' + visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "indexOf", "(I)I", false) + visitVarInsn(ISTORE, indexIndex) // index + visitVarInsn(ALOAD, descriptionIndex) // description + visitVarInsn(ILOAD, indexIndex) // index + visitInsn(ICONST_2) // 2 + visitInsn(IADD) // index + 2 + visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "substring", "(I)Ljava/lang/String;", false) + visitVarInsn(ASTORE, descriptionIndex) // description + } + + /* + return result; + */ + private fun MethodVisitor.visitReturn() { + visitLabel(label6) + + visitVarInsn(returnType.loadInsn, resultIndex) + visitInsn(returnType.returnInsn) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/visitor/SQLiteOpenHelperMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/visitor/SQLiteOpenHelperMethodVisitor.kt index be4b0a67..9302598a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/visitor/SQLiteOpenHelperMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/sqlite/visitor/SQLiteOpenHelperMethodVisitor.kt @@ -8,56 +8,61 @@ import org.objectweb.asm.commons.AdviceAdapter import org.objectweb.asm.commons.Method class SQLiteOpenHelperMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { + instrumentableContext.descriptor, + ) { - private val replacement = Replacement( - "Lio/sentry/android/sqlite/SentrySupportSQLiteOpenHelper;", - "create", - "(Landroidx/sqlite/db/SupportSQLiteOpenHelper;)Landroidx/sqlite/db/SupportSQLiteOpenHelper;" + private val replacement = + Replacement( + "Lio/sentry/android/sqlite/SentrySupportSQLiteOpenHelper;", + "create", + "(Landroidx/sqlite/db/SupportSQLiteOpenHelper;)Landroidx/sqlite/db/SupportSQLiteOpenHelper;", ) - var alreadyInstrumented = false + var alreadyInstrumented = false - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean - ) { - if ( - name == "create" && - owner == "androidx/sqlite/db/SupportSQLiteOpenHelper\$Factory" && - descriptor == "(Landroidx/sqlite/db/SupportSQLiteOpenHelper\$Configuration;)" + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean, + ) { + if ( + name == "create" && + owner == "androidx/sqlite/db/SupportSQLiteOpenHelper\$Factory" && + descriptor == + "(Landroidx/sqlite/db/SupportSQLiteOpenHelper\$Configuration;)" + "Landroidx/sqlite/db/SupportSQLiteOpenHelper;" - ) { - // We can skip any method which contains a call to - // SupportSQLiteOpenHelper$Factory.create (SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper - // This covers the use case of wrappers around an OpenHelper (e.g. SQLiteCopyOpenHelperFactory from Room). - // It works because we would wrap any delegated open helper anyway. - alreadyInstrumented = true - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + ) { + // We can skip any method which contains a call to + // SupportSQLiteOpenHelper$Factory.create (SupportSQLiteOpenHelper.Configuration): + // SupportSQLiteOpenHelper + // This covers the use case of wrappers around an OpenHelper (e.g. SQLiteCopyOpenHelperFactory + // from Room). + // It works because we would wrap any delegated open helper anyway. + alreadyInstrumented = true } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } - override fun onMethodExit(opcode: Int) { - // SupportSQLiteOpenHelper is the return value, thus it's already on top of stack + override fun onMethodExit(opcode: Int) { + // SupportSQLiteOpenHelper is the return value, thus it's already on top of stack - if (!alreadyInstrumented) { - invokeStatic( - Type.getType(replacement.owner), - Method(replacement.name, replacement.descriptor) - ) - } - super.onMethodExit(opcode) + if (!alreadyInstrumented) { + invokeStatic( + Type.getType(replacement.owner), + Method(replacement.name, replacement.descriptor), + ) } + super.onMethodExit(opcode) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/Application.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/Application.kt index 9f97e642..21b55070 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/Application.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/Application.kt @@ -11,43 +11,45 @@ import org.objectweb.asm.MethodVisitor class Application : ClassInstrumentable { - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = "Application", - methodInstrumentables = listOf(ApplicationMethodInstrumentable()), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = "Application", + methodInstrumentables = listOf(ApplicationMethodInstrumentable()), + parameters = parameters, ) - override fun isInstrumentable(data: ClassContext): Boolean = - data.currentClassData.superClasses.contains("android.app.Application") + override fun isInstrumentable(data: ClassContext): Boolean = + data.currentClassData.superClasses.contains("android.app.Application") } class ApplicationMethodInstrumentable : MethodInstrumentable { - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ApplicationMethodVisitor( - apiVersion = apiVersion, - originalVisitor = originalVisitor, - instrumentableContext = instrumentableContext, + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ApplicationMethodVisitor( + apiVersion = apiVersion, + originalVisitor = originalVisitor, + instrumentableContext = instrumentableContext, ) - override fun isInstrumentable(data: MethodContext): Boolean { - // TODO: think about constructors as well - // , ()V - // , ()V + override fun isInstrumentable(data: MethodContext): Boolean { + // TODO: think about constructors as well + // , ()V + // , ()V - // public void onCreate() - // onCreate, ()V - return data.name == "onCreate" && data.descriptor == "()V" - } + // public void onCreate() + // onCreate, ()V + return data.name == "onCreate" && data.descriptor == "()V" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ApplicationMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ApplicationMethodVisitor.kt index 96761b53..005f0ead 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ApplicationMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ApplicationMethodVisitor.kt @@ -5,40 +5,41 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.commons.AdviceAdapter class ApplicationMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { + instrumentableContext.descriptor, + ) { - override fun onMethodEnter() { - super.onMethodEnter() + override fun onMethodEnter() { + super.onMethodEnter() - loadThis() - visitMethodInsn( - INVOKESTATIC, - "io/sentry/android/core/performance/AppStartMetrics", - "onApplicationCreate", - "(Landroid/app/Application;)V", - false - ) - } + loadThis() + visitMethodInsn( + INVOKESTATIC, + "io/sentry/android/core/performance/AppStartMetrics", + "onApplicationCreate", + "(Landroid/app/Application;)V", + false, + ) + } - override fun onMethodExit(opcode: Int) { - super.onMethodExit(opcode) + override fun onMethodExit(opcode: Int) { + super.onMethodExit(opcode) - loadThis() - visitMethodInsn( - INVOKESTATIC, - "io/sentry/android/core/performance/AppStartMetrics", - "onApplicationPostCreate", - "(Landroid/app/Application;)V", - false - ) - } + loadThis() + visitMethodInsn( + INVOKESTATIC, + "io/sentry/android/core/performance/AppStartMetrics", + "onApplicationPostCreate", + "(Landroid/app/Application;)V", + false, + ) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProvider.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProvider.kt index 6622d055..d712212f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProvider.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProvider.kt @@ -11,44 +11,46 @@ import org.objectweb.asm.MethodVisitor class ContentProvider : ClassInstrumentable { - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = "ContentProvider", - methodInstrumentables = listOf(ContentProviderMethodInstrumentable()), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = "ContentProvider", + methodInstrumentables = listOf(ContentProviderMethodInstrumentable()), + parameters = parameters, ) - override fun isInstrumentable(data: ClassContext) = - data.currentClassData.superClasses.contains("android.content.ContentProvider") + override fun isInstrumentable(data: ClassContext) = + data.currentClassData.superClasses.contains("android.content.ContentProvider") } class ContentProviderMethodInstrumentable : MethodInstrumentable { - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ContentProviderMethodVisitor( - apiVersion = apiVersion, - originalVisitor = originalVisitor, - instrumentableContext = instrumentableContext, + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ContentProviderMethodVisitor( + apiVersion = apiVersion, + originalVisitor = originalVisitor, + instrumentableContext = instrumentableContext, ) - override fun isInstrumentable(data: MethodContext): Boolean { - // TODO: think about constructors as well - // , ()V - // , (I)V - // , ()V + override fun isInstrumentable(data: MethodContext): Boolean { + // TODO: think about constructors as well + // , ()V + // , (I)V + // , ()V - // public boolean onCreate() - // onCreate, ()Z - return data.name == "onCreate" && data.descriptor == "()Z" - } + // public boolean onCreate() + // onCreate, ()Z + return data.name == "onCreate" && data.descriptor == "()Z" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProviderMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProviderMethodVisitor.kt index 57f4b544..a24ca4d5 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProviderMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/appstart/ContentProviderMethodVisitor.kt @@ -10,64 +10,63 @@ import org.objectweb.asm.commons.AdviceAdapter * Decorates the onCreate method of a ContentProvider and adds Sentry performance monitoring by * calling AppStartMetrics.onContentProviderCreate and AppStartMetrics.onContentProviderPostCreate * - * Due to bytecode optimization of some popular libraries (e.g. androidx.startup, MlKit) - * we can't trust that the instrumented bytecode conforms to the Java bytecode specification. - * - * E.g. the following is no longer true - * Quoting docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1: - * On instance method invocation, local variable 0 is always used to pass a reference - * to the object on which the instance method is being invoked - * (this in the Java programming language). + * Due to bytecode optimization of some popular libraries (e.g. androidx.startup, MlKit) we can't + * trust that the instrumented bytecode conforms to the Java bytecode specification. * + * E.g. the following is no longer true Quoting + * docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1: On instance method invocation, + * local variable 0 is always used to pass a reference to the object on which the instance method is + * being invoked (this in the Java programming language). */ class ContentProviderMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { + instrumentableContext.descriptor, + ) { - private var thisIdx = 0 + private var thisIdx = 0 - override fun onMethodEnter() { - // add local variables, this seems to confuse the optimizer enough - newLocal(Types.OBJECT) - newLocal(Types.OBJECT) + override fun onMethodEnter() { + // add local variables, this seems to confuse the optimizer enough + newLocal(Types.OBJECT) + newLocal(Types.OBJECT) - // as we can't assume that variable 0 will be this, store it away into a separate local - thisIdx = newLocal(Types.OBJECT) + // as we can't assume that variable 0 will be this, store it away into a separate local + thisIdx = newLocal(Types.OBJECT) - // finally load this and store it in the local variable - loadThis() - storeLocal(thisIdx) + // finally load this and store it in the local variable + loadThis() + storeLocal(thisIdx) - loadLocal(thisIdx) - box(Type.getType("Landroid/content/ContentProvider;")) + loadLocal(thisIdx) + box(Type.getType("Landroid/content/ContentProvider;")) - visitMethodInsn( - INVOKESTATIC, - "io/sentry/android/core/performance/AppStartMetrics", - "onContentProviderCreate", - "(Landroid/content/ContentProvider;)V", - false - ) - } + visitMethodInsn( + INVOKESTATIC, + "io/sentry/android/core/performance/AppStartMetrics", + "onContentProviderCreate", + "(Landroid/content/ContentProvider;)V", + false, + ) + } - override fun onMethodExit(opcode: Int) { - loadLocal(thisIdx) - box(Type.getType("Landroid/content/ContentProvider;")) + override fun onMethodExit(opcode: Int) { + loadLocal(thisIdx) + box(Type.getType("Landroid/content/ContentProvider;")) - visitMethodInsn( - INVOKESTATIC, - "io/sentry/android/core/performance/AppStartMetrics", - "onContentProviderPostCreate", - "(Landroid/content/ContentProvider;)V", - false - ) - } + visitMethodInsn( + INVOKESTATIC, + "io/sentry/android/core/performance/AppStartMetrics", + "onContentProviderPostCreate", + "(Landroid/content/ContentProvider;)V", + false, + ) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/Logcat.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/Logcat.kt index 8c1527f6..ffa14c21 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/Logcat.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/Logcat.kt @@ -9,31 +9,27 @@ import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory import io.sentry.android.gradle.instrumentation.util.isSentryClass import org.objectweb.asm.ClassVisitor -class Logcat : - ClassInstrumentable { +class Logcat : ClassInstrumentable { - companion object { - private const val LOG_CLASSNAME = "Log" - } + companion object { + private const val LOG_CLASSNAME = "Log" + } - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - val logcatMethodList: List = listOf( - LogcatMethodInstrumentable() - ) - return CommonClassVisitor( - apiVersion, - originalVisitor, - LOG_CLASSNAME, - logcatMethodList, - parameters - ) - } + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + val logcatMethodList: List = listOf(LogcatMethodInstrumentable()) + return CommonClassVisitor( + apiVersion, + originalVisitor, + LOG_CLASSNAME, + logcatMethodList, + parameters, + ) + } - override fun isInstrumentable(data: ClassContext) = - !data.isSentryClass() + override fun isInstrumentable(data: ClassContext) = !data.isSentryClass() } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatClassVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatClassVisitor.kt index 3d9e6a1f..1f51376f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatClassVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatClassVisitor.kt @@ -7,61 +7,62 @@ import org.objectweb.asm.commons.AdviceAdapter class LogcatMethodInstrumentable : MethodInstrumentable { - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor { - return LogcatMethodVisitor( - apiVersion, - originalVisitor, - instrumentableContext, - parameters.logcatMinLevel.get() - ) - } + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor { + return LogcatMethodVisitor( + apiVersion, + originalVisitor, + instrumentableContext, + parameters.logcatMinLevel.get(), + ) + } - override fun isInstrumentable(data: MethodContext): Boolean { - return true - } + override fun isInstrumentable(data: MethodContext): Boolean { + return true + } } class LogcatMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext, - private val minLevel: LogcatLevel -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, + private val minLevel: LogcatLevel, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { + instrumentableContext.descriptor, + ) { - override fun visitMethodInsn( - opcode: Int, - owner: String, - name: String, - desc: String?, - itf: Boolean - ) { - if (shouldReplaceLogCall(owner, name, minLevel)) { - // Replace call to Log with call to SentryLogcatAdapter - mv.visitMethodInsn( - INVOKESTATIC, - "io/sentry/android/core/SentryLogcatAdapter", - name, - desc, - false - ) - } else { - super.visitMethodInsn(opcode, owner, name, desc, itf) - } + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + desc: String?, + itf: Boolean, + ) { + if (shouldReplaceLogCall(owner, name, minLevel)) { + // Replace call to Log with call to SentryLogcatAdapter + mv.visitMethodInsn( + INVOKESTATIC, + "io/sentry/android/core/SentryLogcatAdapter", + name, + desc, + false, + ) + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf) } + } - private fun shouldReplaceLogCall(owner: String, name: String, minLevel: LogcatLevel) = - LogcatLevel.logFunctionToLevel(name)?.let { - owner == "android/util/Log" && it.supports(minLevel) - } ?: false + private fun shouldReplaceLogCall(owner: String, name: String, minLevel: LogcatLevel) = + LogcatLevel.logFunctionToLevel(name)?.let { + owner == "android/util/Log" && it.supports(minLevel) + } ?: false } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevel.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevel.kt index 6460ab19..166054e7 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevel.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevel.kt @@ -1,27 +1,27 @@ package io.sentry.android.gradle.instrumentation.logcat enum class LogcatLevel(private val level: Int) { - VERBOSE(0), - DEBUG(1), - INFO(2), - WARNING(3), - ERROR(4); + VERBOSE(0), + DEBUG(1), + INFO(2), + WARNING(3), + ERROR(4); - fun supports(other: LogcatLevel): Boolean { - return level >= other.level - } + fun supports(other: LogcatLevel): Boolean { + return level >= other.level + } - companion object { - fun logFunctionToLevel(str: String): LogcatLevel? { - return when (str) { - "v" -> VERBOSE - "d" -> DEBUG - "i" -> INFO - "w" -> WARNING - "e" -> ERROR - "wtf" -> ERROR - else -> null - } - } + companion object { + fun logFunctionToLevel(str: String): LogcatLevel? { + return when (str) { + "v" -> VERBOSE + "d" -> DEBUG + "i" -> INFO + "w" -> WARNING + "e" -> ERROR + "wtf" -> ERROR + else -> null + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttp.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttp.kt index 67421186..11185c77 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttp.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttp.kt @@ -11,48 +11,51 @@ import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor class OkHttp(private val useSentryAndroidOkHttp: Boolean) : ClassInstrumentable { - override val fqName: String get() = "RealCall" + override val fqName: String + get() = "RealCall" - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = fqName.substringAfterLast('.'), - methodInstrumentables = listOf(ResponseWithInterceptorChain(useSentryAndroidOkHttp)), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = fqName.substringAfterLast('.'), + methodInstrumentables = listOf(ResponseWithInterceptorChain(useSentryAndroidOkHttp)), + parameters = parameters, ) - // OkHttp has renamed the class in v4, hence we are looking for both old and new package names - // https://github.com/square/okhttp/commit/3d3b0f64005f7d2dd7cde80a9eaf665f8df86fb6#diff-e46bb5c1117393fbfb8cd1496fc4a2dcfcd6fcf70d065c50be83ce9215b2ec7b - override fun isInstrumentable(data: ClassContext): Boolean = - data.currentClassData.className == "okhttp3.internal.connection.RealCall" || - data.currentClassData.className == "okhttp3.RealCall" + // OkHttp has renamed the class in v4, hence we are looking for both old and new package names + // https://github.com/square/okhttp/commit/3d3b0f64005f7d2dd7cde80a9eaf665f8df86fb6#diff-e46bb5c1117393fbfb8cd1496fc4a2dcfcd6fcf70d065c50be83ce9215b2ec7b + override fun isInstrumentable(data: ClassContext): Boolean = + data.currentClassData.className == "okhttp3.internal.connection.RealCall" || + data.currentClassData.className == "okhttp3.RealCall" } -class ResponseWithInterceptorChain( - private val useSentryAndroidOkHttp: Boolean -) : MethodInstrumentable { - override val fqName: String get() = "getResponseWithInterceptorChain" +class ResponseWithInterceptorChain(private val useSentryAndroidOkHttp: Boolean) : + MethodInstrumentable { + override val fqName: String + get() = "getResponseWithInterceptorChain" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = ResponseWithInterceptorChainMethodVisitor( - api = apiVersion, - originalVisitor = originalVisitor, - access = instrumentableContext.access, - name = instrumentableContext.name, - descriptor = instrumentableContext.descriptor, - useSentryAndroidOkHttp = useSentryAndroidOkHttp + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + ResponseWithInterceptorChainMethodVisitor( + api = apiVersion, + originalVisitor = originalVisitor, + access = instrumentableContext.access, + name = instrumentableContext.name, + descriptor = instrumentableContext.descriptor, + useSentryAndroidOkHttp = useSentryAndroidOkHttp, ) - override fun isInstrumentable(data: MethodContext): Boolean { - return data.name?.startsWith(fqName) == true - } + override fun isInstrumentable(data: MethodContext): Boolean { + return data.name?.startsWith(fqName) == true + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttpEventListener.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttpEventListener.kt index 98acd124..a48d912e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttpEventListener.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/OkHttpEventListener.kt @@ -12,50 +12,50 @@ import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor class OkHttpEventListener( - private val useSentryAndroidOkHttp: Boolean, - private val okHttpVersion: SemVer + private val useSentryAndroidOkHttp: Boolean, + private val okHttpVersion: SemVer, ) : ClassInstrumentable { - override val fqName: String get() = "okhttp3.OkHttpClient" + override val fqName: String + get() = "okhttp3.OkHttpClient" - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = fqName.substringAfterLast('.'), - methodInstrumentables = listOf( - OkHttpEventListenerMethodInstrumentable( - useSentryAndroidOkHttp, - okHttpVersion - ) - ), - parameters = parameters + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = fqName.substringAfterLast('.'), + methodInstrumentables = + listOf(OkHttpEventListenerMethodInstrumentable(useSentryAndroidOkHttp, okHttpVersion)), + parameters = parameters, ) } class OkHttpEventListenerMethodInstrumentable( - private val useSentryAndroidOkHttp: Boolean, - private val okHttpVersion: SemVer + private val useSentryAndroidOkHttp: Boolean, + private val okHttpVersion: SemVer, ) : MethodInstrumentable { - override val fqName: String get() = "" + override val fqName: String + get() = "" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = OkHttpEventListenerMethodVisitor( - apiVersion = apiVersion, - originalVisitor = originalVisitor, - instrumentableContext = instrumentableContext, - okHttpVersion = okHttpVersion, - useSentryAndroidOkHttp = useSentryAndroidOkHttp + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + OkHttpEventListenerMethodVisitor( + apiVersion = apiVersion, + originalVisitor = originalVisitor, + instrumentableContext = instrumentableContext, + okHttpVersion = okHttpVersion, + useSentryAndroidOkHttp = useSentryAndroidOkHttp, ) - override fun isInstrumentable(data: MethodContext): Boolean { - return data.name == fqName && data.descriptor == "(Lokhttp3/OkHttpClient\$Builder;)V" - } + override fun isInstrumentable(data: MethodContext): Boolean { + return data.name == fqName && data.descriptor == "(Lokhttp3/OkHttpClient\$Builder;)V" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt index 1c38789c..480a8b06 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt @@ -7,81 +7,83 @@ import org.objectweb.asm.Opcodes import org.objectweb.asm.commons.AdviceAdapter class OkHttpEventListenerMethodVisitor( - apiVersion: Int, - originalVisitor: MethodVisitor, - instrumentableContext: MethodContext, - private val okHttpVersion: SemVer, - private val useSentryAndroidOkHttp: Boolean -) : AdviceAdapter( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, + private val okHttpVersion: SemVer, + private val useSentryAndroidOkHttp: Boolean, +) : + AdviceAdapter( apiVersion, originalVisitor, instrumentableContext.access, instrumentableContext.name, - instrumentableContext.descriptor -) { + instrumentableContext.descriptor, + ) { - private val sentryOkHttpEventListener = if (useSentryAndroidOkHttp) { - "io/sentry/android/okhttp/SentryOkHttpEventListener" + private val sentryOkHttpEventListener = + if (useSentryAndroidOkHttp) { + "io/sentry/android/okhttp/SentryOkHttpEventListener" } else { - "io/sentry/okhttp/SentryOkHttpEventListener" + "io/sentry/okhttp/SentryOkHttpEventListener" } - override fun onMethodEnter() { - super.onMethodEnter() - // Add the following call at the beginning of the constructor with the Builder parameter: - // builder.eventListener(new SentryOkHttpEventListener(builder.eventListenerFactory)); + override fun onMethodEnter() { + super.onMethodEnter() + // Add the following call at the beginning of the constructor with the Builder parameter: + // builder.eventListener(new SentryOkHttpEventListener(builder.eventListenerFactory)); - // OkHttpClient.Builder is the parameter, retrieved here - visitVarInsn(Opcodes.ALOAD, 1) + // OkHttpClient.Builder is the parameter, retrieved here + visitVarInsn(Opcodes.ALOAD, 1) - // Let's declare the SentryOkHttpEventListener variable - visitTypeInsn(Opcodes.NEW, sentryOkHttpEventListener) + // Let's declare the SentryOkHttpEventListener variable + visitTypeInsn(Opcodes.NEW, sentryOkHttpEventListener) - // The SentryOkHttpEventListener constructor, which is called later, will consume the - // element without pushing anything back to the stack ( returns void). - // Dup will give a reference to the SentryOkHttpEventListener after the constructor call - visitInsn(Opcodes.DUP) + // The SentryOkHttpEventListener constructor, which is called later, will consume the + // element without pushing anything back to the stack ( returns void). + // Dup will give a reference to the SentryOkHttpEventListener after the constructor call + visitInsn(Opcodes.DUP) - // Puts parameter OkHttpClient.Builder on top of the stack. - visitVarInsn(Opcodes.ALOAD, 1) + // Puts parameter OkHttpClient.Builder on top of the stack. + visitVarInsn(Opcodes.ALOAD, 1) - // Read the "eventListenerFactory" field from OkHttpClient.Builder - // Implementation changed in v4 (including 4.0.0-RCx) - if (okHttpVersion.major >= 4) { - // Call the getter - visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - "okhttp3/OkHttpClient\$Builder", - "getEventListenerFactory\$okhttp", - "()Lokhttp3/EventListener\$Factory;", - false - ) - } else { - // Read the field - visitFieldInsn( - Opcodes.GETFIELD, - "okhttp3/OkHttpClient\$Builder", - "eventListenerFactory", - "Lokhttp3/EventListener\$Factory;" - ) - } + // Read the "eventListenerFactory" field from OkHttpClient.Builder + // Implementation changed in v4 (including 4.0.0-RCx) + if (okHttpVersion.major >= 4) { + // Call the getter + visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "okhttp3/OkHttpClient\$Builder", + "getEventListenerFactory\$okhttp", + "()Lokhttp3/EventListener\$Factory;", + false, + ) + } else { + // Read the field + visitFieldInsn( + Opcodes.GETFIELD, + "okhttp3/OkHttpClient\$Builder", + "eventListenerFactory", + "Lokhttp3/EventListener\$Factory;", + ) + } - // Call SentryOkHttpEventListener constructor passing "eventListenerFactory" as parameter - visitMethodInsn( - Opcodes.INVOKESPECIAL, - sentryOkHttpEventListener, - "", - "(Lokhttp3/EventListener\$Factory;)V", - false - ) + // Call SentryOkHttpEventListener constructor passing "eventListenerFactory" as parameter + visitMethodInsn( + Opcodes.INVOKESPECIAL, + sentryOkHttpEventListener, + "", + "(Lokhttp3/EventListener\$Factory;)V", + false, + ) - // Call "eventListener" function of OkHttpClient.Builder passing SentryOkHttpEventListener - visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - "okhttp3/OkHttpClient\$Builder", - "eventListener", - "(Lokhttp3/EventListener;)Lokhttp3/OkHttpClient\$Builder;", - false - ) - } + // Call "eventListener" function of OkHttpClient.Builder passing SentryOkHttpEventListener + visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "okhttp3/OkHttpClient\$Builder", + "eventListener", + "(Lokhttp3/EventListener;)Lokhttp3/OkHttpClient\$Builder;", + false, + ) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/ResponseWithInterceptorChainMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/ResponseWithInterceptorChainMethodVisitor.kt index 854c7ae5..5b3728e1 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/ResponseWithInterceptorChainMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/okhttp/visitor/ResponseWithInterceptorChainMethodVisitor.kt @@ -8,95 +8,95 @@ import org.objectweb.asm.commons.GeneratorAdapter import org.objectweb.asm.commons.Method class ResponseWithInterceptorChainMethodVisitor( - private val useSentryAndroidOkHttp: Boolean, - api: Int, - private val originalVisitor: MethodVisitor, - access: Int, - name: String?, - descriptor: String? + private val useSentryAndroidOkHttp: Boolean, + api: Int, + private val originalVisitor: MethodVisitor, + access: Int, + name: String?, + descriptor: String?, ) : GeneratorAdapter(api, originalVisitor, access, name, descriptor) { - private var shouldInstrument = false + private var shouldInstrument = false - private val sentryOkInterceptor = if (useSentryAndroidOkHttp) { - Types.SENTRY_ANDROID_OKHTTP_INTERCEPTOR + private val sentryOkInterceptor = + if (useSentryAndroidOkHttp) { + Types.SENTRY_ANDROID_OKHTTP_INTERCEPTOR } else { - Types.SENTRY_OKHTTP_INTERCEPTOR + Types.SENTRY_OKHTTP_INTERCEPTOR } - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean, + ) { + if ( + opcode == Opcodes.INVOKEVIRTUAL && owner == "okhttp3/OkHttpClient" && name == "interceptors" ) { - if (opcode == Opcodes.INVOKEVIRTUAL && - owner == "okhttp3/OkHttpClient" && - name == "interceptors" - ) { - shouldInstrument = true - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + shouldInstrument = true } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } - override fun visitInsn(opcode: Int) { - super.visitInsn(opcode) - if (opcode == Opcodes.POP && shouldInstrument) { - visitAddSentryInterceptor() - shouldInstrument = false - } + override fun visitInsn(opcode: Int) { + super.visitInsn(opcode) + if (opcode == Opcodes.POP && shouldInstrument) { + visitAddSentryInterceptor() + shouldInstrument = false } + } - /* - Roughly constructing this, but in Java: + /* + Roughly constructing this, but in Java: - if (interceptors.find { it is SentryOkHttpInterceptor } != null) { - interceptors += SentryOkHttpInterceptor() - } - */ - private fun MethodVisitor.visitAddSentryInterceptor() { - originalVisitor.visitVarInsn(Opcodes.ALOAD, 1) // interceptors list + if (interceptors.find { it is SentryOkHttpInterceptor } != null) { + interceptors += SentryOkHttpInterceptor() + } + */ + private fun MethodVisitor.visitAddSentryInterceptor() { + originalVisitor.visitVarInsn(Opcodes.ALOAD, 1) // interceptors list - checkCast(Types.ITERABLE) - invokeInterface(Types.ITERABLE, Method.getMethod("java.util.Iterator iterator ()")) - val iteratorIndex = newLocal(Types.ITERATOR) - storeLocal(iteratorIndex) + checkCast(Types.ITERABLE) + invokeInterface(Types.ITERABLE, Method.getMethod("java.util.Iterator iterator ()")) + val iteratorIndex = newLocal(Types.ITERATOR) + storeLocal(iteratorIndex) - val whileLabel = Label() - val endWhileLabel = Label() - visitLabel(whileLabel) - loadLocal(iteratorIndex) - invokeInterface(Types.ITERATOR, Method.getMethod("boolean hasNext ()")) - ifZCmp(EQ, endWhileLabel) - loadLocal(iteratorIndex) - invokeInterface(Types.ITERATOR, Method.getMethod("Object next ()")) + val whileLabel = Label() + val endWhileLabel = Label() + visitLabel(whileLabel) + loadLocal(iteratorIndex) + invokeInterface(Types.ITERATOR, Method.getMethod("boolean hasNext ()")) + ifZCmp(EQ, endWhileLabel) + loadLocal(iteratorIndex) + invokeInterface(Types.ITERATOR, Method.getMethod("Object next ()")) - val interceptorIndex = newLocal(Types.OBJECT) - storeLocal(interceptorIndex) - loadLocal(interceptorIndex) - checkCast(Types.OKHTTP_INTERCEPTOR) - instanceOf(sentryOkInterceptor) - ifZCmp(EQ, whileLabel) - loadLocal(interceptorIndex) - val ifLabel = Label() - goTo(ifLabel) + val interceptorIndex = newLocal(Types.OBJECT) + storeLocal(interceptorIndex) + loadLocal(interceptorIndex) + checkCast(Types.OKHTTP_INTERCEPTOR) + instanceOf(sentryOkInterceptor) + ifZCmp(EQ, whileLabel) + loadLocal(interceptorIndex) + val ifLabel = Label() + goTo(ifLabel) - visitLabel(endWhileLabel) - originalVisitor.visitInsn(Opcodes.ACONST_NULL) - visitLabel(ifLabel) - val originalMethodLabel = Label() - ifNonNull(originalMethodLabel) + visitLabel(endWhileLabel) + originalVisitor.visitInsn(Opcodes.ACONST_NULL) + visitLabel(ifLabel) + val originalMethodLabel = Label() + ifNonNull(originalMethodLabel) - originalVisitor.visitVarInsn(Opcodes.ALOAD, 1) - checkCast(Types.COLLECTION) - newInstance(sentryOkInterceptor) - dup() - val sentryOkHttpCtor = Method.getMethod("void ()") - invokeConstructor(sentryOkInterceptor, sentryOkHttpCtor) - val addInterceptor = Method.getMethod("boolean add (Object)") - invokeInterface(Types.COLLECTION, addInterceptor) - pop() - visitLabel(originalMethodLabel) - } + originalVisitor.visitVarInsn(Opcodes.ALOAD, 1) + checkCast(Types.COLLECTION) + newInstance(sentryOkInterceptor) + dup() + val sentryOkHttpCtor = Method.getMethod("void ()") + invokeConstructor(sentryOkInterceptor, sentryOkHttpCtor) + val addInterceptor = Method.getMethod("boolean add (Object)") + invokeInterface(Types.COLLECTION, addInterceptor) + pop() + visitLabel(originalMethodLabel) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/remap/RemappingInstrumentable.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/remap/RemappingInstrumentable.kt index 56c1b2b4..766d67ee 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/remap/RemappingInstrumentable.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/remap/RemappingInstrumentable.kt @@ -11,20 +11,20 @@ import org.objectweb.asm.commons.ClassRemapper import org.objectweb.asm.commons.SimpleRemapper class RemappingInstrumentable : ClassInstrumentable { - companion object { - private val mapping = mapOf( - "java/io/FileReader" to "io/sentry/instrumentation/file/SentryFileReader", - "java/io/FileWriter" to "io/sentry/instrumentation/file/SentryFileWriter" - ) - } + companion object { + private val mapping = + mapOf( + "java/io/FileReader" to "io/sentry/instrumentation/file/SentryFileReader", + "java/io/FileWriter" to "io/sentry/instrumentation/file/SentryFileWriter", + ) + } - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = - ClassRemapper(originalVisitor, SimpleRemapper(mapping)) + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = ClassRemapper(originalVisitor, SimpleRemapper(mapping)) - override fun isInstrumentable(data: ClassContext): Boolean = !data.isSentryClass() + override fun isInstrumentable(data: ClassContext): Boolean = !data.isSentryClass() } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/AnalyzingVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/AnalyzingVisitor.kt index 7dd7c7f8..8ddd26e2 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/AnalyzingVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/AnalyzingVisitor.kt @@ -5,12 +5,12 @@ import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode class AnalyzingVisitor( - apiVersion: Int, - private val nextVisitor: (List) -> ClassVisitor + apiVersion: Int, + private val nextVisitor: (List) -> ClassVisitor, ) : ClassNode(apiVersion) { - override fun visitEnd() { - super.visitEnd() - accept(nextVisitor(methods)) - } + override fun visitEnd() { + super.visitEnd() + accept(nextVisitor(methods)) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitor.kt index 4248ce67..4c8ae70f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitor.kt @@ -7,30 +7,31 @@ import org.objectweb.asm.MethodVisitor import org.slf4j.Logger interface ExceptionHandler { - fun handle(exception: Throwable) + fun handle(exception: Throwable) } class CatchingMethodVisitor( - apiVersion: Int, - prevVisitor: MethodVisitor, - private val className: String, - private val methodContext: MethodContext, - private val exceptionHandler: ExceptionHandler? = null, - private val logger: Logger = SentryPlugin.logger + apiVersion: Int, + prevVisitor: MethodVisitor, + private val className: String, + private val methodContext: MethodContext, + private val exceptionHandler: ExceptionHandler? = null, + private val logger: Logger = SentryPlugin.logger, ) : MethodVisitor(apiVersion, prevVisitor) { - override fun visitMaxs(maxStack: Int, maxLocals: Int) { - try { - super.visitMaxs(maxStack, maxLocals) - } catch (e: Throwable) { - exceptionHandler?.handle(e) - logger.error(e) { - """ + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + try { + super.visitMaxs(maxStack, maxLocals) + } catch (e: Throwable) { + exceptionHandler?.handle(e) + logger.error(e) { + """ Error while instrumenting $className.${methodContext.name} ${methodContext.descriptor}. Please report this issue at https://github.com/getsentry/sentry-android-gradle-plugin/issues - """.trimIndent() - } - throw e - } + """ + .trimIndent() + } + throw e } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt index 35b3921d..4056b8f2 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt @@ -10,91 +10,94 @@ import org.objectweb.asm.ClassWriter * of the [ClassVisitor]. */ internal fun ClassVisitor.findClassWriter(): ClassWriter? { - var classWriter: ClassVisitor = this - while (!ClassWriter::class.java.isAssignableFrom(classWriter::class.java)) { - val cvField: Field = try { - classWriter::class.java.allFields.find { it.name == "cv" } ?: return null - } catch (e: Throwable) { - return null - } - cvField.isAccessible = true - classWriter = (cvField.get(classWriter) as? ClassVisitor) ?: return null - } - return classWriter as ClassWriter + var classWriter: ClassVisitor = this + while (!ClassWriter::class.java.isAssignableFrom(classWriter::class.java)) { + val cvField: Field = + try { + classWriter::class.java.allFields.find { it.name == "cv" } ?: return null + } catch (e: Throwable) { + return null + } + cvField.isAccessible = true + classWriter = (cvField.get(classWriter) as? ClassVisitor) ?: return null + } + return classWriter as ClassWriter } -/** - * Looks up for [ClassReader] of the [ClassWriter] through intermediate SymbolTable field. - */ +/** Looks up for [ClassReader] of the [ClassWriter] through intermediate SymbolTable field. */ internal fun ClassWriter.findClassReader(): ClassReader? { - val clazz: Class = this::class.java - val symbolTableField: Field = try { - clazz.allFields.find { it.name == "symbolTable" } ?: return null + val clazz: Class = this::class.java + val symbolTableField: Field = + try { + clazz.allFields.find { it.name == "symbolTable" } ?: return null } catch (e: Throwable) { - return null + return null } - symbolTableField.isAccessible = true - val symbolTable = symbolTableField.get(this) - val classReaderField: Field = try { - symbolTable::class.java.getDeclaredField("sourceClassReader") + symbolTableField.isAccessible = true + val symbolTable = symbolTableField.get(this) + val classReaderField: Field = + try { + symbolTable::class.java.getDeclaredField("sourceClassReader") } catch (e: Throwable) { - return null + return null } - classReaderField.isAccessible = true - return (classReaderField.get(symbolTable) as? ClassReader) + classReaderField.isAccessible = true + return (classReaderField.get(symbolTable) as? ClassReader) } internal fun ClassReader.getSimpleClassName(): String { - return className.substringAfterLast("/") + return className.substringAfterLast("/") } -/** - * Looks at the constant pool entries and searches for R8 markers - */ +/** Looks at the constant pool entries and searches for R8 markers */ internal fun ClassReader.isMinifiedClass(): Boolean { - return isR8Minified(this) || classNameLooksMinified(this.getSimpleClassName(), this.className) + return isR8Minified(this) || classNameLooksMinified(this.getSimpleClassName(), this.className) } private fun isR8Minified(classReader: ClassReader): Boolean { - val charBuffer = CharArray(classReader.maxStringLength) - // R8 marker is usually in the first 3-5 entries, so we limit it at 10 to speed it up - // (constant pool size can be huge otherwise) - val poolSize = minOf(10, classReader.itemCount) - for (i in 1 until poolSize) { - try { - val constantPoolEntry = classReader.readConst(i, charBuffer) - if (constantPoolEntry is String && "~~R8" in constantPoolEntry) { - // ~~R8 is a marker in the class' constant pool, which r8 itself is looking at when - // parsing a .class file. See here -> https://r8.googlesource.com/r8/+/refs/heads/main/src/main/java/com/android/tools/r8/dex/Marker.java#53 - return true - } - } catch (e: Throwable) { - // we ignore exceptions here, because some constant pool entries are nulls and the - // readConst method throws IllegalArgumentException when trying to read those - } + val charBuffer = CharArray(classReader.maxStringLength) + // R8 marker is usually in the first 3-5 entries, so we limit it at 10 to speed it up + // (constant pool size can be huge otherwise) + val poolSize = minOf(10, classReader.itemCount) + for (i in 1 until poolSize) { + try { + val constantPoolEntry = classReader.readConst(i, charBuffer) + if (constantPoolEntry is String && "~~R8" in constantPoolEntry) { + // ~~R8 is a marker in the class' constant pool, which r8 itself is looking at when + // parsing a .class file. See here -> + // https://r8.googlesource.com/r8/+/refs/heads/main/src/main/java/com/android/tools/r8/dex/Marker.java#53 + return true + } + } catch (e: Throwable) { + // we ignore exceptions here, because some constant pool entries are nulls and the + // readConst method throws IllegalArgumentException when trying to read those } - return false + } + return false } /** - * See https://github.com/getsentry/sentry-android-gradle-plugin/issues/360 - * and https://github.com/getsentry/sentry-android-gradle-plugin/issues/359#issuecomment-1193782500 + * See https://github.com/getsentry/sentry-android-gradle-plugin/issues/360 and + * https://github.com/getsentry/sentry-android-gradle-plugin/issues/359#issuecomment-1193782500 */ /* ktlint-disable max-line-length */ -private val MINIFIED_CLASSNAME_REGEX = """^(((([a-zA-z])\4{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\7{1,})?)|([a-zA-Z]([0-9])?))(${'\\'}${'$'}((((\w)\14{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\17{1,})?)|(\w([0-9])?)))*${'$'}""".toRegex() +private val MINIFIED_CLASSNAME_REGEX = + """^(((([a-zA-z])\4{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\7{1,})?)|([a-zA-Z]([0-9])?))(${'\\'}${'$'}((((\w)\14{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\17{1,})?)|(\w([0-9])?)))*${'$'}""" + .toRegex() /** - * See https://github.com/getsentry/sentry/blob/c943de2afc785083554e7fdfb10c67d0c0de0f98/static/app/components/events/eventEntries.tsx#L57-L58 + * See + * https://github.com/getsentry/sentry/blob/c943de2afc785083554e7fdfb10c67d0c0de0f98/static/app/components/events/eventEntries.tsx#L57-L58 */ private val MINIFIED_CLASSNAME_SENTRY_REGEX = - """^(([\w\${'$'}]\/[\w\${'$'}]{1,2})|([\w\${'$'}]{2}\/[\w\${'$'}]\/[\w\${'$'}]))(\/|${'$'})""".toRegex() + """^(([\w\${'$'}]\/[\w\${'$'}]{1,2})|([\w\${'$'}]{2}\/[\w\${'$'}]\/[\w\${'$'}]))(\/|${'$'})""" + .toRegex() + /* ktlint-enable max-line-length */ fun classNameLooksMinified(simpleClassName: String, fullClassName: String): Boolean { - return simpleClassName.isNotEmpty() && - simpleClassName[0].isLowerCase() && - ( - MINIFIED_CLASSNAME_REGEX.matches(simpleClassName) || - MINIFIED_CLASSNAME_SENTRY_REGEX.matches(fullClassName) - ) + return simpleClassName.isNotEmpty() && + simpleClassName[0].isLowerCase() && + (MINIFIED_CLASSNAME_REGEX.matches(simpleClassName) || + MINIFIED_CLASSNAME_SENTRY_REGEX.matches(fullClassName)) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FieldUtils.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FieldUtils.kt index ba533e2c..66183312 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FieldUtils.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FieldUtils.kt @@ -21,17 +21,15 @@ package io.sentry.android.gradle.instrumentation.util import java.lang.reflect.Field -/** - * Gets all fields of the given class and its parents (if any). - */ +/** Gets all fields of the given class and its parents (if any). */ internal val Class<*>.allFields: List - get() { - val allFields = mutableListOf() - var currentClass: Class<*>? = this - while (currentClass != null) { - val declaredFields = currentClass.declaredFields - allFields += declaredFields - currentClass = currentClass.superclass - } - return allFields + get() { + val allFields = mutableListOf() + var currentClass: Class<*>? = this + while (currentClass != null) { + val declaredFields = currentClass.declaredFields + allFields += declaredFields + currentClass = currentClass.superclass } + return allFields + } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifier.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifier.kt index 079a1a19..c6981d10 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifier.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifier.kt @@ -5,38 +5,35 @@ import java.io.FileOutputStream import java.io.PrintWriter import org.objectweb.asm.util.Textifier -class FileLogTextifier( - apiVersion: Int, - log: File, - methodName: String?, - methodDescriptor: String? -) : Textifier(apiVersion), ExceptionHandler { +class FileLogTextifier(apiVersion: Int, log: File, methodName: String?, methodDescriptor: String?) : + Textifier(apiVersion), ExceptionHandler { - private var hasThrown = false + private var hasThrown = false - private val fileOutputStream = FileOutputStream(log, true).apply { - write("function $methodName $methodDescriptor".toByteArray()) - write("\n".toByteArray()) + private val fileOutputStream = + FileOutputStream(log, true).apply { + write("function $methodName $methodDescriptor".toByteArray()) + write("\n".toByteArray()) } - override fun visitMethodEnd() { - if (!hasThrown) { - flushPrinter() - } + override fun visitMethodEnd() { + if (!hasThrown) { + flushPrinter() } + } - override fun handle(exception: Throwable) { - hasThrown = true - flushPrinter() - } + override fun handle(exception: Throwable) { + hasThrown = true + flushPrinter() + } - private fun flushPrinter() { - val printWriter = PrintWriter(fileOutputStream) - print(printWriter) - printWriter.flush() - // ASM textifier uses plain "\n" chars, so do we. As it's only for debug and dev purpose - // it doesn't matter to the end user - fileOutputStream.write("\n".toByteArray()) - fileOutputStream.close() - } + private fun flushPrinter() { + val printWriter = PrintWriter(fileOutputStream) + print(printWriter) + printWriter.flush() + // ASM textifier uses plain "\n" chars, so do we. As it's only for debug and dev purpose + // it doesn't matter to the end user + fileOutputStream.write("\n".toByteArray()) + fileOutputStream.close() + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtils.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtils.kt index 64bab909..81b25082 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtils.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtils.kt @@ -5,9 +5,9 @@ package io.sentry.android.gradle.instrumentation.util import com.android.build.api.instrumentation.ClassContext fun ClassContext.isSentryClass(): Boolean = - when { - currentClassData.className.startsWith("io.sentry") && - !currentClassData.className.startsWith("io.sentry.samples") && - !currentClassData.className.startsWith("io.sentry.mobile") -> true - else -> false - } + when { + currentClassData.className.startsWith("io.sentry") && + !currentClassData.className.startsWith("io.sentry.samples") && + !currentClassData.className.startsWith("io.sentry.mobile") -> true + else -> false + } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/Types.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/Types.kt index c83c33e4..90ae41d9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/Types.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/Types.kt @@ -3,24 +3,23 @@ package io.sentry.android.gradle.instrumentation.util import org.objectweb.asm.Type object Types { - // COMMON - val OBJECT = Type.getType("Ljava/lang/Object;") - val STRING = Type.getType("Ljava/lang/String;") - val INT = Type.INT_TYPE - val EXCEPTION = Type.getType("Ljava/lang/Exception;") - val ITERABLE = Type.getType("Ljava/lang/Iterable;") - val ITERATOR = Type.getType("Ljava/util/Iterator;") - val COLLECTION = Type.getType("Ljava/util/Collection;") + // COMMON + val OBJECT = Type.getType("Ljava/lang/Object;") + val STRING = Type.getType("Ljava/lang/String;") + val INT = Type.INT_TYPE + val EXCEPTION = Type.getType("Ljava/lang/Exception;") + val ITERABLE = Type.getType("Ljava/lang/Iterable;") + val ITERATOR = Type.getType("Ljava/util/Iterator;") + val COLLECTION = Type.getType("Ljava/util/Collection;") - // DB - val SQL_EXCEPTION = Type.getType("Landroid/database/SQLException;") - val CURSOR = Type.getType("Landroid/database/Cursor;") - val SPAN = Type.getType("Lio/sentry/Span;") + // DB + val SQL_EXCEPTION = Type.getType("Landroid/database/SQLException;") + val CURSOR = Type.getType("Landroid/database/Cursor;") + val SPAN = Type.getType("Lio/sentry/Span;") - // OKHTTP - val OKHTTP_INTERCEPTOR = Type.getType("Lokhttp3/Interceptor;") - val SENTRY_ANDROID_OKHTTP_INTERCEPTOR = - Type.getType("Lio/sentry/android/okhttp/SentryOkHttpInterceptor;") - val SENTRY_OKHTTP_INTERCEPTOR = - Type.getType("Lio/sentry/okhttp/SentryOkHttpInterceptor;") + // OKHTTP + val OKHTTP_INTERCEPTOR = Type.getType("Lokhttp3/Interceptor;") + val SENTRY_ANDROID_OKHTTP_INTERCEPTOR = + Type.getType("Lio/sentry/android/okhttp/SentryOkHttpInterceptor;") + val SENTRY_OKHTTP_INTERCEPTOR = Type.getType("Lio/sentry/okhttp/SentryOkHttpInterceptor;") } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt index 23fc4118..c2fbd303 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt @@ -1,115 +1,84 @@ // ktlint-disable filename package io.sentry.android.gradle.instrumentation.wrap -data class Replacement( - val owner: String, - val name: String, - val descriptor: String -) { - object FileInputStream { - val STRING = Replacement( - "java/io/FileInputStream", - "", - "(Ljava/lang/String;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", - "create", - "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;" +data class Replacement(val owner: String, val name: String, val descriptor: String) { + object FileInputStream { + val STRING = + Replacement("java/io/FileInputStream", "", "(Ljava/lang/String;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", + "create", + "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;", ) - val FILE = Replacement( - "java/io/FileInputStream", - "", - "(Ljava/io/File;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", - "create", - "(Ljava/io/FileInputStream;Ljava/io/File;)Ljava/io/FileInputStream;" + val FILE = + Replacement("java/io/FileInputStream", "", "(Ljava/io/File;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", + "create", + "(Ljava/io/FileInputStream;Ljava/io/File;)Ljava/io/FileInputStream;", ) - val FILE_DESCRIPTOR = - Replacement( - "java/io/FileInputStream", - "", - "(Ljava/io/FileDescriptor;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", - "create", - "(Ljava/io/FileInputStream;Ljava/io/FileDescriptor;)Ljava/io/FileInputStream;" - ) - } + val FILE_DESCRIPTOR = + Replacement("java/io/FileInputStream", "", "(Ljava/io/FileDescriptor;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", + "create", + "(Ljava/io/FileInputStream;Ljava/io/FileDescriptor;)Ljava/io/FileInputStream;", + ) + } - object FileOutputStream { - val STRING = - Replacement( - "java/io/FileOutputStream", - "", - "(Ljava/lang/String;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream;" - ) - val STRING_BOOLEAN = - Replacement( - "java/io/FileOutputStream", - "", - "(Ljava/lang/String;Z)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/lang/String;Z)Ljava/io/FileOutputStream;" - ) - val FILE = Replacement( - "java/io/FileOutputStream", - "", - "(Ljava/io/File;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/io/File;)Ljava/io/FileOutputStream;" + object FileOutputStream { + val STRING = + Replacement("java/io/FileOutputStream", "", "(Ljava/lang/String;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream;", + ) + val STRING_BOOLEAN = + Replacement("java/io/FileOutputStream", "", "(Ljava/lang/String;Z)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/lang/String;Z)Ljava/io/FileOutputStream;", + ) + val FILE = + Replacement("java/io/FileOutputStream", "", "(Ljava/io/File;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/io/File;)Ljava/io/FileOutputStream;", + ) + val FILE_BOOLEAN = + Replacement("java/io/FileOutputStream", "", "(Ljava/io/File;Z)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/io/File;Z)Ljava/io/FileOutputStream;", ) - val FILE_BOOLEAN = - Replacement( - "java/io/FileOutputStream", - "", - "(Ljava/io/File;Z)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/io/File;Z)Ljava/io/FileOutputStream;" - ) - val FILE_DESCRIPTOR = - Replacement( - "java/io/FileOutputStream", - "", - "(Ljava/io/FileDescriptor;)V" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/io/FileDescriptor;)Ljava/io/FileOutputStream;" - ) - } + val FILE_DESCRIPTOR = + Replacement("java/io/FileOutputStream", "", "(Ljava/io/FileDescriptor;)V") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/io/FileDescriptor;)Ljava/io/FileOutputStream;", + ) + } - object Context { - val OPEN_FILE_INPUT = - Replacement( - "", - "openFileInput", - "(Ljava/lang/String;)Ljava/io/FileInputStream;" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", - "create", - "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;" - ) + object Context { + val OPEN_FILE_INPUT = + Replacement("", "openFileInput", "(Ljava/lang/String;)Ljava/io/FileInputStream;") to + Replacement( + "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", + "create", + "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;", + ) - val OPEN_FILE_OUTPUT = - Replacement( - "", - "openFileOutput", - "(Ljava/lang/String;I)Ljava/io/FileOutputStream;" - ) to Replacement( - "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", - "create", - "(Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream;" - ) - } + val OPEN_FILE_OUTPUT = + Replacement("", "openFileOutput", "(Ljava/lang/String;I)Ljava/io/FileOutputStream;") to + Replacement( + "io/sentry/instrumentation/file/SentryFileOutputStream${'$'}Factory", + "create", + "(Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream;", + ) + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/WrappingInstrumentable.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/WrappingInstrumentable.kt index 43f3019b..37847d11 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/WrappingInstrumentable.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/WrappingInstrumentable.kt @@ -18,77 +18,71 @@ import org.objectweb.asm.tree.MethodNode class WrappingInstrumentable : ClassInstrumentable { - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor { - val simpleClassName = - instrumentableContext.currentClassData.className.substringAfterLast('.') - return AnalyzingVisitor( - apiVersion = apiVersion, - nextVisitor = { methods -> - CommonClassVisitor( - apiVersion = apiVersion, - classVisitor = originalVisitor, - className = simpleClassName, - methodInstrumentables = methods.map { - Wrap(instrumentableContext.currentClassData, it) - }, - parameters = parameters - ) - } + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + val simpleClassName = instrumentableContext.currentClassData.className.substringAfterLast('.') + return AnalyzingVisitor( + apiVersion = apiVersion, + nextVisitor = { methods -> + CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = simpleClassName, + methodInstrumentables = methods.map { Wrap(instrumentableContext.currentClassData, it) }, + parameters = parameters, ) - } + }, + ) + } - override fun isInstrumentable(data: ClassContext): Boolean = !data.isSentryClass() + override fun isInstrumentable(data: ClassContext): Boolean = !data.isSentryClass() } -class Wrap( - private val classContext: ClassData, - private val methodNode: MethodNode -) : MethodInstrumentable { +class Wrap(private val classContext: ClassData, private val methodNode: MethodNode) : + MethodInstrumentable { - override val fqName: String = methodNode.name + override val fqName: String = methodNode.name - companion object { - private val replacements = mapOf( - // FileInputStream to SentryFileInputStream - Replacement.FileInputStream.STRING, - Replacement.FileInputStream.FILE, - Replacement.FileInputStream.FILE_DESCRIPTOR, - // FileOutputStream to SentryFileOutputStream - Replacement.FileOutputStream.STRING, - Replacement.FileOutputStream.STRING_BOOLEAN, - Replacement.FileOutputStream.FILE, - Replacement.FileOutputStream.FILE_BOOLEAN, - Replacement.FileOutputStream.FILE_DESCRIPTOR - // TODO: enable, once https://github.com/getsentry/sentry-java/issues/1842 is resolved - // Context.openFileInput to SentryFileInputStream -// Replacement.Context.OPEN_FILE_INPUT, - // Context.openFileOutput to SentryFileOutputStream -// Replacement.Context.OPEN_FILE_OUTPUT - ) - } + companion object { + private val replacements = + mapOf( + // FileInputStream to SentryFileInputStream + Replacement.FileInputStream.STRING, + Replacement.FileInputStream.FILE, + Replacement.FileInputStream.FILE_DESCRIPTOR, + // FileOutputStream to SentryFileOutputStream + Replacement.FileOutputStream.STRING, + Replacement.FileOutputStream.STRING_BOOLEAN, + Replacement.FileOutputStream.FILE, + Replacement.FileOutputStream.FILE_BOOLEAN, + Replacement.FileOutputStream.FILE_DESCRIPTOR, + // TODO: enable, once https://github.com/getsentry/sentry-java/issues/1842 is resolved + // Context.openFileInput to SentryFileInputStream + // Replacement.Context.OPEN_FILE_INPUT, + // Context.openFileOutput to SentryFileOutputStream + // Replacement.Context.OPEN_FILE_OUTPUT + ) + } - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = - WrappingVisitor( - api = apiVersion, - originalVisitor = originalVisitor, - firstPassVisitor = methodNode, - classContext = classContext, - context = instrumentableContext, - replacements = replacements - ) + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = + WrappingVisitor( + api = apiVersion, + originalVisitor = originalVisitor, + firstPassVisitor = methodNode, + classContext = classContext, + context = instrumentableContext, + replacements = replacements, + ) - override fun isInstrumentable(data: MethodContext): Boolean = - data.name == fqName && - data.descriptor == methodNode.desc && - data.access == methodNode.access + override fun isInstrumentable(data: MethodContext): Boolean = + data.name == fqName && data.descriptor == methodNode.desc && data.access == methodNode.access } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitor.kt index 206595bf..e231836b 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitor.kt @@ -19,177 +19,171 @@ import org.objectweb.asm.tree.VarInsnNode import org.slf4j.Logger class WrappingVisitor( - api: Int, - originalVisitor: MethodVisitor, - private val firstPassVisitor: MethodNode, - private val classContext: ClassData, - private val context: MethodContext, - private val replacements: Map, - private val logger: Logger = SentryPlugin.logger -) : GeneratorAdapter( - api, - originalVisitor, - context.access, - context.name, - context.descriptor -) { - - private val targetTypes = replacements.keys - .toSet() - .map { it.owner } - - private val newInsnsForTargets by lazy { - // filter all NEW insns for the target types that we are going to wrap - val insns = firstPassVisitor.instructions - .toArray() - .filter { it is TypeInsnNode && it.opcode == Opcodes.NEW && it.desc in targetTypes } - LinkedList(insns) + api: Int, + originalVisitor: MethodVisitor, + private val firstPassVisitor: MethodNode, + private val classContext: ClassData, + private val context: MethodContext, + private val replacements: Map, + private val logger: Logger = SentryPlugin.logger, +) : GeneratorAdapter(api, originalVisitor, context.access, context.name, context.descriptor) { + + private val targetTypes = replacements.keys.toSet().map { it.owner } + + private val newInsnsForTargets by lazy { + // filter all NEW insns for the target types that we are going to wrap + val insns = + firstPassVisitor.instructions.toArray().filter { + it is TypeInsnNode && it.opcode == Opcodes.NEW && it.desc in targetTypes + } + LinkedList(insns) + } + private val className = classContext.className.replace('.', '/') + + private var manuallyDuped = false + private var varIndex = -1 + + override fun visitTypeInsn(opcode: Int, type: String?) { + super.visitTypeInsn(opcode, type) + if (opcode == Opcodes.NEW && type in targetTypes) { + val nextInsn = newInsnsForTargets.poll()?.next ?: return + // in case the next insn after NEW is not a DUP, we inject our own DUP and flip a flag + // to later store our wrapping instance with the same index as the original instance + val isNextInsnDup = (nextInsn as? InsnNode)?.opcode == Opcodes.DUP + // in case the next insn after NEW is DUP, but is followed by ASTORE, we do the same + // thing as above. This case cannot be written by a developer, but rather is produced + // by a code obfuscator/compiler, because the usage of uninitialized instance + // is prohibited by the java verifier (NEW should be followed by INVOKESPECIAL before + // using it, which is not the case with ASTORE) + val isDupFollowedByStore = + isNextInsnDup && (nextInsn.next as? VarInsnNode)?.opcode == Opcodes.ASTORE + if (!isNextInsnDup || isDupFollowedByStore) { + dup() + manuallyDuped = true + } } - private val className = classContext.className.replace('.', '/') - - private var manuallyDuped = false - private var varIndex = -1 - - override fun visitTypeInsn(opcode: Int, type: String?) { - super.visitTypeInsn(opcode, type) - if (opcode == Opcodes.NEW && type in targetTypes) { - val nextInsn = newInsnsForTargets.poll()?.next ?: return - // in case the next insn after NEW is not a DUP, we inject our own DUP and flip a flag - // to later store our wrapping instance with the same index as the original instance - val isNextInsnDup = (nextInsn as? InsnNode)?.opcode == Opcodes.DUP - // in case the next insn after NEW is DUP, but is followed by ASTORE, we do the same - // thing as above. This case cannot be written by a developer, but rather is produced - // by a code obfuscator/compiler, because the usage of uninitialized instance - // is prohibited by the java verifier (NEW should be followed by INVOKESPECIAL before - // using it, which is not the case with ASTORE) - val isDupFollowedByStore = - isNextInsnDup && (nextInsn.next as? VarInsnNode)?.opcode == Opcodes.ASTORE - if (!isNextInsnDup || isDupFollowedByStore) { - dup() - manuallyDuped = true - } - } + } + + override fun visitVarInsn(opcode: Int, `var`: Int) { + super.visitVarInsn(opcode, `var`) + // capture the variable index of the instrumented type to later store our wrapped type + // with the same index + if (opcode == Opcodes.ASTORE && manuallyDuped && varIndex == -1) { + varIndex = `var` } - - override fun visitVarInsn(opcode: Int, `var`: Int) { - super.visitVarInsn(opcode, `var`) - // capture the variable index of the instrumented type to later store our wrapped type - // with the same index - if (opcode == Opcodes.ASTORE && manuallyDuped && varIndex == -1) { - varIndex = `var` + } + + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + val methodSig = Replacement(owner, name, descriptor) + val replacement = + if (methodSig in replacements) { + replacements[methodSig] + } else { + // try to look up for a replacement without owner (as the owner sometimes can differ) + replacements[methodSig.copy(owner = "")] + } + when { + opcode == Opcodes.INVOKEDYNAMIC -> { + // we don't instrument invokedynamic, because it's just forwarding to a synthetic method + // which will be instrumented thanks to condition below + logger.info { + "INVOKEDYNAMIC skipped from instrumentation for" + + " ${className.prettyPrintClassName()}.${context.name}" } - } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + replacement != null -> { + val isSuperCallInOverride = + opcode == Opcodes.INVOKESPECIAL && + owner != className && + name == context.name && + descriptor == context.descriptor + + val isSuperCallInCtor = + opcode == Opcodes.INVOKESPECIAL && + name == "" && + classContext.superClasses.firstOrNull()?.fqName() == owner - override fun visitMethodInsn( - opcode: Int, - owner: String, - name: String, - descriptor: String, - isInterface: Boolean - ) { - val methodSig = Replacement(owner, name, descriptor) - val replacement = if (methodSig in replacements) { - replacements[methodSig] - } else { - // try to look up for a replacement without owner (as the owner sometimes can differ) - replacements[methodSig.copy(owner = "")] - } when { - opcode == Opcodes.INVOKEDYNAMIC -> { - // we don't instrument invokedynamic, because it's just forwarding to a synthetic method - // which will be instrumented thanks to condition below - logger.info { - "INVOKEDYNAMIC skipped from instrumentation for" + - " ${className.prettyPrintClassName()}.${context.name}" - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + isSuperCallInOverride -> { + // this will be instrumented on the calling side of the overriding class + logger.info { + "${className.prettyPrintClassName()} skipped from instrumentation " + + "in overridden method $name.$descriptor" } - replacement != null -> { - val isSuperCallInOverride = opcode == Opcodes.INVOKESPECIAL && - owner != className && - name == context.name && - descriptor == context.descriptor - - val isSuperCallInCtor = opcode == Opcodes.INVOKESPECIAL && - name == "" && - classContext.superClasses.firstOrNull()?.fqName() == owner - - when { - isSuperCallInOverride -> { - // this will be instrumented on the calling side of the overriding class - logger.info { - "${className.prettyPrintClassName()} skipped from instrumentation " + - "in overridden method $name.$descriptor" - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - } - isSuperCallInCtor -> { - // this has to be manually instrumented (e.g. by inheriting our runtime classes) - logger.info { - "${className.prettyPrintClassName()} skipped from instrumentation " + - "in constructor $name.$descriptor" - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - } - else -> { - logger.info { - "Wrapping $owner.$name with ${replacement.owner}.${replacement.name} " + - "in ${className.prettyPrintClassName()}.${context.name}" - } - visitWrapping(replacement, opcode, owner, name, descriptor, isInterface) - } - } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + isSuperCallInCtor -> { + // this has to be manually instrumented (e.g. by inheriting our runtime classes) + logger.info { + "${className.prettyPrintClassName()} skipped from instrumentation " + + "in constructor $name.$descriptor" } - else -> super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + else -> { + logger.info { + "Wrapping $owner.$name with ${replacement.owner}.${replacement.name} " + + "in ${className.prettyPrintClassName()}.${context.name}" + } + visitWrapping(replacement, opcode, owner, name, descriptor, isInterface) + } } + } + else -> super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + } + + private fun String.prettyPrintClassName() = replace('/', '.') + + private fun String.fqName() = replace('.', '/') + + private fun GeneratorAdapter.visitWrapping( + replacement: Replacement, + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + // create a new method to figure out the number of arguments + val originalMethod = Method(name, descriptor) + + // replicate arguments on stack, so we can later re-use them for our wrapping + val locals = IntArray(originalMethod.argumentTypes.size) + for (i in locals.size - 1 downTo 0) { + locals[i] = newLocal(originalMethod.argumentTypes[i]) + storeLocal(locals[i]) } - private fun String.prettyPrintClassName() = replace('/', '.') - - private fun String.fqName() = replace('.', '/') - - private fun GeneratorAdapter.visitWrapping( - replacement: Replacement, - opcode: Int, - owner: String, - name: String, - descriptor: String, - isInterface: Boolean - ) { - // create a new method to figure out the number of arguments - val originalMethod = Method(name, descriptor) - - // replicate arguments on stack, so we can later re-use them for our wrapping - val locals = IntArray(originalMethod.argumentTypes.size) - for (i in locals.size - 1 downTo 0) { - locals[i] = newLocal(originalMethod.argumentTypes[i]) - storeLocal(locals[i]) - } - - // load arguments from stack for the original method call - locals.forEach { - loadLocal(it) - } - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + // load arguments from stack for the original method call + locals.forEach { loadLocal(it) } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - // load arguments from stack for the wrapping method call - // only load as many as the new method requires (replacement method may have less arguments) - val newMethod = Method(replacement.name, replacement.descriptor) - for (i in 0 until newMethod.argumentTypes.size - 1) { - loadLocal(locals[i]) - } - // call wrapping (it's always a static method) - super.visitMethodInsn( - Opcodes.INVOKESTATIC, - replacement.owner, - replacement.name, - replacement.descriptor, - false - ) - if (manuallyDuped && varIndex >= 0) { - mv.visitVarInsn(Opcodes.ASTORE, varIndex) - varIndex = -1 - manuallyDuped = false - } + // load arguments from stack for the wrapping method call + // only load as many as the new method requires (replacement method may have less arguments) + val newMethod = Method(replacement.name, replacement.descriptor) + for (i in 0 until newMethod.argumentTypes.size - 1) { + loadLocal(locals[i]) + } + // call wrapping (it's always a static method) + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + replacement.owner, + replacement.name, + replacement.descriptor, + false, + ) + if (manuallyDuped && varIndex >= 0) { + mv.visitVarInsn(Opcodes.ASTORE, varIndex) + varIndex = -1 + manuallyDuped = false } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 75aaaa39..e952800e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -20,160 +20,141 @@ import org.gradle.tooling.events.FinishEvent import org.gradle.tooling.events.OperationCompletionListener abstract class SentryModulesService : - BuildService, - OperationCompletionListener { + BuildService, OperationCompletionListener { - @get:Synchronized - @set:Synchronized - var sentryModules: Map = emptyMap() + @get:Synchronized @set:Synchronized var sentryModules: Map = emptyMap() - @get:Synchronized - @set:Synchronized - var externalModules: Map = emptyMap() + @get:Synchronized + @set:Synchronized + var externalModules: Map = emptyMap() - fun retrieveEnabledInstrumentationFeatures(): Set { - val features = parameters.features.get() - .filter { isInstrumentationEnabled(it) } - .map { it.integrationName } - .toMutableSet() + fun retrieveEnabledInstrumentationFeatures(): Set { + val features = + parameters.features + .get() + .filter { isInstrumentationEnabled(it) } + .map { it.integrationName } + .toMutableSet() - if (isLogcatInstrEnabled()) { - features.add("LogcatInstrumentation") - } - - if (isAppStartInstrEnabled()) { - features.add("AppStartInstrumentation") - } - - if (parameters.sourceContextEnabled.getOrElse(false)) { - features.add("SourceContext") - } - - if (parameters.dexguardEnabled.getOrElse(false)) { - features.add("DexGuard") - } - - return features + if (isLogcatInstrEnabled()) { + features.add("LogcatInstrumentation") } - private fun isInstrumentationEnabled(feature: InstrumentationFeature): Boolean { - return when (feature) { - InstrumentationFeature.DATABASE -> - isOldDatabaseInstrEnabled() || isNewDatabaseInstrEnabled() - InstrumentationFeature.FILE_IO -> isFileIOInstrEnabled() - InstrumentationFeature.OKHTTP -> isOkHttpInstrEnabled() - InstrumentationFeature.COMPOSE -> isComposeInstrEnabled() - } + if (isAppStartInstrEnabled()) { + features.add("AppStartInstrumentation") } - fun isLogcatInstrEnabled(): Boolean = - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_CORE, - SentryVersions.VERSION_LOGCAT - ) && parameters.logcatEnabled.get() - - fun isNewDatabaseInstrEnabled(): Boolean = - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_SQLITE, - SentryVersions.VERSION_SQLITE - ) && parameters.features.get().contains(InstrumentationFeature.DATABASE) - - fun isOldDatabaseInstrEnabled(): Boolean = - !isNewDatabaseInstrEnabled() && - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_CORE, - SentryVersions.VERSION_PERFORMANCE - ) && parameters.features.get().contains(InstrumentationFeature.DATABASE) - - fun isFileIOInstrEnabled(): Boolean = - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_CORE, - SentryVersions.VERSION_FILE_IO - ) && parameters.features.get().contains(InstrumentationFeature.FILE_IO) - - fun isOkHttpListenerInstrEnabled(): Boolean { - val isSentryAndroidOkHttpListener = sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_OKHTTP, - SentryVersions.VERSION_ANDROID_OKHTTP_LISTENER - ) - val isSentryOkHttpListener = sentryModules.isAtLeast( - SentryModules.SENTRY_OKHTTP, - SentryVersions.VERSION_OKHTTP - ) - return (isSentryAndroidOkHttpListener || isSentryOkHttpListener) && - parameters.features.get().contains(InstrumentationFeature.OKHTTP) + if (parameters.sourceContextEnabled.getOrElse(false)) { + features.add("SourceContext") } - fun isOkHttpInstrEnabled(): Boolean { - val isSentryAndroidOkHttp = sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_OKHTTP, - SentryVersions.VERSION_ANDROID_OKHTTP - ) - val isSentryOkHttp = sentryModules.isAtLeast( - SentryModules.SENTRY_OKHTTP, - SentryVersions.VERSION_OKHTTP - ) - return (isSentryAndroidOkHttp || isSentryOkHttp) && - parameters.features.get().contains(InstrumentationFeature.OKHTTP) + if (parameters.dexguardEnabled.getOrElse(false)) { + features.add("DexGuard") } - fun isComposeInstrEnabled(): Boolean = - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_COMPOSE, - SentryVersions.VERSION_COMPOSE - ) && parameters.features.get().contains(InstrumentationFeature.COMPOSE) - - fun isAppStartInstrEnabled(): Boolean = - sentryModules.isAtLeast( - SentryModules.SENTRY_ANDROID_CORE, - SentryVersions.VERSION_APP_START - ) && parameters.appStartEnabled.get() - - private fun Map.isAtLeast( - module: ModuleIdentifier, - minVersion: SemVer - ): Boolean = - getOrDefault(module, SentryVersions.VERSION_DEFAULT) >= minVersion - - companion object { - fun register( - project: Project, - features: Provider>, - logcatEnabled: Provider, - sourceContextEnabled: Provider, - dexguardEnabled: Provider, - appStartEnabled: Provider - ): Provider { - return project.gradle.sharedServices.registerIfAbsent( - getBuildServiceName(SentryModulesService::class.java), - SentryModulesService::class.java - ) { - it.parameters.features.setDisallowChanges(features) - it.parameters.logcatEnabled.setDisallowChanges(logcatEnabled) - it.parameters.sourceContextEnabled.setDisallowChanges(sourceContextEnabled) - it.parameters.dexguardEnabled.setDisallowChanges(dexguardEnabled) - it.parameters.appStartEnabled.setDisallowChanges(appStartEnabled) - } - } + return features + } + + private fun isInstrumentationEnabled(feature: InstrumentationFeature): Boolean { + return when (feature) { + InstrumentationFeature.DATABASE -> isOldDatabaseInstrEnabled() || isNewDatabaseInstrEnabled() + InstrumentationFeature.FILE_IO -> isFileIOInstrEnabled() + InstrumentationFeature.OKHTTP -> isOkHttpInstrEnabled() + InstrumentationFeature.COMPOSE -> isComposeInstrEnabled() + } + } + + fun isLogcatInstrEnabled(): Boolean = + sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_CORE, SentryVersions.VERSION_LOGCAT) && + parameters.logcatEnabled.get() + + fun isNewDatabaseInstrEnabled(): Boolean = + sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_SQLITE, SentryVersions.VERSION_SQLITE) && + parameters.features.get().contains(InstrumentationFeature.DATABASE) + + fun isOldDatabaseInstrEnabled(): Boolean = + !isNewDatabaseInstrEnabled() && + sentryModules.isAtLeast( + SentryModules.SENTRY_ANDROID_CORE, + SentryVersions.VERSION_PERFORMANCE, + ) && + parameters.features.get().contains(InstrumentationFeature.DATABASE) + + fun isFileIOInstrEnabled(): Boolean = + sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_CORE, SentryVersions.VERSION_FILE_IO) && + parameters.features.get().contains(InstrumentationFeature.FILE_IO) + + fun isOkHttpListenerInstrEnabled(): Boolean { + val isSentryAndroidOkHttpListener = + sentryModules.isAtLeast( + SentryModules.SENTRY_ANDROID_OKHTTP, + SentryVersions.VERSION_ANDROID_OKHTTP_LISTENER, + ) + val isSentryOkHttpListener = + sentryModules.isAtLeast(SentryModules.SENTRY_OKHTTP, SentryVersions.VERSION_OKHTTP) + return (isSentryAndroidOkHttpListener || isSentryOkHttpListener) && + parameters.features.get().contains(InstrumentationFeature.OKHTTP) + } + + fun isOkHttpInstrEnabled(): Boolean { + val isSentryAndroidOkHttp = + sentryModules.isAtLeast( + SentryModules.SENTRY_ANDROID_OKHTTP, + SentryVersions.VERSION_ANDROID_OKHTTP, + ) + val isSentryOkHttp = + sentryModules.isAtLeast(SentryModules.SENTRY_OKHTTP, SentryVersions.VERSION_OKHTTP) + return (isSentryAndroidOkHttp || isSentryOkHttp) && + parameters.features.get().contains(InstrumentationFeature.OKHTTP) + } + + fun isComposeInstrEnabled(): Boolean = + sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_COMPOSE, SentryVersions.VERSION_COMPOSE) && + parameters.features.get().contains(InstrumentationFeature.COMPOSE) + + fun isAppStartInstrEnabled(): Boolean = + sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_CORE, SentryVersions.VERSION_APP_START) && + parameters.appStartEnabled.get() + + private fun Map.isAtLeast( + module: ModuleIdentifier, + minVersion: SemVer, + ): Boolean = getOrDefault(module, SentryVersions.VERSION_DEFAULT) >= minVersion + + companion object { + fun register( + project: Project, + features: Provider>, + logcatEnabled: Provider, + sourceContextEnabled: Provider, + dexguardEnabled: Provider, + appStartEnabled: Provider, + ): Provider { + return project.gradle.sharedServices.registerIfAbsent( + getBuildServiceName(SentryModulesService::class.java), + SentryModulesService::class.java, + ) { + it.parameters.features.setDisallowChanges(features) + it.parameters.logcatEnabled.setDisallowChanges(logcatEnabled) + it.parameters.sourceContextEnabled.setDisallowChanges(sourceContextEnabled) + it.parameters.dexguardEnabled.setDisallowChanges(dexguardEnabled) + it.parameters.appStartEnabled.setDisallowChanges(appStartEnabled) + } } + } - override fun onFinish(event: FinishEvent?) = Unit // no-op + override fun onFinish(event: FinishEvent?) = Unit // no-op - interface Parameters : BuildServiceParameters { + interface Parameters : BuildServiceParameters { - @get:Input - val features: SetProperty + @get:Input val features: SetProperty - @get:Input - val logcatEnabled: Property + @get:Input val logcatEnabled: Property - @get:Input - val sourceContextEnabled: Property + @get:Input val sourceContextEnabled: Property - @get:Input - val dexguardEnabled: Property + @get:Input val dexguardEnabled: Property - @get:Input - val appStartEnabled: Property - } + @get:Input val appStartEnabled: Property + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt index 1d0c50d7..39904536 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/BundleSourcesTask.kt @@ -27,89 +27,81 @@ import org.gradle.api.tasks.TaskProvider abstract class BundleSourcesTask : SentryCliExecTask() { - init { - group = SENTRY_GROUP - description = "Creates a Sentry source bundle file." + init { + group = SENTRY_GROUP + description = "Creates a Sentry source bundle file." - @Suppress("LeakingThis") - onlyIf { - includeSourceContext.getOrElse(false) && - !sourceDir.asFileTree.isEmpty - } - } + @Suppress("LeakingThis") + onlyIf { includeSourceContext.getOrElse(false) && !sourceDir.asFileTree.isEmpty } + } - @get:Input - abstract val includeSourceContext: Property + @get:Input abstract val includeSourceContext: Property - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputDirectory - abstract val sourceDir: DirectoryProperty + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputDirectory + abstract val sourceDir: DirectoryProperty - @get:InputFile - abstract val bundleIdFile: RegularFileProperty + @get:InputFile abstract val bundleIdFile: RegularFileProperty - @get:OutputDirectory - abstract val output: DirectoryProperty + @get:OutputDirectory abstract val output: DirectoryProperty - override fun getArguments(args: MutableList) { - val bundleId = readBundleIdFromFile(bundleIdFile.get().asFile) + override fun getArguments(args: MutableList) { + val bundleId = readBundleIdFromFile(bundleIdFile.get().asFile) - args.add("debug-files") - args.add("bundle-jvm") - args.add("--output=${output.asFile.get().absolutePath}") - args.add("--debug-id=$bundleId") + args.add("debug-files") + args.add("bundle-jvm") + args.add("--output=${output.asFile.get().absolutePath}") + args.add("--debug-id=$bundleId") - args.add(sourceDir.get().asFile.absolutePath) - } + args.add(sourceDir.get().asFile.absolutePath) + } - companion object { - internal fun readBundleIdFromFile(file: File): String { - val props = PropertiesUtil.load(file) - val bundleId: String? = props.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - check(bundleId != null) { - "$SENTRY_BUNDLE_ID_PROPERTY property is missing" - } - return bundleId - } + companion object { + internal fun readBundleIdFromFile(file: File): String { + val props = PropertiesUtil.load(file) + val bundleId: String? = props.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + check(bundleId != null) { "$SENTRY_BUNDLE_ID_PROPERTY property is missing" } + return bundleId + } - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - variant: SentryVariant, - generateDebugIdTask: TaskProvider, - collectSourcesTask: TaskProvider, - output: Provider, - debug: Property, - cliExecutable: Provider, - sentryOrg: Provider, - sentryProject: Provider, - sentryAuthToken: Property, - sentryUrl: Property, - includeSourceContext: Property, - taskSuffix: String = "" - ): TaskProvider { - return project.tasks.register( - "sentryBundleSources$taskSuffix", - BundleSourcesTask::class.java - ) { task -> - task.debug.set(debug) - task.sentryOrganization.set(sentryOrg) - task.sentryProject.set(sentryProject) - task.sentryAuthToken.set(sentryAuthToken) - task.sentryUrl.set(sentryUrl) - task.sourceDir.set(collectSourcesTask.flatMap { it.output }) - task.cliExecutable.set(cliExecutable) - SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { - task.sentryProperties.set(File(it)) - } - task.bundleIdFile.set(generateDebugIdTask.flatMap { it.outputFile }) - task.output.set(output) - task.includeSourceContext.set(includeSourceContext) - sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } - task.asSentryCliExec() - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + variant: SentryVariant, + generateDebugIdTask: TaskProvider, + collectSourcesTask: TaskProvider, + output: Provider, + debug: Property, + cliExecutable: Provider, + sentryOrg: Provider, + sentryProject: Provider, + sentryAuthToken: Property, + sentryUrl: Property, + includeSourceContext: Property, + taskSuffix: String = "", + ): TaskProvider { + return project.tasks.register( + "sentryBundleSources$taskSuffix", + BundleSourcesTask::class.java, + ) { task -> + task.debug.set(debug) + task.sentryOrganization.set(sentryOrg) + task.sentryProject.set(sentryProject) + task.sentryAuthToken.set(sentryAuthToken) + task.sentryUrl.set(sentryUrl) + task.sourceDir.set(collectSourcesTask.flatMap { it.output }) + task.cliExecutable.set(cliExecutable) + SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { + task.sentryProperties.set(File(it)) } + task.bundleIdFile.set(generateDebugIdTask.flatMap { it.outputFile }) + task.output.set(output) + task.includeSourceContext.set(includeSourceContext) + sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } + task.asSentryCliExec() + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt index 04e49e79..94b35840 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt @@ -26,106 +26,101 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class CollectSourcesTask : DirectoryOutputTask() { - init { - group = SENTRY_GROUP - description = "Collects sources into a single directory so they can be bundled together." + init { + group = SENTRY_GROUP + description = "Collects sources into a single directory so they can be bundled together." - @Suppress("LeakingThis") - onlyIf { - includeSourceContext.getOrElse(false) - } - } + @Suppress("LeakingThis") onlyIf { includeSourceContext.getOrElse(false) } + } - @get:Input - abstract val includeSourceContext: Property + @get:Input abstract val includeSourceContext: Property - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputFiles - abstract val sourceDirs: ConfigurableFileCollection + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputFiles + abstract val sourceDirs: ConfigurableFileCollection - @TaskAction - fun action() { - val outDir = output.getAndDelete() - outDir.mkdirs() - SourceCollector().collectSources(outDir, sourceDirs) - } + @TaskAction + fun action() { + val outDir = output.getAndDelete() + outDir.mkdirs() + SourceCollector().collectSources(outDir, sourceDirs) + } - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - sourceDirs: Provider>?, - output: Provider, - includeSourceContext: Property, - taskSuffix: String = "" - ): TaskProvider { - return project.tasks.register( - "sentryCollectSources$taskSuffix", - CollectSourcesTask::class.java - ) { task -> - task.sourceDirs.setFrom(sourceDirs) - task.output.set(output) - task.includeSourceContext.set(includeSourceContext) - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - } + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + sourceDirs: Provider>?, + output: Provider, + includeSourceContext: Property, + taskSuffix: String = "", + ): TaskProvider { + return project.tasks.register( + "sentryCollectSources$taskSuffix", + CollectSourcesTask::class.java, + ) { task -> + task.sourceDirs.setFrom(sourceDirs) + task.output.set(output) + task.includeSourceContext.set(includeSourceContext) + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } internal class SourceCollector { - fun collectSources(outDir: File, sourceDirs: ConfigurableFileCollection) { - sourceDirs.forEach { sourceDir -> - if (sourceDir.exists()) { - SentryPlugin.logger.debug { "Collecting sources in ${sourceDir.absolutePath}" } - sourceDir.walk().forEach { sourceFile -> - val relativePath = - sourceFile.absolutePath.removePrefix(sourceDir.absolutePath) - .removePrefix(File.separator) - val targetFile = outDir.resolve(File(relativePath)) - if (sourceFile.isFile) { - if (relativePath.isBlank()) { - /* ktlint-disable max-line-length */ - SentryPlugin.logger.debug { - "Skipping ${sourceFile.absolutePath} as the plugin was unable to determine a relative path for it." - } - /* ktlint-enable max-line-length */ - } else { - SentryPlugin.logger.debug { - "Copying file ${sourceFile.absolutePath} " + - "to ${targetFile.absolutePath}" - } - sourceFile.copyTo(targetFile, true) - } - } - } + fun collectSources(outDir: File, sourceDirs: ConfigurableFileCollection) { + sourceDirs.forEach { sourceDir -> + if (sourceDir.exists()) { + SentryPlugin.logger.debug { "Collecting sources in ${sourceDir.absolutePath}" } + sourceDir.walk().forEach { sourceFile -> + val relativePath = + sourceFile.absolutePath + .removePrefix(sourceDir.absolutePath) + .removePrefix(File.separator) + val targetFile = outDir.resolve(File(relativePath)) + if (sourceFile.isFile) { + if (relativePath.isBlank()) { + /* ktlint-disable max-line-length */ + SentryPlugin.logger.debug { + "Skipping ${sourceFile.absolutePath} as the plugin was unable to determine a relative path for it." + } + /* ktlint-enable max-line-length */ } else { - SentryPlugin.logger.debug { - "Skipping source collection in ${sourceDir.absolutePath} as it doesn't " + - "exist." - } + SentryPlugin.logger.debug { + "Copying file ${sourceFile.absolutePath} " + "to ${targetFile.absolutePath}" + } + sourceFile.copyTo(targetFile, true) } + } + } + } else { + SentryPlugin.logger.debug { + "Skipping source collection in ${sourceDir.absolutePath} as it doesn't " + "exist." } + } } + } } internal fun DirectoryProperty.getAndDelete(): File { - val file = get().asFile - if (file.isDirectory) { - file.deleteRecursively() - } else { - file.delete() - } - return file + val file = get().asFile + if (file.isDirectory) { + file.deleteRecursively() + } else { + file.delete() + } + return file } internal fun RegularFileProperty.getAndDelete(): File { - val file = get().asFile - if (file.isDirectory) { - file.deleteRecursively() - } else { - file.delete() - } - return file + val file = get().asFile + if (file.isDirectory) { + file.deleteRecursively() + } else { + file.delete() + } + return file } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/GenerateBundleIdTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/GenerateBundleIdTask.kt index 64ff53db..308fc747 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/GenerateBundleIdTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/GenerateBundleIdTask.kt @@ -25,71 +25,60 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class GenerateBundleIdTask : PropertiesFileOutputTask() { - init { - description = "Generates a unique build ID to be used " + - "when bundling sources for upload to Sentry" + init { + description = + "Generates a unique build ID to be used " + "when bundling sources for upload to Sentry" - @Suppress("LeakingThis") - onlyIf { - includeSourceContext.getOrElse(false) - } - } + @Suppress("LeakingThis") onlyIf { includeSourceContext.getOrElse(false) } + } - @get:Input - abstract val includeSourceContext: Property + @get:Input abstract val includeSourceContext: Property - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputFiles - abstract val sourceDirs: ConfigurableFileCollection + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputFiles + abstract val sourceDirs: ConfigurableFileCollection - @get:Internal - override val outputFile: Provider get() = output.file(SENTRY_BUNDLE_ID_OUTPUT) + @get:Internal + override val outputFile: Provider + get() = output.file(SENTRY_BUNDLE_ID_OUTPUT) - @TaskAction - fun generateProperties() { - val outputDir = output.get().asFile - outputDir.mkdirs() + @TaskAction + fun generateProperties() { + val outputDir = output.get().asFile + outputDir.mkdirs() - val debugId = UUID.randomUUID() + val debugId = UUID.randomUUID() - val props = Properties().also { - it.setProperty(SENTRY_BUNDLE_ID_PROPERTY, debugId.toString()) - } + val props = Properties().also { it.setProperty(SENTRY_BUNDLE_ID_PROPERTY, debugId.toString()) } - outputFile.get().asFile.writer().use { writer -> - props.store(writer, "") - } + outputFile.get().asFile.writer().use { writer -> props.store(writer, "") } - logger.info { - "GenerateSourceBundleIdTask - outputFile: $outputFile, debugId: $debugId" - } - } + logger.info { "GenerateSourceBundleIdTask - outputFile: $outputFile, debugId: $debugId" } + } - companion object { - internal const val SENTRY_BUNDLE_ID_OUTPUT = "sentry-bundle-id.properties" - const val SENTRY_BUNDLE_ID_PROPERTY = "io.sentry.bundle-ids" + companion object { + internal const val SENTRY_BUNDLE_ID_OUTPUT = "sentry-bundle-id.properties" + const val SENTRY_BUNDLE_ID_PROPERTY = "io.sentry.bundle-ids" - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - sourceDirs: Provider>?, - output: Provider? = null, - includeSourceContext: Property, - taskSuffix: String = "" - ): TaskProvider { - val generateBundleIdTask = project.tasks.register( - taskName(taskSuffix), - GenerateBundleIdTask::class.java - ) { task -> - output?.let { task.output.set(it) } - task.includeSourceContext.set(includeSourceContext) - task.withSentryTelemetry(extension, sentryTelemetryProvider) - task.sourceDirs.setFrom(sourceDirs) - } - return generateBundleIdTask + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + sourceDirs: Provider>?, + output: Provider? = null, + includeSourceContext: Property, + taskSuffix: String = "", + ): TaskProvider { + val generateBundleIdTask = + project.tasks.register(taskName(taskSuffix), GenerateBundleIdTask::class.java) { task -> + output?.let { task.output.set(it) } + task.includeSourceContext.set(includeSourceContext) + task.withSentryTelemetry(extension, sentryTelemetryProvider) + task.sourceDirs.setFrom(sourceDirs) } - - fun taskName(taskSuffix: String) = "generateSentryBundleId$taskSuffix" + return generateBundleIdTask } + + fun taskName(taskSuffix: String) = "generateSentryBundleId$taskSuffix" + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/OutputPaths.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/OutputPaths.kt index 0c66651b..31f46387 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/OutputPaths.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/OutputPaths.kt @@ -4,17 +4,15 @@ import org.gradle.api.Project internal const val ROOT_DIR = "intermediates/sentry" -class OutputPaths( - private val project: Project, - variantName: String -) { - private fun file(path: String) = project.layout.buildDirectory.file(path) - private fun dir(path: String) = project.layout.buildDirectory.dir(path) +class OutputPaths(private val project: Project, variantName: String) { + private fun file(path: String) = project.layout.buildDirectory.file(path) - private val variantDirectory = "$ROOT_DIR/$variantName" + private fun dir(path: String) = project.layout.buildDirectory.dir(path) - val proguardUuidDir = dir("$variantDirectory/proguard-uuid") - val bundleIdDir = dir("$variantDirectory/bundle-id") - val sourceDir = dir("$variantDirectory/source-to-bundle") - val bundleDir = dir("$variantDirectory/source-bundle") + private val variantDirectory = "$ROOT_DIR/$variantName" + + val proguardUuidDir = dir("$variantDirectory/proguard-uuid") + val bundleIdDir = dir("$variantDirectory/bundle-id") + val sourceDir = dir("$variantDirectory/source-to-bundle") + val bundleDir = dir("$variantDirectory/source-bundle") } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt index dc5455c3..f1835b27 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/SourceContext.kt @@ -9,87 +9,91 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider class SourceContext { - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - variant: SentryVariant, - paths: OutputPaths, - sourceFiles: Provider>?, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String?, - taskSuffix: String - ): SourceContextTasks { - val generateBundleIdTask = GenerateBundleIdTask.register( - project, - extension, - sentryTelemetryProvider, - sourceFiles, - output = paths.bundleIdDir, - extension.includeSourceContext, - taskSuffix - ) + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + variant: SentryVariant, + paths: OutputPaths, + sourceFiles: Provider>?, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, + taskSuffix: String, + ): SourceContextTasks { + val generateBundleIdTask = + GenerateBundleIdTask.register( + project, + extension, + sentryTelemetryProvider, + sourceFiles, + output = paths.bundleIdDir, + extension.includeSourceContext, + taskSuffix, + ) - val collectSourcesTask = CollectSourcesTask.register( - project, - extension, - sentryTelemetryProvider, - sourceFiles, - output = paths.sourceDir, - extension.includeSourceContext, - taskSuffix - ) + val collectSourcesTask = + CollectSourcesTask.register( + project, + extension, + sentryTelemetryProvider, + sourceFiles, + output = paths.sourceDir, + extension.includeSourceContext, + taskSuffix, + ) - val bundleSourcesTask = BundleSourcesTask.register( - project, - extension, - sentryTelemetryProvider, - variant, - generateBundleIdTask, - collectSourcesTask, - output = paths.bundleDir, - extension.debug, - cliExecutable, - sentryOrg?.let { project.provider { it } } ?: extension.org, - sentryProject?.let { project.provider { it } } ?: extension.projectName, - extension.authToken, - extension.url, - extension.includeSourceContext, - taskSuffix - ) + val bundleSourcesTask = + BundleSourcesTask.register( + project, + extension, + sentryTelemetryProvider, + variant, + generateBundleIdTask, + collectSourcesTask, + output = paths.bundleDir, + extension.debug, + cliExecutable, + sentryOrg?.let { project.provider { it } } ?: extension.org, + sentryProject?.let { project.provider { it } } ?: extension.projectName, + extension.authToken, + extension.url, + extension.includeSourceContext, + taskSuffix, + ) - val uploadSourceBundleTask = UploadSourceBundleTask.register( - project, - extension, - sentryTelemetryProvider, - variant, - bundleSourcesTask, - extension.debug, - cliExecutable, - extension.autoUploadSourceContext, - sentryOrg?.let { project.provider { it } } ?: extension.org, - sentryProject?.let { project.provider { it } } ?: extension.projectName, - extension.authToken, - extension.url, - extension.includeSourceContext, - taskSuffix - ) + val uploadSourceBundleTask = + UploadSourceBundleTask.register( + project, + extension, + sentryTelemetryProvider, + variant, + bundleSourcesTask, + extension.debug, + cliExecutable, + extension.autoUploadSourceContext, + sentryOrg?.let { project.provider { it } } ?: extension.org, + sentryProject?.let { project.provider { it } } ?: extension.projectName, + extension.authToken, + extension.url, + extension.includeSourceContext, + taskSuffix, + ) - return SourceContextTasks( - generateBundleIdTask, - collectSourcesTask, - bundleSourcesTask, - uploadSourceBundleTask - ) - } + return SourceContextTasks( + generateBundleIdTask, + collectSourcesTask, + bundleSourcesTask, + uploadSourceBundleTask, + ) } + } - class SourceContextTasks( - val generateBundleIdTask: TaskProvider, - val collectSourcesTask: TaskProvider, - val bundleSourcesTask: TaskProvider, - val uploadSourceBundleTask: TaskProvider - ) + class SourceContextTasks( + val generateBundleIdTask: TaskProvider, + val collectSourcesTask: TaskProvider, + val bundleSourcesTask: TaskProvider, + val uploadSourceBundleTask: TaskProvider, + ) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt index 665a10d9..9345ba79 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/UploadSourceBundleTask.kt @@ -19,81 +19,78 @@ import org.gradle.api.tasks.TaskProvider abstract class UploadSourceBundleTask : SentryCliExecTask() { - init { - group = SENTRY_GROUP - description = "Uploads a Sentry source bundle file." + init { + group = SENTRY_GROUP + description = "Uploads a Sentry source bundle file." - @Suppress("LeakingThis") - onlyIf { - includeSourceContext.getOrElse(false) && - !sourceBundleDir.asFileTree.isEmpty - } + @Suppress("LeakingThis") + onlyIf { includeSourceContext.getOrElse(false) && !sourceBundleDir.asFileTree.isEmpty } - // Allows gradle to consider this task up-to-date if the inputs haven't changed - // As this task does not have any outputs, it will always be considered to be out-of-date otherwise - // More info here https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes - // and https://docs.gradle.org/current/userguide/incremental_build.html#sec:custom_up_to_date_logic - outputs.upToDateWhen { true } - } + // Allows gradle to consider this task up-to-date if the inputs haven't changed + // As this task does not have any outputs, it will always be considered to be out-of-date + // otherwise + // More info here + // https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes + // and + // https://docs.gradle.org/current/userguide/incremental_build.html#sec:custom_up_to_date_logic + outputs.upToDateWhen { true } + } - @get:Input - abstract val includeSourceContext: Property + @get:Input abstract val includeSourceContext: Property - @get:InputDirectory - abstract val sourceBundleDir: DirectoryProperty + @get:InputDirectory abstract val sourceBundleDir: DirectoryProperty - @get:Input - abstract val autoUploadSourceContext: Property + @get:Input abstract val autoUploadSourceContext: Property - override fun getArguments(args: MutableList) { - args.add("debug-files") - args.add("upload") - args.add("--type=jvm") + override fun getArguments(args: MutableList) { + args.add("debug-files") + args.add("upload") + args.add("--type=jvm") - if (!autoUploadSourceContext.get()) { - args.add("--no-upload") - } - - args.add(sourceBundleDir.get().asFile.absolutePath) + if (!autoUploadSourceContext.get()) { + args.add("--no-upload") } - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - variant: SentryVariant, - bundleSourcesTask: TaskProvider, - debug: Property, - cliExecutable: Provider, - autoUploadSourceContext: Property, - sentryOrg: Provider, - sentryProject: Provider, - sentryAuthToken: Property, - sentryUrl: Property, - includeSourceContext: Property, - taskSuffix: String = "" - ): TaskProvider { - return project.tasks.register( - "sentryUploadSourceBundle$taskSuffix", - UploadSourceBundleTask::class.java - ) { task -> - task.debug.set(debug) - task.sentryOrganization.set(sentryOrg) - task.sentryProject.set(sentryProject) - task.sentryAuthToken.set(sentryAuthToken) - task.sentryUrl.set(sentryUrl) - task.sourceBundleDir.set(bundleSourcesTask.flatMap { it.output }) - task.cliExecutable.set(cliExecutable) - task.autoUploadSourceContext.set(autoUploadSourceContext) - SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { - task.sentryProperties.set(File(it)) - } - task.includeSourceContext.set(includeSourceContext) - sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } - task.asSentryCliExec() - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } + args.add(sourceBundleDir.get().asFile.absolutePath) + } + + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + variant: SentryVariant, + bundleSourcesTask: TaskProvider, + debug: Property, + cliExecutable: Provider, + autoUploadSourceContext: Property, + sentryOrg: Provider, + sentryProject: Provider, + sentryAuthToken: Property, + sentryUrl: Property, + includeSourceContext: Property, + taskSuffix: String = "", + ): TaskProvider { + return project.tasks.register( + "sentryUploadSourceBundle$taskSuffix", + UploadSourceBundleTask::class.java, + ) { task -> + task.debug.set(debug) + task.sentryOrganization.set(sentryOrg) + task.sentryProject.set(sentryProject) + task.sentryAuthToken.set(sentryAuthToken) + task.sentryUrl.set(sentryUrl) + task.sourceBundleDir.set(bundleSourcesTask.flatMap { it.output }) + task.cliExecutable.set(cliExecutable) + task.autoUploadSourceContext.set(autoUploadSourceContext) + SentryPropertiesFileProvider.getPropertiesFilePath(project, variant)?.let { + task.sentryProperties.set(File(it)) } + task.includeSourceContext.set(includeSourceContext) + sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } + task.asSentryCliExec() + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/DirectoryOutputTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/DirectoryOutputTask.kt index 66606900..4aec9b8f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/DirectoryOutputTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/DirectoryOutputTask.kt @@ -6,6 +6,5 @@ import org.gradle.api.tasks.OutputDirectory abstract class DirectoryOutputTask : DefaultTask() { - @get:OutputDirectory - abstract val output: DirectoryProperty + @get:OutputDirectory abstract val output: DirectoryProperty } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/InjectSentryMetaPropertiesIntoAssetsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/InjectSentryMetaPropertiesIntoAssetsTask.kt index b6a67530..45d7c8be 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/InjectSentryMetaPropertiesIntoAssetsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/InjectSentryMetaPropertiesIntoAssetsTask.kt @@ -1,4 +1,5 @@ -// Adapted from https://github.com/android/gradle-recipes/blob/efeedbc78465547280b5c13b3e04a65b70fa1e26/transformDirectory/build-logic/plugins/src/main/kotlin/TransformAssetsTask.kt +// Adapted from +// https://github.com/android/gradle-recipes/blob/efeedbc78465547280b5c13b3e04a65b70fa1e26/transformDirectory/build-logic/plugins/src/main/kotlin/TransformAssetsTask.kt /* * Copyright (C) 2024 The Android Open Source Project * @@ -40,85 +41,79 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class InjectSentryMetaPropertiesIntoAssetsTask : DefaultTask() { - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputFiles - abstract val inputDir: DirectoryProperty - - @get:OutputDirectory - val outputDir: DirectoryProperty = project.objects.directoryProperty() - get() { - // AGP < 8.3 sets an output folder which contains the input folder - // input: app/intermediates/assets/release/mergeReleaseAssets - // output: app/intermediates/assets/release/ - // re-route output to a sub directory instead, - // as otherwise this breaks the gradle cache functionality - - @Suppress("SENSELESS_COMPARISON") - if (field == null || !field.isPresent) { - return field - } - - if (!field.get().asFile.name.equals(name)) { - field.set(File(field.get().asFile, name)) - } - - return field - } - - // we only care about file contents - @get:PathSensitive(PathSensitivity.NONE) - @get:InputFiles - abstract val inputPropertyFiles: ConfigurableFileCollection - - @TaskAction - fun taskAction() { - val input = inputDir.get().asFile - val output = outputDir.get().asFile - - if (!output.exists()) { - output.mkdirs() - } - - input.copyRecursively(output, overwrite = true) - - // merge props - val props = Properties() - props.setProperty("io.sentry.build-tool", "gradle") - inputPropertyFiles.forEach { inputFile -> - PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) } - } - - // write props - val propsFile = File(output, SENTRY_DEBUG_META_PROPERTIES_OUTPUT) - propsFile.writer().use { - props.store( - it, - "Generated by sentry-android-gradle-plugin" - ) - } + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputFiles + abstract val inputDir: DirectoryProperty + + @get:OutputDirectory + val outputDir: DirectoryProperty = project.objects.directoryProperty() + get() { + // AGP < 8.3 sets an output folder which contains the input folder + // input: app/intermediates/assets/release/mergeReleaseAssets + // output: app/intermediates/assets/release/ + // re-route output to a sub directory instead, + // as otherwise this breaks the gradle cache functionality + + @Suppress("SENSELESS_COMPARISON") + if (field == null || !field.isPresent) { + return field + } + + if (!field.get().asFile.name.equals(name)) { + field.set(File(field.get().asFile, name)) + } + + return field } - companion object { - internal const val SENTRY_DEBUG_META_PROPERTIES_OUTPUT = "sentry-debug-meta.properties" - - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - tasksGeneratingProperties: List>, - taskSuffix: String = "" - ): TaskProvider { - val inputFiles: List> = tasksGeneratingProperties.mapNotNull { - it.flatMap { task -> task.outputFile } - } - return project.tasks.register( - "injectSentryDebugMetaPropertiesIntoAssets$taskSuffix", - InjectSentryMetaPropertiesIntoAssetsTask::class.java - ) { task -> - task.inputPropertyFiles.setFrom(inputFiles) - - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - } + // we only care about file contents + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFiles + abstract val inputPropertyFiles: ConfigurableFileCollection + + @TaskAction + fun taskAction() { + val input = inputDir.get().asFile + val output = outputDir.get().asFile + + if (!output.exists()) { + output.mkdirs() + } + + input.copyRecursively(output, overwrite = true) + + // merge props + val props = Properties() + props.setProperty("io.sentry.build-tool", "gradle") + inputPropertyFiles.forEach { inputFile -> + PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) } + } + + // write props + val propsFile = File(output, SENTRY_DEBUG_META_PROPERTIES_OUTPUT) + propsFile.writer().use { props.store(it, "Generated by sentry-android-gradle-plugin") } + } + + companion object { + internal const val SENTRY_DEBUG_META_PROPERTIES_OUTPUT = "sentry-debug-meta.properties" + + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + tasksGeneratingProperties: List>, + taskSuffix: String = "", + ): TaskProvider { + val inputFiles: List> = + tasksGeneratingProperties.mapNotNull { it.flatMap { task -> task.outputFile } } + return project.tasks.register( + "injectSentryDebugMetaPropertiesIntoAssets$taskSuffix", + InjectSentryMetaPropertiesIntoAssetsTask::class.java, + ) { task -> + task.inputPropertyFiles.setFrom(inputFiles) + + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/PropertiesFileOutputTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/PropertiesFileOutputTask.kt index 8dfa2151..7f01afaf 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/PropertiesFileOutputTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/PropertiesFileOutputTask.kt @@ -5,6 +5,5 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Internal abstract class PropertiesFileOutputTask : DirectoryOutputTask() { - @get:Internal - abstract val outputFile: Provider + @get:Internal abstract val outputFile: Provider } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt index c49a47cc..9b2fda82 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTask.kt @@ -16,117 +16,99 @@ import org.gradle.api.tasks.Optional abstract class SentryCliExecTask : Exec() { - @get:Input - @get:Optional - abstract val debug: Property - - @get:Input - abstract val cliExecutable: Property - - @get:InputFile - @get:Optional - abstract val sentryProperties: RegularFileProperty - - @get:Input - @get:Optional - abstract val sentryOrganization: Property - - @get:Input - @get:Optional - abstract val sentryProject: Property - - @get:Input - @get:Optional - abstract val sentryAuthToken: Property - - @get:Input - @get:Optional - abstract val sentryUrl: Property - - @get:Internal - abstract val sentryTelemetryService: Property - - private val buildDirectory: Provider = project.layout.buildDirectory.asFile - - override fun exec() { - computeCommandLineArgs().let { - commandLine(it) - logger.info { "cli args: $it" } - } - setSentryPropertiesEnv() - setSentryAuthTokenEnv() - super.exec() - } + @get:Input @get:Optional abstract val debug: Property + + @get:Input abstract val cliExecutable: Property + + @get:InputFile @get:Optional abstract val sentryProperties: RegularFileProperty + + @get:Input @get:Optional abstract val sentryOrganization: Property - abstract fun getArguments(args: MutableList) + @get:Input @get:Optional abstract val sentryProject: Property - private fun preArgs(): List { - val args = mutableListOf() + @get:Input @get:Optional abstract val sentryAuthToken: Property - sentryUrl.orNull?.let { - args.add("--url") - args.add(it) - } + @get:Input @get:Optional abstract val sentryUrl: Property - if (debug.getOrElse(false)) { - args.add("--log-level=debug") - } + @get:Internal abstract val sentryTelemetryService: Property - sentryTelemetryService.orNull?.traceCli()?.let { args.addAll(it) } + private val buildDirectory: Provider = project.layout.buildDirectory.asFile - return args + override fun exec() { + computeCommandLineArgs().let { + commandLine(it) + logger.info { "cli args: $it" } } + setSentryPropertiesEnv() + setSentryAuthTokenEnv() + super.exec() + } - private fun postArgs(args: MutableList): List { - sentryOrganization.orNull?.let { - args.add("--org") - args.add(it) - } - - sentryProject.orNull?.let { - args.add("--project") - args.add(it) - } - return args + abstract fun getArguments(args: MutableList) + + private fun preArgs(): List { + val args = mutableListOf() + + sentryUrl.orNull?.let { + args.add("--url") + args.add(it) } - /** - * Computes the full list of arguments for the task - */ - fun computeCommandLineArgs(): List { - val args = mutableListOf() - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - args.add(0, "cmd") - args.add(1, "/c") - } - - val cliPath = SentryCliProvider.maybeExtractFromResources( - buildDirectory.get(), - cliExecutable.get() - ) - args.add(cliPath) - args.addAll(preArgs()) - - getArguments(args) - - return postArgs(args) + if (debug.getOrElse(false)) { + args.add("--log-level=debug") } - internal fun setSentryPropertiesEnv() { - val sentryProperties = sentryProperties.orNull - if (sentryProperties != null) { - environment("SENTRY_PROPERTIES", sentryProperties) - } else { - logger.info { "propsFile is null" } - } + sentryTelemetryService.orNull?.traceCli()?.let { args.addAll(it) } + + return args + } + + private fun postArgs(args: MutableList): List { + sentryOrganization.orNull?.let { + args.add("--org") + args.add(it) } - internal fun setSentryAuthTokenEnv() { - val sentryAuthToken = sentryAuthToken.orNull - if (sentryAuthToken != null) { - environment("SENTRY_AUTH_TOKEN", sentryAuthToken) - } else { - logger.info { "sentryAuthToken is null" } - } + sentryProject.orNull?.let { + args.add("--project") + args.add(it) + } + return args + } + + /** Computes the full list of arguments for the task */ + fun computeCommandLineArgs(): List { + val args = mutableListOf() + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + args.add(0, "cmd") + args.add(1, "/c") + } + + val cliPath = + SentryCliProvider.maybeExtractFromResources(buildDirectory.get(), cliExecutable.get()) + args.add(cliPath) + args.addAll(preArgs()) + + getArguments(args) + + return postArgs(args) + } + + internal fun setSentryPropertiesEnv() { + val sentryProperties = sentryProperties.orNull + if (sentryProperties != null) { + environment("SENTRY_PROPERTIES", sentryProperties) + } else { + logger.info { "propsFile is null" } + } + } + + internal fun setSentryAuthTokenEnv() { + val sentryAuthToken = sentryAuthToken.orNull + if (sentryAuthToken != null) { + environment("SENTRY_AUTH_TOKEN", sentryAuthToken) + } else { + logger.info { "sentryAuthToken is null" } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTask.kt index aae13a01..0e1e35e7 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateDebugMetaPropertiesTask.kt @@ -21,59 +21,54 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class SentryGenerateDebugMetaPropertiesTask : DirectoryOutputTask() { - init { - description = "Combines multiple properties files into sentry-debug-meta.properties" - } + init { + description = "Combines multiple properties files into sentry-debug-meta.properties" + } - @get:Internal - val outputFile: Provider get() = output.file(SENTRY_DEBUG_META_PROPERTIES_OUTPUT) + @get:Internal + val outputFile: Provider + get() = output.file(SENTRY_DEBUG_META_PROPERTIES_OUTPUT) - // we only care about file contents - @get:PathSensitive(NONE) - @get:InputFiles - abstract val inputFiles: ConfigurableFileCollection + // we only care about file contents + @get:PathSensitive(NONE) @get:InputFiles abstract val inputFiles: ConfigurableFileCollection - @TaskAction - fun generateProperties() { - val outputDir = output.get().asFile - outputDir.mkdirs() + @TaskAction + fun generateProperties() { + val outputDir = output.get().asFile + outputDir.mkdirs() - val debugMetaPropertiesFile = outputFile.get().asFile - val props = Properties() - props.setProperty("io.sentry.build-tool", "gradle") - inputFiles.forEach { inputFile -> - PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) } - } - debugMetaPropertiesFile.writer().use { - props.store( - it, - "Generated by sentry-android-gradle-plugin" - ) - } + val debugMetaPropertiesFile = outputFile.get().asFile + val props = Properties() + props.setProperty("io.sentry.build-tool", "gradle") + inputFiles.forEach { inputFile -> + PropertiesUtil.loadMaybe(inputFile)?.let { props.putAll(it) } + } + debugMetaPropertiesFile.writer().use { + props.store(it, "Generated by sentry-android-gradle-plugin") } + } - companion object { - internal const val SENTRY_DEBUG_META_PROPERTIES_OUTPUT = "sentry-debug-meta.properties" + companion object { + internal const val SENTRY_DEBUG_META_PROPERTIES_OUTPUT = "sentry-debug-meta.properties" - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - tasksGeneratingProperties: List>, - output: Provider? = null, - taskSuffix: String = "" - ): TaskProvider { - val inputFiles: List> = tasksGeneratingProperties.mapNotNull { - it.flatMap { task -> task.outputFile } - } - return project.tasks.register( - "generateSentryDebugMetaProperties$taskSuffix", - SentryGenerateDebugMetaPropertiesTask::class.java - ) { task -> - task.inputFiles.setFrom(inputFiles) - output?.let { task.output.set(it) } - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - } + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + tasksGeneratingProperties: List>, + output: Provider? = null, + taskSuffix: String = "", + ): TaskProvider { + val inputFiles: List> = + tasksGeneratingProperties.mapNotNull { it.flatMap { task -> task.outputFile } } + return project.tasks.register( + "generateSentryDebugMetaProperties$taskSuffix", + SentryGenerateDebugMetaPropertiesTask::class.java, + ) { task -> + task.inputFiles.setFrom(inputFiles) + output?.let { task.output.set(it) } + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateIntegrationListTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateIntegrationListTask.kt index 91042311..8fdb6521 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateIntegrationListTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateIntegrationListTask.kt @@ -18,55 +18,44 @@ import org.gradle.api.tasks.TaskAction @CacheableTask abstract class SentryGenerateIntegrationListTask : DefaultTask() { - companion object { - const val ATTR_INTEGRATIONS = "io.sentry.gradle-plugin-integrations" - } - - init { - description = "Writes enabled integrations to AndroidManifest.xml" - } - - // we only care about contents - @get:PathSensitive(NONE) - @get:InputFile - abstract val mergedManifest: RegularFileProperty - - @get:OutputFile - abstract val updatedManifest: RegularFileProperty - - @get:Input - abstract val integrations: SetProperty - - @TaskAction - fun writeIntegrationListToManifest() { - logger.info { - "SentryGenerateIntegrationListTask - outputFile: ${updatedManifest.get()}" - } - val integrations = integrations.get() - val manifestFile = mergedManifest.asFile.get() - val updatedManifestFile = updatedManifest.asFile.get() - - if (integrations.isNotEmpty()) { - val manifestWriter = ManifestWriter() - val integrationsList = integrations - .toList() - .sorted() - .joinToString(",") - manifestWriter.writeMetaData( - manifestFile, - updatedManifestFile, - ATTR_INTEGRATIONS, - integrationsList - ) - } else { - logger.info { - "No Integrations present, copying input manifest to output" - } - Files.copy( - manifestFile.toPath(), - updatedManifestFile.toPath(), - StandardCopyOption.REPLACE_EXISTING - ) - } + companion object { + const val ATTR_INTEGRATIONS = "io.sentry.gradle-plugin-integrations" + } + + init { + description = "Writes enabled integrations to AndroidManifest.xml" + } + + // we only care about contents + @get:PathSensitive(NONE) @get:InputFile abstract val mergedManifest: RegularFileProperty + + @get:OutputFile abstract val updatedManifest: RegularFileProperty + + @get:Input abstract val integrations: SetProperty + + @TaskAction + fun writeIntegrationListToManifest() { + logger.info { "SentryGenerateIntegrationListTask - outputFile: ${updatedManifest.get()}" } + val integrations = integrations.get() + val manifestFile = mergedManifest.asFile.get() + val updatedManifestFile = updatedManifest.asFile.get() + + if (integrations.isNotEmpty()) { + val manifestWriter = ManifestWriter() + val integrationsList = integrations.toList().sorted().joinToString(",") + manifestWriter.writeMetaData( + manifestFile, + updatedManifestFile, + ATTR_INTEGRATIONS, + integrationsList, + ) + } else { + logger.info { "No Integrations present, copying input manifest to output" } + Files.copy( + manifestFile.toPath(), + updatedManifestFile.toPath(), + StandardCopyOption.REPLACE_EXISTING, + ) } + } } 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 d7c57bf5..78897dc2 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 @@ -22,65 +22,61 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class SentryGenerateProguardUuidTask : PropertiesFileOutputTask() { - init { - description = "Generates a unique build ID to be used " + - "when uploading the Sentry mapping file" - } + init { + description = + "Generates a unique build ID to be used " + "when uploading the Sentry mapping file" + } - @get:Internal - override val outputFile: Provider get() = output.file(SENTRY_UUID_OUTPUT) + @get:Internal + override val outputFile: Provider + get() = output.file(SENTRY_UUID_OUTPUT) - @get:Input - abstract val proguardMappingFileHash: Property + @get:Input abstract val proguardMappingFileHash: Property - @TaskAction - fun generateProperties() { - val outputDir = output.get().asFile - outputDir.mkdirs() + @TaskAction + fun generateProperties() { + val outputDir = output.get().asFile + outputDir.mkdirs() - val uuid = UUID.randomUUID() + val uuid = UUID.randomUUID() - val props = Properties().also { - it.setProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY, uuid.toString()) - } + val props = + Properties().also { it.setProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY, uuid.toString()) } - outputFile.get().asFile.writer().use { writer -> - props.store(writer, "") - } + outputFile.get().asFile.writer().use { writer -> props.store(writer, "") } - logger.info { - "SentryGenerateProguardUuidTask - outputFile: $outputFile, uuid: $uuid" - } - } + logger.info { "SentryGenerateProguardUuidTask - outputFile: $outputFile, uuid: $uuid" } + } - companion object { - internal const val STATIC_HASH = "" - internal const val SENTRY_UUID_OUTPUT = "sentry-proguard-uuid.properties" - const val SENTRY_PROGUARD_MAPPING_UUID_PROPERTY = "io.sentry.ProguardUuids" + companion object { + internal const val STATIC_HASH = "" + internal const val SENTRY_UUID_OUTPUT = "sentry-proguard-uuid.properties" + const val SENTRY_PROGUARD_MAPPING_UUID_PROPERTY = "io.sentry.ProguardUuids" - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - output: Provider? = null, - proguardMappingFile: Provider?, - taskSuffix: String = "" - ): TaskProvider { - val generateUuidTask = project.tasks.register( - "generateSentryProguardUuid$taskSuffix", - SentryGenerateProguardUuidTask::class.java - ) { 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 } - ) - } - return generateUuidTask + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + output: Provider? = null, + proguardMappingFile: Provider?, + taskSuffix: String = "", + ): TaskProvider { + val generateUuidTask = + project.tasks.register( + "generateSentryProguardUuid$taskSuffix", + SentryGenerateProguardUuidTask::class.java, + ) { 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 } + ) } + return generateUuidTask } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt index e9b346c5..41f23faf 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTask.kt @@ -19,117 +19,113 @@ import org.gradle.api.tasks.TaskProvider abstract class SentryUploadNativeSymbolsTask : SentryCliExecTask() { - init { - description = "Uploads native symbols to Sentry" - } + init { + description = "Uploads native symbols to Sentry" + } - @get:Input - abstract val autoUploadNativeSymbol: Property + @get:Input abstract val autoUploadNativeSymbol: Property - @get:Input - abstract val includeNativeSources: Property + @get:Input abstract val includeNativeSources: Property - @get:Internal - abstract val variantName: Property + @get:Internal abstract val variantName: Property - private val buildDir: Provider = project.layout.buildDirectory.asFile + private val buildDir: Provider = project.layout.buildDirectory.asFile - override fun getArguments(args: MutableList) { - args.add("debug-files") - args.add("upload") + override fun getArguments(args: MutableList) { + args.add("debug-files") + args.add("upload") - if (!autoUploadNativeSymbol.get()) { - args.add("--no-upload") - } + if (!autoUploadNativeSymbol.get()) { + args.add("--no-upload") + } - val sep = File.separator + val sep = File.separator - // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variantName} - // where {variantName} could be debug/release... - args.add( - File( - buildDir.get(), - "intermediates${sep}merged_native_libs${sep}${variantName.get()}" - ).absolutePath - ) + // eg absoluteProjectFolderPath/build/intermediates/merged_native_libs/{variantName} + // where {variantName} could be debug/release... + args.add( + File(buildDir.get(), "intermediates${sep}merged_native_libs${sep}${variantName.get()}") + .absolutePath + ) - // Only include sources if includeNativeSources is enabled, as this is opt-in feature - if (includeNativeSources.get()) { - args.add("--include-sources") - } + // Only include sources if includeNativeSources is enabled, as this is opt-in feature + if (includeNativeSources.get()) { + args.add("--include-sources") } + } - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - variantName: String, - debug: Property, - cliExecutable: Provider, - sentryProperties: String?, - sentryOrg: Provider, - sentryProject: Provider, - sentryAuthToken: Property, - sentryUrl: Property, - includeNativeSources: Property, - autoUploadNativeSymbols: Property, - taskSuffix: String = "", - ): TaskProvider { - val uploadSentryNativeSymbolsTask = project.tasks.register( - "uploadSentryNativeSymbolsFor$taskSuffix", - SentryUploadNativeSymbolsTask::class.java - ) { task -> - task.workingDir(project.rootDir) - task.debug.set(debug) - task.autoUploadNativeSymbol.set(autoUploadNativeSymbols) - task.cliExecutable.set(cliExecutable) - task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) - task.includeNativeSources.set(includeNativeSources) - task.variantName.set(variantName) - task.sentryOrganization.set(sentryOrg) - task.sentryProject.set(sentryProject) - task.sentryAuthToken.set(sentryAuthToken) - task.sentryUrl.set(sentryUrl) - task.sentryTelemetryService.set(sentryTelemetryProvider) - task.asSentryCliExec() - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - return uploadSentryNativeSymbolsTask + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + variantName: String, + debug: Property, + cliExecutable: Provider, + sentryProperties: String?, + sentryOrg: Provider, + sentryProject: Provider, + sentryAuthToken: Property, + sentryUrl: Property, + includeNativeSources: Property, + autoUploadNativeSymbols: Property, + taskSuffix: String = "", + ): TaskProvider { + val uploadSentryNativeSymbolsTask = + project.tasks.register( + "uploadSentryNativeSymbolsFor$taskSuffix", + SentryUploadNativeSymbolsTask::class.java, + ) { task -> + task.workingDir(project.rootDir) + task.debug.set(debug) + task.autoUploadNativeSymbol.set(autoUploadNativeSymbols) + task.cliExecutable.set(cliExecutable) + task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) + task.includeNativeSources.set(includeNativeSources) + task.variantName.set(variantName) + task.sentryOrganization.set(sentryOrg) + task.sentryProject.set(sentryProject) + task.sentryAuthToken.set(sentryAuthToken) + task.sentryUrl.set(sentryUrl) + task.sentryTelemetryService.set(sentryTelemetryProvider) + task.asSentryCliExec() + task.withSentryTelemetry(extension, sentryTelemetryProvider) } + return uploadSentryNativeSymbolsTask } + } } fun SentryVariant.configureNativeSymbolsTask( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - cliExecutable: Provider, - sentryOrg: String?, - sentryProject: String? + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + cliExecutable: Provider, + sentryOrg: String?, + sentryProject: String?, ) { - if (!isDebuggable && extension.uploadNativeSymbols.get()) { - val sentryProps = SentryPropertiesFileProvider.getPropertiesFilePath(project, this) - // Setup the task to upload native symbols task after the assembling task - val uploadSentryNativeSymbolsTask = SentryUploadNativeSymbolsTask.register( - project = project, - extension = extension, - sentryTelemetryProvider = sentryTelemetryProvider, - variantName = name, - debug = extension.debug, - cliExecutable = cliExecutable, - sentryProperties = sentryProps, - autoUploadNativeSymbols = extension.autoUploadNativeSymbols, - includeNativeSources = extension.includeNativeSources, - sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, - sentryProject = sentryProject?.let { project.provider { it } } - ?: extension.projectName, - sentryAuthToken = extension.authToken, - sentryUrl = extension.url, - taskSuffix = name.capitalized - ) - uploadSentryNativeSymbolsTask.hookWithAssembleTasks(project, this) - } else { - project.logger.info { "uploadSentryNativeSymbols won't be executed" } - } + if (!isDebuggable && extension.uploadNativeSymbols.get()) { + val sentryProps = SentryPropertiesFileProvider.getPropertiesFilePath(project, this) + // Setup the task to upload native symbols task after the assembling task + val uploadSentryNativeSymbolsTask = + SentryUploadNativeSymbolsTask.register( + project = project, + extension = extension, + sentryTelemetryProvider = sentryTelemetryProvider, + variantName = name, + debug = extension.debug, + cliExecutable = cliExecutable, + sentryProperties = sentryProps, + autoUploadNativeSymbols = extension.autoUploadNativeSymbols, + includeNativeSources = extension.includeNativeSources, + sentryOrg = sentryOrg?.let { project.provider { it } } ?: extension.org, + sentryProject = sentryProject?.let { project.provider { it } } ?: extension.projectName, + sentryAuthToken = extension.authToken, + sentryUrl = extension.url, + taskSuffix = name.capitalized, + ) + uploadSentryNativeSymbolsTask.hookWithAssembleTasks(project, this) + } else { + project.logger.info { "uploadSentryNativeSymbols won't be executed" } + } } 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 21c428db..dca4d7c8 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 @@ -20,121 +20,118 @@ import org.gradle.api.tasks.TaskProvider abstract class SentryUploadProguardMappingsTask : SentryCliExecTask() { - init { - description = "Uploads the proguard mappings file to Sentry" - - // Allows gradle to consider this task up-to-date if the inputs haven't changed - // As this task does not have any outputs, it will always be considered to be out-of-date otherwise - // More info here https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes - // and https://docs.gradle.org/current/userguide/incremental_build.html#sec:custom_up_to_date_logic - outputs.upToDateWhen { true } - } + init { + description = "Uploads the proguard mappings file to Sentry" - @get:InputFile - abstract val uuidFile: RegularFileProperty + // Allows gradle to consider this task up-to-date if the inputs haven't changed + // As this task does not have any outputs, it will always be considered to be out-of-date + // otherwise + // More info here + // https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes + // and + // https://docs.gradle.org/current/userguide/incremental_build.html#sec:custom_up_to_date_logic + outputs.upToDateWhen { true } + } - @get:InputFiles - abstract var mappingsFiles: Provider + @get:InputFile abstract val uuidFile: RegularFileProperty - @get:Input - abstract val autoUploadProguardMapping: Property + @get:InputFiles abstract var mappingsFiles: Provider - @get:Input - abstract val releaseInfo: Property + @get:Input abstract val autoUploadProguardMapping: Property - override fun exec() { - if (!mappingsFiles.isPresent || mappingsFiles.get().isEmpty) { - error("[sentry] Mapping files are missing!") - } - super.exec() - } + @get:Input abstract val releaseInfo: Property - override fun getArguments(args: MutableList) { - val uuid = readUuidFromFile(uuidFile.get().asFile) - val firstExistingFile = mappingsFiles.get().files.firstOrNull { it.exists() } - - val mappingFile = if (firstExistingFile == null) { - logger.warn( - "None of the provided mappingFiles was found on disk. " + - "Upload is most likely going to be skipped" - ) - mappingsFiles.get().files.first() - } else { - firstExistingFile - } - - args.add("upload-proguard") - args.add("--uuid") - args.add(uuid) - args.add(mappingFile.toString()) - - if (!autoUploadProguardMapping.get()) { - args.add("--no-upload") - } - - releaseInfo.get().let { - it.versionCode?.let { versionCode -> - args.add("--version-code") - args.add(versionCode.toString()) - } - args.add("--app-id") - args.add(it.applicationId) - args.add("--version") - args.add(it.versionName) - } + 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() } + + val mappingFile = + if (firstExistingFile == null) { + logger.warn( + "None of the provided mappingFiles was found on disk. " + + "Upload is most likely going to be skipped" + ) + mappingsFiles.get().files.first() + } else { + firstExistingFile + } + + args.add("upload-proguard") + args.add("--uuid") + args.add(uuid) + args.add(mappingFile.toString()) + + if (!autoUploadProguardMapping.get()) { + args.add("--no-upload") } - companion object { - internal fun readUuidFromFile(file: File): String { - val props = PropertiesUtil.load(file) - val uuid: String? = props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - check(uuid != null) { - "$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY property is missing" - } - return uuid - } + releaseInfo.get().let { + it.versionCode?.let { versionCode -> + args.add("--version-code") + args.add(versionCode.toString()) + } + args.add("--app-id") + args.add(it.applicationId) + args.add("--version") + args.add(it.versionName) + } + } + + companion object { + internal fun readUuidFromFile(file: File): String { + val props = PropertiesUtil.load(file) + val uuid: String? = props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) + check(uuid != null) { "$SENTRY_PROGUARD_MAPPING_UUID_PROPERTY property is missing" } + return uuid + } - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider?, - debug: Property, - cliExecutable: Provider, - sentryProperties: String?, - generateUuidTask: Provider, - mappingFiles: Provider, - sentryOrg: Provider, - sentryProject: Provider, - sentryAuthToken: Property, - sentryUrl: Property, - autoUploadProguardMapping: Property, - taskSuffix: String = "", - releaseInfo: ReleaseInfo - ): TaskProvider { - val uploadSentryProguardMappingsTask = project.tasks.register( - "uploadSentryProguardMappings$taskSuffix", - SentryUploadProguardMappingsTask::class.java - ) { task -> - task.dependsOn(generateUuidTask) - task.workingDir(project.rootDir) - task.debug.set(debug) - task.cliExecutable.set(cliExecutable) - task.sentryProperties.set( - sentryProperties?.let { file -> project.file(file) } - ) - task.uuidFile.set(generateUuidTask.flatMap { it.outputFile }) - task.mappingsFiles = mappingFiles - task.autoUploadProguardMapping.set(autoUploadProguardMapping) - task.sentryOrganization.set(sentryOrg) - task.sentryProject.set(sentryProject) - task.releaseInfo.set(releaseInfo) - task.sentryAuthToken.set(sentryAuthToken) - task.sentryUrl.set(sentryUrl) - sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } - task.asSentryCliExec() - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - return uploadSentryProguardMappingsTask + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, + debug: Property, + cliExecutable: Provider, + sentryProperties: String?, + generateUuidTask: Provider, + mappingFiles: Provider, + sentryOrg: Provider, + sentryProject: Provider, + sentryAuthToken: Property, + sentryUrl: Property, + autoUploadProguardMapping: Property, + taskSuffix: String = "", + releaseInfo: ReleaseInfo, + ): TaskProvider { + val uploadSentryProguardMappingsTask = + project.tasks.register( + "uploadSentryProguardMappings$taskSuffix", + SentryUploadProguardMappingsTask::class.java, + ) { task -> + task.dependsOn(generateUuidTask) + task.workingDir(project.rootDir) + task.debug.set(debug) + task.cliExecutable.set(cliExecutable) + task.sentryProperties.set(sentryProperties?.let { file -> project.file(file) }) + task.uuidFile.set(generateUuidTask.flatMap { it.outputFile }) + task.mappingsFiles = mappingFiles + task.autoUploadProguardMapping.set(autoUploadProguardMapping) + task.sentryOrganization.set(sentryOrg) + task.sentryProject.set(sentryProject) + task.releaseInfo.set(releaseInfo) + task.sentryAuthToken.set(sentryAuthToken) + task.sentryUrl.set(sentryUrl) + sentryTelemetryProvider?.let { task.sentryTelemetryService.set(it) } + task.asSentryCliExec() + task.withSentryTelemetry(extension, sentryTelemetryProvider) } + return uploadSentryProguardMappingsTask } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTask.kt index 16de1085..23947847 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTask.kt @@ -26,80 +26,71 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class SentryExternalDependenciesReportTask : DirectoryOutputTask() { - @get:Input - abstract val includeReport: Property + @get:Input abstract val includeReport: Property - init { - description = "Generates an external dependencies report" + init { + description = "Generates an external dependencies report" - if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_4) { - @Suppress("LeakingThis") - notCompatibleWithConfigurationCache("Cannot serialize Configurations") - } - @Suppress("LeakingThis") - onlyIf { includeReport.get() } + if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_4) { + @Suppress("LeakingThis") + notCompatibleWithConfigurationCache("Cannot serialize Configurations") } + @Suppress("LeakingThis") onlyIf { includeReport.get() } + } - @Transient - private lateinit var runtimeConfiguration: Configuration + @Transient private lateinit var runtimeConfiguration: Configuration - fun setRuntimeConfiguration(configuration: Configuration) { - runtimeConfiguration = configuration - } + fun setRuntimeConfiguration(configuration: Configuration) { + runtimeConfiguration = configuration + } - @get:Input - abstract val attributeValueJar: Property + @get:Input abstract val attributeValueJar: Property - // this is a proper input, so our task gets triggered whenever the dependency set changes - @PathSensitive(PathSensitivity.NAME_ONLY) - @InputFiles - fun getRuntimeClasspath(): FileCollection = runtimeConfiguration.artifactsFor( - attributeValueJar.get() - ).artifactFiles + // this is a proper input, so our task gets triggered whenever the dependency set changes + @PathSensitive(PathSensitivity.NAME_ONLY) + @InputFiles + fun getRuntimeClasspath(): FileCollection = + runtimeConfiguration.artifactsFor(attributeValueJar.get()).artifactFiles - @TaskAction - fun action() { - val outputDir = output.get().asFile - outputDir.mkdirs() + @TaskAction + fun action() { + val outputDir = output.get().asFile + outputDir.mkdirs() - val dependencies = runtimeConfiguration - .incoming - .resolutionResult - .allComponents - // we're only interested in external deps - .filter { it.id is ModuleComponentIdentifier } - // and those that have proper version defined (e.g. flat jars don't have it) - .filter { it.moduleVersion?.version?.isNotEmpty() == true } - .map { it.id.displayName } - .toSortedSet() + val dependencies = + runtimeConfiguration.incoming.resolutionResult.allComponents + // we're only interested in external deps + .filter { it.id is ModuleComponentIdentifier } + // and those that have proper version defined (e.g. flat jars don't have it) + .filter { it.moduleVersion?.version?.isNotEmpty() == true } + .map { it.id.displayName } + .toSortedSet() - val outputFile = File(outputDir, SENTRY_DEPENDENCIES_REPORT_OUTPUT) - outputFile.writeText(dependencies.joinToString("\n")) - } + val outputFile = File(outputDir, SENTRY_DEPENDENCIES_REPORT_OUTPUT) + outputFile.writeText(dependencies.joinToString("\n")) + } - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - configurationName: String, - attributeValueJar: String, - output: Provider?, - includeReport: Provider, - taskSuffix: String = "" - ): TaskProvider { - return project.tasks.register( - "collectExternal${taskSuffix}DependenciesForSentry", - SentryExternalDependenciesReportTask::class.java - ) { task -> - task.includeReport.set(includeReport) - task.attributeValueJar.set(attributeValueJar) - task.setRuntimeConfiguration( - project.configurations.getByName(configurationName) - ) - output?.let { task.output.set(it) } - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - } + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + configurationName: String, + attributeValueJar: String, + output: Provider?, + includeReport: Provider, + taskSuffix: String = "", + ): TaskProvider { + return project.tasks.register( + "collectExternal${taskSuffix}DependenciesForSentry", + SentryExternalDependenciesReportTask::class.java, + ) { task -> + task.includeReport.set(includeReport) + task.attributeValueJar.set(attributeValueJar) + task.setRuntimeConfiguration(project.configurations.getByName(configurationName)) + output?.let { task.output.set(it) } + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskFactory.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskFactory.kt index 0448ba90..35ceae42 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskFactory.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskFactory.kt @@ -11,42 +11,42 @@ import org.gradle.api.tasks.TaskProvider object SentryExternalDependenciesReportTaskFactory { - internal const val SENTRY_DEPENDENCIES_REPORT_OUTPUT = "sentry-external-modules.txt" + internal const val SENTRY_DEPENDENCIES_REPORT_OUTPUT = "sentry-external-modules.txt" - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - configurationName: String, - attributeValueJar: String, - includeReport: Provider, - output: Provider? = null, - taskSuffix: String = "" - ): TaskProvider { - // gradle 7.5 supports passing configuration resolution as task input and respects config - // cache, so we have a different implementation from that version onwards - return if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_5) { - SentryExternalDependenciesReportTaskV2.register( - project, - extension, - sentryTelemetryProvider, - configurationName, - attributeValueJar, - output, - includeReport, - taskSuffix - ) - } else { - SentryExternalDependenciesReportTask.register( - project, - extension, - sentryTelemetryProvider, - configurationName, - attributeValueJar, - output, - includeReport, - taskSuffix - ) - } + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + configurationName: String, + attributeValueJar: String, + includeReport: Provider, + output: Provider? = null, + taskSuffix: String = "", + ): TaskProvider { + // gradle 7.5 supports passing configuration resolution as task input and respects config + // cache, so we have a different implementation from that version onwards + return if (GradleVersions.CURRENT >= GradleVersions.VERSION_7_5) { + SentryExternalDependenciesReportTaskV2.register( + project, + extension, + sentryTelemetryProvider, + configurationName, + attributeValueJar, + output, + includeReport, + taskSuffix, + ) + } else { + SentryExternalDependenciesReportTask.register( + project, + extension, + sentryTelemetryProvider, + configurationName, + attributeValueJar, + output, + includeReport, + taskSuffix, + ) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskV2.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskV2.kt index 434f502a..568c4b1b 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskV2.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskV2.kt @@ -21,60 +21,59 @@ import org.gradle.api.tasks.TaskProvider @CacheableTask abstract class SentryExternalDependenciesReportTaskV2 : DirectoryOutputTask() { - @get:Input - abstract val includeReport: Property + @get:Input abstract val includeReport: Property - init { - description = "Generates an external dependencies report" + init { + description = "Generates an external dependencies report" - @Suppress("LeakingThis") - onlyIf { includeReport.get() } - } + @Suppress("LeakingThis") onlyIf { includeReport.get() } + } - @get:Input - abstract val artifactIds: SetProperty + @get:Input abstract val artifactIds: SetProperty - @TaskAction - fun action() { - val outputDir = output.get().asFile - outputDir.mkdirs() + @TaskAction + fun action() { + val outputDir = output.get().asFile + outputDir.mkdirs() - val dependencies = artifactIds.get().toSortedSet() + val dependencies = artifactIds.get().toSortedSet() - val outputFile = File(outputDir, SENTRY_DEPENDENCIES_REPORT_OUTPUT) - outputFile.writeText(dependencies.joinToString("\n")) - } + val outputFile = File(outputDir, SENTRY_DEPENDENCIES_REPORT_OUTPUT) + outputFile.writeText(dependencies.joinToString("\n")) + } - companion object { - fun register( - project: Project, - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider, - configurationName: String, - attributeValueJar: String, - output: Provider?, - includeReport: Provider, - taskSuffix: String = "" - ): TaskProvider { - return project.tasks.register( - "collectExternal${taskSuffix}DependenciesForSentry", - SentryExternalDependenciesReportTaskV2::class.java - ) { task -> - val configuration = project.configurations.getByName(configurationName) - val artifacts = configuration.artifactsFor(attributeValueJar).resolvedArtifacts - val artifactIds = artifacts.map { list -> - list.map { artifact -> artifact.id.componentIdentifier } - // we're only interested in external deps - .filterIsInstance() - // and those that have proper version defined (e.g. flat jars don't have it) - .filter { id -> id.version.isNotEmpty() } - .map { id -> id.displayName } - } - task.artifactIds.set(artifactIds) - task.includeReport.set(includeReport) - output?.let { task.output.set(it) } - task.withSentryTelemetry(extension, sentryTelemetryProvider) - } - } + companion object { + fun register( + project: Project, + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider, + configurationName: String, + attributeValueJar: String, + output: Provider?, + includeReport: Provider, + taskSuffix: String = "", + ): TaskProvider { + return project.tasks.register( + "collectExternal${taskSuffix}DependenciesForSentry", + SentryExternalDependenciesReportTaskV2::class.java, + ) { task -> + val configuration = project.configurations.getByName(configurationName) + val artifacts = configuration.artifactsFor(attributeValueJar).resolvedArtifacts + val artifactIds = + artifacts.map { list -> + list + .map { artifact -> artifact.id.componentIdentifier } + // we're only interested in external deps + .filterIsInstance() + // and those that have proper version defined (e.g. flat jars don't have it) + .filter { id -> id.version.isNotEmpty() } + .map { id -> id.displayName } + } + task.artifactIds.set(artifactIds) + task.includeReport.set(includeReport) + output?.let { task.output.set(it) } + task.withSentryTelemetry(extension, sentryTelemetryProvider) + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt index 4b74b8ed..64536981 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryService.kt @@ -53,524 +53,464 @@ import org.gradle.internal.operations.OperationStartEvent import org.gradle.process.ExecOperations import org.gradle.util.GradleVersion -abstract class SentryTelemetryService : - BuildService, - BuildOperationListener, - AutoCloseable { - - private var hub: IHub = NoOpHub.getInstance() - private var transaction: ITransaction? = null - private var didAddChildSpans: Boolean = false - private var started: Boolean = false - - @Synchronized - fun start(paramsCallback: () -> SentryTelemetryServiceParams) { - if (started) { - return - } - val startParameters = paramsCallback() +abstract class SentryTelemetryService : BuildService, BuildOperationListener, AutoCloseable { - try { - if (startParameters.saas == false) { - SentryPlugin.logger.info { - "Sentry is running against a self hosted instance. " + - "Telemetry has been disabled." - } - hub = NoOpHub.getInstance() - } else if (!startParameters.sendTelemetry) { - SentryPlugin.logger.info { "Sentry telemetry has been disabled." } - hub = NoOpHub.getInstance() - } else { - if (!Sentry.isEnabled()) { - SentryPlugin.logger.info { - "Sentry telemetry is enabled. To disable set `telemetry=false` " + - "in the sentry config block." - } - Sentry.init { options -> - options.dsn = startParameters.dsn - options.isDebug = startParameters.isDebug - options.isEnablePrettySerializationOutput = false - options.tracesSampleRate = 1.0 - options.release = BuildConfig.Version - options.isSendModules = false - options.environment = startParameters.buildType - options.setTag("SDK_VERSION", BuildConfig.SdkVersion) - options.setTag("BUILD_SYSTEM", "gradle") - options.setTag("GRADLE_VERSION", GradleVersion.current().version) - startParameters.cliVersion?.let { - options.setTag("SENTRY_CLI_VERSION", it) - } - - startParameters.extraTags.forEach { (key, value) -> - options.setTag( - key, - value - ) - } - - try { - options.setTag("AGP_VERSION", AgpVersions.CURRENT.toString()) - } catch (t: Throwable) {} - } - } - hub = Sentry.getCurrentHub() - startRun("gradle build ${startParameters.buildType}") - - hub.configureScope { scope -> - scope.user = User().also { user -> - startParameters.defaultSentryOrganization?.let { org -> - if (org != "-") { - user.id = org - } - } - startParameters.sentryOrganization?.let { user.id = it } - } - } + private var hub: IHub = NoOpHub.getInstance() + private var transaction: ITransaction? = null + private var didAddChildSpans: Boolean = false + private var started: Boolean = false - started = true - } - } catch (t: Throwable) { - SentryPlugin.logger.error(t) { "Sentry failed to initialize." } - } + @Synchronized + fun start(paramsCallback: () -> SentryTelemetryServiceParams) { + if (started) { + return } + val startParameters = paramsCallback() - override fun started(descriptor: BuildOperationDescriptor, event: OperationStartEvent) {} - - override fun progress(identifier: OperationIdentifier, event: OperationProgressEvent) {} - - override fun finished( - buildOperationDescriptor: BuildOperationDescriptor, - operationFinishEvent: OperationFinishEvent - ) { - val details = buildOperationDescriptor.details - - operationFinishEvent.failure?.let { error -> - if (isSentryError(error, details)) { - captureError(error, "build") - transaction?.status = SpanStatus.UNKNOWN_ERROR + try { + if (startParameters.saas == false) { + SentryPlugin.logger.info { + "Sentry is running against a self hosted instance. " + "Telemetry has been disabled." + } + hub = NoOpHub.getInstance() + } else if (!startParameters.sendTelemetry) { + SentryPlugin.logger.info { "Sentry telemetry has been disabled." } + hub = NoOpHub.getInstance() + } else { + if (!Sentry.isEnabled()) { + SentryPlugin.logger.info { + "Sentry telemetry is enabled. To disable set `telemetry=false` " + + "in the sentry config block." + } + Sentry.init { options -> + options.dsn = startParameters.dsn + options.isDebug = startParameters.isDebug + options.isEnablePrettySerializationOutput = false + options.tracesSampleRate = 1.0 + options.release = BuildConfig.Version + options.isSendModules = false + options.environment = startParameters.buildType + options.setTag("SDK_VERSION", BuildConfig.SdkVersion) + options.setTag("BUILD_SYSTEM", "gradle") + options.setTag("GRADLE_VERSION", GradleVersion.current().version) + startParameters.cliVersion?.let { options.setTag("SENTRY_CLI_VERSION", it) } + + startParameters.extraTags.forEach { (key, value) -> options.setTag(key, value) } + + try { + options.setTag("AGP_VERSION", AgpVersions.CURRENT.toString()) + } catch (t: Throwable) {} + } + } + hub = Sentry.getCurrentHub() + startRun("gradle build ${startParameters.buildType}") + + hub.configureScope { scope -> + scope.user = + User().also { user -> + startParameters.defaultSentryOrganization?.let { org -> + if (org != "-") { + user.id = org + } + } + startParameters.sentryOrganization?.let { user.id = it } } } - if (details is RunRootBuildWorkBuildOperationType.Details) { - endRun() - } + started = true + } + } catch (t: Throwable) { + SentryPlugin.logger.error(t) { "Sentry failed to initialize." } } + } - private fun isSentryError(throwable: Throwable, details: Any?): Boolean { - val isSentryTaskName = (details as? ExecuteTaskBuildOperationDetails) - ?.let { - it.task.name.substringAfterLast(":") - .contains("sentry", ignoreCase = true) - } ?: false - return isSentryTaskName || - throwable.stackTrace.any { - it.className.startsWith("io.sentry") && - !( - it.className.contains("test", ignoreCase = true) || - it.className.contains("rule", ignoreCase = true) - ) - } - } + override fun started(descriptor: BuildOperationDescriptor, event: OperationStartEvent) {} - fun captureError(exception: Throwable, operation: String?) { - val message = if (exception is SentryCliException) { - "$operation failed with SentryCliException and reason ${exception.reason}" - } else { - "$operation failed with ${exception.javaClass}" - } + override fun progress(identifier: OperationIdentifier, event: OperationProgressEvent) {} - val mechanism = Mechanism().also { - it.type = MECHANISM_TYPE - it.isHandled = false - } - val mechanismException: Throwable = - ExceptionMechanismException( - mechanism, - SentryMinimalException(message), - Thread.currentThread() - ) - val event = SentryEvent(mechanismException).also { - it.level = SentryLevel.FATAL - } - hub.captureEvent(event) - } + override fun finished( + buildOperationDescriptor: BuildOperationDescriptor, + operationFinishEvent: OperationFinishEvent, + ) { + val details = buildOperationDescriptor.details - fun startRun(transactionName: String) { - hub.startSession() - val options = TransactionOptions() - options.isBindToScope = true - transaction = hub.startTransaction(transactionName, "build", options) + operationFinishEvent.failure?.let { error -> + if (isSentryError(error, details)) { + captureError(error, "build") + transaction?.status = SpanStatus.UNKNOWN_ERROR + } } - fun endRun() { - if (didAddChildSpans) { - transaction?.finish() - hub.endSession() - } + if (details is RunRootBuildWorkBuildOperationType.Details) { + endRun() } - - fun traceCli(): List { - val args = mutableListOf() - hub.traceparent?.let { header -> - args.add("--header") - args.add("${header.name}:${header.value}") - } - hub.baggage?.let { header -> - args.add("--header") - args.add("${header.name}:${header.value}") - } - return args + } + + private fun isSentryError(throwable: Throwable, details: Any?): Boolean { + val isSentryTaskName = + (details as? ExecuteTaskBuildOperationDetails)?.let { + it.task.name.substringAfterLast(":").contains("sentry", ignoreCase = true) + } ?: false + return isSentryTaskName || + throwable.stackTrace.any { + it.className.startsWith("io.sentry") && + !(it.className.contains("test", ignoreCase = true) || + it.className.contains("rule", ignoreCase = true)) + } + } + + fun captureError(exception: Throwable, operation: String?) { + val message = + if (exception is SentryCliException) { + "$operation failed with SentryCliException and reason ${exception.reason}" + } else { + "$operation failed with ${exception.javaClass}" + } + + val mechanism = + Mechanism().also { + it.type = MECHANISM_TYPE + it.isHandled = false + } + val mechanismException: Throwable = + ExceptionMechanismException( + mechanism, + SentryMinimalException(message), + Thread.currentThread(), + ) + val event = SentryEvent(mechanismException).also { it.level = SentryLevel.FATAL } + hub.captureEvent(event) + } + + fun startRun(transactionName: String) { + hub.startSession() + val options = TransactionOptions() + options.isBindToScope = true + transaction = hub.startTransaction(transactionName, "build", options) + } + + fun endRun() { + if (didAddChildSpans) { + transaction?.finish() + hub.endSession() } + } - fun startTask(operation: String): ISpan? { - didAddChildSpans = true - hub.setTag("step", operation) - return hub.span?.startChild(operation) + fun traceCli(): List { + val args = mutableListOf() + hub.traceparent?.let { header -> + args.add("--header") + args.add("${header.name}:${header.value}") } - - fun endTask(span: ISpan?, task: Task) { - span?.let { span -> - task.state.failure?.let { throwable -> - captureError(throwable, span.operation) - span.status = SpanStatus.UNKNOWN_ERROR - } - - span.finish() - } + hub.baggage?.let { header -> + args.add("--header") + args.add("${header.name}:${header.value}") } + return args + } + + fun startTask(operation: String): ISpan? { + didAddChildSpans = true + hub.setTag("step", operation) + return hub.span?.startChild(operation) + } + + fun endTask(span: ISpan?, task: Task) { + span?.let { span -> + task.state.failure?.let { throwable -> + captureError(throwable, span.operation) + span.status = SpanStatus.UNKNOWN_ERROR + } + + span.finish() + } + } - override fun close() { - if (transaction?.isFinished == false) { - endRun() - } - Sentry.close() + override fun close() { + if (transaction?.isFinished == false) { + endRun() + } + Sentry.close() + } + + companion object { + val SENTRY_SAAS_DSN: String = + "https://000e5dea9770b4537055f8a6d28c021e@o1.ingest.sentry.io/4506241308295168" + val MECHANISM_TYPE: String = "GradleTelemetry" + private val orgRegex = Regex("""(?m)Default Organization: (.*)$""") + private val versionRegex = Regex("""(?m)sentry-cli (.*)$""") + + fun createParameters( + project: Project, + variant: SentryVariant?, + extension: SentryPluginExtension, + cliExecutable: Provider, + sentryOrg: String?, + buildType: String, + ): SentryTelemetryServiceParams { + val tags = extraTagsFromExtension(project, extension) + val org = sentryOrg ?: extension.org.orNull + val isTelemetryEnabled = extension.telemetry.get() + + // if telemetry is disabled we don't even need to exec sentry-cli as telemetry service + // will be no-op + if (isExecAvailable() && isTelemetryEnabled) { + paramsWithExecAvailable(project, cliExecutable, extension, variant, org, buildType, tags) + ?.let { + return it + } + } + // fallback: sentry-cli is not available or e.g. auth token is not configured + return SentryTelemetryServiceParams( + isTelemetryEnabled, + extension.telemetryDsn.get(), + org, + buildType, + tags, + extension.debug.get(), + saas = extension.url.orNull == null, + cliVersion = BuildConfig.CliVersion, + ) } - companion object { - val SENTRY_SAAS_DSN: String = - "https://000e5dea9770b4537055f8a6d28c021e@o1.ingest.sentry.io/4506241308295168" - val MECHANISM_TYPE: String = "GradleTelemetry" - private val orgRegex = Regex("""(?m)Default Organization: (.*)$""") - private val versionRegex = Regex("""(?m)sentry-cli (.*)$""") - - fun createParameters( - project: Project, - variant: SentryVariant?, - extension: SentryPluginExtension, - cliExecutable: Provider, - sentryOrg: String?, - buildType: String - ): SentryTelemetryServiceParams { - val tags = extraTagsFromExtension(project, extension) - val org = sentryOrg ?: extension.org.orNull - val isTelemetryEnabled = extension.telemetry.get() - - // if telemetry is disabled we don't even need to exec sentry-cli as telemetry service - // will be no-op - if (isExecAvailable() && isTelemetryEnabled) { - paramsWithExecAvailable( - project, - cliExecutable, - extension, - variant, - org, - buildType, - tags - )?.let { - return it - } + private fun paramsWithExecAvailable( + project: Project, + cliExecutable: Provider, + extension: SentryPluginExtension, + variant: SentryVariant?, + sentryOrg: String?, + buildType: String, + tags: Map, + ): SentryTelemetryServiceParams? { + var cliVersion: String? = BuildConfig.CliVersion + var defaultSentryOrganization: String? = null + val infoOutput = + project.providers + .of(SentryCliInfoValueSource::class.java) { cliVS -> + cliVS.parameters.buildDirectory.set(project.buildDir) + cliVS.parameters.cliExecutable.set(cliExecutable) + cliVS.parameters.authToken.set(extension.authToken) + cliVS.parameters.url.set(extension.url) + variant?.let { v -> + cliVS.parameters.propertiesFilePath.set( + SentryPropertiesFileProvider.getPropertiesFilePath(project, v) + ) } - // fallback: sentry-cli is not available or e.g. auth token is not configured - return SentryTelemetryServiceParams( - isTelemetryEnabled, - extension.telemetryDsn.get(), - org, - buildType, - tags, - extension.debug.get(), - saas = extension.url.orNull == null, - cliVersion = BuildConfig.CliVersion - ) + } + .get() + + if (infoOutput.isEmpty()) { + return null + } + val isSaas = infoOutput.contains("(?m)Sentry Server: .*sentry.io$".toRegex()) + + orgRegex.find(infoOutput)?.let { matchResult -> + val groupValues = matchResult.groupValues + if (groupValues.size > 1) { + defaultSentryOrganization = groupValues[1] } - - private fun paramsWithExecAvailable( - project: Project, - cliExecutable: Provider, - extension: SentryPluginExtension, - variant: SentryVariant?, - sentryOrg: String?, - buildType: String, - tags: Map - ): SentryTelemetryServiceParams? { - var cliVersion: String? = BuildConfig.CliVersion - var defaultSentryOrganization: String? = null - val infoOutput = project.providers.of(SentryCliInfoValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.buildDir) - cliVS.parameters.cliExecutable.set(cliExecutable) - cliVS.parameters.authToken.set(extension.authToken) - cliVS.parameters.url.set(extension.url) - variant?.let { v -> - cliVS.parameters.propertiesFilePath.set( - SentryPropertiesFileProvider.getPropertiesFilePath(project, v) - ) - } - }.get() - - if (infoOutput.isEmpty()) { - return null - } - val isSaas = infoOutput.contains("(?m)Sentry Server: .*sentry.io$".toRegex()) - - orgRegex.find(infoOutput)?.let { matchResult -> - val groupValues = matchResult.groupValues - if (groupValues.size > 1) { - defaultSentryOrganization = groupValues[1] - } - } - - val versionOutput = - project.providers.of(SentryCliVersionValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.buildDir) - cliVS.parameters.cliExecutable.set(cliExecutable) - cliVS.parameters.url.set(extension.url) - }.get() - - versionRegex.find(versionOutput)?.let { matchResult -> - val groupValues = matchResult.groupValues - if (groupValues.size > 1) { - cliVersion = groupValues[1] - } - } - - return SentryTelemetryServiceParams( - extension.telemetry.get(), - extension.telemetryDsn.get(), - sentryOrg, - buildType, - tags, - extension.debug.get(), - defaultSentryOrganization, - isSaas, - cliVersion = cliVersion - ) + } + + val versionOutput = + project.providers + .of(SentryCliVersionValueSource::class.java) { cliVS -> + cliVS.parameters.buildDirectory.set(project.buildDir) + cliVS.parameters.cliExecutable.set(cliExecutable) + cliVS.parameters.url.set(extension.url) + } + .get() + + versionRegex.find(versionOutput)?.let { matchResult -> + val groupValues = matchResult.groupValues + if (groupValues.size > 1) { + cliVersion = groupValues[1] } + } + + return SentryTelemetryServiceParams( + extension.telemetry.get(), + extension.telemetryDsn.get(), + sentryOrg, + buildType, + tags, + extension.debug.get(), + defaultSentryOrganization, + isSaas, + cliVersion = cliVersion, + ) + } - fun register(project: Project): Provider { - return project.gradle.sharedServices.registerIfAbsent( - getBuildServiceName(SentryTelemetryService::class.java), - SentryTelemetryService::class.java - ) {} - } + fun register(project: Project): Provider { + return project.gradle.sharedServices.registerIfAbsent( + getBuildServiceName(SentryTelemetryService::class.java), + SentryTelemetryService::class.java, + ) {} + } - private fun isExecAvailable(): Boolean { - return GradleVersions.CURRENT >= GradleVersions.VERSION_7_5 - } + private fun isExecAvailable(): Boolean { + return GradleVersions.CURRENT >= GradleVersions.VERSION_7_5 + } - private fun extraTagsFromExtension( - project: Project, - extension: SentryPluginExtension - ): Map { - val tags = mutableMapOf() - - tags.put("debug", extension.debug.get().toString()) - tags.put( - "includeProguardMapping", - extension.includeProguardMapping.get().toString() - ) - tags.put( - "autoUploadProguardMapping", - extension.autoUploadProguardMapping.get().toString() - ) - tags.put("autoUpload", extension.autoUpload.get().toString()) - tags.put("uploadNativeSymbols", extension.uploadNativeSymbols.get().toString()) - tags.put( - "autoUploadNativeSymbols", - extension.autoUploadNativeSymbols.get().toString() - ) - tags.put("includeNativeSources", extension.includeNativeSources.get().toString()) - tags.put( - "ignoredVariants_set", - extension.ignoredVariants.get().isNotEmpty().toString() - ) - tags.put( - "ignoredBuildTypes_set", - extension.ignoredBuildTypes.get().isNotEmpty().toString() - ) - tags.put( - "ignoredFlavors_set", - extension.ignoredFlavors.get().isNotEmpty().toString() - ) - tags.put( - "dexguardEnabled", - extension.dexguardEnabled.get().toString() - ) - tags.put( - "tracing_enabled", - extension.tracingInstrumentation.enabled.get().toString() - ) - tags.put( - "tracing_debug", - extension.tracingInstrumentation.debug.get().toString() - ) - tags.put( - "tracing_forceInstrumentDependencies", - extension.tracingInstrumentation.forceInstrumentDependencies.get().toString() - ) - tags.put( - "tracing_features", - extension.tracingInstrumentation.features.get().toString() - ) - tags.put( - "tracing_logcat_enabled", - extension.tracingInstrumentation.logcat.enabled.get().toString() - ) - tags.put( - "tracing_logcat_minLevel", - extension.tracingInstrumentation.logcat.minLevel.get().toString() - ) - tags.put( - "autoInstallation_enabled", - extension.autoInstallation.enabled.get().toString() - ) - tags.put( - "autoInstallation_sentryVersion", - extension.autoInstallation.sentryVersion.get().toString() - ) - tags.put( - "includeDependenciesReport", - extension.includeDependenciesReport.get().toString() - ) - tags.put("includeSourceContext", extension.includeSourceContext.get().toString()) - tags.put( - "additionalSourceDirsForSourceContext_set", - extension.additionalSourceDirsForSourceContext.get().isNotEmpty().toString() - ) - // TODO PII? -// extension.projectName.orNull?.let { tags.put("projectName", it) } - - return tags - } + private fun extraTagsFromExtension( + project: Project, + extension: SentryPluginExtension, + ): Map { + val tags = mutableMapOf() + + tags.put("debug", extension.debug.get().toString()) + tags.put("includeProguardMapping", extension.includeProguardMapping.get().toString()) + tags.put("autoUploadProguardMapping", extension.autoUploadProguardMapping.get().toString()) + tags.put("autoUpload", extension.autoUpload.get().toString()) + tags.put("uploadNativeSymbols", extension.uploadNativeSymbols.get().toString()) + tags.put("autoUploadNativeSymbols", extension.autoUploadNativeSymbols.get().toString()) + tags.put("includeNativeSources", extension.includeNativeSources.get().toString()) + tags.put("ignoredVariants_set", extension.ignoredVariants.get().isNotEmpty().toString()) + tags.put("ignoredBuildTypes_set", extension.ignoredBuildTypes.get().isNotEmpty().toString()) + tags.put("ignoredFlavors_set", extension.ignoredFlavors.get().isNotEmpty().toString()) + tags.put("dexguardEnabled", extension.dexguardEnabled.get().toString()) + tags.put("tracing_enabled", extension.tracingInstrumentation.enabled.get().toString()) + tags.put("tracing_debug", extension.tracingInstrumentation.debug.get().toString()) + tags.put( + "tracing_forceInstrumentDependencies", + extension.tracingInstrumentation.forceInstrumentDependencies.get().toString(), + ) + tags.put("tracing_features", extension.tracingInstrumentation.features.get().toString()) + tags.put( + "tracing_logcat_enabled", + extension.tracingInstrumentation.logcat.enabled.get().toString(), + ) + tags.put( + "tracing_logcat_minLevel", + extension.tracingInstrumentation.logcat.minLevel.get().toString(), + ) + tags.put("autoInstallation_enabled", extension.autoInstallation.enabled.get().toString()) + tags.put( + "autoInstallation_sentryVersion", + extension.autoInstallation.sentryVersion.get().toString(), + ) + tags.put("includeDependenciesReport", extension.includeDependenciesReport.get().toString()) + tags.put("includeSourceContext", extension.includeSourceContext.get().toString()) + tags.put( + "additionalSourceDirsForSourceContext_set", + extension.additionalSourceDirsForSourceContext.get().isNotEmpty().toString(), + ) + // TODO PII? + // extension.projectName.orNull?.let { tags.put("projectName", it) } + + return tags } + } } class SentryMinimalException(message: String) : RuntimeException(message) { - override fun getStackTrace(): Array { - val superStackTrace = super.getStackTrace() - return if (superStackTrace.isEmpty()) superStackTrace else arrayOf(superStackTrace[0]) - } + override fun getStackTrace(): Array { + val superStackTrace = super.getStackTrace() + return if (superStackTrace.isEmpty()) superStackTrace else arrayOf(superStackTrace[0]) + } } abstract class SentryCliInfoValueSource : ValueSource { - interface InfoParams : ValueSourceParameters { - @get:Input - val buildDirectory: Property + interface InfoParams : ValueSourceParameters { + @get:Input val buildDirectory: Property - @get:Input - val cliExecutable: Property + @get:Input val cliExecutable: Property - @get:Input - val propertiesFilePath: Property + @get:Input val propertiesFilePath: Property - @get:Input - val url: Property + @get:Input val url: Property - @get:Input - val authToken: Property - } - - @get:Inject - abstract val execOperations: ExecOperations + @get:Input val authToken: Property + } - override fun obtain(): String? { - val stdOutput = ByteArrayOutputStream() - val errOutput = ByteArrayOutputStream() + @get:Inject abstract val execOperations: ExecOperations - val execResult = execOperations.exec { - it.isIgnoreExitValue = true - SentryCliProvider.maybeExtractFromResources( - parameters.buildDirectory.get(), - parameters.cliExecutable.get() - ) + override fun obtain(): String? { + val stdOutput = ByteArrayOutputStream() + val errOutput = ByteArrayOutputStream() - val args = mutableListOf(parameters.cliExecutable.get()) - - parameters.url.orNull?.let { url -> - args.add("--url") - args.add(url) - } + val execResult = + execOperations.exec { + it.isIgnoreExitValue = true + SentryCliProvider.maybeExtractFromResources( + parameters.buildDirectory.get(), + parameters.cliExecutable.get(), + ) - args.add("--log-level=error") - args.add("info") + val args = mutableListOf(parameters.cliExecutable.get()) - parameters.propertiesFilePath.orNull?.let { path -> - it.environment("SENTRY_PROPERTIES", path) - } + parameters.url.orNull?.let { url -> + args.add("--url") + args.add(url) + } - parameters.authToken.orNull?.let { authToken -> - it.environment("SENTRY_AUTH_TOKEN", authToken) - } + args.add("--log-level=error") + args.add("info") - it.commandLine(args) - it.standardOutput = stdOutput - it.errorOutput = errOutput + parameters.propertiesFilePath.orNull?.let { path -> + it.environment("SENTRY_PROPERTIES", path) } - if (execResult.exitValue == 0) { - return String(stdOutput.toByteArray(), Charset.defaultCharset()) - } else { - logger.info { - "Failed to execute sentry-cli info. Error Output: " + - String(errOutput.toByteArray(), Charset.defaultCharset()) - } - return "" + parameters.authToken.orNull?.let { authToken -> + it.environment("SENTRY_AUTH_TOKEN", authToken) } + + it.commandLine(args) + it.standardOutput = stdOutput + it.errorOutput = errOutput + } + + if (execResult.exitValue == 0) { + return String(stdOutput.toByteArray(), Charset.defaultCharset()) + } else { + logger.info { + "Failed to execute sentry-cli info. Error Output: " + + String(errOutput.toByteArray(), Charset.defaultCharset()) + } + return "" } + } } abstract class SentryCliVersionValueSource : ValueSource { - interface VersionParams : ValueSourceParameters { - @get:Input - val buildDirectory: Property + interface VersionParams : ValueSourceParameters { + @get:Input val buildDirectory: Property - @get:Input - val cliExecutable: Property + @get:Input val cliExecutable: Property - @get:Input - val url: Property - } + @get:Input val url: Property + } - @get:Inject - abstract val execOperations: ExecOperations + @get:Inject abstract val execOperations: ExecOperations - override fun obtain(): String { - val output = ByteArrayOutputStream() - execOperations.exec { - it.isIgnoreExitValue = true - SentryCliProvider.maybeExtractFromResources( - parameters.buildDirectory.get(), - parameters.cliExecutable.get() - ) + override fun obtain(): String { + val output = ByteArrayOutputStream() + execOperations.exec { + it.isIgnoreExitValue = true + SentryCliProvider.maybeExtractFromResources( + parameters.buildDirectory.get(), + parameters.cliExecutable.get(), + ) - val args = mutableListOf(parameters.cliExecutable.get()) + val args = mutableListOf(parameters.cliExecutable.get()) - args.add("--log-level=error") - args.add("--version") + args.add("--log-level=error") + args.add("--version") - it.commandLine(args) - it.standardOutput = output - } - return String(output.toByteArray(), Charset.defaultCharset()) + it.commandLine(args) + it.standardOutput = output } + return String(output.toByteArray(), Charset.defaultCharset()) + } } data class SentryTelemetryServiceParams( - val sendTelemetry: Boolean, - val dsn: String, - val sentryOrganization: String?, - val buildType: String, - val extraTags: Map, - val isDebug: Boolean, - val defaultSentryOrganization: String? = null, - val saas: Boolean? = null, - val cliVersion: String? = null, + val sendTelemetry: Boolean, + val dsn: String, + val sentryOrganization: String?, + val buildType: String, + val extraTags: Map, + val isDebug: Boolean, + val defaultSentryOrganization: String? = null, + val saas: Boolean? = null, + val cliVersion: String? = null, ) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryTaskWrapper.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryTaskWrapper.kt index 5c80d810..0708c4cb 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryTaskWrapper.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryTaskWrapper.kt @@ -12,24 +12,23 @@ import org.gradle.api.provider.Provider * phase (=when registering a task). */ fun Task.withSentryTelemetry( - extension: SentryPluginExtension, - sentryTelemetryProvider: Provider? + extension: SentryPluginExtension, + sentryTelemetryProvider: Provider?, ) { - sentryTelemetryProvider?.let { usesService(it) } - val projectHash = CRC32().also { - it.update(this.project.name.toByteArray(Charset.defaultCharset())) - }.value - var sentrySpan: ISpan? = null - doFirst { - if (extension.telemetry.orNull != false) { - sentrySpan = sentryTelemetryProvider?.orNull - ?.startTask("${projectHash}_${this.javaClass.simpleName}") - } + sentryTelemetryProvider?.let { usesService(it) } + val projectHash = + CRC32().also { it.update(this.project.name.toByteArray(Charset.defaultCharset())) }.value + var sentrySpan: ISpan? = null + doFirst { + if (extension.telemetry.orNull != false) { + sentrySpan = + sentryTelemetryProvider?.orNull?.startTask("${projectHash}_${this.javaClass.simpleName}") } + } - doLast { - if (extension.telemetry.orNull != false) { - sentryTelemetryProvider?.orNull?.endTask(sentrySpan, this) - } + doLast { + if (extension.telemetry.orNull != false) { + sentryTelemetryProvider?.orNull?.endTask(sentrySpan, this) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransform.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransform.kt index b508824f..084aa66e 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransform.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransform.kt @@ -26,150 +26,146 @@ import org.gradle.api.tasks.PathSensitivity /** * Gradle's [TransformAction] that strips out unsupported Java classes from - * resources/META-INF/versions folder of a jar. This is the case for [Multi-Release JARs](https://openjdk.java.net/jeps/238), - * when a single jar contains classes for different Java versions. + * resources/META-INF/versions folder of a jar. This is the case for + * [Multi-Release JARs](https://openjdk.java.net/jeps/238), when a single jar contains classes for + * different Java versions. * * For Android it may have bad consequences, as the min supported Java version is 11 at the moment, * and this can cause incompatibilities, if AGP or other plugins erroneously consider .class files * under the META-INF folder during build-time transformations/instrumentation. * - * The minimum supported Java version of the latest AGP is 11 -> https://developer.android.com/studio/releases/gradle-plugin#7-1-0 + * The minimum supported Java version of the latest AGP is 11 -> + * https://developer.android.com/studio/releases/gradle-plugin#7-1-0 */ @CacheableTransform abstract class MetaInfStripTransform : TransformAction { - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:InputArtifact - abstract val inputArtifact: Provider + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputArtifact + abstract val inputArtifact: Provider - interface Parameters : TransformParameters { - // only used for development purposes - @get:Input - @get:Optional - val invalidate: Property - } + interface Parameters : TransformParameters { + // only used for development purposes + @get:Input @get:Optional val invalidate: Property + } - override fun transform(outputs: TransformOutputs) { - val input = inputArtifact.get().asFile - val jarFile = JarFile(input) - if (jarFile.isMultiRelease) { - val outputFilename = "${input.nameWithoutExtension}-meta-inf-stripped.jar" - val tmpOutputFile = File.createTempFile( - "sentry-transformed-${input.nameWithoutExtension}", - ".jar" - ) - var isStillMultiRelease = false - tmpOutputFile.jarOutputStream().use { outStream -> - val entries = jarFile.entries() - // copy each .jar entry, except those that are under META-INF/versions/${unsupported_java_version} - while (entries.hasMoreElements()) { - val jarEntry = entries.nextElement() - if (jarEntry.name.equals(JarFile.MANIFEST_NAME, ignoreCase = true)) { - // we deal with the manifest as a last step, since we need to know if there - // any multi-release entries remained - continue - } + override fun transform(outputs: TransformOutputs) { + val input = inputArtifact.get().asFile + val jarFile = JarFile(input) + if (jarFile.isMultiRelease) { + val outputFilename = "${input.nameWithoutExtension}-meta-inf-stripped.jar" + val tmpOutputFile = + File.createTempFile("sentry-transformed-${input.nameWithoutExtension}", ".jar") + var isStillMultiRelease = false + tmpOutputFile.jarOutputStream().use { outStream -> + val entries = jarFile.entries() + // copy each .jar entry, except those that are under + // META-INF/versions/${unsupported_java_version} + while (entries.hasMoreElements()) { + val jarEntry = entries.nextElement() + if (jarEntry.name.equals(JarFile.MANIFEST_NAME, ignoreCase = true)) { + // we deal with the manifest as a last step, since we need to know if there + // any multi-release entries remained + continue + } - if (jarEntry.isSignatureEntry) { - SentryPlugin.logger.warn { - """ + if (jarEntry.isSignatureEntry) { + SentryPlugin.logger.warn { + """ Signed Multirelease Jar (${jarFile.name}) found, skipping transform. This might lead to auto-instrumentation issues due to a bug in AGP (https://issuetracker.google.com/issues/206655905). Please update to AGP >= 7.1.2 (https://developer.android.com/studio/releases/gradle-plugin) in order to keep using `autoInstrumentation` option. - """.trimIndent() - } - tmpOutputFile.delete() - outputs.file(inputArtifact) - return - } - - if (jarEntry.name.startsWith(versionsDir, ignoreCase = true)) { - val javaVersion = jarEntry.javaVersion - if (javaVersion > MIN_SUPPORTED_JAVA_VERSION) { - continue - } else if (javaVersion > 0) { - isStillMultiRelease = true - } - } - outStream.putNextEntry(jarEntry) - jarFile.getInputStream(jarEntry).buffered().copyTo(outStream) - outStream.closeEntry() - } - - val manifest = jarFile.manifest - if (manifest != null) { - // write MANIFEST.MF as a last entry and modify Multi-Release attribute accordingly - manifest.mainAttributes.put( - Attributes.Name.MULTI_RELEASE, - isStillMultiRelease.toString() - ) - outStream.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME)) - manifest.write(outStream.buffered()) - outStream.closeEntry() - } + """ + .trimIndent() } - val transformedOutput = outputs.file(outputFilename) - Files.move(tmpOutputFile.toPath(), transformedOutput.toPath()) - } else { + tmpOutputFile.delete() outputs.file(inputArtifact) + return + } + + if (jarEntry.name.startsWith(versionsDir, ignoreCase = true)) { + val javaVersion = jarEntry.javaVersion + if (javaVersion > MIN_SUPPORTED_JAVA_VERSION) { + continue + } else if (javaVersion > 0) { + isStillMultiRelease = true + } + } + outStream.putNextEntry(jarEntry) + jarFile.getInputStream(jarEntry).buffered().copyTo(outStream) + outStream.closeEntry() } + + val manifest = jarFile.manifest + if (manifest != null) { + // write MANIFEST.MF as a last entry and modify Multi-Release attribute accordingly + manifest.mainAttributes.put(Attributes.Name.MULTI_RELEASE, isStillMultiRelease.toString()) + outStream.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME)) + manifest.write(outStream.buffered()) + outStream.closeEntry() + } + } + val transformedOutput = outputs.file(outputFilename) + Files.move(tmpOutputFile.toPath(), transformedOutput.toPath()) + } else { + outputs.file(inputArtifact) } + } - private val JarEntry.isSignatureEntry get() = signatureFileRegex.matches(name) + private val JarEntry.isSignatureEntry + get() = signatureFileRegex.matches(name) - private val JarEntry.javaVersion: Int get() = regex.find(name)?.value?.toIntOrNull() ?: 0 + private val JarEntry.javaVersion: Int + get() = regex.find(name)?.value?.toIntOrNull() ?: 0 - private fun File.jarOutputStream(): JarOutputStream = JarOutputStream(outputStream()) + private fun File.jarOutputStream(): JarOutputStream = JarOutputStream(outputStream()) - companion object { - private val regex = "(?<=/)([0-9]*)(?=/)".toRegex() - private val signatureFileRegex = "^META-INF/.*\\.(SF|DSA|RSA|EC)|^META-INF/SIG-.*" - .toRegex() - private const val versionsDir = "META-INF/versions/" - private const val MIN_SUPPORTED_JAVA_VERSION = 11 + companion object { + private val regex = "(?<=/)([0-9]*)(?=/)".toRegex() + private val signatureFileRegex = "^META-INF/.*\\.(SF|DSA|RSA|EC)|^META-INF/SIG-.*".toRegex() + private const val versionsDir = "META-INF/versions/" + private const val MIN_SUPPORTED_JAVA_VERSION = 11 - internal val artifactType: Attribute = - Attribute.of("artifactType", String::class.java) - internal val metaInfStripped: Attribute = - Attribute.of("meta-inf-stripped", Boolean::class.javaObjectType) + internal val artifactType: Attribute = Attribute.of("artifactType", String::class.java) + internal val metaInfStripped: Attribute = + Attribute.of("meta-inf-stripped", Boolean::class.javaObjectType) - internal fun register(dependencies: DependencyHandler, forceInstrument: Boolean) { - // add our attribute to schema - dependencies.attributesSchema { schema -> - schema.attribute(metaInfStripped) { matchingStrategy -> - /* - * This is necessary so our transform is selected before the AGP's one. - * AGP's transform chain looks roughly like that: - * - * IdentityTransform (jar to processed-jar) -> IdentityTransform (processed-jar to classes-jar) - * -> AsmClassesTransform (classes-jar to asm-transformed-classes-jar) - * - * We want out transform to run before the first IdentityTransform, so the chain - * would look like that: - * - * MetaInfStripTransform (jar to processed-jar) -> IdentityTransform (jar to processed-jar) - * -> IdentityTransform (...) -> AsmClassesTransform (...) - * - * Since the first two transforms have conflicting attributes, we define a - * disambiguation rule to make Gradle select our transform first. - */ - matchingStrategy.disambiguationRules - .pickFirst { o1, o2 -> if (o1 > o2) -1 else if (o1 < o2) 1 else 0 } - } - } - // sets meta-inf-stripped attr to false for all .jar artifacts - dependencies.artifactTypes.named("jar") { - it.attributes.attribute(metaInfStripped, false) - } - // registers a transform - dependencies.registerTransform(MetaInfStripTransform::class.java) { - it.from.attribute(artifactType, "jar").attribute(metaInfStripped, false) - it.to.attribute(artifactType, "processed-jar").attribute(metaInfStripped, true) + internal fun register(dependencies: DependencyHandler, forceInstrument: Boolean) { + // add our attribute to schema + dependencies.attributesSchema { schema -> + schema.attribute(metaInfStripped) { matchingStrategy -> + /* + * This is necessary so our transform is selected before the AGP's one. + * AGP's transform chain looks roughly like that: + * + * IdentityTransform (jar to processed-jar) -> IdentityTransform (processed-jar to classes-jar) + * -> AsmClassesTransform (classes-jar to asm-transformed-classes-jar) + * + * We want out transform to run before the first IdentityTransform, so the chain + * would look like that: + * + * MetaInfStripTransform (jar to processed-jar) -> IdentityTransform (jar to processed-jar) + * -> IdentityTransform (...) -> AsmClassesTransform (...) + * + * Since the first two transforms have conflicting attributes, we define a + * disambiguation rule to make Gradle select our transform first. + */ + matchingStrategy.disambiguationRules.pickFirst { o1, o2 -> + if (o1 > o2) -1 else if (o1 < o2) 1 else 0 + } + } + } + // sets meta-inf-stripped attr to false for all .jar artifacts + dependencies.artifactTypes.named("jar") { it.attributes.attribute(metaInfStripped, false) } + // registers a transform + dependencies.registerTransform(MetaInfStripTransform::class.java) { + it.from.attribute(artifactType, "jar").attribute(metaInfStripped, false) + it.to.attribute(artifactType, "processed-jar").attribute(metaInfStripped, true) - if (forceInstrument) { - it.parameters.invalidate.set(System.currentTimeMillis()) - } - } + if (forceInstrument) { + it.parameters.invalidate.set(System.currentTimeMillis()) } + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/CliFailureReason.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/CliFailureReason.kt index e539b79f..662cbc33 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/CliFailureReason.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/CliFailureReason.kt @@ -1,14 +1,15 @@ package io.sentry.android.gradle.util enum class CliFailureReason(val message: (taskName: String) -> String) { - OUTDATED({ - """ + OUTDATED({ + """ The task '$it' hit a non-existing endpoint on Sentry. Most likely you have to update your self-hosted Sentry version to get all of the latest features. - """.trimIndent() - }), - ORG_SLUG({ """ + .trimIndent() + }), + ORG_SLUG({ + """ An organization slug is required. You might want to provide your Sentry org name via the 'sentry.properties' file: ``` defaults.org=my-org @@ -19,10 +20,11 @@ enum class CliFailureReason(val message: (taskName: String) -> String) { sentry { org.set("my-org") } - """.trimIndent() - }), - PROJECT_SLUG({ """ + .trimIndent() + }), + PROJECT_SLUG({ + """ A project slug is required. You might want to provide your Sentry project name via the 'sentry.properties' file: ``` defaults.project=my-project @@ -34,10 +36,11 @@ enum class CliFailureReason(val message: (taskName: String) -> String) { projectName.set("my-project") } ``` - """.trimIndent() - }), - INVALID_ORG_AUTH_TOKEN({ """ + .trimIndent() + }), + INVALID_ORG_AUTH_TOKEN({ + """ Failed to parse org auth token. You might want to provide a custom url to your self-hosted Sentry instance via the 'sentry.properties' file: ``` defaults.url=https://mysentry.invalid/ @@ -49,31 +52,33 @@ enum class CliFailureReason(val message: (taskName: String) -> String) { url.set("https://mysentry.invalid/") } ``` - """.trimIndent() - }), - INVALID_TOKEN({ """ + .trimIndent() + }), + INVALID_TOKEN({ + """ An API request has failed due to an invalid token. Please provide a valid token with required permissions. - """.trimIndent() - }), - UNKNOWN({ """ + .trimIndent() + }), + UNKNOWN({ + """ An error occurred while executing task '$it'. Please check the detailed sentry-cli output above. - """.trimIndent() - }); + """ + .trimIndent() + }); - companion object { - fun fromErrOut(errOut: String): CliFailureReason { - return when { - errOut.contains("error: resource not found") -> OUTDATED - errOut.contains("error: An organization slug is required") -> ORG_SLUG - errOut.contains("error: A project slug is required") -> PROJECT_SLUG - errOut.contains("error: Failed to parse org auth token") -> INVALID_ORG_AUTH_TOKEN - errOut.contains("error: API request failed") && errOut.contains( - """Invalid token \(http status: \d+\)""".toRegex() - ) -> INVALID_TOKEN - else -> UNKNOWN - } - } + companion object { + fun fromErrOut(errOut: String): CliFailureReason { + return when { + errOut.contains("error: resource not found") -> OUTDATED + errOut.contains("error: An organization slug is required") -> ORG_SLUG + errOut.contains("error: A project slug is required") -> PROJECT_SLUG + errOut.contains("error: Failed to parse org auth token") -> INVALID_ORG_AUTH_TOKEN + errOut.contains("error: API request failed") && + errOut.contains("""Invalid token \(http status: \d+\)""".toRegex()) -> INVALID_TOKEN + else -> UNKNOWN + } } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Files.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Files.kt index 81ac5390..fe66fd52 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Files.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Files.kt @@ -5,38 +5,36 @@ import java.security.DigestInputStream import java.security.MessageDigest fun ByteArray.toHex(): String { - val result = CharArray(size * 2) { ' ' } - var i = 0 - forEach { - val n = it.toInt() - result[i++] = Character.forDigit(n shr 4 and 0xF, 16) - result[i++] = Character.forDigit(n and 0xF, 16) - } - return String(result) + val result = CharArray(size * 2) { ' ' } + var i = 0 + forEach { + val n = it.toInt() + result[i++] = Character.forDigit(n shr 4 and 0xF, 16) + result[i++] = Character.forDigit(n and 0xF, 16) + } + return String(result) } -/** - * Returns md5/sha256 hash of the file contents - */ +/** Returns md5/sha256 hash of the file contents */ fun File.contentHash(): String { - // first try to read hash generated by R8, which is located in the first 20 lines of mapping - // lines are lazily iterated over, so even on huge files performance should not be a problem - var hash: String? = null - var index = 0 - bufferedReader().lines().use { lines -> - for (line in lines) { - hash = line.substringAfter("# pg_map_hash: SHA-256 ", missingDelimiterValue = "") - if (++index > 20 || !hash.isNullOrBlank()) { - break - } - } - } - if (!hash.isNullOrBlank()) { - return hash!! + // first try to read hash generated by R8, which is located in the first 20 lines of mapping + // lines are lazily iterated over, so even on huge files performance should not be a problem + var hash: String? = null + var index = 0 + bufferedReader().lines().use { lines -> + for (line in lines) { + hash = line.substringAfter("# pg_map_hash: SHA-256 ", missingDelimiterValue = "") + if (++index > 20 || !hash.isNullOrBlank()) { + break + } } + } + if (!hash.isNullOrBlank()) { + return hash!! + } - // otherwise fall back to calculating hash ourselves - val md = MessageDigest.getInstance("MD5") - DigestInputStream(inputStream(), md).buffered().readAllBytes() - return md.digest().toHex() + // otherwise fall back to calculating hash ourselves + val md = MessageDigest.getInstance("MD5") + DigestInputStream(inputStream(), md).buffered().readAllBytes() + return md.digest().toHex() } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/PropertiesUtil.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/PropertiesUtil.kt index 572c780f..eb644a7d 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/PropertiesUtil.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/PropertiesUtil.kt @@ -4,25 +4,19 @@ import java.io.File import java.util.Properties class PropertiesUtil { - companion object { - fun load(file: File): Properties { - check(file.exists()) { - "${file.name} properties file is missing" - } + companion object { + fun load(file: File): Properties { + check(file.exists()) { "${file.name} properties file is missing" } - return Properties().also { properties -> - file.inputStream().use { - properties.load(it) - } - } - } + return Properties().also { properties -> file.inputStream().use { properties.load(it) } } + } - fun loadMaybe(file: File): Properties? { - if (!file.exists()) { - return null - } + fun loadMaybe(file: File): Properties? { + if (!file.exists()) { + return null + } - return load(file) - } + return load(file) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/ReleaseInfo.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/ReleaseInfo.kt index 4eef932d..0a857613 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/ReleaseInfo.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/ReleaseInfo.kt @@ -3,7 +3,7 @@ package io.sentry.android.gradle.util import java.io.Serializable data class ReleaseInfo( - val applicationId: String, - val versionName: String, - val versionCode: Int? = null + val applicationId: String, + val versionName: String, + val versionCode: Int? = null, ) : Serializable diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt index 817deedc..b111da36 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt @@ -31,144 +31,149 @@ import java.io.Serializable import kotlin.math.min data class SemVer( - val major: Int = 0, - val minor: Int = 0, - val patch: Int = 0, - val preRelease: String? = null, - val buildMetadata: String? = null + val major: Int = 0, + val minor: Int = 0, + val patch: Int = 0, + val preRelease: String? = null, + val buildMetadata: String? = null, ) : Comparable, Serializable { - companion object { - /* ktlint-disable max-line-length */ - val pattern = - Regex( - """(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:-([\dA-z\-]+(?:\.[\dA-z\-]+)*))?(?:\+([\dA-z\-]+(?:\.[\dA-z\-]+)*))?""" - ) - - /* ktlint-enable max-line-length */ - - /** - * Parse the version string to [SemVer] data object. - * @param version version string. - * @throws IllegalArgumentException if the version is not valid. - */ - @JvmStatic - fun parse(version: String): SemVer { - val result = pattern.matchEntire(version) - ?: throw IllegalArgumentException("Invalid version string [$version]") - return SemVer( - major = if (result.groupValues[1].isEmpty()) 0 else result.groupValues[1].toInt(), - minor = if (result.groupValues[2].isEmpty()) 0 else result.groupValues[2].toInt(), - patch = if (result.groupValues[3].isEmpty()) 0 else result.groupValues[3].toInt(), - preRelease = if (result.groupValues[4].isEmpty()) null else result.groupValues[4], - buildMetadata = if (result.groupValues[5].isEmpty()) null else result.groupValues[5] - ) - } - } + companion object { + /* ktlint-disable max-line-length */ + val pattern = + Regex( + """(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:-([\dA-z\-]+(?:\.[\dA-z\-]+)*))?(?:\+([\dA-z\-]+(?:\.[\dA-z\-]+)*))?""" + ) - init { - require(major >= 0) { "Major version must be a positive number" } - require(minor >= 0) { "Minor version must be a positive number" } - require(patch >= 0) { "Patch version must be a positive number" } - if (preRelease != null) { - require(preRelease.matches(Regex("""[\dA-z\-]+(?:\.[\dA-z\-]+)*"""))) { - "Pre-release version is not valid" - } - } - if (buildMetadata != null) { - require(buildMetadata.matches(Regex("""[\dA-z\-]+(?:\.[\dA-z\-]+)*"""))) { - "Build metadata is not valid" - } - } - } + /* ktlint-enable max-line-length */ /** - * Build the version name string. - * @return version name string in Semantic Versioning 2.0.0 specification. + * Parse the version string to [SemVer] data object. + * + * @param version version string. + * @throws IllegalArgumentException if the version is not valid. */ - override fun toString(): String = buildString { - append("$major.$minor.$patch") - if (preRelease != null) { - append('-') - append(preRelease) + @JvmStatic + fun parse(version: String): SemVer { + val result = + pattern.matchEntire(version) + ?: throw IllegalArgumentException("Invalid version string [$version]") + return SemVer( + major = if (result.groupValues[1].isEmpty()) 0 else result.groupValues[1].toInt(), + minor = if (result.groupValues[2].isEmpty()) 0 else result.groupValues[2].toInt(), + patch = if (result.groupValues[3].isEmpty()) 0 else result.groupValues[3].toInt(), + preRelease = if (result.groupValues[4].isEmpty()) null else result.groupValues[4], + buildMetadata = if (result.groupValues[5].isEmpty()) null else result.groupValues[5], + ) + } + } + + init { + require(major >= 0) { "Major version must be a positive number" } + require(minor >= 0) { "Minor version must be a positive number" } + require(patch >= 0) { "Patch version must be a positive number" } + if (preRelease != null) { + require(preRelease.matches(Regex("""[\dA-z\-]+(?:\.[\dA-z\-]+)*"""))) { + "Pre-release version is not valid" + } + } + if (buildMetadata != null) { + require(buildMetadata.matches(Regex("""[\dA-z\-]+(?:\.[\dA-z\-]+)*"""))) { + "Build metadata is not valid" + } + } + } + + /** + * Build the version name string. + * + * @return version name string in Semantic Versioning 2.0.0 specification. + */ + override fun toString(): String = buildString { + append("$major.$minor.$patch") + if (preRelease != null) { + append('-') + append(preRelease) + } + if (buildMetadata != null) { + append('+') + append(buildMetadata) + } + } + + /** + * Compare two SemVer objects using major, minor, patch and pre-release version as specified in + * SemVer specification. + * + * For comparing the whole SemVer object including build metadata, use [equals] instead. + * + * @return a negative integer, zero, or a positive integer as this object is less than, equal to, + * or greater than the specified object. + */ + override fun compareTo(other: SemVer): Int { + if (major > other.major) return 1 + if (major < other.major) return -1 + if (minor > other.minor) return 1 + if (minor < other.minor) return -1 + if (patch > other.patch) return 1 + if (patch < other.patch) return -1 + + if (preRelease == null && other.preRelease == null) return 0 + if (preRelease != null && other.preRelease == null) return -1 + if (preRelease == null && other.preRelease != null) return 1 + + val parts = preRelease.orEmpty().split(".") + val otherParts = other.preRelease.orEmpty().split(".") + + val endIndex = min(parts.size, otherParts.size) - 1 + for (i in 0..endIndex) { + val part = parts[i] + val otherPart = otherParts[i] + if (part == otherPart) continue + + val partIsNumeric = part.isNumeric() + val otherPartIsNumeric = otherPart.isNumeric() + + when { + partIsNumeric && !otherPartIsNumeric -> { + // lower priority + return -1 } - if (buildMetadata != null) { - append('+') - append(buildMetadata) + + !partIsNumeric && otherPartIsNumeric -> { + // higher priority + return 1 } - } - /** - * Compare two SemVer objects using major, minor, patch and pre-release version as specified in SemVer specification. - * - * For comparing the whole SemVer object including build metadata, use [equals] instead. - * - * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. - */ - override fun compareTo(other: SemVer): Int { - if (major > other.major) return 1 - if (major < other.major) return -1 - if (minor > other.minor) return 1 - if (minor < other.minor) return -1 - if (patch > other.patch) return 1 - if (patch < other.patch) return -1 - - if (preRelease == null && other.preRelease == null) return 0 - if (preRelease != null && other.preRelease == null) return -1 - if (preRelease == null && other.preRelease != null) return 1 - - val parts = preRelease.orEmpty().split(".") - val otherParts = other.preRelease.orEmpty().split(".") - - val endIndex = min(parts.size, otherParts.size) - 1 - for (i in 0..endIndex) { - val part = parts[i] - val otherPart = otherParts[i] - if (part == otherPart) continue - - val partIsNumeric = part.isNumeric() - val otherPartIsNumeric = otherPart.isNumeric() - - when { - partIsNumeric && !otherPartIsNumeric -> { - // lower priority - return -1 - } - - !partIsNumeric && otherPartIsNumeric -> { - // higher priority - return 1 - } - - !partIsNumeric && !otherPartIsNumeric -> { - if (part > otherPart) return 1 - if (part < otherPart) return -1 - } - - else -> { - try { - val partInt = part.toInt() - val otherPartInt = otherPart.toInt() - if (partInt > otherPartInt) return 1 - if (partInt < otherPartInt) return -1 - } catch (_: NumberFormatException) { - // When part or otherPart doesn't fit in an Int, compare as strings - return part.compareTo(otherPart) - } - } - } + !partIsNumeric && !otherPartIsNumeric -> { + if (part > otherPart) return 1 + if (part < otherPart) return -1 } - return if (parts.size == endIndex + 1 && otherParts.size > endIndex + 1) { - // parts is ended and otherParts is not ended - -1 - } else if (parts.size > endIndex + 1 && otherParts.size == endIndex + 1) { - // parts is not ended and otherParts is ended - 1 - } else { - 0 + else -> { + try { + val partInt = part.toInt() + val otherPartInt = otherPart.toInt() + if (partInt > otherPartInt) return 1 + if (partInt < otherPartInt) return -1 + } catch (_: NumberFormatException) { + // When part or otherPart doesn't fit in an Int, compare as strings + return part.compareTo(otherPart) + } } + } + } + + return if (parts.size == endIndex + 1 && otherParts.size > endIndex + 1) { + // parts is ended and otherParts is not ended + -1 + } else if (parts.size > endIndex + 1 && otherParts.size == endIndex + 1) { + // parts is not ended and otherParts is ended + 1 + } else { + 0 } + } - private fun String.isNumeric(): Boolean = this.matches(Regex("""\d+""")) + private fun String.isNumeric(): Boolean = this.matches(Regex("""\d+""")) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt index 9386b662..efc9c226 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryCliExec.kt @@ -10,29 +10,27 @@ import org.gradle.api.tasks.Exec * called at configuration phase (=when registering a task). */ fun Exec.asSentryCliExec() { - isIgnoreExitValue = true - // this is a workaround, otherwise doFirst is not needed https://github.com/gradle/gradle/issues/16535 - doFirst { - errorOutput = ByteArrayOutputStream() - } + isIgnoreExitValue = true + // this is a workaround, otherwise doFirst is not needed + // https://github.com/gradle/gradle/issues/16535 + doFirst { errorOutput = ByteArrayOutputStream() } - doLast { - val err = errorOutput.toString() - val exitValue = executionResult.orNull?.exitValue ?: 0 - if (exitValue != 0) { - when (val reason = CliFailureReason.fromErrOut(err)) { - OUTDATED -> logger.warn { reason.message(name) } - else -> { - logger.lifecycle(err) - throw SentryCliException(reason, name) - } - } - } else if (err.isNotEmpty()) { - logger.lifecycle(err) + doLast { + val err = errorOutput.toString() + val exitValue = executionResult.orNull?.exitValue ?: 0 + if (exitValue != 0) { + when (val reason = CliFailureReason.fromErrOut(err)) { + OUTDATED -> logger.warn { reason.message(name) } + else -> { + logger.lifecycle(err) + throw SentryCliException(reason, name) } + } + } else if (err.isNotEmpty()) { + logger.lifecycle(err) } + } } -open class SentryCliException(val reason: CliFailureReason, name: String) : GradleException( - reason.message(name) -) +open class SentryCliException(val reason: CliFailureReason, name: String) : + GradleException(reason.message(name)) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryLogging.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryLogging.kt index b7adb1d8..d2aa7b03 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryLogging.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryLogging.kt @@ -3,17 +3,17 @@ package io.sentry.android.gradle.util import org.slf4j.Logger fun Logger.warn(throwable: Throwable? = null, message: () -> String) { - warn("[sentry] ${message()}", throwable) + warn("[sentry] ${message()}", throwable) } fun Logger.error(throwable: Throwable? = null, message: () -> String) { - error("[sentry] ${message()}", throwable) + error("[sentry] ${message()}", throwable) } fun Logger.debug(throwable: Throwable? = null, message: () -> String) { - debug("[sentry] ${message()}", throwable) + debug("[sentry] ${message()}", throwable) } fun Logger.info(throwable: Throwable? = null, message: () -> String) { - info("[sentry] ${message()}", throwable) + info("[sentry] ${message()}", throwable) } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryModulesCollector.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryModulesCollector.kt index 80fee877..ba842462 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryModulesCollector.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryModulesCollector.kt @@ -9,56 +9,55 @@ import org.gradle.api.logging.Logger import org.gradle.api.provider.Provider fun Project.collectModules( - configurationName: String, - variantName: String, - sentryModulesService: Provider + configurationName: String, + variantName: String, + sentryModulesService: Provider, ) { - val configProvider = try { - configurations.named(configurationName) + val configProvider = + try { + configurations.named(configurationName) } catch (e: UnknownDomainObjectException) { - logger.warn { - "Unable to find configuration $configurationName for variant $variantName." - } - sentryModulesService.get().sentryModules = emptyMap() - sentryModulesService.get().externalModules = emptyMap() - return + logger.warn { "Unable to find configuration $configurationName for variant $variantName." } + sentryModulesService.get().sentryModules = emptyMap() + sentryModulesService.get().externalModules = emptyMap() + return } - configProvider.configure { configuration -> - configuration.incoming.afterResolve { - val allModules = it.resolutionResult.allComponents.versionMap(logger) - val sentryModules = allModules.filter { (identifier, _) -> - identifier.group == "io.sentry" - }.toMap() + configProvider.configure { configuration -> + configuration.incoming.afterResolve { + val allModules = it.resolutionResult.allComponents.versionMap(logger) + val sentryModules = + allModules.filter { (identifier, _) -> identifier.group == "io.sentry" }.toMap() - val externalModules = allModules.filter { (identifier, _) -> - identifier.group != "io.sentry" - }.toMap() + val externalModules = + allModules.filter { (identifier, _) -> identifier.group != "io.sentry" }.toMap() - logger.info { - "Detected Sentry modules $sentryModules " + - "for variant: $variantName, config: $configurationName" - } - sentryModulesService.get().sentryModules = sentryModules - sentryModulesService.get().externalModules = externalModules - } + logger.info { + "Detected Sentry modules $sentryModules " + + "for variant: $variantName, config: $configurationName" + } + sentryModulesService.get().sentryModules = sentryModules + sentryModulesService.get().externalModules = externalModules } + } } -private fun Set.versionMap(logger: Logger): - List> { - return mapNotNull { - it.moduleVersion?.let { moduleVersion -> - val identifier = moduleVersion.module - val version = it.moduleVersion?.version ?: "" - val semver = try { - SemVer.parse(version) - } catch (e: Throwable) { - logger.info { "Unable to parse version $version of $identifier" } - SemVer() - } - return@mapNotNull Pair(identifier, semver) +private fun Set.versionMap( + logger: Logger +): List> { + return mapNotNull { + it.moduleVersion?.let { moduleVersion -> + val identifier = moduleVersion.module + val version = it.moduleVersion?.version ?: "" + val semver = + try { + SemVer.parse(version) + } catch (e: Throwable) { + logger.info { "Unable to parse version $version of $identifier" } + SemVer() } - null + return@mapNotNull Pair(identifier, semver) } + null + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryPluginUtils.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryPluginUtils.kt index 2d637555..7d065965 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryPluginUtils.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SentryPluginUtils.kt @@ -15,59 +15,50 @@ import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension internal object SentryPluginUtils { - fun withLogging( - logger: Logger, - varName: String, - initializer: () -> TaskProvider? - ) = initializer().also { - logger.info { "$varName is ${it?.name}" } - } + fun withLogging(logger: Logger, varName: String, initializer: () -> TaskProvider?) = + initializer().also { logger.info { "$varName is ${it?.name}" } } - fun String.capitalizeUS() = if (isEmpty()) { - "" + fun String.capitalizeUS() = + if (isEmpty()) { + "" } else { - substring(0, 1).toUpperCase(Locale.US) + substring(1) + substring(0, 1).toUpperCase(Locale.US) + substring(1) } - fun isMinificationEnabled( - project: Project, - variant: SentryVariant, - dexguardEnabled: Boolean = false - ): Boolean { - if (dexguardEnabled) { - var isConfiguredWithGuardsquareProguard = false - project.plugins.withId("com.guardsquare.proguard") { - val proguardExtension = project.extensions.getByType( - ProGuardAndroidExtension::class.java - ) - val variantConfiguration = proguardExtension.configurations.findByName(variant.name) - isConfiguredWithGuardsquareProguard = variantConfiguration != null - } - val isConfiguredWithGuardsquareDexguard = isDexguardEnabledForVariant( - project, - variant.name - ) - if (isConfiguredWithGuardsquareProguard || isConfiguredWithGuardsquareDexguard) { - return true - } - } - return variant.isMinifyEnabled + fun isMinificationEnabled( + project: Project, + variant: SentryVariant, + dexguardEnabled: Boolean = false, + ): Boolean { + if (dexguardEnabled) { + var isConfiguredWithGuardsquareProguard = false + project.plugins.withId("com.guardsquare.proguard") { + val proguardExtension = project.extensions.getByType(ProGuardAndroidExtension::class.java) + val variantConfiguration = proguardExtension.configurations.findByName(variant.name) + isConfiguredWithGuardsquareProguard = variantConfiguration != null + } + val isConfiguredWithGuardsquareDexguard = isDexguardEnabledForVariant(project, variant.name) + if (isConfiguredWithGuardsquareProguard || isConfiguredWithGuardsquareDexguard) { + return true + } } + return variant.isMinifyEnabled + } - fun getAndDeleteFile(property: Provider): File { - val file = property.get().asFile - file.delete() - return file - } + fun getAndDeleteFile(property: Provider): File { + val file = property.get().asFile + file.delete() + return file + } - fun isVariantAllowed( - extension: SentryPluginExtension, - variantName: String, - flavorName: String?, - buildType: String? - ): Boolean { - return variantName !in extension.ignoredVariants.get() && - flavorName !in extension.ignoredFlavors.get() && - buildType !in extension.ignoredBuildTypes.get() - } + fun isVariantAllowed( + extension: SentryPluginExtension, + variantName: String, + flavorName: String?, + buildType: String?, + ): Boolean { + return variantName !in extension.ignoredVariants.get() && + flavorName !in extension.ignoredFlavors.get() && + buildType !in extension.ignoredBuildTypes.get() + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt index a891e8b1..93948891 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt @@ -5,121 +5,67 @@ import org.gradle.api.internal.artifacts.DefaultModuleIdentifier import org.gradle.util.GradleVersion internal object AgpVersions { - val CURRENT: SemVer = SemVer.parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) - val VERSION_7_1_2: SemVer = SemVer.parse("7.1.2") - val VERSION_7_0_0: SemVer = SemVer.parse("7.0.0") - val VERSION_7_4_0: SemVer = SemVer.parse("7.4.0-rc01") - val isAGP74: Boolean get() = isAGP74(CURRENT) - fun isAGP74(current: SemVer) = current >= VERSION_7_4_0 + val CURRENT: SemVer = SemVer.parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) + val VERSION_7_1_2: SemVer = SemVer.parse("7.1.2") + val VERSION_7_0_0: SemVer = SemVer.parse("7.0.0") + val VERSION_7_4_0: SemVer = SemVer.parse("7.4.0-rc01") + val isAGP74: Boolean + get() = isAGP74(CURRENT) + + fun isAGP74(current: SemVer) = current >= VERSION_7_4_0 } internal object GradleVersions { - val CURRENT: SemVer = SemVer.parse(GradleVersion.current().version) - val VERSION_7_4: SemVer = SemVer.parse("7.4") - val VERSION_7_5: SemVer = SemVer.parse("7.5") - val VERSION_8_0: SemVer = SemVer.parse("8.0") + val CURRENT: SemVer = SemVer.parse(GradleVersion.current().version) + val VERSION_7_4: SemVer = SemVer.parse("7.4") + val VERSION_7_5: SemVer = SemVer.parse("7.5") + val VERSION_8_0: SemVer = SemVer.parse("8.0") } internal object SentryVersions { - internal val VERSION_DEFAULT = SemVer() - internal val VERSION_PERFORMANCE = SemVer(4, 0, 0) - internal val VERSION_ANDROID_OKHTTP = SemVer(5, 0, 0) - internal val VERSION_FILE_IO = SemVer(5, 5, 0) - internal val VERSION_COMPOSE = SemVer(6, 7, 0) - internal val VERSION_LOGCAT = SemVer(6, 17, 0) - internal val VERSION_APP_START = SemVer(7, 1, 0) - internal val VERSION_SQLITE = SemVer(6, 21, 0) - internal val VERSION_ANDROID_OKHTTP_LISTENER = SemVer(6, 20, 0) - internal val VERSION_OKHTTP = SemVer(7, 0, 0) + internal val VERSION_DEFAULT = SemVer() + internal val VERSION_PERFORMANCE = SemVer(4, 0, 0) + internal val VERSION_ANDROID_OKHTTP = SemVer(5, 0, 0) + internal val VERSION_FILE_IO = SemVer(5, 5, 0) + internal val VERSION_COMPOSE = SemVer(6, 7, 0) + internal val VERSION_LOGCAT = SemVer(6, 17, 0) + internal val VERSION_APP_START = SemVer(7, 1, 0) + internal val VERSION_SQLITE = SemVer(6, 21, 0) + internal val VERSION_ANDROID_OKHTTP_LISTENER = SemVer(6, 20, 0) + internal val VERSION_OKHTTP = SemVer(7, 0, 0) } internal object SentryModules { - internal val SENTRY = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry" - ) - internal val SENTRY_ANDROID = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android" - ) - internal val SENTRY_ANDROID_CORE = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-core" - ) - internal val SENTRY_ANDROID_NDK = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-ndk" - ) - internal val SENTRY_ANDROID_SQLITE = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-sqlite" - ) - internal val SENTRY_ANDROID_OKHTTP = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-okhttp" - ) - internal val SENTRY_ANDROID_COMPOSE = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-compose-android" - ) - internal val SENTRY_ANDROID_FRAGMENT = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-fragment" - ) - internal val SENTRY_ANDROID_NAVIGATION = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-navigation" - ) - internal val SENTRY_ANDROID_TIMBER = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-android-timber" - ) - internal val SENTRY_OKHTTP = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-okhttp" - ) - internal val SENTRY_KOTLIN_EXTENSIONS = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-kotlin-extensions" - ) - internal val SENTRY_JDBC = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-jdbc" - ) - internal val SENTRY_GRAPHQL = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-graphql" - ) - internal val SENTRY_LOG4J2 = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-log4j2" - ) - internal val SENTRY_LOGBACK = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-logback" - ) - internal val SENTRY_QUARTZ = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-quartz" - ) - internal val SENTRY_SPRING5 = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-spring" - ) - internal val SENTRY_SPRING6 = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-spring-jakarta" - ) - internal val SENTRY_SPRING_BOOT2 = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-spring-boot" - ) - internal val SENTRY_SPRING_BOOT3 = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-spring-boot-jakarta" - ) - internal val SENTRY_BOM = DefaultModuleIdentifier.newId( - "io.sentry", - "sentry-bom" - ) + internal val SENTRY = DefaultModuleIdentifier.newId("io.sentry", "sentry") + internal val SENTRY_ANDROID = DefaultModuleIdentifier.newId("io.sentry", "sentry-android") + internal val SENTRY_ANDROID_CORE = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-core") + internal val SENTRY_ANDROID_NDK = DefaultModuleIdentifier.newId("io.sentry", "sentry-android-ndk") + internal val SENTRY_ANDROID_SQLITE = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-sqlite") + internal val SENTRY_ANDROID_OKHTTP = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-okhttp") + internal val SENTRY_ANDROID_COMPOSE = + DefaultModuleIdentifier.newId("io.sentry", "sentry-compose-android") + internal val SENTRY_ANDROID_FRAGMENT = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-fragment") + internal val SENTRY_ANDROID_NAVIGATION = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-navigation") + internal val SENTRY_ANDROID_TIMBER = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-timber") + internal val SENTRY_OKHTTP = DefaultModuleIdentifier.newId("io.sentry", "sentry-okhttp") + internal val SENTRY_KOTLIN_EXTENSIONS = + DefaultModuleIdentifier.newId("io.sentry", "sentry-kotlin-extensions") + internal val SENTRY_JDBC = DefaultModuleIdentifier.newId("io.sentry", "sentry-jdbc") + internal val SENTRY_GRAPHQL = DefaultModuleIdentifier.newId("io.sentry", "sentry-graphql") + internal val SENTRY_LOG4J2 = DefaultModuleIdentifier.newId("io.sentry", "sentry-log4j2") + internal val SENTRY_LOGBACK = DefaultModuleIdentifier.newId("io.sentry", "sentry-logback") + internal val SENTRY_QUARTZ = DefaultModuleIdentifier.newId("io.sentry", "sentry-quartz") + internal val SENTRY_SPRING5 = DefaultModuleIdentifier.newId("io.sentry", "sentry-spring") + internal val SENTRY_SPRING6 = DefaultModuleIdentifier.newId("io.sentry", "sentry-spring-jakarta") + internal val SENTRY_SPRING_BOOT2 = + DefaultModuleIdentifier.newId("io.sentry", "sentry-spring-boot") + internal val SENTRY_SPRING_BOOT3 = + DefaultModuleIdentifier.newId("io.sentry", "sentry-spring-boot-jakarta") + internal val SENTRY_BOM = DefaultModuleIdentifier.newId("io.sentry", "sentry-bom") } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/artifacts.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/artifacts.kt index 382b3475..fa9cf75f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/artifacts.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/artifacts.kt @@ -25,17 +25,14 @@ import org.gradle.api.attributes.Attribute private val attributeKey: Attribute = Attribute.of("artifactType", String::class.java) -fun Configuration.artifactsFor( - attrValue: String -) = externalArtifactViewOf(attrValue) - .artifacts +fun Configuration.artifactsFor(attrValue: String) = externalArtifactViewOf(attrValue).artifacts -fun Configuration.externalArtifactViewOf( - attrValue: String -): ArtifactView = incoming.artifactView { view -> +fun Configuration.externalArtifactViewOf(attrValue: String): ArtifactView = + incoming.artifactView { view -> view.attributes.attribute(attributeKey, attrValue) // If some dependency doesn't have the expected attribute, don't fail. Continue! view.lenient(true) - // Only resolve external dependencies! Without this, all project dependencies will get _compiled_. + // Only resolve external dependencies! Without this, all project dependencies will get + // _compiled_. view.componentFilter { id -> id is ModuleComponentIdentifier } -} + } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/buildServices.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/buildServices.kt index 7f175ce5..90fc23d3 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/buildServices.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/buildServices.kt @@ -25,13 +25,13 @@ import org.gradle.api.services.BuildServiceParameters import org.gradle.api.services.BuildServiceRegistry fun , ParamsT : BuildServiceParameters> getBuildService( - buildServiceRegistry: BuildServiceRegistry, - buildServiceClass: Class + buildServiceRegistry: BuildServiceRegistry, + buildServiceClass: Class, ): Provider { - val serviceName = getBuildServiceName(buildServiceClass) - return buildServiceRegistry.registerIfAbsent(serviceName, buildServiceClass) { - throw IllegalStateException("Service $serviceName is not registered.") - } + val serviceName = getBuildServiceName(buildServiceClass) + return buildServiceRegistry.registerIfAbsent(serviceName, buildServiceClass) { + throw IllegalStateException("Service $serviceName is not registered.") + } } /* diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/tasks.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/tasks.kt index caf35a9b..6638b7b5 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/tasks.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/tasks.kt @@ -14,70 +14,45 @@ import org.gradle.api.Task import org.gradle.api.tasks.TaskProvider fun TaskProvider.hookWithMinifyTasks( - project: Project, - variantName: String, - dexguardEnabled: Boolean + project: Project, + variantName: String, + dexguardEnabled: Boolean, ) { - // we need to wait for project evaluation to have all tasks available, otherwise the new - // AndroidComponentsExtension is configured too early to look up for the tasks - project.afterEvaluate { - val minifyTask = getMinifyTask( - project, - variantName, - dexguardEnabled - ) + // we need to wait for project evaluation to have all tasks available, otherwise the new + // AndroidComponentsExtension is configured too early to look up for the tasks + project.afterEvaluate { + val minifyTask = getMinifyTask(project, variantName, dexguardEnabled) - // we just hack ourselves into the Proguard/R8/DexGuard task's doLast. - minifyTask?.configure { - it.finalizedBy(this) - } - } + // we just hack ourselves into the Proguard/R8/DexGuard task's doLast. + minifyTask?.configure { it.finalizedBy(this) } + } } -fun TaskProvider.hookWithPackageTasks( - project: Project, - variant: SentryVariant -) { - val variantName = variant.name - val preBundleTaskProvider = withLogging(project.logger, "preBundleTask") { - getPreBundleTask(project, variantName) - } - val packageBundleTaskProvider = withLogging(project.logger, "packageBundleTask") { - getPackageBundleTask(project, variantName) - } +fun TaskProvider.hookWithPackageTasks(project: Project, variant: SentryVariant) { + val variantName = variant.name + val preBundleTaskProvider = + withLogging(project.logger, "preBundleTask") { getPreBundleTask(project, variantName) } + val packageBundleTaskProvider = + withLogging(project.logger, "packageBundleTask") { getPackageBundleTask(project, variantName) } - // To include proguard uuid file into aab, run before bundle task. - preBundleTaskProvider?.configure { task -> - task.dependsOn(this) - } - // The package task will only be executed if the generateUuidTask has already been executed. - getPackageProvider(variant)?.configure { task -> - task.dependsOn(this) - } + // To include proguard uuid file into aab, run before bundle task. + preBundleTaskProvider?.configure { task -> task.dependsOn(this) } + // The package task will only be executed if the generateUuidTask has already been executed. + getPackageProvider(variant)?.configure { task -> task.dependsOn(this) } - // App bundle has different package task - packageBundleTaskProvider?.configure { task -> - task.dependsOn(this) - } + // App bundle has different package task + packageBundleTaskProvider?.configure { task -> task.dependsOn(this) } } -fun TaskProvider.hookWithAssembleTasks( - project: Project, - variant: SentryVariant -) { - // we need to wait for project evaluation to have all tasks available, otherwise the new - // AndroidComponentsExtension is configured too early to look up for the tasks - project.afterEvaluate { - val bundleTask = withLogging(project.logger, "bundleTask") { - getBundleTask(project, variant.name) - } - getAssembleTaskProvider(project, variant)?.configure { - it.finalizedBy(this) - } - getInstallTaskProvider(project, variant)?.configure { - it.finalizedBy(this) - } - // if its a bundle aab, assemble might not be executed, so we hook into bundle task - bundleTask?.configure { it.finalizedBy(this) } - } +fun TaskProvider.hookWithAssembleTasks(project: Project, variant: SentryVariant) { + // we need to wait for project evaluation to have all tasks available, otherwise the new + // AndroidComponentsExtension is configured too early to look up for the tasks + project.afterEvaluate { + val bundleTask = + withLogging(project.logger, "bundleTask") { getBundleTask(project, variant.name) } + getAssembleTaskProvider(project, variant)?.configure { it.finalizedBy(this) } + getInstallTaskProvider(project, variant)?.configure { it.finalizedBy(this) } + // if its a bundle aab, assemble might not be executed, so we hook into bundle task + bundleTask?.configure { it.finalizedBy(this) } + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt b/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt index d9333de9..d38a1fb8 100644 --- a/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt +++ b/plugin-build/src/main/kotlin/io/sentry/jvm/gradle/SentryJvmPlugin.kt @@ -23,122 +23,120 @@ import org.gradle.api.plugins.ExtraPropertiesExtension import org.gradle.api.plugins.JavaPluginExtension import org.gradle.internal.build.event.BuildEventListenerRegistryInternal -class SentryJvmPlugin @Inject constructor( - private val buildEvents: BuildEventListenerRegistryInternal -) : Plugin { - - /** - * Since we're listening for the JavaBasePlugin, there may be multiple plugins inherting from it - * applied to the same project, e.g. Spring Boot + Kotlin Jvm, hence we only want our plugin to - * be configured only once. - */ - private val configuredForJavaProject = AtomicBoolean(false) - - override fun apply(project: Project) { - val extension = project.extensions.create( - "sentry", - SentryPluginExtension::class.java, - project +class SentryJvmPlugin +@Inject +constructor(private val buildEvents: BuildEventListenerRegistryInternal) : Plugin { + + /** + * Since we're listening for the JavaBasePlugin, there may be multiple plugins inherting from it + * applied to the same project, e.g. Spring Boot + Kotlin Jvm, hence we only want our plugin to be + * configured only once. + */ + private val configuredForJavaProject = AtomicBoolean(false) + + override fun apply(project: Project) { + val extension = project.extensions.create("sentry", SentryPluginExtension::class.java, project) + + project.pluginManager.withPlugin("org.gradle.java") { + if (configuredForJavaProject.getAndSet(true)) { + SentryPlugin.logger.info { "The Sentry Gradle plugin was already configured" } + return@withPlugin + } + + val javaExtension = project.extensions.getByType(JavaPluginExtension::class.java) + + val sentryResDir = project.layout.buildDirectory.dir("generated${sep}sentry") + + val javaVariant = JavaVariant(project, javaExtension) + val outputPaths = OutputPaths(project, "java") + val cliExecutable = project.cliExecutableProvider() + + val extraProperties = project.extensions.getByName("ext") as ExtraPropertiesExtension + + val sentryOrgParameter = + runCatching { extraProperties.get(SentryPlugin.SENTRY_ORG_PARAMETER).toString() } + .getOrNull() + val sentryProjectParameter = + runCatching { extraProperties.get(SentryPlugin.SENTRY_PROJECT_PARAMETER).toString() } + .getOrNull() + + val sentryTelemetryProvider = SentryTelemetryService.register(project) + project.gradle.taskGraph.whenReady { + sentryTelemetryProvider.get().start { + SentryTelemetryService.createParameters( + project, + javaVariant, + extension, + cliExecutable, + sentryOrgParameter, + "JVM", + ) + } + buildEvents.onOperationCompletion(sentryTelemetryProvider) + } + + val additionalSourcesProvider = + project.provider { + extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()).map { + project.layout.projectDirectory.dir(it) + } + } + val sourceFiles = javaVariant.sources(project, additionalSourcesProvider) + + val sourceContextTasks = + SourceContext.register( + project, + extension, + sentryTelemetryProvider, + javaVariant, + outputPaths, + sourceFiles, + cliExecutable, + sentryOrgParameter, + sentryProjectParameter, + "Java", ) - project.pluginManager.withPlugin("org.gradle.java") { - if (configuredForJavaProject.getAndSet(true)) { - SentryPlugin.logger.info { "The Sentry Gradle plugin was already configured" } - return@withPlugin - } - - val javaExtension = project.extensions.getByType(JavaPluginExtension::class.java) - - val sentryResDir = project.layout.buildDirectory.dir("generated${sep}sentry") - - val javaVariant = JavaVariant(project, javaExtension) - val outputPaths = OutputPaths(project, "java") - val cliExecutable = project.cliExecutableProvider() - - val extraProperties = project.extensions.getByName("ext") - as ExtraPropertiesExtension - - val sentryOrgParameter = runCatching { - extraProperties.get(SentryPlugin.SENTRY_ORG_PARAMETER).toString() - }.getOrNull() - val sentryProjectParameter = runCatching { - extraProperties.get(SentryPlugin.SENTRY_PROJECT_PARAMETER).toString() - }.getOrNull() - - val sentryTelemetryProvider = SentryTelemetryService.register(project) - project.gradle.taskGraph.whenReady { - sentryTelemetryProvider.get().start { - SentryTelemetryService.createParameters( - project, - javaVariant, - extension, - cliExecutable, - sentryOrgParameter, - "JVM" - ) - } - buildEvents.onOperationCompletion(sentryTelemetryProvider) - } - - val additionalSourcesProvider = project.provider { - extension.additionalSourceDirsForSourceContext.getOrElse(emptySet()) - .map { project.layout.projectDirectory.dir(it) } - } - val sourceFiles = javaVariant.sources( - project, - additionalSourcesProvider - ) - - val sourceContextTasks = SourceContext.register( - project, - extension, - sentryTelemetryProvider, - javaVariant, - outputPaths, - sourceFiles, - cliExecutable, - sentryOrgParameter, - sentryProjectParameter, - "Java" - ) - - sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, javaVariant) - - javaExtension.sourceSets.getByName("main").resources { sourceSet -> - sourceSet.srcDir(sentryResDir) - } - - val generateDebugMetaPropertiesTask = SentryGenerateDebugMetaPropertiesTask.register( - project, - extension, - sentryTelemetryProvider, - listOf(sourceContextTasks.generateBundleIdTask), - sentryResDir, - "java" - ) - - val reportDependenciesTask = SentryExternalDependenciesReportTaskFactory.register( - project = project, - extension, - sentryTelemetryProvider, - configurationName = "runtimeClasspath", - attributeValueJar = "jar", - includeReport = extension.includeDependenciesReport, - output = sentryResDir - ) - val resourcesTask = SentryPluginUtils.withLogging(project.logger, "processResources") { - SentryTasksProvider.getProcessResourcesProvider(project) - } - resourcesTask?.configure { task -> - task.dependsOn(reportDependenciesTask) - task.dependsOn(generateDebugMetaPropertiesTask) - } - - project.installDependencies(extension, false) + sourceContextTasks.uploadSourceBundleTask.hookWithAssembleTasks(project, javaVariant) + + javaExtension.sourceSets.getByName("main").resources { sourceSet -> + sourceSet.srcDir(sentryResDir) + } + + val generateDebugMetaPropertiesTask = + SentryGenerateDebugMetaPropertiesTask.register( + project, + extension, + sentryTelemetryProvider, + listOf(sourceContextTasks.generateBundleIdTask), + sentryResDir, + "java", + ) + + val reportDependenciesTask = + SentryExternalDependenciesReportTaskFactory.register( + project = project, + extension, + sentryTelemetryProvider, + configurationName = "runtimeClasspath", + attributeValueJar = "jar", + includeReport = extension.includeDependenciesReport, + output = sentryResDir, + ) + val resourcesTask = + SentryPluginUtils.withLogging(project.logger, "processResources") { + SentryTasksProvider.getProcessResourcesProvider(project) } - } + resourcesTask?.configure { task -> + task.dependsOn(reportDependenciesTask) + task.dependsOn(generateDebugMetaPropertiesTask) + } - companion object { - internal val sep = File.separator + project.installDependencies(extension, false) } + } + + companion object { + internal val sep = File.separator + } } diff --git a/plugin-build/src/main/kotlin/io/sentry/kotlin/gradle/SentryKotlinCompilerGradlePlugin.kt b/plugin-build/src/main/kotlin/io/sentry/kotlin/gradle/SentryKotlinCompilerGradlePlugin.kt index 7113ff14..5cb7a277 100644 --- a/plugin-build/src/main/kotlin/io/sentry/kotlin/gradle/SentryKotlinCompilerGradlePlugin.kt +++ b/plugin-build/src/main/kotlin/io/sentry/kotlin/gradle/SentryKotlinCompilerGradlePlugin.kt @@ -9,25 +9,23 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginOption class SentryKotlinCompilerGradlePlugin : KotlinCompilerPluginSupportPlugin { - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true - override fun getCompilerPluginId(): String = "io.sentry.kotlin.compiler" + override fun getCompilerPluginId(): String = "io.sentry.kotlin.compiler" - override fun getPluginArtifact(): SubpluginArtifact { - // needs to match sentry-kotlin-compiler-plugin/build.gradle.kts - return SubpluginArtifact( - groupId = "io.sentry", - artifactId = "sentry-kotlin-compiler-plugin", - version = BuildConfig.Version - ) - } + override fun getPluginArtifact(): SubpluginArtifact { + // needs to match sentry-kotlin-compiler-plugin/build.gradle.kts + return SubpluginArtifact( + groupId = "io.sentry", + artifactId = "sentry-kotlin-compiler-plugin", + version = BuildConfig.Version, + ) + } - override fun applyToCompilation( - kotlinCompilation: KotlinCompilation<*> - ): Provider> { - val project = kotlinCompilation.target.project - return project.provider { - emptyList() - } - } + override fun applyToCompilation( + kotlinCompilation: KotlinCompilation<*> + ): Provider> { + val project = kotlinCompilation.target.project + return project.provider { emptyList() } + } } diff --git a/plugin-build/src/test/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.kt b/plugin-build/src/test/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.kt index 84971967..11bf1366 100644 --- a/plugin-build/src/test/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.kt +++ b/plugin-build/src/test/kotlin/androidx/sqlite/db/SupportSQLiteOpenHelper.kt @@ -1,5 +1,5 @@ package androidx.sqlite.db interface SupportSQLiteOpenHelper { - abstract class Callback + abstract class Callback } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryCliProviderTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryCliProviderTest.kt index 51a4e63e..0e8ddd78 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryCliProviderTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryCliProviderTest.kt @@ -19,189 +19,161 @@ import org.junit.rules.TemporaryFolder class SentryCliProviderTest { - @get:Rule - val testProjectDir = TemporaryFolder() - - @get:Rule - val systemPropertyRule = SystemPropertyRule() - - @Test - fun `getSentryPropertiesPath returns local properties file`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - testProjectDir.newFile("sentry.properties") - - assertEquals( - project.file("sentry.properties").path, - getSentryPropertiesPath(project.projectDir, project.rootDir) - ) - } - - @Test - fun `getSentryPropertiesPath fallbacks to top level properties file`() { - val topLevelProject = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - val project = ProjectBuilder - .builder() - .withParent(topLevelProject) - .build() - - testProjectDir.newFile("sentry.properties") - - assertEquals( - topLevelProject.file("sentry.properties").path, - getSentryPropertiesPath(project.projectDir, project.rootDir) - ) - } - - @Test - fun `getSentryPropertiesPath returns null if no properties file is found`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - assertNull(getSentryPropertiesPath(project.projectDir, project.rootDir)) - } - - @Test - fun `searchCliInPropertiesFile returns cli-executable correctly`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - testProjectDir.newFile("sentry.properties").apply { - writeText("cli.executable=vim") - } + @get:Rule val testProjectDir = TemporaryFolder() + + @get:Rule val systemPropertyRule = SystemPropertyRule() + + @Test + fun `getSentryPropertiesPath returns local properties file`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + testProjectDir.newFile("sentry.properties") + + assertEquals( + project.file("sentry.properties").path, + getSentryPropertiesPath(project.projectDir, project.rootDir), + ) + } + + @Test + fun `getSentryPropertiesPath fallbacks to top level properties file`() { + val topLevelProject = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + val project = ProjectBuilder.builder().withParent(topLevelProject).build() + + testProjectDir.newFile("sentry.properties") + + assertEquals( + topLevelProject.file("sentry.properties").path, + getSentryPropertiesPath(project.projectDir, project.rootDir), + ) + } + + @Test + fun `getSentryPropertiesPath returns null if no properties file is found`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + assertNull(getSentryPropertiesPath(project.projectDir, project.rootDir)) + } - assertEquals("vim", searchCliInPropertiesFile(project.projectDir, project.rootDir)) - } + @Test + fun `searchCliInPropertiesFile returns cli-executable correctly`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() - @Test - fun `searchCliInPropertiesFile ignores other fields`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() + testProjectDir.newFile("sentry.properties").apply { writeText("cli.executable=vim") } + + assertEquals("vim", searchCliInPropertiesFile(project.projectDir, project.rootDir)) + } + + @Test + fun `searchCliInPropertiesFile ignores other fields`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + testProjectDir.newFile("sentry.properties").apply { writeText("another=field") } + + assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) + } + + @Test + fun `searchCliInPropertiesFile returns null for empty file`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + testProjectDir.newFile("sentry.properties") + + assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) + } + + @Test + fun `searchCliInPropertiesFile returns null for missing file`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + + assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) + } + + @Test + fun `searchCliInResources finds the file correctly`() { + val resourcePath = "./dummy-bin/dummy-sentry-cli" + val resourceFile = + javaClass + .getResource(".") + ?.let { File(it.file, resourcePath) } + ?.apply { + parentFile.mkdirs() + createNewFile() + } - testProjectDir.newFile("sentry.properties").apply { - writeText("another=field") + val foundPath = getResourceUrl(resourcePath) + assertNotNull(foundPath) + assertTrue(foundPath.endsWith("${File.separator}dummy-bin${File.separator}dummy-sentry-cli")) + + resourceFile?.delete() + } + + @Test + fun `searchCliInResources returns null if file does not exist`() { + val resourcePath = "./dummy-bin/i-dont-exist" + + assertNull(getResourceUrl(resourcePath)) + } + + @Test + fun `loadCliFromResourcesToTemp finds the file correctly`() { + val resourcePath = "./dummy-bin/dummy-sentry-cli" + val resourceFile = + javaClass + .getResource(".") + ?.let { File(it.file, resourcePath) } + ?.apply { + parentFile.mkdirs() + createNewFile() + writeText("echo \"This is just a dummy script\"") } - assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) - } - - @Test - fun `searchCliInPropertiesFile returns null for empty file`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - testProjectDir.newFile("sentry.properties") - - assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) - } - - @Test - fun `searchCliInPropertiesFile returns null for missing file`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() - - assertNull(searchCliInPropertiesFile(project.projectDir, project.rootDir)) - } - - @Test - fun `searchCliInResources finds the file correctly`() { - val resourcePath = "./dummy-bin/dummy-sentry-cli" - val resourceFile = javaClass.getResource(".") - ?.let { File(it.file, resourcePath) } - ?.apply { - parentFile.mkdirs() - createNewFile() - } - - val foundPath = getResourceUrl(resourcePath) - assertNotNull(foundPath) - assertTrue( - foundPath.endsWith("${File.separator}dummy-bin${File.separator}dummy-sentry-cli") - ) - - resourceFile?.delete() - } - - @Test - fun `searchCliInResources returns null if file does not exist`() { - val resourcePath = "./dummy-bin/i-dont-exist" - - assertNull(getResourceUrl(resourcePath)) - } - - @Test - fun `loadCliFromResourcesToTemp finds the file correctly`() { - val resourcePath = "./dummy-bin/dummy-sentry-cli" - val resourceFile = javaClass.getResource(".") - ?.let { File(it.file, resourcePath) } - ?.apply { - parentFile.mkdirs() - createNewFile() - writeText("echo \"This is just a dummy script\"") - } - - val outputFile = File("bin", "output-bin") - val loadedPath = extractCliFromResources(resourcePath, outputFile) - assertNotNull(loadedPath) - - val binContent = File(loadedPath).readText() - assertEquals("echo \"This is just a dummy script\"", binContent) - - resourceFile?.delete() - outputFile.delete() - } - - @Test - fun `loadCliFromResourcesToTemp returns null if file does not exist`() { - val resourcePath = "./dummy-bin/i-dont-exist" - - assertNull(extractCliFromResources(resourcePath, File("."))) - } - - @Test - @WithSystemProperty(["os.name"], ["mac"]) - fun `getCliSuffix on mac returns Darwin-universal`() { - assertEquals("Darwin-universal", getCliSuffix()) - } - - @Test - @WithSystemProperty(["os.name", "os.arch"], ["linux", "amd64"]) - fun `getCliSuffix on linux amd64 returns Linux-x86_64`() { - assertEquals("Linux-x86_64", getCliSuffix()) - } - - @Test - @WithSystemProperty(["os.name", "os.arch"], ["linux", "armV7"]) - fun `getCliSuffix on linux armV7 returns Linux-armV7`() { - assertEquals("Linux-armV7", getCliSuffix()) - } - - @Test - @WithSystemProperty(["os.name"], ["windows"]) - fun `getCliSuffix on win returns Windows-i686`() { - assertEquals("Windows-i686.exe", getCliSuffix()) - } - - @Test - @WithSystemProperty(["os.name"], ["¯\\_(ツ)_/¯"]) - fun `getCliSuffix on an unknown platform returns null`() { - assertNull(getCliSuffix()) - } + val outputFile = File("bin", "output-bin") + val loadedPath = extractCliFromResources(resourcePath, outputFile) + assertNotNull(loadedPath) + + val binContent = File(loadedPath).readText() + assertEquals("echo \"This is just a dummy script\"", binContent) + + resourceFile?.delete() + outputFile.delete() + } + + @Test + fun `loadCliFromResourcesToTemp returns null if file does not exist`() { + val resourcePath = "./dummy-bin/i-dont-exist" + + assertNull(extractCliFromResources(resourcePath, File("."))) + } + + @Test + @WithSystemProperty(["os.name"], ["mac"]) + fun `getCliSuffix on mac returns Darwin-universal`() { + assertEquals("Darwin-universal", getCliSuffix()) + } + + @Test + @WithSystemProperty(["os.name", "os.arch"], ["linux", "amd64"]) + fun `getCliSuffix on linux amd64 returns Linux-x86_64`() { + assertEquals("Linux-x86_64", getCliSuffix()) + } + + @Test + @WithSystemProperty(["os.name", "os.arch"], ["linux", "armV7"]) + fun `getCliSuffix on linux armV7 returns Linux-armV7`() { + assertEquals("Linux-armV7", getCliSuffix()) + } + + @Test + @WithSystemProperty(["os.name"], ["windows"]) + fun `getCliSuffix on win returns Windows-i686`() { + assertEquals("Windows-i686.exe", getCliSuffix()) + } + + @Test + @WithSystemProperty(["os.name"], ["¯\\_(ツ)_/¯"]) + fun `getCliSuffix on an unknown platform returns null`() { + assertNull(getCliSuffix()) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginMRJarTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginMRJarTest.kt index 40fab7ed..a0fd927f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginMRJarTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginMRJarTest.kt @@ -5,48 +5,46 @@ import kotlin.test.assertTrue import org.junit.Test class SentryPluginMRJarTest : - BaseSentryPluginTest(androidGradlePluginVersion = "7.0.4", gradleVersion = "7.3") { + BaseSentryPluginTest(androidGradlePluginVersion = "7.0.4", gradleVersion = "7.3") { - @Test - fun `does not break when there is a MR-JAR dependency with unsupported java version`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `does not break when there is a MR-JAR dependency with unsupported java version`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation("com.squareup.moshi:moshi-kotlin:1.13.0") } sentry.tracingInstrumentation.enabled = true - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleDebug") - .build() + val result = runner.appendArguments("app:assembleDebug").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - @Test - fun `shows a warning when there is a signed MR-JAR dependency`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `shows a warning when there is a signed MR-JAR dependency`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation("org.bouncycastle:bcprov-jdk15on:1.63") } sentry.tracingInstrumentation.enabled = true - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleDebug") - .build() + val result = runner.appendArguments("app:assembleDebug").build() - assertTrue(result.output) { "Please update to AGP >= 7.1.2" in result.output } - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "Please update to AGP >= 7.1.2" in result.output } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - override val additionalRootProjectConfig: String = "" + override val additionalRootProjectConfig: String = "" } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPropertiesFileProviderTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPropertiesFileProviderTest.kt index b930b641..b988c14e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPropertiesFileProviderTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPropertiesFileProviderTest.kt @@ -12,213 +12,199 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) -class SentryPropertiesFileProviderTest( - private val agpVersion: SemVer -) { - - private val sep = File.separator - - @Test - fun `getPropertiesFilePath finds file inside debug folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - createTestFile(project.projectDir, "src${sep}debug${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "debug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside project folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - createTestFile(project.projectDir, "sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "release") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside flavorName folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - productFlavors.create("full") - } - createTestFile(project.projectDir, "src${sep}lite${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside flavorName-buildType folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - productFlavors.create("full") - } - createTestFile(project.projectDir, "src${sep}lite${sep}debug${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside buildType-flavorName folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - productFlavors.create("full") - } - createTestFile(project.projectDir, "src${sep}debug${sep}lite${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) +class SentryPropertiesFileProviderTest(private val agpVersion: SemVer) { + + private val sep = File.separator + + @Test + fun `getPropertiesFilePath finds file inside debug folder`() { + val (project, _) = createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + createTestFile(project.projectDir, "src${sep}debug${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "debug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside project folder`() { + val (project, _) = createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + createTestFile(project.projectDir, "sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "release") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside flavorName folder`() { + val (project, _) = + createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) { + flavorDimensions("version") + productFlavors.create("lite") + productFlavors.create("full") + } + createTestFile(project.projectDir, "src${sep}lite${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside flavorName-buildType folder`() { + val (project, _) = + createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) { + flavorDimensions("version") + productFlavors.create("lite") + productFlavors.create("full") + } + createTestFile(project.projectDir, "src${sep}lite${sep}debug${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside buildType-flavorName folder`() { + val (project, _) = + createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) { + flavorDimensions("version") + productFlavors.create("lite") + productFlavors.create("full") + } + createTestFile(project.projectDir, "src${sep}debug${sep}lite${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath with multiple flavorDimensions finds file inside flavor folder`() { + val (project, _) = + createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) { + flavorDimensions("version", "api") + productFlavors.create("lite") { it.dimension("version") } + productFlavors.create("api30") { it.dimension("api") } + } + createTestFile(project.projectDir, "src${sep}liteApi30${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteApi30Debug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside other productFlavor folders`() { + val (project, _) = + createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) { + flavorDimensions("version", "api") + productFlavors.create("lite") { it.dimension("version") } + productFlavors.create("api30") { it.dimension("api") } + } + createTestFile(project.projectDir, "src${sep}api30${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteApi30Debug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside root project folder`() { + val rootProject = ProjectBuilder.builder().build() + val (project, _) = + createTestAndroidProject( + parent = rootProject, + forceEvaluate = !AgpVersions.isAGP74(agpVersion), + ) + createTestFile(rootProject.projectDir, "sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "release") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside root buildType folder`() { + val rootProject = ProjectBuilder.builder().build() + val (project, _) = + createTestAndroidProject( + parent = rootProject, + forceEvaluate = !AgpVersions.isAGP74(agpVersion), + ) + createTestFile(rootProject.projectDir, "src${sep}debug${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "debug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside root flavor folder`() { + val rootProject = ProjectBuilder.builder().build() + val (project, _) = + createTestAndroidProject( + parent = rootProject, + forceEvaluate = !AgpVersions.isAGP74(agpVersion), + ) { + flavorDimensions("version") + productFlavors.create("lite") + } + createTestFile(rootProject.projectDir, "src${sep}lite${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside root flavor buildType folder`() { + val rootProject = ProjectBuilder.builder().build() + val (project, _) = + createTestAndroidProject( + parent = rootProject, + forceEvaluate = !AgpVersions.isAGP74(agpVersion), + ) { + flavorDimensions("version") + productFlavors.create("lite") + } + createTestFile(rootProject.projectDir, "src${sep}lite${sep}debug${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + @Test + fun `getPropertiesFilePath finds file inside root buildType flavor folder`() { + val rootProject = ProjectBuilder.builder().build() + val (project, _) = + createTestAndroidProject( + parent = rootProject, + forceEvaluate = !AgpVersions.isAGP74(agpVersion), + ) { + flavorDimensions("version") + productFlavors.create("lite") + } + createTestFile(rootProject.projectDir, "src${sep}debug${sep}lite${sep}sentry.properties") + + val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") + + assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) + } + + private fun createTestFile(parent: File, path: String) = + File(parent, path).apply { + parentFile.mkdirs() + createNewFile() + writeText("42") } - @Test - fun `getPropertiesFilePath with multiple flavorDimensions finds file inside flavor folder`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version", "api") - productFlavors.create("lite") { - it.dimension("version") - } - productFlavors.create("api30") { - it.dimension("api") - } - } - createTestFile(project.projectDir, "src${sep}liteApi30${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteApi30Debug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside other productFlavor folders`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version", "api") - productFlavors.create("lite") { - it.dimension("version") - } - productFlavors.create("api30") { - it.dimension("api") - } - } - createTestFile(project.projectDir, "src${sep}api30${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteApi30Debug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside root project folder`() { - val rootProject = ProjectBuilder.builder().build() - val (project, _) = createTestAndroidProject( - parent = rootProject, - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - createTestFile(rootProject.projectDir, "sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "release") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside root buildType folder`() { - val rootProject = ProjectBuilder.builder().build() - val (project, _) = createTestAndroidProject( - parent = rootProject, - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - createTestFile(rootProject.projectDir, "src${sep}debug${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "debug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside root flavor folder`() { - val rootProject = ProjectBuilder.builder().build() - val (project, _) = createTestAndroidProject( - parent = rootProject, - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - } - createTestFile(rootProject.projectDir, "src${sep}lite${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside root flavor buildType folder`() { - val rootProject = ProjectBuilder.builder().build() - val (project, _) = createTestAndroidProject( - parent = rootProject, - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - } - createTestFile(rootProject.projectDir, "src${sep}lite${sep}debug${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - @Test - fun `getPropertiesFilePath finds file inside root buildType flavor folder`() { - val rootProject = ProjectBuilder.builder().build() - val (project, _) = createTestAndroidProject( - parent = rootProject, - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) { - flavorDimensions("version") - productFlavors.create("lite") - } - createTestFile(rootProject.projectDir, "src${sep}debug${sep}lite${sep}sentry.properties") - - val variant = project.retrieveAndroidVariant(agpVersion, "liteDebug") - - assertEquals("42", File(getPropertiesFilePath(project, variant)!!).readText()) - } - - private fun createTestFile(parent: File, path: String) = - File(parent, path).apply { - parentFile.mkdirs() - createNewFile() - writeText("42") - } - - companion object { - @Parameterized.Parameters(name = "AGP {0}") - @JvmStatic - fun parameters() = listOf(AgpVersions.VERSION_7_0_0, AgpVersions.VERSION_7_4_0) - } + companion object { + @Parameterized.Parameters(name = "AGP {0}") + @JvmStatic + fun parameters() = listOf(AgpVersions.VERSION_7_0_0, AgpVersions.VERSION_7_4_0) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryTaskProviderTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryTaskProviderTest.kt index b85099b1..4755bc3c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryTaskProviderTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryTaskProviderTest.kt @@ -25,287 +25,257 @@ import org.junit.rules.TemporaryFolder class SentryTaskProviderTest { - @get:Rule - val testProjectDir = TemporaryFolder() + @get:Rule val testProjectDir = TemporaryFolder() - @Test - fun `getTransformerTask returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getTransformerTask returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getMinifyTask(project, "debug")) - } + assertNull(getMinifyTask(project, "debug")) + } - @Test - fun `getTransformerTask returns transform task for standalone Proguard with opt-in`() { - val (project, task) = getTestProjectWithTask( - "transformClassesAndResourcesWithProguardTransformForDebug" - ) + @Test + fun `getTransformerTask returns transform task for standalone Proguard with opt-in`() { + val (project, task) = + getTestProjectWithTask("transformClassesAndResourcesWithProguardTransformForDebug") - assertEquals( - task, - getMinifyTask( - project, - "debug", - dexguardEnabled = true - )!!.get() - ) - } + assertEquals(task, getMinifyTask(project, "debug", dexguardEnabled = true)!!.get()) + } - @Test - fun `getTransformerTask returns null for standalone Proguard without opt-in`() { - val (project, _) = getTestProjectWithTask( - "transformClassesAndResourcesWithProguardTransformForDebug" - ) + @Test + fun `getTransformerTask returns null for standalone Proguard without opt-in`() { + val (project, _) = + getTestProjectWithTask("transformClassesAndResourcesWithProguardTransformForDebug") - assertNull( - getMinifyTask( - project, - "debug", - dexguardEnabled = false - ) - ) - } + assertNull(getMinifyTask(project, "debug", dexguardEnabled = false)) + } - @Test - fun `getTransformerTask returns minify for R8`() { - val (project, task) = getTestProjectWithTask("minifyDebugWithR8") + @Test + fun `getTransformerTask returns minify for R8`() { + val (project, task) = getTestProjectWithTask("minifyDebugWithR8") - assertEquals(task, getMinifyTask(project, "debug")!!.get()) - } + assertEquals(task, getMinifyTask(project, "debug")!!.get()) + } - @Test - fun `getTransformerTask returns minify for embedded Proguard`() { - val (project, task) = getTestProjectWithTask("minifyDebugWithProguard") + @Test + fun `getTransformerTask returns minify for embedded Proguard`() { + val (project, task) = getTestProjectWithTask("minifyDebugWithProguard") - assertEquals(task, getMinifyTask(project, "debug")!!.get()) - } + assertEquals(task, getMinifyTask(project, "debug")!!.get()) + } - @Test - fun `getTransformerTask gives standalone Proguard priority with opt-in`() { - val (project, _) = getTestProjectWithTask("minifyDebugWithR8") - project.tasks.register("transformClassesAndResourcesWithProguardTransformForDebug") + @Test + fun `getTransformerTask gives standalone Proguard priority with opt-in`() { + val (project, _) = getTestProjectWithTask("minifyDebugWithR8") + project.tasks.register("transformClassesAndResourcesWithProguardTransformForDebug") - assertEquals( - "transformClassesAndResourcesWithProguardTransformForDebug", - getMinifyTask( - project, - "debug", - dexguardEnabled = true - )!!.name - ) - } + assertEquals( + "transformClassesAndResourcesWithProguardTransformForDebug", + getMinifyTask(project, "debug", dexguardEnabled = true)!!.name, + ) + } - @Test - fun `getTransformerTask ignores standalone Proguard priority without opt-in`() { - val (project, r8task) = getTestProjectWithTask("minifyDebugWithR8") - project.tasks.register("transformClassesAndResourcesWithProguardTransformForDebug") + @Test + fun `getTransformerTask ignores standalone Proguard priority without opt-in`() { + val (project, r8task) = getTestProjectWithTask("minifyDebugWithR8") + project.tasks.register("transformClassesAndResourcesWithProguardTransformForDebug") - assertEquals( - r8task, - getMinifyTask( - project, - "debug", - dexguardEnabled = false - )!!.get() - ) - } + assertEquals(r8task, getMinifyTask(project, "debug", dexguardEnabled = false)!!.get()) + } - @Test - fun `getPreBundleTask returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getPreBundleTask returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getPreBundleTask(project, "debug")?.get()) - } + assertNull(getPreBundleTask(project, "debug")?.get()) + } - @Test - fun `getPreBundleTask returns correct task`() { - val (project, task) = getTestProjectWithTask("buildDebugPreBundle") + @Test + fun `getPreBundleTask returns correct task`() { + val (project, task) = getTestProjectWithTask("buildDebugPreBundle") - assertEquals(task, getPreBundleTask(project, "debug")?.get()) - } + assertEquals(task, getPreBundleTask(project, "debug")?.get()) + } - @Test - fun `getBundleTask returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getBundleTask returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getBundleTask(project, "debug")?.get()) - } + assertNull(getBundleTask(project, "debug")?.get()) + } - @Test - fun `getBundleTask returns correct task`() { - val (project, task) = getTestProjectWithTask("bundleDebug") + @Test + fun `getBundleTask returns correct task`() { + val (project, task) = getTestProjectWithTask("bundleDebug") - assertEquals(task, getBundleTask(project, "debug")?.get()) - } + assertEquals(task, getBundleTask(project, "debug")?.get()) + } - @Test - fun `getPackageBundleTask returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getPackageBundleTask returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getPackageBundleTask(project, "debug")?.get()) - } + assertNull(getPackageBundleTask(project, "debug")?.get()) + } - @Test - fun `getPackageBundleTask returns package bundle task`() { - val (project, task) = getTestProjectWithTask("packageDebugBundle") + @Test + fun `getPackageBundleTask returns package bundle task`() { + val (project, task) = getTestProjectWithTask("packageDebugBundle") - assertEquals(task, getPackageBundleTask(project, "debug")?.get()) - } + assertEquals(task, getPackageBundleTask(project, "debug")?.get()) + } - @Test - fun `getAssembleTaskProvider works correctly for all the variants AGP70`() { - val (project, android) = getAndroidExtFromProject() - - android.applicationVariants.configureEach { - if (it.name == "debug") { - assertEquals( - "assembleDebug", - getAssembleTaskProvider(project, AndroidVariant70(it))?.get()?.name - ) - } else { - assertEquals( - "assembleRelease", - getAssembleTaskProvider(project, AndroidVariant70(it))?.get()?.name - ) - } - } - } + @Test + fun `getAssembleTaskProvider works correctly for all the variants AGP70`() { + val (project, android) = getAndroidExtFromProject() - @Test - fun `getAssembleTaskProvider falls back to findTask if assembleProvider is null`() { - val (project, android) = getAndroidExtFromProject() - - android.applicationVariants.configureEach { - val sentryVariant = object : SentryVariant by AndroidVariant70(it) { - override val assembleProvider: TaskProvider? get() = null - } - if (it.name == "debug") { - assertEquals( - "assembleDebug", - getAssembleTaskProvider(project, sentryVariant)?.get()?.name - ) - } else { - assertEquals( - "assembleRelease", - getAssembleTaskProvider(project, sentryVariant)?.get()?.name - ) - } - } + android.applicationVariants.configureEach { + if (it.name == "debug") { + assertEquals( + "assembleDebug", + getAssembleTaskProvider(project, AndroidVariant70(it))?.get()?.name, + ) + } else { + assertEquals( + "assembleRelease", + getAssembleTaskProvider(project, AndroidVariant70(it))?.get()?.name, + ) + } } + } - @Test - fun `getInstallTaskProvider works correctly for all the variants AGP70`() { - val (project, android) = getAndroidExtFromProject() - - android.applicationVariants.configureEach { - if (it.name == "debug") { - assertEquals( - "installDebug", - getInstallTaskProvider(project, AndroidVariant70(it))?.get()?.name - ) - } - } - } + @Test + fun `getAssembleTaskProvider falls back to findTask if assembleProvider is null`() { + val (project, android) = getAndroidExtFromProject() - @Test - fun `getInstallTaskProvider falls back to findTask if assembleProvider is null`() { - val (project, android) = getAndroidExtFromProject() - - android.applicationVariants.configureEach { - val sentryVariant = object : SentryVariant by AndroidVariant70(it) { - override val installProvider: TaskProvider? get() = null - } - if (it.name == "debug") { - assertEquals( - "installDebug", - getInstallTaskProvider(project, sentryVariant)?.get()?.name - ) - } + android.applicationVariants.configureEach { + val sentryVariant = + object : SentryVariant by AndroidVariant70(it) { + override val assembleProvider: TaskProvider? + get() = null } + if (it.name == "debug") { + assertEquals("assembleDebug", getAssembleTaskProvider(project, sentryVariant)?.get()?.name) + } else { + assertEquals( + "assembleRelease", + getAssembleTaskProvider(project, sentryVariant)?.get()?.name, + ) + } } + } - @Test - fun `getMergeAssetsProvider works correctly for all the variants`() { - val (_, android) = getAndroidExtFromProject() + @Test + fun `getInstallTaskProvider works correctly for all the variants AGP70`() { + val (project, android) = getAndroidExtFromProject() - android.applicationVariants.configureEach { - if (it.name == "debug") { - assertEquals("mergeDebugAssets", getMergeAssetsProvider(it)?.get()?.name) - } else { - assertEquals("mergeReleaseAssets", getMergeAssetsProvider(it)?.get()?.name) - } - } + android.applicationVariants.configureEach { + if (it.name == "debug") { + assertEquals( + "installDebug", + getInstallTaskProvider(project, AndroidVariant70(it))?.get()?.name, + ) + } } + } - @Test - fun `getPackageProvider works correctly for all the variants`() { - val (_, android) = getAndroidExtFromProject() + @Test + fun `getInstallTaskProvider falls back to findTask if assembleProvider is null`() { + val (project, android) = getAndroidExtFromProject() - android.applicationVariants.configureEach { - if (it.name == "debug") { - assertEquals("packageDebug", getPackageProvider(AndroidVariant70(it))?.name) - } else { - assertEquals("packageRelease", getPackageProvider(AndroidVariant70(it))?.name) - } + android.applicationVariants.configureEach { + val sentryVariant = + object : SentryVariant by AndroidVariant70(it) { + override val installProvider: TaskProvider? + get() = null } + if (it.name == "debug") { + assertEquals("installDebug", getInstallTaskProvider(project, sentryVariant)?.get()?.name) + } } + } + + @Test + fun `getMergeAssetsProvider works correctly for all the variants`() { + val (_, android) = getAndroidExtFromProject() + + android.applicationVariants.configureEach { + if (it.name == "debug") { + assertEquals("mergeDebugAssets", getMergeAssetsProvider(it)?.get()?.name) + } else { + assertEquals("mergeReleaseAssets", getMergeAssetsProvider(it)?.get()?.name) + } + } + } + + @Test + fun `getPackageProvider works correctly for all the variants`() { + val (_, android) = getAndroidExtFromProject() + + android.applicationVariants.configureEach { + if (it.name == "debug") { + assertEquals("packageDebug", getPackageProvider(AndroidVariant70(it))?.name) + } else { + assertEquals("packageRelease", getPackageProvider(AndroidVariant70(it))?.name) + } + } + } - @Test - fun `getLintVitalAnalyze returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getLintVitalAnalyze returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getLintVitalAnalyzeProvider(project, "debug")?.get()) - } + assertNull(getLintVitalAnalyzeProvider(project, "debug")?.get()) + } - @Test - fun `getLintVitalAnalyze returns correct task`() { - val (project, task) = getTestProjectWithTask("lintVitalAnalyzeDebug") + @Test + fun `getLintVitalAnalyze returns correct task`() { + val (project, task) = getTestProjectWithTask("lintVitalAnalyzeDebug") - assertEquals(task, getLintVitalAnalyzeProvider(project, "debug")?.get()) - } + assertEquals(task, getLintVitalAnalyzeProvider(project, "debug")?.get()) + } - @Test - fun `getLintVitalReport returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getLintVitalReport returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getLintVitalReportProvider(project, "debug")?.get()) - } + assertNull(getLintVitalReportProvider(project, "debug")?.get()) + } - @Test - fun `getLintVitalReport returns correct task`() { - val (project, task) = getTestProjectWithTask("lintVitalReportDebug") + @Test + fun `getLintVitalReport returns correct task`() { + val (project, task) = getTestProjectWithTask("lintVitalReportDebug") - assertEquals(task, getLintVitalReportProvider(project, "debug")?.get()) - } + assertEquals(task, getLintVitalReportProvider(project, "debug")?.get()) + } - @Test - fun `getProcessResources returns null for missing task`() { - val project = ProjectBuilder.builder().build() + @Test + fun `getProcessResources returns null for missing task`() { + val project = ProjectBuilder.builder().build() - assertNull(getProcessResourcesProvider(project)?.get()) - } + assertNull(getProcessResourcesProvider(project)?.get()) + } - @Test - fun `getProcessResources returns correct task`() { - val (project, task) = getTestProjectWithTask("processResources") + @Test + fun `getProcessResources returns correct task`() { + val (project, task) = getTestProjectWithTask("processResources") - assertEquals(task, getProcessResourcesProvider(project)?.get()) - } + assertEquals(task, getProcessResourcesProvider(project)?.get()) + } - private fun getAndroidExtFromProject(): Pair { - val project = ProjectBuilder.builder().build() - project.plugins.apply("com.android.application") - val android = project.extensions.getByType(AppExtension::class.java).apply { - compileSdkVersion(30) - } + private fun getAndroidExtFromProject(): Pair { + val project = ProjectBuilder.builder().build() + project.plugins.apply("com.android.application") + val android = + project.extensions.getByType(AppExtension::class.java).apply { compileSdkVersion(30) } - // This forces the project to be evaluated - project.getTasksByName("assembleDebug", false) - return project to android - } + // This forces the project to be evaluated + project.getTasksByName("assembleDebug", false) + return project to android + } - private fun getTestProjectWithTask(taskName: String): Pair { - val project = ProjectBuilder.builder().build() - return project to project.tasks.register(taskName).get() - } + private fun getTestProjectWithTask(taskName: String): Pair { + val project = ProjectBuilder.builder().build() + return project to project.tasks.register(taskName).get() + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt index 3122ae43..7566c874 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt @@ -25,177 +25,179 @@ import org.junit.rules.TemporaryFolder /* ktlint-disable max-line-length */ private val ASSET_PATTERN_PROGUARD = - Regex( - """^io\.sentry\.ProguardUuids=([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$""" - .trimMargin() - ) + Regex( + """^io\.sentry\.ProguardUuids=([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$""" + .trimMargin() + ) private val ASSET_PATTERN_SOURCE_CONTEXT = - Regex( - """^$SENTRY_BUNDLE_ID_PROPERTY=([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$""" - .trimMargin() - ) + Regex( + """^$SENTRY_BUNDLE_ID_PROPERTY=([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$""" + .trimMargin() + ) + /* ktlint-enable max-line-length */ internal fun verifyProguardUuid( - rootFile: File, - variant: String = "release", - signed: Boolean = true, - inGeneratedFolder: Boolean = false + rootFile: File, + variant: String = "release", + signed: Boolean = true, + inGeneratedFolder: Boolean = false, ): UUID { - val signedStr = if (signed) "-unsigned" else "" - val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") - val sentryProperties = if (inGeneratedFolder) { - /* ktlint-disable max-line-length experimental:argument-list-wrapping */ - val propsFile = if (AgpVersions.isAGP74) { - rootFile - .resolve( - "app/build/generated/assets" + - "/generateSentryDebugMetaProperties${variant.capitalized}" + - "/sentry-debug-meta.properties" - ) + val signedStr = if (signed) "-unsigned" else "" + val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") + val sentryProperties = + if (inGeneratedFolder) { + /* ktlint-disable max-line-length experimental:argument-list-wrapping */ + val propsFile = + if (AgpVersions.isAGP74) { + rootFile.resolve( + "app/build/generated/assets" + + "/generateSentryDebugMetaProperties${variant.capitalized}" + + "/sentry-debug-meta.properties" + ) } else { - rootFile.resolve( - "app/build/generated/assets" + - "/sentry/debug-meta-properties/$variant/sentry-debug-meta.properties" - ) + rootFile.resolve( + "app/build/generated/assets" + + "/sentry/debug-meta-properties/$variant/sentry-debug-meta.properties" + ) } - /* ktlint-enable max-line-length experimental:argument-list-wrapping */ - if (propsFile.exists()) propsFile.readText() else "" + /* ktlint-enable max-line-length experimental:argument-list-wrapping */ + if (propsFile.exists()) propsFile.readText() else "" } else { - extractZip(apk, "assets/sentry-debug-meta.properties") + extractZip(apk, "assets/sentry-debug-meta.properties") } - val matcher = sentryProperties.lines().mapNotNull { line -> - ASSET_PATTERN_PROGUARD.matchEntire(line) - }.firstOrNull() + val matcher = + sentryProperties + .lines() + .mapNotNull { line -> ASSET_PATTERN_PROGUARD.matchEntire(line) } + .firstOrNull() - assertTrue("Properties file is missing from the APK") { sentryProperties.isNotBlank() } - assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_PROGUARD") + assertTrue("Properties file is missing from the APK") { sentryProperties.isNotBlank() } + assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_PROGUARD") - return UUID.fromString(matcher.groupValues[1]) + return UUID.fromString(matcher.groupValues[1]) } internal fun verifySourceContextId( - rootFile: File, - variant: String = "release", - signed: Boolean = true + rootFile: File, + variant: String = "release", + signed: Boolean = true, ): UUID { - val signedStr = if (signed) "-unsigned" else "" - val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") - val sentryProperties = extractZip(apk, "assets/sentry-debug-meta.properties") - val matcher = sentryProperties.lines().mapNotNull { line -> - ASSET_PATTERN_SOURCE_CONTEXT.matchEntire(line) - }.firstOrNull() - - assertTrue("Properties file is missing from the APK") { sentryProperties.isNotBlank() } - assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_SOURCE_CONTEXT") - - return UUID.fromString(matcher.groupValues[1]) + val signedStr = if (signed) "-unsigned" else "" + val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") + val sentryProperties = extractZip(apk, "assets/sentry-debug-meta.properties") + val matcher = + sentryProperties + .lines() + .mapNotNull { line -> ASSET_PATTERN_SOURCE_CONTEXT.matchEntire(line) } + .firstOrNull() + + assertTrue("Properties file is missing from the APK") { sentryProperties.isNotBlank() } + assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_SOURCE_CONTEXT") + + return UUID.fromString(matcher.groupValues[1]) } internal fun verifyIntegrationList( - rootFile: File, - variant: String = "release", - signed: Boolean = true + rootFile: File, + variant: String = "release", + signed: Boolean = true, ): List { - return retrieveMetaDataFromManifest( - rootFile, - variant, - "io.sentry.gradle-plugin-integrations", - signed + return retrieveMetaDataFromManifest( + rootFile, + variant, + "io.sentry.gradle-plugin-integrations", + signed, ) - .split(',') + .split(',') } internal fun retrieveMetaDataFromManifest( - rootFile: File, - variant: String = "release", - metaDataName: String, - signed: Boolean = true + rootFile: File, + variant: String = "release", + metaDataName: String, + signed: Boolean = true, ): String { - val signedStr = if (signed) "-unsigned" else "" - val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") + val signedStr = if (signed) "-unsigned" else "" + val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") - val apkModule = ApkModule.loadApkFile(apk) - val attribute = apkModule.androidManifestBlock + val apkModule = ApkModule.loadApkFile(apk) + val attribute = apkModule.androidManifestBlock - val metaDataValue = attribute.applicationElement.listElements() - .filter { it.tag == "meta-data" } - .filter { it.searchAttributeByName("name").valueAsString == metaDataName } - .map { it.searchAttributeByName("value").valueAsString }.first() + val metaDataValue = + attribute.applicationElement + .listElements() + .filter { it.tag == "meta-data" } + .filter { it.searchAttributeByName("name").valueAsString == metaDataName } + .map { it.searchAttributeByName("value").valueAsString } + .first() - return metaDataValue + return metaDataValue } -internal fun verifyDependenciesReportAndroid( - rootFile: File, - variant: String = "debug" -): String { - val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant.apk") - val dependenciesReport = extractZip(apk, "assets/sentry-external-modules.txt") +internal fun verifyDependenciesReportAndroid(rootFile: File, variant: String = "debug"): String { + val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant.apk") + val dependenciesReport = extractZip(apk, "assets/sentry-external-modules.txt") - assertTrue("Dependencies file is missing from the APK") { dependenciesReport.isNotBlank() } - return dependenciesReport + assertTrue("Dependencies file is missing from the APK") { dependenciesReport.isNotBlank() } + return dependenciesReport } -internal fun verifyDependenciesReportJava( - rootFile: File -): String { - val apk = rootFile.resolve("module/build/libs/module.jar") - val dependenciesReport = extractZip(apk, "sentry-external-modules.txt") +internal fun verifyDependenciesReportJava(rootFile: File): String { + val apk = rootFile.resolve("module/build/libs/module.jar") + val dependenciesReport = extractZip(apk, "sentry-external-modules.txt") - assertTrue("Dependencies file is missing from the APK") { dependenciesReport.isNotBlank() } - return dependenciesReport + assertTrue("Dependencies file is missing from the APK") { dependenciesReport.isNotBlank() } + return dependenciesReport } private fun extractZip(zipFile: File, fileToExtract: String): String { - val zip = ZipFile(zipFile) - try { - zip.getInputStream(zip.getFileHeader(fileToExtract)).use { zis -> - return readZippedContent(zis) - } - } catch (e: ZipException) { - println("No entry $fileToExtract in $zipFile") + val zip = ZipFile(zipFile) + try { + zip.getInputStream(zip.getFileHeader(fileToExtract)).use { zis -> + return readZippedContent(zis) } - return "" + } catch (e: ZipException) { + println("No entry $fileToExtract in $zipFile") + } + return "" } private fun readZippedContent(zipInputStream: ZipInputStream): String { - val baos = ByteArrayOutputStream() - val content = ByteArray(1024) - var len: Int = zipInputStream.read(content) - while (len > 0) { - baos.write(content, 0, len) - len = zipInputStream.read(content) - } - val stringContent = baos.toString(Charset.defaultCharset().name()) - baos.close() - return stringContent + val baos = ByteArrayOutputStream() + val content = ByteArray(1024) + var len: Int = zipInputStream.read(content) + while (len > 0) { + baos.write(content, 0, len) + len = zipInputStream.read(content) + } + val stringContent = baos.toString(Charset.defaultCharset().name()) + baos.close() + return stringContent } fun Project.retrieveAndroidVariant(agpVersion: SemVer, variantName: String): SentryVariant { - return if (AgpVersions.isAGP74(agpVersion)) { - var debug: Variant? = null - val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) - extension.onVariants(extension.selector().withName(variantName)) { - debug = it - } - project.forceEvaluate() - - return AndroidVariant74(debug!!) - } else { - val variant = project - .extensions - .getByType(AppExtension::class.java) - .applicationVariants.first { it.name == variantName } - AndroidVariant70(variant) - } + return if (AgpVersions.isAGP74(agpVersion)) { + var debug: Variant? = null + val extension = project.extensions.getByType(AndroidComponentsExtension::class.java) + extension.onVariants(extension.selector().withName(variantName)) { debug = it } + project.forceEvaluate() + + return AndroidVariant74(debug!!) + } else { + val variant = + project.extensions.getByType(AppExtension::class.java).applicationVariants.first { + it.name == variantName + } + AndroidVariant70(variant) + } } fun TemporaryFolder.withDummyComposeFile(): String { - val contents = - // language=kotlin - """ + val contents = + // language=kotlin + """ package com.example import androidx.compose.runtime.Composable @@ -205,87 +207,86 @@ fun TemporaryFolder.withDummyComposeFile(): String { fun FancyButton() { BasicText("Hello World") } - """.trimIndent() - val sourceFile = - File(newFolder("app/src/main/kotlin/com/example/"), "Example.kt") + """ + .trimIndent() + val sourceFile = File(newFolder("app/src/main/kotlin/com/example/"), "Example.kt") - sourceFile.writeText(contents) - return contents + sourceFile.writeText(contents) + return contents } fun TemporaryFolder.withDummyKtFile(): String { - val contents = - // language=kotlin - """ + val contents = + // language=kotlin + """ package com.example fun math(a: Int) = a * 2 - """.trimIndent() - val sourceFile = - File(newFolder("app/src/main/kotlin/com/example/"), "Example.kt") + """ + .trimIndent() + val sourceFile = File(newFolder("app/src/main/kotlin/com/example/"), "Example.kt") - sourceFile.writeText(contents) - return contents + sourceFile.writeText(contents) + return contents } fun TemporaryFolder.withDummyJavaFile(): String { - val contents = - // language=java - """ + val contents = + // language=java + """ package com.example; public class TestJava { } - """.trimIndent() - val sourceFile = - File(newFolder("app/src/main/java/com/example/"), "TestJava.java") + """ + .trimIndent() + val sourceFile = File(newFolder("app/src/main/java/com/example/"), "TestJava.java") - sourceFile.writeText(contents) - return contents + sourceFile.writeText(contents) + return contents } fun TemporaryFolder.withDummyCustomFile(): String { - val contents = - // language=kotlin - """ + val contents = + // language=kotlin + """ package io.other class TestKotlin { fun math(a: Int) = a * 2 } - """.trimIndent() - val sourceFile = - File(newFolder("app/src/custom/kotlin/io/other/"), "TestCustom.kt") + """ + .trimIndent() + val sourceFile = File(newFolder("app/src/custom/kotlin/io/other/"), "TestCustom.kt") - sourceFile.writeText(contents) - return contents + sourceFile.writeText(contents) + return contents } internal fun verifySourceBundleContents( - rootFile: File, - sourceFilePath: String, - contents: String, - variant: String = "release", - archivePath: String = "app/build/outputs/apk/$variant/app-$variant-unsigned.apk" + rootFile: File, + sourceFilePath: String, + contents: String, + variant: String = "release", + archivePath: String = "app/build/outputs/apk/$variant/app-$variant-unsigned.apk", ) { - // first, extract the bundle-id to find the source bundle later in "/intermediates/sentry/" - val apk = rootFile.resolve(archivePath) - val sentryProperties = extractZip( - apk, - "${if (variant == "java") "" else "assets/"}sentry-debug-meta.properties" - ) - val matcher = sentryProperties.lines().mapNotNull { line -> - ASSET_PATTERN_SOURCE_CONTEXT.matchEntire(line) - }.firstOrNull() - assertTrue("Properties file is missing from the archive") { sentryProperties.isNotBlank() } - assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_SOURCE_CONTEXT") - val sourceBundleId = matcher.groupValues[1] - - // then, extract the source bundle zip file contents and verify them against expected contents - val sourceBundle = rootFile.resolve( - "app/build/intermediates/sentry/$variant/source-bundle/$sourceBundleId.zip" - ) - val sourceFileContents = extractZip(sourceBundle, sourceFilePath) - - assertEquals(contents, sourceFileContents, "$sourceFilePath contents do not match $contents") + // first, extract the bundle-id to find the source bundle later in "/intermediates/sentry/" + val apk = rootFile.resolve(archivePath) + val sentryProperties = + extractZip(apk, "${if (variant == "java") "" else "assets/"}sentry-debug-meta.properties") + val matcher = + sentryProperties + .lines() + .mapNotNull { line -> ASSET_PATTERN_SOURCE_CONTEXT.matchEntire(line) } + .firstOrNull() + assertTrue("Properties file is missing from the archive") { sentryProperties.isNotBlank() } + assertNotNull(matcher, "$sentryProperties does not match pattern $ASSET_PATTERN_SOURCE_CONTEXT") + val sourceBundleId = matcher.groupValues[1] + + // then, extract the source bundle zip file contents and verify them against expected contents + val sourceBundle = + rootFile.resolve("app/build/intermediates/sentry/$variant/source-bundle/$sourceBundleId.zip") + val sourceFileContents = extractZip(sourceBundle, sourceFilePath) + + assertEquals(contents, sourceFileContents, "$sourceFilePath contents do not match $contents") } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategyTest.kt index 4aaa836f..12da1f18 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/AbstractInstallStrategyTest.kt @@ -12,41 +12,38 @@ import org.junit.Test import org.slf4j.Logger class AbstractInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - } + class Fixture { + val logger = CapturingTestLogger() + val metadataDetails = mock() + val metadataContext = + mock { whenever(it.details).thenReturn(metadataDetails) } - fun getSut(): AbstractInstallStrategy { - with(AutoInstallState.getInstance()) { - this.enabled = false - } - return RandomInstallStrategy(logger) - } + fun getSut(): AbstractInstallStrategy { + with(AutoInstallState.getInstance()) { this.enabled = false } + return RandomInstallStrategy(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when autoInstallation is disabled does nothing`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `when autoInstallation is disabled does nothing`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] random-module won't be installed because autoInstallation is disabled" - } - verify(fixture.metadataContext, never()).details + assertTrue { + fixture.logger.capturedMessage == + "[sentry] random-module won't be installed because autoInstallation is disabled" } + verify(fixture.metadataContext, never()).details + } - private class RandomInstallStrategy( - logger: Logger, - override val sentryModuleId: String = "random-module" - ) : AbstractInstallStrategy() { - init { - this.logger = logger - } + private class RandomInstallStrategy( + logger: Logger, + override val sentryModuleId: String = "random-module", + ) : AbstractInstallStrategy() { + init { + this.logger = logger } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategyTest.kt index ceb91f4a..d75955df 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/compose/ComposeInstallStrategyTest.kt @@ -22,69 +22,65 @@ import org.junit.Test import org.slf4j.Logger class ComposeInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - composeVersion: String = "1.0.0" - ): ComposeInstallStrategy { - val id = mock { - whenever(it.version).doReturn(composeVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(composeVersion: String = "1.0.0"): ComposeInstallStrategy { + val id = mock { whenever(it.version).doReturn(composeVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.7.0" - } - return ComposeInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.7.0" + } + return ComposeInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when sentry version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(composeVersion = "0.9.0") - sut.execute(fixture.metadataContext) + @Test + fun `when sentry version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(composeVersion = "0.9.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-compose-android won't be installed because the current " + - "version is lower than the minimum supported version (1.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-compose-android won't be installed because the current " + + "version is lower than the minimum supported version (1.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-android-compose with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-android-compose with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-compose-android was successfully installed with version: 6.7.0" - } - verify(fixture.dependencies).add( - check { - assertEquals("io.sentry:sentry-compose-android:6.7.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-compose-android was successfully installed with version: 6.7.0" } + verify(fixture.dependencies) + .add(check { assertEquals("io.sentry:sentry-compose-android:6.7.0", it) }) + } - private class ComposeInstallStrategyImpl(logger: Logger) : ComposeInstallStrategy(logger) + private class ComposeInstallStrategyImpl(logger: Logger) : ComposeInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategyTest.kt index 5879a3e8..293b4d80 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/fragment/FragmentInstallStrategyTest.kt @@ -21,54 +21,52 @@ import org.junit.Test import org.slf4j.Logger class FragmentInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut(): FragmentInstallStrategy { - val id = mock { - whenever(it.version).doReturn("1.3.5") - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(): FragmentInstallStrategy { + val id = mock { whenever(it.version).doReturn("1.3.5") } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "5.6.1" - } - return FragmentInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "5.6.1" + } + return FragmentInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `installs sentry-android-fragment with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-android-fragment with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-fragment was successfully installed with version: 5.6.1" - } - verify(fixture.dependencies).add( - check { - assertEquals("io.sentry:sentry-android-fragment:5.6.1", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-fragment was successfully installed with version: 5.6.1" } + verify(fixture.dependencies) + .add(check { assertEquals("io.sentry:sentry-android-fragment:5.6.1", it) }) + } - private class FragmentInstallStrategyImpl(logger: Logger) : FragmentInstallStrategy(logger) + private class FragmentInstallStrategyImpl(logger: Logger) : FragmentInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategyTest.kt index 3cf2e926..7b700280 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/graphql/GraphqlInstallStrategyTest.kt @@ -20,56 +20,56 @@ import org.junit.Test import org.slf4j.Logger class GraphqlInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - graphqlVersion: String = "2.0.0" - ): GraphqlInstallStrategy { - val id = mock { - whenever(it.version).doReturn(graphqlVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(graphqlVersion: String = "2.0.0"): GraphqlInstallStrategy { + val id = mock { whenever(it.version).doReturn(graphqlVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.25.2" - } - return GraphqlInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.25.2" + } + return GraphqlInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `installs sentry-graphql with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-graphql with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-graphql was successfully installed with version: 6.25.2" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-graphql:6.25.2", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-graphql was successfully installed with version: 6.25.2" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-graphql:6.25.2", it) + } + ) + } - private class GraphqlInstallStrategyImpl(logger: Logger) : GraphqlInstallStrategy(logger) + private class GraphqlInstallStrategyImpl(logger: Logger) : GraphqlInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategyTest.kt index b0f3427b..4f1b2564 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/jdbc/JdbcInstallStrategyTest.kt @@ -20,56 +20,56 @@ import org.junit.Test import org.slf4j.Logger class JdbcInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - jdbcVersion: String = "2.0.0" - ): JdbcInstallStrategy { - val id = mock { - whenever(it.version).doReturn(jdbcVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(jdbcVersion: String = "2.0.0"): JdbcInstallStrategy { + val id = mock { whenever(it.version).doReturn(jdbcVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.21.0" - } - return JdbcInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.21.0" + } + return JdbcInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `installs sentry-jdbc with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-jdbc with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-jdbc was successfully installed with version: 6.21.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-jdbc:6.21.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-jdbc was successfully installed with version: 6.21.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-jdbc:6.21.0", it) + } + ) + } - private class JdbcInstallStrategyImpl(logger: Logger) : JdbcInstallStrategy(logger) + private class JdbcInstallStrategyImpl(logger: Logger) : JdbcInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategyTest.kt index 22da5b38..76a504c7 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/kotlin/KotlinExtensionsInstallStrategyTest.kt @@ -21,72 +21,71 @@ import org.junit.Test import org.slf4j.Logger class KotlinExtensionsInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - kotlinExtensionsVersion: String = "1.6.1" - ): KotlinExtensionsInstallStrategy { - val id = mock { - whenever(it.version).doReturn(kotlinExtensionsVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(kotlinExtensionsVersion: String = "1.6.1"): KotlinExtensionsInstallStrategy { + val id = + mock { whenever(it.version).doReturn(kotlinExtensionsVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.25.2" - } - return KotlinExtensionsInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.25.2" + } + return KotlinExtensionsInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when kotlinExtensions version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(kotlinExtensionsVersion = "1.6.0") - sut.execute(fixture.metadataContext) + @Test + fun `when kotlinExtensions version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(kotlinExtensionsVersion = "1.6.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-kotlin-extensions won't be installed because the current " + - "version is lower than the minimum supported version (1.6.1)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-kotlin-extensions won't be installed because the current " + + "version is lower than the minimum supported version (1.6.1)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-kotlin-extensions with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-kotlin-extensions with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-kotlin-extensions was successfully installed with version: 6.25.2" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-kotlin-extensions:6.25.2", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-kotlin-extensions was successfully installed with version: 6.25.2" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-kotlin-extensions:6.25.2", it) + } + ) + } - private class KotlinExtensionsInstallStrategyImpl(logger: Logger) : - KotlinExtensionsInstallStrategy( - logger - ) + private class KotlinExtensionsInstallStrategyImpl(logger: Logger) : + KotlinExtensionsInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategyTest.kt index b57a6588..7247ea37 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/log4j2/Log4j2InstallStrategyTest.kt @@ -21,69 +21,69 @@ import org.junit.Test import org.slf4j.Logger class Log4j2InstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - log4j2Version: String = "2.0.0" - ): Log4j2InstallStrategy { - val id = mock { - whenever(it.version).doReturn(log4j2Version) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(log4j2Version: String = "2.0.0"): Log4j2InstallStrategy { + val id = mock { whenever(it.version).doReturn(log4j2Version) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.25.2" - } - return Log4j2InstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.25.2" + } + return Log4j2InstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when log4j2 version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(log4j2Version = "1.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when log4j2 version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(log4j2Version = "1.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-log4j2 won't be installed because the current " + - "version is lower than the minimum supported version (2.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-log4j2 won't be installed because the current " + + "version is lower than the minimum supported version (2.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-log4j2 with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-log4j2 with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-log4j2 was successfully installed with version: 6.25.2" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-log4j2:6.25.2", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-log4j2 was successfully installed with version: 6.25.2" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-log4j2:6.25.2", it) + } + ) + } - private class Log4j2InstallStrategyImpl(logger: Logger) : Log4j2InstallStrategy(logger) + private class Log4j2InstallStrategyImpl(logger: Logger) : Log4j2InstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategyTest.kt index 39c6fff6..115b41aa 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/logback/LogbackInstallStrategyTest.kt @@ -21,69 +21,69 @@ import org.junit.Test import org.slf4j.Logger class LogbackInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - logbackVersion: String = "2.0.0" - ): LogbackInstallStrategy { - val id = mock { - whenever(it.version).doReturn(logbackVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(logbackVersion: String = "2.0.0"): LogbackInstallStrategy { + val id = mock { whenever(it.version).doReturn(logbackVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.25.2" - } - return LogbackInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.25.2" + } + return LogbackInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when logback version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(logbackVersion = "0.0.1") - sut.execute(fixture.metadataContext) + @Test + fun `when logback version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(logbackVersion = "0.0.1") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-logback won't be installed because the current " + - "version is lower than the minimum supported version (1.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-logback won't be installed because the current " + + "version is lower than the minimum supported version (1.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-logback with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-logback with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-logback was successfully installed with version: 6.25.2" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-logback:6.25.2", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-logback was successfully installed with version: 6.25.2" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-logback:6.25.2", it) + } + ) + } - private class LogbackInstallStrategyImpl(logger: Logger) : LogbackInstallStrategy(logger) + private class LogbackInstallStrategyImpl(logger: Logger) : LogbackInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategyTest.kt index 8f80d26f..7bb5b5f6 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/AndroidOkHttpInstallStrategyTest.kt @@ -22,85 +22,82 @@ import org.junit.Test import org.slf4j.Logger class AndroidOkHttpInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - okHttpVersion: String = "4.9.3", - sentryVersion: String = "5.6.1" - ): AndroidOkHttpInstallStrategy { - val id = mock { - whenever(it.version).doReturn(okHttpVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut( + okHttpVersion: String = "4.9.3", + sentryVersion: String = "5.6.1", + ): AndroidOkHttpInstallStrategy { + val id = mock { whenever(it.version).doReturn(okHttpVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = sentryVersion - } - return AndroidOkHttpInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = sentryVersion + } + return AndroidOkHttpInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when okhttp version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(okHttpVersion = "3.11.0") - sut.execute(fixture.metadataContext) + @Test + fun `when okhttp version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(okHttpVersion = "3.11.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-okhttp won't be installed because the current " + - "version is lower than the minimum supported version (3.13.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-okhttp won't be installed because the current " + + "version is lower than the minimum supported version (3.13.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `when sentry version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(sentryVersion = "7.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when sentry version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(sentryVersion = "7.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-okhttp won't be installed because the current " + - "sentry version is higher than the maximum supported sentry version (6.9999.9999)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-okhttp won't be installed because the current " + + "sentry version is higher than the maximum supported sentry version (6.9999.9999)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-android-okhttp with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-android-okhttp with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-okhttp was successfully installed with version: 5.6.1" - } - verify(fixture.dependencies).add( - check { - assertEquals("io.sentry:sentry-android-okhttp:5.6.1", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-okhttp was successfully installed with version: 5.6.1" } + verify(fixture.dependencies) + .add(check { assertEquals("io.sentry:sentry-android-okhttp:5.6.1", it) }) + } - private class AndroidOkHttpInstallStrategyImpl( - logger: Logger - ) : AndroidOkHttpInstallStrategy(logger) + private class AndroidOkHttpInstallStrategyImpl(logger: Logger) : + AndroidOkHttpInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategyTest.kt index d0f50546..8e1d5abd 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/okhttp/OkHttpInstallStrategyTest.kt @@ -22,83 +22,81 @@ import org.junit.Test import org.slf4j.Logger class OkHttpInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - okHttpVersion: String = "4.9.3", - sentryVersion: String = "7.0.0" - ): OkHttpInstallStrategy { - val id = mock { - whenever(it.version).doReturn(okHttpVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut( + okHttpVersion: String = "4.9.3", + sentryVersion: String = "7.0.0", + ): OkHttpInstallStrategy { + val id = mock { whenever(it.version).doReturn(okHttpVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = sentryVersion - } - return OkHttpInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = sentryVersion + } + return OkHttpInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when okhttp version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(okHttpVersion = "3.11.0") - sut.execute(fixture.metadataContext) + @Test + fun `when okhttp version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(okHttpVersion = "3.11.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-okhttp won't be installed because the current " + - "version is lower than the minimum supported version (3.13.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-okhttp won't be installed because the current " + + "version is lower than the minimum supported version (3.13.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `when sentry version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(sentryVersion = "6.33.0") - sut.execute(fixture.metadataContext) + @Test + fun `when sentry version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(sentryVersion = "6.33.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-okhttp won't be installed because the current sentry " + - "version is lower than the minimum supported sentry version (7.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-okhttp won't be installed because the current sentry " + + "version is lower than the minimum supported sentry version (7.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-okhttp with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-okhttp with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-okhttp was successfully installed with version: 7.0.0" - } - verify(fixture.dependencies).add( - check { - assertEquals("io.sentry:sentry-okhttp:7.0.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-okhttp was successfully installed with version: 7.0.0" } + verify(fixture.dependencies) + .add(check { assertEquals("io.sentry:sentry-okhttp:7.0.0", it) }) + } - private class OkHttpInstallStrategyImpl(logger: Logger) : OkHttpInstallStrategy(logger) + private class OkHttpInstallStrategyImpl(logger: Logger) : OkHttpInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt index 3f20fdff..1e289e6b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt @@ -19,70 +19,70 @@ import org.junit.Test import org.slf4j.Logger class WarnOnOverrideStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - } - - fun getSut( - enabled: Boolean = true, - userDefinedVersion: String = "6.0.0" - ): WarnOnOverrideStrategy { - val id = mock { - whenever(it.module).doReturn(SentryModules.SENTRY_ANDROID) - whenever(it.version).doReturn(userDefinedVersion) - } - whenever(metadataDetails.id).thenReturn(id) + class Fixture { + val logger = CapturingTestLogger() + val metadataDetails = mock() + val metadataContext = + mock { whenever(it.details).thenReturn(metadataDetails) } - with(AutoInstallState.getInstance()) { - this.enabled = enabled - this.sentryVersion = "6.34.0" - } - return WarnOnOverrideStrategyImpl(logger) + fun getSut( + enabled: Boolean = true, + userDefinedVersion: String = "6.0.0", + ): WarnOnOverrideStrategy { + val id = + mock { + whenever(it.module).doReturn(SentryModules.SENTRY_ANDROID) + whenever(it.version).doReturn(userDefinedVersion) } + whenever(metadataDetails.id).thenReturn(id) + + with(AutoInstallState.getInstance()) { + this.enabled = enabled + this.sentryVersion = "6.34.0" + } + return WarnOnOverrideStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when autoInstall is disabled does nothing`() { - val sut = fixture.getSut(enabled = false) - sut.execute(fixture.metadataContext) + @Test + fun `when autoInstall is disabled does nothing`() { + val sut = fixture.getSut(enabled = false) + sut.execute(fixture.metadataContext) - verify(fixture.metadataContext, never()).details - } + verify(fixture.metadataContext, never()).details + } - @Test - fun `when unknown version does nothing`() { - val sut = fixture.getSut(userDefinedVersion = "whatever") - sut.execute(fixture.metadataContext) + @Test + fun `when unknown version does nothing`() { + val sut = fixture.getSut(userDefinedVersion = "whatever") + sut.execute(fixture.metadataContext) - assertEquals( - "[sentry] Unable to parse version whatever as a semantic version.", - fixture.logger.capturedMessage - ) - } + assertEquals( + "[sentry] Unable to parse version whatever as a semantic version.", + fixture.logger.capturedMessage, + ) + } - @Test - fun `when user defined version is higher than the plugin version does nothing`() { - val sut = fixture.getSut(userDefinedVersion = "7.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when user defined version is higher than the plugin version does nothing`() { + val sut = fixture.getSut(userDefinedVersion = "7.0.0") + sut.execute(fixture.metadataContext) - assertNull(fixture.logger.capturedMessage) - } + assertNull(fixture.logger.capturedMessage) + } - @Test - fun `when user defined version is lower than the plugin version prints a warning`() { - val sut = fixture.getSut(userDefinedVersion = "6.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when user defined version is lower than the plugin version prints a warning`() { + val sut = fixture.getSut(userDefinedVersion = "6.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "WARNING: Version of 'io.sentry:sentry-android' was overridden from '6.0.0' to '6.34.0' by the Sentry Gradle plugin. If you want to use the older version, you can add `autoInstallation.sentryVersion.set(\"6.0.0\")` in the `sentry {}` plugin configuration block" - } + assertTrue { + fixture.logger.capturedMessage == + "WARNING: Version of 'io.sentry:sentry-android' was overridden from '6.0.0' to '6.34.0' by the Sentry Gradle plugin. If you want to use the older version, you can add `autoInstallation.sentryVersion.set(\"6.0.0\")` in the `sentry {}` plugin configuration block" } + } - private class WarnOnOverrideStrategyImpl(logger: Logger) : WarnOnOverrideStrategy(logger) + private class WarnOnOverrideStrategyImpl(logger: Logger) : WarnOnOverrideStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategyTest.kt index 68991102..e0de4c97 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/quartz/QuartzInstallStrategyTest.kt @@ -20,56 +20,56 @@ import org.junit.Test import org.slf4j.Logger class QuartzInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - quartzVersion: String = "2.0.0" - ): QuartzInstallStrategy { - val id = mock { - whenever(it.version).doReturn(quartzVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(quartzVersion: String = "2.0.0"): QuartzInstallStrategy { + val id = mock { whenever(it.version).doReturn(quartzVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.30.0" - } - return QuartzInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.30.0" + } + return QuartzInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `installs sentry-quartz with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-quartz with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-quartz was successfully installed with version: 6.30.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-quartz:6.30.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-quartz was successfully installed with version: 6.30.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-quartz:6.30.0", it) + } + ) + } - private class QuartzInstallStrategyImpl(logger: Logger) : QuartzInstallStrategy(logger) + private class QuartzInstallStrategyImpl(logger: Logger) : QuartzInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategyTest.kt index 6133f62b..7840e1da 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring5InstallStrategyTest.kt @@ -21,82 +21,82 @@ import org.junit.Test import org.slf4j.Logger class Spring5InstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - springVersion: String = "5.1.2" - ): Spring5InstallStrategy { - val id = mock { - whenever(it.version).doReturn(springVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(springVersion: String = "5.1.2"): Spring5InstallStrategy { + val id = mock { whenever(it.version).doReturn(springVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.21.0" - } - return Spring5InstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.21.0" + } + return Spring5InstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when spring version is too low logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "5.1.1") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too low logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "5.1.1") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring won't be installed because the current " + - "version is lower than the minimum supported version (5.1.2)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring won't be installed because the current " + + "version is lower than the minimum supported version (5.1.2)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `when spring version is too high logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "6.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too high logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "6.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring won't be installed because the current " + - "version is higher than the maximum supported version (5.9999.9999)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring won't be installed because the current " + + "version is higher than the maximum supported version (5.9999.9999)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-spring with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-spring with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring was successfully installed with version: 6.21.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-spring:6.21.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring was successfully installed with version: 6.21.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-spring:6.21.0", it) + } + ) + } - private class Spring5InstallStrategyImpl(logger: Logger) : Spring5InstallStrategy(logger) + private class Spring5InstallStrategyImpl(logger: Logger) : Spring5InstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategyTest.kt index e3995afe..74d72ca9 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/Spring6InstallStrategyTest.kt @@ -21,69 +21,69 @@ import org.junit.Test import org.slf4j.Logger class Spring6InstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - springVersion: String = "6.0.0" - ): Spring6InstallStrategy { - val id = mock { - whenever(it.version).doReturn(springVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(springVersion: String = "6.0.0"): Spring6InstallStrategy { + val id = mock { whenever(it.version).doReturn(springVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.21.0" - } - return Spring6InstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.21.0" + } + return Spring6InstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when spring version is too low logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "5.7.4") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too low logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "5.7.4") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-jakarta won't be installed because the current " + - "version is lower than the minimum supported version (6.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-jakarta won't be installed because the current " + + "version is lower than the minimum supported version (6.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-spring-jakarta with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-spring-jakarta with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-jakarta was successfully installed with version: 6.21.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-spring-jakarta:6.21.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-jakarta was successfully installed with version: 6.21.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-spring-jakarta:6.21.0", it) + } + ) + } - private class Spring6InstallStrategyImpl(logger: Logger) : Spring6InstallStrategy(logger) + private class Spring6InstallStrategyImpl(logger: Logger) : Spring6InstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategyTest.kt index d2a1063c..2d6f3f6a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot2InstallStrategyTest.kt @@ -21,85 +21,82 @@ import org.junit.Test import org.slf4j.Logger class SpringBoot2InstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - springVersion: String = "2.1.0" - ): SpringBoot2InstallStrategy { - val id = mock { - whenever(it.version).doReturn(springVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(springVersion: String = "2.1.0"): SpringBoot2InstallStrategy { + val id = mock { whenever(it.version).doReturn(springVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.28.0" - } - return SpringBoot2InstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.28.0" + } + return SpringBoot2InstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when spring version is too low logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "2.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too low logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "2.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-boot won't be installed because the current " + - "version is lower than the minimum supported version (2.1.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-boot won't be installed because the current " + + "version is lower than the minimum supported version (2.1.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `when spring version is too high logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "3.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too high logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "3.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-boot won't be installed because the current " + - "version is higher than the maximum supported version (2.9999.9999)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-boot won't be installed because the current " + + "version is higher than the maximum supported version (2.9999.9999)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-spring-boot with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-spring-boot with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-boot was successfully installed with version: " + - "6.28.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-spring-boot:6.28.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-boot was successfully installed with version: " + "6.28.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-spring-boot:6.28.0", it) + } + ) + } - private class SpringBoot2InstallStrategyImpl(logger: Logger) : SpringBoot2InstallStrategy( - logger - ) + private class SpringBoot2InstallStrategyImpl(logger: Logger) : SpringBoot2InstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategyTest.kt index 2c47fd2e..7351d540 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/spring/SpringBoot3InstallStrategyTest.kt @@ -21,72 +21,69 @@ import org.junit.Test import org.slf4j.Logger class SpringBoot3InstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - springVersion: String = "3.0.0" - ): SpringBoot3InstallStrategy { - val id = mock { - whenever(it.version).doReturn(springVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(springVersion: String = "3.0.0"): SpringBoot3InstallStrategy { + val id = mock { whenever(it.version).doReturn(springVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.28.0" - } - return SpringBoot3InstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.28.0" + } + return SpringBoot3InstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when spring version is too low logs a message and does nothing`() { - val sut = fixture.getSut(springVersion = "2.7.13") - sut.execute(fixture.metadataContext) + @Test + fun `when spring version is too low logs a message and does nothing`() { + val sut = fixture.getSut(springVersion = "2.7.13") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-boot-jakarta won't be installed because the " + - "current version is lower than the minimum supported version (3.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-boot-jakarta won't be installed because the " + + "current version is lower than the minimum supported version (3.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-spring-boot-jakarta with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-spring-boot-jakarta with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-spring-boot-jakarta was successfully installed with " + - "version: 6.28.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-spring-boot-jakarta:6.28.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-spring-boot-jakarta was successfully installed with " + "version: 6.28.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-spring-boot-jakarta:6.28.0", it) + } + ) + } - private class SpringBoot3InstallStrategyImpl(logger: Logger) : SpringBoot3InstallStrategy( - logger - ) + private class SpringBoot3InstallStrategyImpl(logger: Logger) : SpringBoot3InstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SqliteInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SqliteInstallStrategyTest.kt index 656c486d..77ac9d80 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SqliteInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/sqlite/SqliteInstallStrategyTest.kt @@ -21,69 +21,69 @@ import org.junit.Test import org.slf4j.Logger class SqliteInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - sqliteVersion: String = "2.0.0" - ): SQLiteInstallStrategy { - val id = mock { - whenever(it.version).doReturn(sqliteVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(sqliteVersion: String = "2.0.0"): SQLiteInstallStrategy { + val id = mock { whenever(it.version).doReturn(sqliteVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "6.21.0" - } - return SQLiteInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "6.21.0" + } + return SQLiteInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when sqlite version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(sqliteVersion = "1.0.0") - sut.execute(fixture.metadataContext) + @Test + fun `when sqlite version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(sqliteVersion = "1.0.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-sqlite won't be installed because the current " + - "version is lower than the minimum supported version (2.0.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-sqlite won't be installed because the current " + + "version is lower than the minimum supported version (2.0.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-android-sqlite with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-android-sqlite with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-sqlite was successfully installed with version: 6.21.0" - } - verify(fixture.dependencies).add( - com.nhaarman.mockitokotlin2.check { - assertEquals("io.sentry:sentry-android-sqlite:6.21.0", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-sqlite was successfully installed with version: 6.21.0" } + verify(fixture.dependencies) + .add( + com.nhaarman.mockitokotlin2.check { + assertEquals("io.sentry:sentry-android-sqlite:6.21.0", it) + } + ) + } - private class SQLiteInstallStrategyImpl(logger: Logger) : SQLiteInstallStrategy(logger) + private class SQLiteInstallStrategyImpl(logger: Logger) : SQLiteInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategyTest.kt index 532d55bc..15591d5c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/timber/TimberInstallStrategyTest.kt @@ -22,69 +22,65 @@ import org.junit.Test import org.slf4j.Logger class TimberInstallStrategyTest { - class Fixture { - val logger = CapturingTestLogger() - val dependencies = mock() - val metadataDetails = mock() - val metadataContext = mock { - whenever(it.details).thenReturn(metadataDetails) - val metadata = mock() - doAnswer { - (it.arguments[0] as Action).execute(dependencies) - }.whenever(metadata).withDependencies(any>()) + class Fixture { + val logger = CapturingTestLogger() + val dependencies = mock() + val metadataDetails = mock() + val metadataContext = + mock { + whenever(it.details).thenReturn(metadataDetails) + val metadata = mock() + doAnswer { (it.arguments[0] as Action).execute(dependencies) } + .whenever(metadata) + .withDependencies(any>()) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(metadata) - }.whenever(metadataDetails).allVariants(any>()) - } + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(metadata) + } + .whenever(metadataDetails) + .allVariants(any>()) + } - fun getSut( - timberVersion: String = "4.7.1" - ): TimberInstallStrategy { - val id = mock { - whenever(it.version).doReturn(timberVersion) - } - whenever(metadataDetails.id).thenReturn(id) + fun getSut(timberVersion: String = "4.7.1"): TimberInstallStrategy { + val id = mock { whenever(it.version).doReturn(timberVersion) } + whenever(metadataDetails.id).thenReturn(id) - with(AutoInstallState.getInstance()) { - this.enabled = true - this.sentryVersion = "5.6.1" - } - return TimberInstallStrategyImpl(logger) - } + with(AutoInstallState.getInstance()) { + this.enabled = true + this.sentryVersion = "5.6.1" + } + return TimberInstallStrategyImpl(logger) } + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when timber version is unsupported logs a message and does nothing`() { - val sut = fixture.getSut(timberVersion = "4.5.0") - sut.execute(fixture.metadataContext) + @Test + fun `when timber version is unsupported logs a message and does nothing`() { + val sut = fixture.getSut(timberVersion = "4.5.0") + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-timber won't be installed because the current " + - "version is lower than the minimum supported version (4.6.0)" - } - verify(fixture.metadataDetails, never()).allVariants(any()) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-timber won't be installed because the current " + + "version is lower than the minimum supported version (4.6.0)" } + verify(fixture.metadataDetails, never()).allVariants(any()) + } - @Test - fun `installs sentry-android-timber with info message`() { - val sut = fixture.getSut() - sut.execute(fixture.metadataContext) + @Test + fun `installs sentry-android-timber with info message`() { + val sut = fixture.getSut() + sut.execute(fixture.metadataContext) - assertTrue { - fixture.logger.capturedMessage == - "[sentry] sentry-android-timber was successfully installed with version: 5.6.1" - } - verify(fixture.dependencies).add( - check { - assertEquals("io.sentry:sentry-android-timber:5.6.1", it) - } - ) + assertTrue { + fixture.logger.capturedMessage == + "[sentry] sentry-android-timber was successfully installed with version: 5.6.1" } + verify(fixture.dependencies) + .add(check { assertEquals("io.sentry:sentry-android-timber:5.6.1", it) }) + } - private class TimberInstallStrategyImpl(logger: Logger) : TimberInstallStrategy(logger) + private class TimberInstallStrategyImpl(logger: Logger) : TimberInstallStrategy(logger) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentableTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentableTest.kt index 0944bd44..fd34858e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentableTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/ChainedInstrumentableTest.kt @@ -11,98 +11,101 @@ import org.objectweb.asm.ClassVisitor import org.objectweb.asm.Opcodes class ChainedInstrumentableTest { - class Fixture { - fun getSut( - originalVisitor: ClassVisitor, - instrumentables: List = emptyList() - ): ClassVisitor { - return ChainedInstrumentable(instrumentables).getVisitor( - TestClassContext(TestClassData("RandomClass")), - Opcodes.ASM7, - originalVisitor, - TestSpanAddingParameters(inMemoryDir = File("")) - ) - } - } - - private val fixture = Fixture() - - @Test - fun `when empty instrumentables list returns original visitor`() { - val sut = fixture.getSut(OriginalVisitor()) - - assertTrue { sut is OriginalVisitor } - } - - @Test - fun `when no isInstrumentable found returns original visitor`() { - val sut = fixture.getSut( - OriginalVisitor(), - listOf( - FirstInstrumentable(isInstrumentable = false), - SecondInstrumentable(isInstrumentable = false) - ) + class Fixture { + fun getSut( + originalVisitor: ClassVisitor, + instrumentables: List = emptyList(), + ): ClassVisitor { + return ChainedInstrumentable(instrumentables) + .getVisitor( + TestClassContext(TestClassData("RandomClass")), + Opcodes.ASM7, + originalVisitor, + TestSpanAddingParameters(inMemoryDir = File("")), ) - - assertTrue { sut is OriginalVisitor } } - - @Test - fun `skip non-instrumentables in the chain`() { - val sut = fixture.getSut( - OriginalVisitor(), - listOf( - FirstInstrumentable(isInstrumentable = false), - SecondInstrumentable(isInstrumentable = true) - ) - ) - - assertTrue { - sut is SecondInstrumentable.SecondVisitor && sut.prevVisitor is OriginalVisitor - } - } - - @Test - fun `all instrumentables`() { - val sut = - fixture.getSut(OriginalVisitor(), listOf(FirstInstrumentable(), SecondInstrumentable())) - - assertTrue { - sut is SecondInstrumentable.SecondVisitor && - sut.prevVisitor is FirstInstrumentable.FirstVisitor && - (sut.prevVisitor as FirstInstrumentable.FirstVisitor).prevVisitor is OriginalVisitor - } + } + + private val fixture = Fixture() + + @Test + fun `when empty instrumentables list returns original visitor`() { + val sut = fixture.getSut(OriginalVisitor()) + + assertTrue { sut is OriginalVisitor } + } + + @Test + fun `when no isInstrumentable found returns original visitor`() { + val sut = + fixture.getSut( + OriginalVisitor(), + listOf( + FirstInstrumentable(isInstrumentable = false), + SecondInstrumentable(isInstrumentable = false), + ), + ) + + assertTrue { sut is OriginalVisitor } + } + + @Test + fun `skip non-instrumentables in the chain`() { + val sut = + fixture.getSut( + OriginalVisitor(), + listOf( + FirstInstrumentable(isInstrumentable = false), + SecondInstrumentable(isInstrumentable = true), + ), + ) + + assertTrue { sut is SecondInstrumentable.SecondVisitor && sut.prevVisitor is OriginalVisitor } + } + + @Test + fun `all instrumentables`() { + val sut = + fixture.getSut(OriginalVisitor(), listOf(FirstInstrumentable(), SecondInstrumentable())) + + assertTrue { + sut is SecondInstrumentable.SecondVisitor && + sut.prevVisitor is FirstInstrumentable.FirstVisitor && + (sut.prevVisitor as FirstInstrumentable.FirstVisitor).prevVisitor is OriginalVisitor } + } } class OriginalVisitor : ClassVisitor(Opcodes.ASM7) class FirstInstrumentable(val isInstrumentable: Boolean = true) : ClassInstrumentable { - class FirstVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { - val prevVisitor get() = cv - } - - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = FirstVisitor(originalVisitor) - - override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable + class FirstVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { + val prevVisitor + get() = cv + } + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = FirstVisitor(originalVisitor) + + override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable } class SecondInstrumentable(val isInstrumentable: Boolean = true) : ClassInstrumentable { - class SecondVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { - val prevVisitor get() = cv - } - - override fun getVisitor( - instrumentableContext: ClassContext, - apiVersion: Int, - originalVisitor: ClassVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): ClassVisitor = SecondVisitor(originalVisitor) - - override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable + class SecondVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { + val prevVisitor + get() = cv + } + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor = SecondVisitor(originalVisitor) + + override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitorTest.kt index be3ce359..894caf1f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/CommonClassVisitorTest.kt @@ -14,107 +14,106 @@ import org.objectweb.asm.Opcodes class CommonClassVisitorTest { - class Fixture { - - fun getSut(tmpDir: File, debug: Boolean = false) = - CommonClassVisitor( - Opcodes.ASM7, - ParentClassVisitor(), - "SomeClass", - listOf(TestInstrumentable()), - TestSpanAddingParameters(debugOutput = debug, inMemoryDir = tmpDir) - ) - } - - @get:Rule - val tmpDir = TemporaryFolder() - - private val fixture = Fixture() - - @Test - fun `when debug - creates a file with class name on init`() { - fixture.getSut(tmpDir.root, true) - - val file = File(tmpDir.root, "SomeClass-instrumentation.log") - assertTrue { file.exists() } - } - - @Test - fun `when debug and is instrumentable - prepends with TraceMethodVisitor`() { - val mv = fixture.getSut(tmpDir.root, true) - .visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) - - mv.visitVarInsn(Opcodes.ASTORE, 0) - mv.visitEnd() - - // we read the file and compare its content to ensure that TraceMethodVisitor was called and - // wrote the instructions into the file - val file = File(tmpDir.root, "SomeClass-instrumentation.log") - assertEquals( - file.readText(), - """ + class Fixture { + + fun getSut(tmpDir: File, debug: Boolean = false) = + CommonClassVisitor( + Opcodes.ASM7, + ParentClassVisitor(), + "SomeClass", + listOf(TestInstrumentable()), + TestSpanAddingParameters(debugOutput = debug, inMemoryDir = tmpDir), + ) + } + + @get:Rule val tmpDir = TemporaryFolder() + + private val fixture = Fixture() + + @Test + fun `when debug - creates a file with class name on init`() { + fixture.getSut(tmpDir.root, true) + + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertTrue { file.exists() } + } + + @Test + fun `when debug and is instrumentable - prepends with TraceMethodVisitor`() { + val mv = + fixture.getSut(tmpDir.root, true).visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) + + mv.visitVarInsn(Opcodes.ASTORE, 0) + mv.visitEnd() + + // we read the file and compare its content to ensure that TraceMethodVisitor was called and + // wrote the instructions into the file + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertEquals( + file.readText(), + """ |function test null | ASTORE 0 | | - """.trimMargin() - ) - } - - @Test - fun `when no debug and is instrumentable - skips TraceMethodVisitor`() { - val mv = fixture.getSut(tmpDir.root, true) - .visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) - - mv.visitVarInsn(Opcodes.ASTORE, 0) - mv.visitEnd() - - // we read the file and compare its content to ensure that TraceMethodVisitor was skipped - val file = File(tmpDir.root, "SomeClass-instrumentation.log") - assertTrue { file.readText().isEmpty() } - } - - @Test - fun `when matches method name returns instrumentable visitor wrapped into catching visitor`() { - val mv = - fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) - - assertTrue { mv is CatchingMethodVisitor } - } - - @Test - fun `when doesn't match method name return original visitor`() { - val mv = - fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) - - assertTrue { mv is ParentClassVisitor.ParentMethodVisitor } - } + """ + .trimMargin(), + ) + } + + @Test + fun `when no debug and is instrumentable - skips TraceMethodVisitor`() { + val mv = + fixture.getSut(tmpDir.root, true).visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) + + mv.visitVarInsn(Opcodes.ASTORE, 0) + mv.visitEnd() + + // we read the file and compare its content to ensure that TraceMethodVisitor was skipped + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertTrue { file.readText().isEmpty() } + } + + @Test + fun `when matches method name returns instrumentable visitor wrapped into catching visitor`() { + val mv = fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) + + assertTrue { mv is CatchingMethodVisitor } + } + + @Test + fun `when doesn't match method name return original visitor`() { + val mv = fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) + + assertTrue { mv is ParentClassVisitor.ParentMethodVisitor } + } } class ParentClassVisitor : ClassVisitor(Opcodes.ASM7) { - inner class ParentMethodVisitor : MethodVisitor(Opcodes.ASM7) + inner class ParentMethodVisitor : MethodVisitor(Opcodes.ASM7) - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor = ParentMethodVisitor() + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor = ParentMethodVisitor() } class TestInstrumentable : MethodInstrumentable { - inner class TestVisitor(originalVisitor: MethodVisitor) : - MethodVisitor(Opcodes.ASM7, originalVisitor) + inner class TestVisitor(originalVisitor: MethodVisitor) : + MethodVisitor(Opcodes.ASM7, originalVisitor) - override val fqName: String get() = "test" + override val fqName: String + get() = "test" - override fun getVisitor( - instrumentableContext: MethodContext, - apiVersion: Int, - originalVisitor: MethodVisitor, - parameters: SpanAddingClassVisitorFactory.SpanAddingParameters - ): MethodVisitor = TestVisitor(originalVisitor) + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = TestVisitor(originalVisitor) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt index b569ca4a..56ba455b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/VisitorTest.kt @@ -37,216 +37,223 @@ import org.objectweb.asm.util.CheckClassAdapter @Suppress("UnstableApiUsage") @RunWith(Parameterized::class) class VisitorTest( - private val instrumentedProject: String, - private val className: String, - private val instrumentable: Instrumentable, - private val classContext: ClassContext? + private val instrumentedProject: String, + private val className: String, + private val instrumentable: Instrumentable, + private val classContext: ClassContext?, ) { - @get:Rule - val tmpDir = TemporaryFolder() - - @Test - fun `instrumented class passes Java verifier`() { - // first we read the original bytecode and pass it through the ClassWriter, so it computes - // MAXS for us automatically (that's what AGP will do as well) - val inputStream = - FileInputStream( - "src/test/resources/testFixtures/instrumentation/" + - "$instrumentedProject/$className.class" - ) - val classReader = ClassReader(inputStream) - val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) - val classContext = this.classContext ?: TestClassContext(instrumentable.fqName) - val classVisitor = instrumentable.getVisitor( - classContext, - Opcodes.ASM9, - classWriter, - parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root) - ) - // here we visit the bytecode, so it gets modified by our instrumentation visitor - // the ClassReader flags here are identical to those that are set by AGP and R8 - classReader.accept(classVisitor, ClassReader.SKIP_FRAMES) - - // after that we convert the modified bytecode with computed MAXS back to byte array - // and pass it through CheckClassAdapter to verify that the bytecode is correct and can be accepted by JVM - val bytes = classWriter.toByteArray() - val verifyReader = ClassReader(bytes) - val checkAdapter = CheckClassAdapter(ClassWriter(ClassWriter.COMPUTE_FRAMES), true) -// val methodNamePrintingVisitor = MethodNamePrintingVisitor(Opcodes.ASM7, checkAdapter) - verifyReader.accept(checkAdapter, 0) - - // this will verify that the output of the above verifyReader is empty - val stringWriter = StringWriter() - val printWriter = PrintWriter(stringWriter) - CheckClassAdapter.verify( - verifyReader, - GeneratingMissingClassesClassLoader(), - false, - printWriter - ) - assertEquals( - "Instrumented class verification failed with the following exception:\n$stringWriter", - stringWriter.toString(), - "" - ) - } + @get:Rule val tmpDir = TemporaryFolder() - @After - fun printLogs() { - // only print bytecode when running locally - if (System.getenv("CI")?.toBoolean() != true) { - tmpDir.root.listFiles() - ?.filter { it.name.contains("instrumentation") } - ?.forEach { - print(it.readText()) - } - } - } + @Test + fun `instrumented class passes Java verifier`() { + // first we read the original bytecode and pass it through the ClassWriter, so it computes + // MAXS for us automatically (that's what AGP will do as well) + val inputStream = + FileInputStream( + "src/test/resources/testFixtures/instrumentation/" + "$instrumentedProject/$className.class" + ) + val classReader = ClassReader(inputStream) + val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS) + val classContext = this.classContext ?: TestClassContext(instrumentable.fqName) + val classVisitor = + instrumentable.getVisitor( + classContext, + Opcodes.ASM9, + classWriter, + parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root), + ) + // here we visit the bytecode, so it gets modified by our instrumentation visitor + // the ClassReader flags here are identical to those that are set by AGP and R8 + classReader.accept(classVisitor, ClassReader.SKIP_FRAMES) + + // after that we convert the modified bytecode with computed MAXS back to byte array + // and pass it through CheckClassAdapter to verify that the bytecode is correct and can be + // accepted by JVM + val bytes = classWriter.toByteArray() + val verifyReader = ClassReader(bytes) + val checkAdapter = CheckClassAdapter(ClassWriter(ClassWriter.COMPUTE_FRAMES), true) + // val methodNamePrintingVisitor = MethodNamePrintingVisitor(Opcodes.ASM7, checkAdapter) + verifyReader.accept(checkAdapter, 0) + + // this will verify that the output of the above verifyReader is empty + val stringWriter = StringWriter() + val printWriter = PrintWriter(stringWriter) + CheckClassAdapter.verify( + verifyReader, + GeneratingMissingClassesClassLoader(), + false, + printWriter, + ) + assertEquals( + "Instrumented class verification failed with the following exception:\n$stringWriter", + stringWriter.toString(), + "", + ) + } - companion object { - - @Parameterized.Parameters(name = "{0}/{1}") - @JvmStatic - fun parameters() = listOf( - arrayOf( - "androidxSqlite", - "FrameworkSQLiteOpenHelperFactory", - AndroidXSQLiteOpenHelper(), - null - ), - arrayOf("androidxSqlite", "FrameworkSQLiteDatabase", AndroidXSQLiteDatabase(), null), - arrayOf( - "androidxSqlite", - "FrameworkSQLiteStatement", - AndroidXSQLiteStatement(SemVer(2, 3, 0)), - null - ), - roomDaoTestParameters("DeleteAndReturnUnit"), - roomDaoTestParameters("InsertAndReturnLong"), - roomDaoTestParameters("InsertAndReturnUnit"), - roomDaoTestParameters("UpdateAndReturnUnit"), - roomDaoTestParameters("SelectInTransaction"), - deletionDaoTestParameters("DeleteAndReturnInteger"), - deletionDaoTestParameters("DeleteAndReturnVoid"), - deletionDaoTestParameters("DeleteQuery"), - deletionDaoTestParameters("DeleteQueryAndReturnInteger"), - deletionDaoTestParameters("Impl"), - insertionDaoTestParameters("Impl"), - updateDaoTestParameters("UpdateAndReturnInteger"), - updateDaoTestParameters("UpdateAndReturnVoid"), - updateDaoTestParameters("UpdateQuery"), - updateDaoTestParameters("UpdateQueryAndReturnInteger"), - updateDaoTestParameters("Impl"), - selectDaoTestParameters("FlowSingle"), - selectDaoTestParameters("FlowList"), - selectDaoTestParameters("LiveDataSingle"), - selectDaoTestParameters("LiveDataList"), - selectDaoTestParameters("Paging"), - selectDaoTestParameters("Impl"), - kspFavoritesDaoTestParameters("all"), - kspFavoritesDaoTestParameters("count"), - kspFavoritesDaoTestParameters("delete"), - kspFavoritesDaoTestParameters("insert"), - kspFavoritesDaoTestParameters("insertAll"), - kspFavoritesDaoTestParameters("update"), - kspTracksDaoTestParameters("all"), - kspTracksDaoTestParameters("count"), - kspTracksDaoTestParameters("delete"), - kspTracksDaoTestParameters("insert"), - kspTracksDaoTestParameters("insertAll"), - kspTracksDaoTestParameters("update"), - arrayOf("fileIO", "SQLiteCopyOpenHelper", WrappingInstrumentable(), null), - arrayOf("fileIO", "TypefaceCompatUtil", WrappingInstrumentable(), null), - arrayOf( - "fileIO", - "Test", - ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())), - null - ), - arrayOf( - "fileIO", - "zzhm", - ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())), - null - ), - arrayOf("okhttp/v3", "RealCall", OkHttp(true), null), - arrayOf("okhttp/v4", "RealCall", OkHttp(true), null), - arrayOf("okhttp/v3", "RealCall", OkHttp(false), null), - arrayOf("okhttp/v4", "RealCall", OkHttp(false), null), - arrayOf("okhttp/v3", "OkHttpClient", OkHttpEventListener(true, SemVer(3, 0, 0)), null), - arrayOf("okhttp/v4", "OkHttpClient", OkHttpEventListener(true, SemVer(4, 0, 0)), null), - arrayOf("okhttp/v3", "OkHttpClient", OkHttpEventListener(false, SemVer(3, 0, 0)), null), - arrayOf("okhttp/v4", "OkHttpClient", OkHttpEventListener(false, SemVer(4, 0, 0)), null), - arrayOf("androidxCompose", "NavHostControllerKt", ComposeNavigation(), null), - arrayOf("logcat", "LogcatTest", Logcat(), null), - arrayOf("appstart", "MyApplication", Application(), null), - arrayOf("appstart", "MyContentProvider", ContentProvider(), null), - arrayOf("appstart", "MlKitInitProvider", ContentProvider(), null), - arrayOf("appstart", "FacebookInitProvider", ContentProvider(), null) - ) - - private fun roomDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/java", - "TracksDao_$suffix", - AndroidXRoomDao(), - TestClassContext("TracksDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun deletionDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/java/delete", - "DeletionDao_$suffix", - AndroidXRoomDao(), - TestClassContext("DeletionDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun insertionDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/java/insert", - "InsertionDao_$suffix", - AndroidXRoomDao(), - TestClassContext("InsertionDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun updateDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/java/update", - "UpdateDao_$suffix", - AndroidXRoomDao(), - TestClassContext("UpdateDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun selectDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/java/select", - "SelectDao_$suffix", - AndroidXRoomDao(), - TestClassContext("SelectDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun kspFavoritesDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/ksp/favoritesDao", - "FavoritesDao_$suffix", - AndroidXRoomDao(), - TestClassContext("FavoritesDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) - - private fun kspTracksDaoTestParameters(suffix: String = "") = arrayOf( - "androidxRoom/ksp/tracksDao", - "TracksDao_$suffix", - AndroidXRoomDao(), - TestClassContext("TracksDao_$suffix") { lookupName -> - TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) - } - ) + @After + fun printLogs() { + // only print bytecode when running locally + if (System.getenv("CI")?.toBoolean() != true) { + tmpDir.root + .listFiles() + ?.filter { it.name.contains("instrumentation") } + ?.forEach { print(it.readText()) } } + } + + companion object { + + @Parameterized.Parameters(name = "{0}/{1}") + @JvmStatic + fun parameters() = + listOf( + arrayOf( + "androidxSqlite", + "FrameworkSQLiteOpenHelperFactory", + AndroidXSQLiteOpenHelper(), + null, + ), + arrayOf("androidxSqlite", "FrameworkSQLiteDatabase", AndroidXSQLiteDatabase(), null), + arrayOf( + "androidxSqlite", + "FrameworkSQLiteStatement", + AndroidXSQLiteStatement(SemVer(2, 3, 0)), + null, + ), + roomDaoTestParameters("DeleteAndReturnUnit"), + roomDaoTestParameters("InsertAndReturnLong"), + roomDaoTestParameters("InsertAndReturnUnit"), + roomDaoTestParameters("UpdateAndReturnUnit"), + roomDaoTestParameters("SelectInTransaction"), + deletionDaoTestParameters("DeleteAndReturnInteger"), + deletionDaoTestParameters("DeleteAndReturnVoid"), + deletionDaoTestParameters("DeleteQuery"), + deletionDaoTestParameters("DeleteQueryAndReturnInteger"), + deletionDaoTestParameters("Impl"), + insertionDaoTestParameters("Impl"), + updateDaoTestParameters("UpdateAndReturnInteger"), + updateDaoTestParameters("UpdateAndReturnVoid"), + updateDaoTestParameters("UpdateQuery"), + updateDaoTestParameters("UpdateQueryAndReturnInteger"), + updateDaoTestParameters("Impl"), + selectDaoTestParameters("FlowSingle"), + selectDaoTestParameters("FlowList"), + selectDaoTestParameters("LiveDataSingle"), + selectDaoTestParameters("LiveDataList"), + selectDaoTestParameters("Paging"), + selectDaoTestParameters("Impl"), + kspFavoritesDaoTestParameters("all"), + kspFavoritesDaoTestParameters("count"), + kspFavoritesDaoTestParameters("delete"), + kspFavoritesDaoTestParameters("insert"), + kspFavoritesDaoTestParameters("insertAll"), + kspFavoritesDaoTestParameters("update"), + kspTracksDaoTestParameters("all"), + kspTracksDaoTestParameters("count"), + kspTracksDaoTestParameters("delete"), + kspTracksDaoTestParameters("insert"), + kspTracksDaoTestParameters("insertAll"), + kspTracksDaoTestParameters("update"), + arrayOf("fileIO", "SQLiteCopyOpenHelper", WrappingInstrumentable(), null), + arrayOf("fileIO", "TypefaceCompatUtil", WrappingInstrumentable(), null), + arrayOf( + "fileIO", + "Test", + ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())), + null, + ), + arrayOf( + "fileIO", + "zzhm", + ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())), + null, + ), + arrayOf("okhttp/v3", "RealCall", OkHttp(true), null), + arrayOf("okhttp/v4", "RealCall", OkHttp(true), null), + arrayOf("okhttp/v3", "RealCall", OkHttp(false), null), + arrayOf("okhttp/v4", "RealCall", OkHttp(false), null), + arrayOf("okhttp/v3", "OkHttpClient", OkHttpEventListener(true, SemVer(3, 0, 0)), null), + arrayOf("okhttp/v4", "OkHttpClient", OkHttpEventListener(true, SemVer(4, 0, 0)), null), + arrayOf("okhttp/v3", "OkHttpClient", OkHttpEventListener(false, SemVer(3, 0, 0)), null), + arrayOf("okhttp/v4", "OkHttpClient", OkHttpEventListener(false, SemVer(4, 0, 0)), null), + arrayOf("androidxCompose", "NavHostControllerKt", ComposeNavigation(), null), + arrayOf("logcat", "LogcatTest", Logcat(), null), + arrayOf("appstart", "MyApplication", Application(), null), + arrayOf("appstart", "MyContentProvider", ContentProvider(), null), + arrayOf("appstart", "MlKitInitProvider", ContentProvider(), null), + arrayOf("appstart", "FacebookInitProvider", ContentProvider(), null), + ) + + private fun roomDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/java", + "TracksDao_$suffix", + AndroidXRoomDao(), + TestClassContext("TracksDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun deletionDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/java/delete", + "DeletionDao_$suffix", + AndroidXRoomDao(), + TestClassContext("DeletionDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun insertionDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/java/insert", + "InsertionDao_$suffix", + AndroidXRoomDao(), + TestClassContext("InsertionDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun updateDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/java/update", + "UpdateDao_$suffix", + AndroidXRoomDao(), + TestClassContext("UpdateDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun selectDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/java/select", + "SelectDao_$suffix", + AndroidXRoomDao(), + TestClassContext("SelectDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun kspFavoritesDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/ksp/favoritesDao", + "FavoritesDao_$suffix", + AndroidXRoomDao(), + TestClassContext("FavoritesDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + + private fun kspTracksDaoTestParameters(suffix: String = "") = + arrayOf( + "androidxRoom/ksp/tracksDao", + "TracksDao_$suffix", + AndroidXRoomDao(), + TestClassContext("TracksDao_$suffix") { lookupName -> + TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName)) + }, + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitorTest.kt index 0828095b..4bf4c618 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitorTest.kt @@ -12,124 +12,135 @@ import org.objectweb.asm.tree.MethodNode class InstrumentableMethodsCollectingVisitorTest { - class Fixture { - val logger = CapturingTestLogger() - var nextVisitorInitializer: NextVisitorInitializer = { ClassWriter(0) } - - val sut - get() = InstrumentableMethodsCollectingVisitor( - Opcodes.ASM7, - nextVisitorInitializer, - logger - ) + class Fixture { + val logger = CapturingTestLogger() + var nextVisitorInitializer: NextVisitorInitializer = { ClassWriter(0) } + + val sut + get() = InstrumentableMethodsCollectingVisitor(Opcodes.ASM7, nextVisitorInitializer, logger) + } + + private val fixture: Fixture = Fixture() + + @Test + fun `visitEnd triggers nextVisitor`() { + val nextVisitor = NextVisitor() + fixture.nextVisitorInitializer = { nextVisitor } + fixture.sut.visitEnd() + + assertTrue { nextVisitor.visited } + } + + @Test + fun `if method is not in the lookup - does not add to methodsToInstrument`() { + lateinit var methodsToInstrument: List> + fixture.nextVisitorInitializer = { + methodsToInstrument = it + NextVisitor() } - private val fixture: Fixture = Fixture() + val sut = fixture.sut + val mv = sut.visitMethod(0, "test", null, null, null) + mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "endTransaction", null, false) + sut.visitEnd() - @Test - fun `visitEnd triggers nextVisitor`() { - val nextVisitor = NextVisitor() - fixture.nextVisitorInitializer = { nextVisitor } - fixture.sut.visitEnd() + assertTrue { methodsToInstrument.isEmpty() } + } - assertTrue { nextVisitor.visited } + @Test + fun `if method is a transaction - adds to methodsToInstrument with a proper type`() { + lateinit var methodsToInstrument: List> + fixture.nextVisitorInitializer = { + methodsToInstrument = it + NextVisitor() } - @Test - fun `if method is not in the lookup - does not add to methodsToInstrument`() { - lateinit var methodsToInstrument: List> - fixture.nextVisitorInitializer = { methodsToInstrument = it; NextVisitor() } + val sut = fixture.sut + val mv = sut.visitMethod(0, "test", null, null, null) + mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "beginTransaction", null, false) + sut.visitEnd() - val sut = fixture.sut - val mv = sut.visitMethod(0, "test", null, null, null) - mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "endTransaction", null, false) - sut.visitEnd() - - assertTrue { methodsToInstrument.isEmpty() } + assertTrue { + val (node, type) = methodsToInstrument.first() + node.name == "test" && type == RoomMethodType.TRANSACTION + } + } + + @Test + fun `if method is a query - adds to methodsToInstrument with a proper type`() { + lateinit var methodsToInstrument: List> + fixture.nextVisitorInitializer = { + methodsToInstrument = it + NextVisitor() } - @Test - fun `if method is a transaction - adds to methodsToInstrument with a proper type`() { - lateinit var methodsToInstrument: List> - fixture.nextVisitorInitializer = { methodsToInstrument = it; NextVisitor() } - - val sut = fixture.sut - val mv = sut.visitMethod(0, "test", null, null, null) - mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "beginTransaction", null, false) - sut.visitEnd() + val sut = fixture.sut + val mv = sut.visitMethod(0, "test", null, null, null) + mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) + sut.visitEnd() - assertTrue { - val (node, type) = methodsToInstrument.first() - node.name == "test" && type == RoomMethodType.TRANSACTION - } + assertTrue { + val (node, type) = methodsToInstrument.first() + node.name == "test" && type == RoomMethodType.QUERY + } + } + + @Test + fun `if method is a query with transaction - adds to methodsToInstrument with a proper type`() { + lateinit var methodsToInstrument: List> + fixture.nextVisitorInitializer = { + methodsToInstrument = it + NextVisitor() } - @Test - fun `if method is a query - adds to methodsToInstrument with a proper type`() { - lateinit var methodsToInstrument: List> - fixture.nextVisitorInitializer = { methodsToInstrument = it; NextVisitor() } - - val sut = fixture.sut - val mv = sut.visitMethod(0, "test", null, null, null) - mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) - sut.visitEnd() + val sut = fixture.sut + val mv = sut.visitMethod(0, "test", null, null, null) + mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) + mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "beginTransaction", null, false) + sut.visitEnd() - assertTrue { - val (node, type) = methodsToInstrument.first() - node.name == "test" && type == RoomMethodType.QUERY - } + assertTrue { + val (node, type) = methodsToInstrument.first() + node.name == "test" && type == RoomMethodType.QUERY_WITH_TRANSACTION } - - @Test - fun `if method is a query with transaction - adds to methodsToInstrument with a proper type`() { - lateinit var methodsToInstrument: List> - fixture.nextVisitorInitializer = { methodsToInstrument = it; NextVisitor() } - - val sut = fixture.sut - val mv = sut.visitMethod(0, "test", null, null, null) - mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) - mv.visitMethodInsn(0, "androidx/room/RoomDatabase", "beginTransaction", null, false) - sut.visitEnd() - - assertTrue { - val (node, type) = methodsToInstrument.first() - node.name == "test" && type == RoomMethodType.QUERY_WITH_TRANSACTION - } + } + + @Test + fun `if method type is unknown - logs message and removes from methodsToInstrument`() { + lateinit var methodsToInstrument: List> + fixture.nextVisitorInitializer = { + methodsToInstrument = it + NextVisitor() } - @Test - fun `if method type is unknown - logs message and removes from methodsToInstrument`() { - lateinit var methodsToInstrument: List> - fixture.nextVisitorInitializer = { methodsToInstrument = it; NextVisitor() } - - val sut = fixture.sut - val mv = sut.visitMethod(0, "unknownFunctionType", null, null, null) - mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) - mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) - sut.visitEnd() - - assertTrue { methodsToInstrument.isEmpty() } - assertEquals( - fixture.logger.capturedMessage, - "[sentry] Unable to identify RoomMethodType, " + - "skipping unknownFunctionType from instrumentation" - ) - } + val sut = fixture.sut + val mv = sut.visitMethod(0, "unknownFunctionType", null, null, null) + mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) + mv.visitMethodInsn(0, "androidx/room/util/DBUtil", "query", null, false) + sut.visitEnd() + + assertTrue { methodsToInstrument.isEmpty() } + assertEquals( + fixture.logger.capturedMessage, + "[sentry] Unable to identify RoomMethodType, " + + "skipping unknownFunctionType from instrumentation", + ) + } } class NextVisitor : ClassVisitor(Opcodes.ASM7) { - var visited: Boolean = false - - override fun visit( - version: Int, - access: Int, - name: String?, - signature: String?, - superName: String?, - interfaces: Array? - ) { - visited = true - super.visit(version, access, name, signature, superName, interfaces) - } + var visited: Boolean = false + + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array?, + ) { + visited = true + super.visit(version, access, name, signature, superName, interfaces) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Common.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Common.kt index c496b579..57a9114b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Common.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Common.kt @@ -3,34 +3,34 @@ package io.sentry.android.gradle.instrumentation.classloader fun LimitOffsetPagingSource(ofType: String) = - "androidx.room.paging.LimitOffsetPagingSource<$ofType>" + "androidx.room.paging.LimitOffsetPagingSource<$ofType>" -fun EntityInsertionAdapter(ofType: String) = - "androidx.room.EntityInsertionAdapter<$ofType>" +fun EntityInsertionAdapter(ofType: String) = "androidx.room.EntityInsertionAdapter<$ofType>" fun EntityDeletionOrUpdateAdapter(ofType: String) = - "androidx.room.EntityDeletionOrUpdateAdapter<$ofType>" + "androidx.room.EntityDeletionOrUpdateAdapter<$ofType>" fun SharedSQLiteStatement() = "androidx.room.SharedSQLiteStatement" fun Callable() = "java.util.concurrent.Callable" fun standardClassSource( - name: String, - superclass: String = "", - interfaces: Array = emptyArray() + name: String, + superclass: String = "", + interfaces: Array = emptyArray(), ): String { - val className = name.substringAfterLast('.') - val path = name.substringBeforeLast('.') + val className = name.substringAfterLast('.') + val path = name.substringBeforeLast('.') - fun superclassString() = if (superclass.isNotEmpty()) "extends $superclass" else "" - fun interfacesString() = - if (interfaces.isNotEmpty()) "implements ${interfaces.joinToString()}" else "" + fun superclassString() = if (superclass.isNotEmpty()) "extends $superclass" else "" + fun interfacesString() = + if (interfaces.isNotEmpty()) "implements ${interfaces.joinToString()}" else "" - // here it's just enough to have an abstract class, as the Java verifier just checks - // if superclasses/interfaces can be resolved, but omits the implementation details - return """ + // here it's just enough to have an abstract class, as the Java verifier just checks + // if superclasses/interfaces can be resolved, but omits the implementation details + return """ package $path; public abstract class $className ${superclassString()} ${interfacesString()} { } - """.trimIndent() + """ + .trimIndent() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Compiler.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Compiler.kt index 9de349d6..7beef0d4 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Compiler.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/Compiler.kt @@ -11,30 +11,29 @@ import javax.tools.StandardJavaFileManager import javax.tools.ToolProvider fun compileClass(fqName: String, source: String): ByteArrayOutputStream { - val baos = ByteArrayOutputStream() - val simpleJavaFileObject = - object : SimpleJavaFileObject( - URI.create("$fqName.java"), - JavaFileObject.Kind.SOURCE - ) { - override fun getCharContent(ignoreEncodingErrors: Boolean) = source + val baos = ByteArrayOutputStream() + val simpleJavaFileObject = + object : SimpleJavaFileObject(URI.create("$fqName.java"), JavaFileObject.Kind.SOURCE) { + override fun getCharContent(ignoreEncodingErrors: Boolean) = source - override fun openOutputStream() = baos - } + override fun openOutputStream() = baos + } - val javaFileManager = object : ForwardingJavaFileManager( + val javaFileManager = + object : + ForwardingJavaFileManager( ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null) - ) { - override fun getJavaFileForOutput( - location: JavaFileManager.Location?, - className: String?, - kind: JavaFileObject.Kind?, - sibling: FileObject? - ) = simpleJavaFileObject + ) { + override fun getJavaFileForOutput( + location: JavaFileManager.Location?, + className: String?, + kind: JavaFileObject.Kind?, + sibling: FileObject?, + ) = simpleJavaFileObject } - ToolProvider.getSystemJavaCompiler() - .getTask(null, javaFileManager, null, null, null, listOf(simpleJavaFileObject)) - .call() - return baos + ToolProvider.getSystemJavaCompiler() + .getTask(null, javaFileManager, null, null, null, listOf(simpleJavaFileObject)) + .call() + return baos } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/GeneratingMissingClassesClassLoader.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/GeneratingMissingClassesClassLoader.kt index a9d2323c..acc15e3b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/GeneratingMissingClassesClassLoader.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/GeneratingMissingClassesClassLoader.kt @@ -9,35 +9,33 @@ import io.sentry.android.gradle.instrumentation.classloader.mapping.updateDaoMis class GeneratingMissingClassesClassLoader : ClassLoader(getSystemClassLoader()) { - companion object { - private val missingClasses = mapOf( - *deletionDaoMissingClasses, - *insertionDaoMissingClasses, - *updateDaoMissingClasses, - *selectDaoMissingClasses, - *sqliteCopyOpenHelperMissingClasses, - *okHttpMissingClasses - ) - } - - override fun findClass(name: String): Class<*> { - if (name in missingClasses) { - return generateClass(name, missingClasses[name]!!.invoke(name)) - } + companion object { + private val missingClasses = + mapOf( + *deletionDaoMissingClasses, + *insertionDaoMissingClasses, + *updateDaoMissingClasses, + *selectDaoMissingClasses, + *sqliteCopyOpenHelperMissingClasses, + *okHttpMissingClasses, + ) + } - return try { - super.findClass(name) - } catch (e: ClassNotFoundException) { - generateClass(name) - } + override fun findClass(name: String): Class<*> { + if (name in missingClasses) { + return generateClass(name, missingClasses[name]!!.invoke(name)) } - private fun generateClass( - name: String, - source: String = standardClassSource(name) - ): Class<*> { - val fqName = name.replace('.', '/') - val bytes = compileClass(fqName, source) - return defineClass(name, bytes.toByteArray(), 0, bytes.size()) + return try { + super.findClass(name) + } catch (e: ClassNotFoundException) { + generateClass(name) } + } + + private fun generateClass(name: String, source: String = standardClassSource(name)): Class<*> { + val fqName = name.replace('.', '/') + val bytes = compileClass(fqName, source) + return defineClass(name, bytes.toByteArray(), 0, bytes.size()) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/DeletionDaoMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/DeletionDaoMapping.kt index a69dfc8a..26c9bcbd 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/DeletionDaoMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/DeletionDaoMapping.kt @@ -5,38 +5,50 @@ import io.sentry.android.gradle.instrumentation.classloader.EntityDeletionOrUpda import io.sentry.android.gradle.instrumentation.classloader.SharedSQLiteStatement import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val deletionDaoMissingClasses = arrayOf String>>( - "io.sentry.android.roomsample.data.DeletionDao_Impl$1" to { name -> +val deletionDaoMissingClasses = + arrayOf String>>( + "io.sentry.android.roomsample.data.DeletionDao_Impl$1" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("Track")) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$2" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$2" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("MultiPKeyEntity")) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$3" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$3" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("Album")) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$4" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$4" to + { name -> standardClassSource(name, SharedSQLiteStatement()) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$5" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$5" to + { name -> standardClassSource(name, SharedSQLiteStatement()) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$6" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$6" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$7" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$7" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$8" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$8" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$9" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$9" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$10" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$10" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.DeletionDao_Impl$11" to { name -> + }, + "io.sentry.android.roomsample.data.DeletionDao_Impl$11" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - } -) + }, + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/InsertionDaoMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/InsertionDaoMapping.kt index 16c053f5..93dedf1c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/InsertionDaoMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/InsertionDaoMapping.kt @@ -3,14 +3,18 @@ package io.sentry.android.gradle.instrumentation.classloader.mapping import io.sentry.android.gradle.instrumentation.classloader.EntityInsertionAdapter import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val insertionDaoMissingClasses = arrayOf String>>( - "io.sentry.android.roomsample.data.InsertionDao_Impl$1" to { name -> +val insertionDaoMissingClasses = + arrayOf String>>( + "io.sentry.android.roomsample.data.InsertionDao_Impl$1" to + { name -> standardClassSource(name, EntityInsertionAdapter("Track")) - }, - "io.sentry.android.roomsample.data.InsertionDao_Impl$2" to { name -> + }, + "io.sentry.android.roomsample.data.InsertionDao_Impl$2" to + { name -> standardClassSource(name, EntityInsertionAdapter("Track")) - }, - "io.sentry.android.roomsample.data.InsertionDao_Impl$3" to { name -> + }, + "io.sentry.android.roomsample.data.InsertionDao_Impl$3" to + { name -> standardClassSource(name, EntityInsertionAdapter("Album")) - } -) + }, + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/OkHttpMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/OkHttpMapping.kt index 5b234a8a..5bfcafb3 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/OkHttpMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/OkHttpMapping.kt @@ -2,11 +2,14 @@ package io.sentry.android.gradle.instrumentation.classloader.mapping import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val okHttpMissingClasses = arrayOf String>>( - "okhttp3.internal.http.RealInterceptorChain" to { name -> +val okHttpMissingClasses = + arrayOf String>>( + "okhttp3.internal.http.RealInterceptorChain" to + { name -> standardClassSource(name, interfaces = arrayOf("okhttp3.Interceptor.Chain")) - }, - "okhttp3.OkHttpClient\$1" to { name -> + }, + "okhttp3.OkHttpClient\$1" to + { name -> standardClassSource(name, superclass = "okhttp3.internal.Internal") - } -) + }, + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SQLiteCopyOpenHelperMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SQLiteCopyOpenHelperMapping.kt index 91f650d0..3ac0e7b9 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SQLiteCopyOpenHelperMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SQLiteCopyOpenHelperMapping.kt @@ -2,8 +2,10 @@ package io.sentry.android.gradle.instrumentation.classloader.mapping import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val sqliteCopyOpenHelperMissingClasses = arrayOf String>>( - "androidx.room.SQLiteCopyOpenHelper$1" to { name -> +val sqliteCopyOpenHelperMissingClasses = + arrayOf String>>( + "androidx.room.SQLiteCopyOpenHelper$1" to + { name -> standardClassSource(name, "androidx.sqlite.db.SupportSQLiteOpenHelper.Callback") - } -) + } + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SelectDaoMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SelectDaoMapping.kt index 5473d9db..f38719b3 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SelectDaoMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/SelectDaoMapping.kt @@ -4,20 +4,26 @@ import io.sentry.android.gradle.instrumentation.classloader.Callable import io.sentry.android.gradle.instrumentation.classloader.LimitOffsetPagingSource import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val selectDaoMissingClasses = arrayOf String>>( - "io.sentry.android.roomsample.data.SelectDao_Impl$1" to { name -> +val selectDaoMissingClasses = + arrayOf String>>( + "io.sentry.android.roomsample.data.SelectDao_Impl$1" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.SelectDao_Impl$2" to { name -> + }, + "io.sentry.android.roomsample.data.SelectDao_Impl$2" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.SelectDao_Impl$3" to { name -> + }, + "io.sentry.android.roomsample.data.SelectDao_Impl$3" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.SelectDao_Impl$4" to { name -> + }, + "io.sentry.android.roomsample.data.SelectDao_Impl$4" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.SelectDao_Impl$5" to { name -> + }, + "io.sentry.android.roomsample.data.SelectDao_Impl$5" to + { name -> standardClassSource(name, superclass = LimitOffsetPagingSource("SubAlbum")) - } -) + }, + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/UpdateDaoMapping.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/UpdateDaoMapping.kt index 8b102922..0d8154ff 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/UpdateDaoMapping.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/classloader/mapping/UpdateDaoMapping.kt @@ -5,38 +5,50 @@ import io.sentry.android.gradle.instrumentation.classloader.EntityDeletionOrUpda import io.sentry.android.gradle.instrumentation.classloader.SharedSQLiteStatement import io.sentry.android.gradle.instrumentation.classloader.standardClassSource -val updateDaoMissingClasses = arrayOf String>>( - "io.sentry.android.roomsample.data.UpdateDao_Impl$1" to { name -> +val updateDaoMissingClasses = + arrayOf String>>( + "io.sentry.android.roomsample.data.UpdateDao_Impl$1" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("Track")) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$2" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$2" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("MultiPKeyEntity")) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$3" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$3" to + { name -> standardClassSource(name, EntityDeletionOrUpdateAdapter("Album")) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$4" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$4" to + { name -> standardClassSource(name, SharedSQLiteStatement()) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$5" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$5" to + { name -> standardClassSource(name, SharedSQLiteStatement()) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$6" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$6" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$7" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$7" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$8" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$8" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$9" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$9" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$10" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$10" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - }, - "io.sentry.android.roomsample.data.UpdateDao_Impl$11" to { name -> + }, + "io.sentry.android.roomsample.data.UpdateDao_Impl$11" to + { name -> standardClassSource(name, interfaces = arrayOf(Callable())) - } -) + }, + ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/BaseTestLogger.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/BaseTestLogger.kt index b4d4e326..a82dcfd7 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/BaseTestLogger.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/BaseTestLogger.kt @@ -6,147 +6,147 @@ import org.slf4j.Marker abstract class BaseTestLogger : Logger { - override fun isTraceEnabled(): Boolean = true + override fun isTraceEnabled(): Boolean = true - override fun isTraceEnabled(marker: Marker): Boolean = true + override fun isTraceEnabled(marker: Marker): Boolean = true - override fun trace(msg: String) = Unit + override fun trace(msg: String) = Unit - override fun trace(msg: String, arg: Any) = Unit + override fun trace(msg: String, arg: Any) = Unit - override fun trace(msg: String, arg: Any, arg2: Any) = Unit + override fun trace(msg: String, arg: Any, arg2: Any) = Unit - override fun trace(msg: String, vararg args: Any) = Unit + override fun trace(msg: String, vararg args: Any) = Unit - override fun trace(msg: String, throwable: Throwable?) = Unit + override fun trace(msg: String, throwable: Throwable?) = Unit - override fun trace(marker: Marker, msg: String) = Unit + override fun trace(marker: Marker, msg: String) = Unit - override fun trace(marker: Marker, msg: String, arg2: Any) = Unit + override fun trace(marker: Marker, msg: String, arg2: Any) = Unit - override fun trace(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + override fun trace(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit - override fun trace(marker: Marker, msg: String, vararg args: Any) = Unit + override fun trace(marker: Marker, msg: String, vararg args: Any) = Unit - override fun trace(marker: Marker, msg: String, throwable: Throwable?) = Unit + override fun trace(marker: Marker, msg: String, throwable: Throwable?) = Unit - override fun isDebugEnabled(): Boolean = true + override fun isDebugEnabled(): Boolean = true - override fun isDebugEnabled(marker: Marker): Boolean = true + override fun isDebugEnabled(marker: Marker): Boolean = true - override fun debug(msg: String) = Unit + override fun debug(msg: String) = Unit - override fun debug(msg: String, arg: Any) = Unit + override fun debug(msg: String, arg: Any) = Unit - override fun debug(msg: String, arg: Any, arg2: Any) = Unit + override fun debug(msg: String, arg: Any, arg2: Any) = Unit - override fun debug(msg: String, vararg args: Any) = Unit + override fun debug(msg: String, vararg args: Any) = Unit - override fun debug(msg: String, throwable: Throwable?) = Unit + override fun debug(msg: String, throwable: Throwable?) = Unit - override fun debug(marker: Marker, msg: String) = Unit + override fun debug(marker: Marker, msg: String) = Unit - override fun debug(marker: Marker, msg: String, arg2: Any) = Unit + override fun debug(marker: Marker, msg: String, arg2: Any) = Unit - override fun debug(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + override fun debug(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit - override fun debug(marker: Marker, msg: String, vararg args: Any) = Unit + override fun debug(marker: Marker, msg: String, vararg args: Any) = Unit - override fun debug(marker: Marker, msg: String, throwable: Throwable?) = Unit + override fun debug(marker: Marker, msg: String, throwable: Throwable?) = Unit - override fun isInfoEnabled(): Boolean = true + override fun isInfoEnabled(): Boolean = true - override fun isInfoEnabled(marker: Marker): Boolean = true + override fun isInfoEnabled(marker: Marker): Boolean = true - override fun info(msg: String) = Unit + override fun info(msg: String) = Unit - override fun info(msg: String, arg: Any) = Unit + override fun info(msg: String, arg: Any) = Unit - override fun info(msg: String, arg: Any, arg2: Any) = Unit + override fun info(msg: String, arg: Any, arg2: Any) = Unit - override fun info(msg: String, vararg args: Any) = Unit + override fun info(msg: String, vararg args: Any) = Unit - override fun info(msg: String, throwable: Throwable?) = Unit + override fun info(msg: String, throwable: Throwable?) = Unit - override fun info(marker: Marker, msg: String) = Unit + override fun info(marker: Marker, msg: String) = Unit - override fun info(marker: Marker, msg: String, arg2: Any) = Unit + override fun info(marker: Marker, msg: String, arg2: Any) = Unit - override fun info(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + override fun info(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit - override fun info(marker: Marker, msg: String, vararg args: Any) = Unit + override fun info(marker: Marker, msg: String, vararg args: Any) = Unit - override fun info(marker: Marker, msg: String, throwable: Throwable?) = Unit + override fun info(marker: Marker, msg: String, throwable: Throwable?) = Unit - override fun isWarnEnabled(): Boolean = true + override fun isWarnEnabled(): Boolean = true - override fun isWarnEnabled(marker: Marker): Boolean = true + override fun isWarnEnabled(marker: Marker): Boolean = true - override fun warn(msg: String) = Unit + override fun warn(msg: String) = Unit - override fun warn(msg: String, arg: Any) = Unit + override fun warn(msg: String, arg: Any) = Unit - override fun warn(msg: String, vararg args: Any) = Unit + override fun warn(msg: String, vararg args: Any) = Unit - override fun warn(msg: String, arg: Any, arg2: Any) = Unit + override fun warn(msg: String, arg: Any, arg2: Any) = Unit - override fun warn(msg: String, throwable: Throwable?) = Unit + override fun warn(msg: String, throwable: Throwable?) = Unit - override fun warn(marker: Marker, msg: String) = Unit + override fun warn(marker: Marker, msg: String) = Unit - override fun warn(marker: Marker, msg: String, arg2: Any) = Unit + override fun warn(marker: Marker, msg: String, arg2: Any) = Unit - override fun warn(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + override fun warn(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit - override fun warn(marker: Marker, msg: String, vararg args: Any) = Unit + override fun warn(marker: Marker, msg: String, vararg args: Any) = Unit - override fun warn(marker: Marker, msg: String, throwable: Throwable?) = Unit + override fun warn(marker: Marker, msg: String, throwable: Throwable?) = Unit - override fun isErrorEnabled(): Boolean = true + override fun isErrorEnabled(): Boolean = true - override fun isErrorEnabled(marker: Marker): Boolean = true + override fun isErrorEnabled(marker: Marker): Boolean = true - override fun error(msg: String) = Unit + override fun error(msg: String) = Unit - override fun error(msg: String, arg: Any) = Unit + override fun error(msg: String, arg: Any) = Unit - override fun error(msg: String, arg: Any, arg2: Any) = Unit + override fun error(msg: String, arg: Any, arg2: Any) = Unit - override fun error(msg: String, vararg args: Any) = Unit + override fun error(msg: String, vararg args: Any) = Unit - override fun error(msg: String, throwable: Throwable?) = Unit + override fun error(msg: String, throwable: Throwable?) = Unit - override fun error(marker: Marker, msg: String) = Unit + override fun error(marker: Marker, msg: String) = Unit - override fun error(marker: Marker, msg: String, arg2: Any) = Unit + override fun error(marker: Marker, msg: String, arg2: Any) = Unit - override fun error(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + override fun error(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit - override fun error(marker: Marker, msg: String, vararg args: Any) = Unit + override fun error(marker: Marker, msg: String, vararg args: Any) = Unit - override fun error(marker: Marker, msg: String, throwable: Throwable?) = Unit + override fun error(marker: Marker, msg: String, throwable: Throwable?) = Unit - override fun isLifecycleEnabled(): Boolean = true + override fun isLifecycleEnabled(): Boolean = true - override fun lifecycle(message: String?) = Unit + override fun lifecycle(message: String?) = Unit - override fun lifecycle(message: String?, vararg objects: Any?) = Unit + override fun lifecycle(message: String?, vararg objects: Any?) = Unit - override fun lifecycle(message: String?, throwable: Throwable?) = Unit + override fun lifecycle(message: String?, throwable: Throwable?) = Unit - override fun isQuietEnabled(): Boolean = true + override fun isQuietEnabled(): Boolean = true - override fun quiet(message: String?) = Unit + override fun quiet(message: String?) = Unit - override fun quiet(message: String?, vararg objects: Any?) = Unit + override fun quiet(message: String?, vararg objects: Any?) = Unit - override fun quiet(message: String?, throwable: Throwable?) = Unit + override fun quiet(message: String?, throwable: Throwable?) = Unit - override fun isEnabled(level: LogLevel?): Boolean = true + override fun isEnabled(level: LogLevel?): Boolean = true - override fun log(level: LogLevel?, message: String?) = Unit + override fun log(level: LogLevel?, message: String?) = Unit - override fun log(level: LogLevel?, message: String?, vararg objects: Any?) = Unit + override fun log(level: LogLevel?, message: String?, vararg objects: Any?) = Unit - override fun log(level: LogLevel?, message: String?, throwable: Throwable?) = Unit + override fun log(level: LogLevel?, message: String?, throwable: Throwable?) = Unit } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/CapturingTestLogger.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/CapturingTestLogger.kt index 26086835..4dceb585 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/CapturingTestLogger.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/CapturingTestLogger.kt @@ -1,32 +1,32 @@ package io.sentry.android.gradle.instrumentation.fakes class CapturingTestLogger : BaseTestLogger() { - override fun getName(): String = "SentryPluginTest" + override fun getName(): String = "SentryPluginTest" - var capturedMessage: String? = null - var capturedThrowable: Throwable? = null + var capturedMessage: String? = null + var capturedThrowable: Throwable? = null - override fun error(msg: String, throwable: Throwable?) { - capturedMessage = msg - capturedThrowable = throwable - } + override fun error(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } - override fun warn(msg: String, throwable: Throwable?) { - capturedMessage = msg - capturedThrowable = throwable - } + override fun warn(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } - override fun warn(msg: String) { - capturedMessage = msg - } + override fun warn(msg: String) { + capturedMessage = msg + } - override fun info(msg: String, throwable: Throwable?) { - capturedMessage = msg - capturedThrowable = throwable - } + override fun info(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } - override fun debug(msg: String, throwable: Throwable?) { - capturedMessage = msg - capturedThrowable = throwable - } + override fun debug(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/MethodNamePrintingVisitor.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/MethodNamePrintingVisitor.kt index 58edc964..94950cfa 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/MethodNamePrintingVisitor.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/MethodNamePrintingVisitor.kt @@ -4,22 +4,20 @@ import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor /** - * Prints each method that is being visited in the class - * useful when a Class is failing Java bytecode verifier, but not clear which method is causing it + * Prints each method that is being visited in the class useful when a Class is failing Java + * bytecode verifier, but not clear which method is causing it */ -class MethodNamePrintingVisitor( - api: Int, - originalVisitor: ClassVisitor -) : ClassVisitor(api, originalVisitor) { +class MethodNamePrintingVisitor(api: Int, originalVisitor: ClassVisitor) : + ClassVisitor(api, originalVisitor) { - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor { - println(name) - return super.visitMethod(access, name, descriptor, signature, exceptions) - } + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array?, + ): MethodVisitor { + println(name) + return super.visitMethod(access, name, descriptor, signature, exceptions) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestClassContext.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestClassContext.kt index 4db089e8..6afe2c84 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestClassContext.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestClassContext.kt @@ -6,21 +6,23 @@ import com.android.build.api.instrumentation.ClassContext import com.android.build.api.instrumentation.ClassData data class TestClassData( - override val className: String, - override val classAnnotations: List = emptyList(), - override val interfaces: List = emptyList(), - override val superClasses: List = emptyList() + override val className: String, + override val classAnnotations: List = emptyList(), + override val interfaces: List = emptyList(), + override val superClasses: List = emptyList(), ) : ClassData data class TestClassContext( - override val currentClassData: ClassData, - private val classLoader: (String) -> ClassData? = { null } + override val currentClassData: ClassData, + private val classLoader: (String) -> ClassData? = { null }, ) : ClassContext { - constructor(className: String) : this(TestClassData(className)) + constructor(className: String) : this(TestClassData(className)) - constructor(className: String, classLoader: (String) -> ClassData?) : - this(TestClassData(className), classLoader) + constructor( + className: String, + classLoader: (String) -> ClassData?, + ) : this(TestClassData(className), classLoader) - override fun loadClassData(className: String): ClassData? = classLoader(className) + override fun loadClassData(className: String): ClassData? = classLoader(className) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt index 0ece637e..aedd2d73 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt @@ -10,26 +10,26 @@ import org.gradle.api.internal.provider.PropertyHost import org.gradle.api.provider.Property class TestSpanAddingParameters( - private val debugOutput: Boolean = true, - private val inMemoryDir: File + private val debugOutput: Boolean = true, + private val inMemoryDir: File, ) : SpanAddingClassVisitorFactory.SpanAddingParameters { - override val invalidate: Property - get() = DefaultProperty(PropertyHost.NO_OP, Long::class.java).convention(0L) + override val invalidate: Property + get() = DefaultProperty(PropertyHost.NO_OP, Long::class.java).convention(0L) - override val debug: Property - get() = DefaultProperty(PropertyHost.NO_OP, Boolean::class.javaObjectType) - .convention(debugOutput) + override val debug: Property + get() = + DefaultProperty(PropertyHost.NO_OP, Boolean::class.javaObjectType).convention(debugOutput) - override val logcatMinLevel: Property - get() = DefaultProperty(PropertyHost.NO_OP, LogcatLevel::class.java) - .convention(LogcatLevel.WARNING) + override val logcatMinLevel: Property + get() = + DefaultProperty(PropertyHost.NO_OP, LogcatLevel::class.java).convention(LogcatLevel.WARNING) - override val sentryModulesService: Property - get() = TODO() + override val sentryModulesService: Property + get() = TODO() - override val tmpDir: Property - get() = DefaultProperty(PropertyHost.NO_OP, File::class.java).convention(inMemoryDir) + override val tmpDir: Property + get() = DefaultProperty(PropertyHost.NO_OP, File::class.java).convention(inMemoryDir) - override var _instrumentable: ClassInstrumentable? = null + override var _instrumentable: ClassInstrumentable? = null } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevelTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevelTest.kt index 4fd3e55a..11b1200a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevelTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/logcat/LogcatLevelTest.kt @@ -6,14 +6,14 @@ import org.junit.Test class LogcatLevelTest { - @Test - fun `test logFunctionToLevel`() { - assertEquals(LogcatLevel.VERBOSE, LogcatLevel.logFunctionToLevel("v")) - assertEquals(LogcatLevel.DEBUG, LogcatLevel.logFunctionToLevel("d")) - assertEquals(LogcatLevel.INFO, LogcatLevel.logFunctionToLevel("i")) - assertEquals(LogcatLevel.WARNING, LogcatLevel.logFunctionToLevel("w")) - assertEquals(LogcatLevel.ERROR, LogcatLevel.logFunctionToLevel("e")) - assertEquals(LogcatLevel.ERROR, LogcatLevel.logFunctionToLevel("wtf")) - assertNull(LogcatLevel.logFunctionToLevel("invalid")) - } + @Test + fun `test logFunctionToLevel`() { + assertEquals(LogcatLevel.VERBOSE, LogcatLevel.logFunctionToLevel("v")) + assertEquals(LogcatLevel.DEBUG, LogcatLevel.logFunctionToLevel("d")) + assertEquals(LogcatLevel.INFO, LogcatLevel.logFunctionToLevel("i")) + assertEquals(LogcatLevel.WARNING, LogcatLevel.logFunctionToLevel("w")) + assertEquals(LogcatLevel.ERROR, LogcatLevel.logFunctionToLevel("e")) + assertEquals(LogcatLevel.ERROR, LogcatLevel.logFunctionToLevel("wtf")) + assertNull(LogcatLevel.logFunctionToLevel("invalid")) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitorTest.kt index 42030997..b7cb6714 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/CatchingMethodVisitorTest.kt @@ -9,73 +9,72 @@ import org.objectweb.asm.Opcodes class CatchingMethodVisitorTest { - class Fixture { - private val throwingVisitor = ThrowingMethodVisitor() - val handler = CapturingExceptionHandler() - val logger = CapturingTestLogger() + class Fixture { + private val throwingVisitor = ThrowingMethodVisitor() + val handler = CapturingExceptionHandler() + val logger = CapturingTestLogger() - private val methodContext = - MethodContext(Opcodes.ACC_PUBLIC, "someMethod", null, null, null) - val sut - get() = CatchingMethodVisitor( - Opcodes.ASM7, - throwingVisitor, - "SomeClass", - methodContext, - handler, - logger - ) - } + private val methodContext = MethodContext(Opcodes.ACC_PUBLIC, "someMethod", null, null, null) + val sut + get() = + CatchingMethodVisitor( + Opcodes.ASM7, + throwingVisitor, + "SomeClass", + methodContext, + handler, + logger, + ) + } - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `forwards exception to ExceptionHandler`() { - try { - fixture.sut.visitMaxs(0, 0) - } catch (ignored: Throwable) { - } finally { - assertEquals(fixture.handler.capturedException!!.message, "This method throws!") - } + @Test + fun `forwards exception to ExceptionHandler`() { + try { + fixture.sut.visitMaxs(0, 0) + } catch (ignored: Throwable) {} finally { + assertEquals(fixture.handler.capturedException!!.message, "This method throws!") } + } - @Test(expected = CustomException::class) - fun `rethrows exception`() { - fixture.sut.visitMaxs(0, 0) - } + @Test(expected = CustomException::class) + fun `rethrows exception`() { + fixture.sut.visitMaxs(0, 0) + } - @Test - fun `prints message to log`() { - try { - fixture.sut.visitMaxs(0, 0) - } catch (ignored: Throwable) { - } finally { - assertEquals(fixture.logger.capturedThrowable!!.message, "This method throws!") - assertEquals( - fixture.logger.capturedMessage, - """ + @Test + fun `prints message to log`() { + try { + fixture.sut.visitMaxs(0, 0) + } catch (ignored: Throwable) {} finally { + assertEquals(fixture.logger.capturedThrowable!!.message, "This method throws!") + assertEquals( + fixture.logger.capturedMessage, + """ [sentry] Error while instrumenting SomeClass.someMethod null. Please report this issue at https://github.com/getsentry/sentry-android-gradle-plugin/issues - """.trimIndent() - ) - } + """ + .trimIndent(), + ) } + } } class CustomException : RuntimeException("This method throws!") class ThrowingMethodVisitor : MethodVisitor(Opcodes.ASM7) { - override fun visitMaxs(maxStack: Int, maxLocals: Int) { - throw CustomException() - } + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + throw CustomException() + } } class CapturingExceptionHandler : ExceptionHandler { - var capturedException: Throwable? = null + var capturedException: Throwable? = null - override fun handle(exception: Throwable) { - capturedException = exception - } + override fun handle(exception: Throwable) { + capturedException = exception + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifierTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifierTest.kt index 0d9b53b2..2cfc1d6a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifierTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/FileLogTextifierTest.kt @@ -10,96 +10,90 @@ import org.objectweb.asm.Opcodes class FileLogTextifierTest { - class Fixture { + class Fixture { - fun getSut(tmpFile: File) = - FileLogTextifier( - Opcodes.ASM7, - tmpFile, - "SomeMethod", - "(Ljava/lang/Throwable;)V" - ) + fun getSut(tmpFile: File) = + FileLogTextifier(Opcodes.ASM7, tmpFile, "SomeMethod", "(Ljava/lang/Throwable;)V") - fun visitMethodInstructions(sut: FileLogTextifier) { - sut.visitVarInsn(Opcodes.ASTORE, 0) - sut.visitLabel(Label()) - sut.visitLdcInsn("db") - } + fun visitMethodInstructions(sut: FileLogTextifier) { + sut.visitVarInsn(Opcodes.ASTORE, 0) + sut.visitLabel(Label()) + sut.visitLdcInsn("db") } + } - @get:Rule - val tmpDir = TemporaryFolder() + @get:Rule val tmpDir = TemporaryFolder() - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `prints methodName on ccreation`() { - fixture.getSut(tmpDir.newFile("instrumentation.log")) + @Test + fun `prints methodName on ccreation`() { + fixture.getSut(tmpDir.newFile("instrumentation.log")) - val file = File(tmpDir.root, "instrumentation.log") - assertEquals( - file.readText(), - "function SomeMethod (Ljava/lang/Throwable;)V\n" - ) - } + val file = File(tmpDir.root, "instrumentation.log") + assertEquals(file.readText(), "function SomeMethod (Ljava/lang/Throwable;)V\n") + } - @Test - fun `visitMethodEnd flushes output to file if hasn't thrown`() { - val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) - fixture.visitMethodInstructions(sut) - sut.visitMethodEnd() + @Test + fun `visitMethodEnd flushes output to file if hasn't thrown`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + fixture.visitMethodInstructions(sut) + sut.visitMethodEnd() - val file = File(tmpDir.root, "instrumentation.log") - assertEquals( - file.readText(), - """ + val file = File(tmpDir.root, "instrumentation.log") + assertEquals( + file.readText(), + """ |function SomeMethod (Ljava/lang/Throwable;)V | ASTORE 0 | L0 | LDC "db" | | - """.trimMargin() - ) - } + """ + .trimMargin(), + ) + } - @Test - fun `visitMethodEnd does nothing if has thrown`() { - val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) - sut.handle(RuntimeException()) - fixture.visitMethodInstructions(sut) - sut.visitMethodEnd() + @Test + fun `visitMethodEnd does nothing if has thrown`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + sut.handle(RuntimeException()) + fixture.visitMethodInstructions(sut) + sut.visitMethodEnd() - val file = File(tmpDir.root, "instrumentation.log") - // sut.handle will add one more newline to the end of file, but actual visited instructions - // will not be flushed to file - assertEquals( - file.readText(), - """ + val file = File(tmpDir.root, "instrumentation.log") + // sut.handle will add one more newline to the end of file, but actual visited instructions + // will not be flushed to file + assertEquals( + file.readText(), + """ |function SomeMethod (Ljava/lang/Throwable;)V | | - """.trimMargin() - ) - } + """ + .trimMargin(), + ) + } - @Test - fun `handle exception flushes output to file`() { - val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) - fixture.visitMethodInstructions(sut) - sut.handle(RuntimeException()) + @Test + fun `handle exception flushes output to file`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + fixture.visitMethodInstructions(sut) + sut.handle(RuntimeException()) - val file = File(tmpDir.root, "instrumentation.log") - assertEquals( - file.readText(), - """ + val file = File(tmpDir.root, "instrumentation.log") + assertEquals( + file.readText(), + """ |function SomeMethod (Ljava/lang/Throwable;)V | ASTORE 0 | L0 | LDC "db" | | - """.trimMargin() - ) - } + """ + .trimMargin(), + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt index 07fa6304..77f9b0da 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt @@ -6,62 +6,51 @@ import org.junit.Test class MinifiedClassDetectionTest { - @Test - fun `detects minified class names`() { - val classNames = listOf( - "l0", - """a${'$'}a""", - "ccc017zz", - """ccc017zz${'$'}a""", - "aa", - "aa${'$'}a", - "ab", - "aa${'$'}ab", - "ab${'$'}a" - ) - - classNames.forEach { - assertTrue(classNameLooksMinified(it, "com/example/$it"), it) - } - } - - @Test - fun `detects minified class names with minified package name`() { - val classNames = listOf( - """a${'$'}""", - "aa" - ) - - classNames.forEach { - assertTrue(classNameLooksMinified(it, "a/$it"), it) - } - } - - @Test - fun `does not consider non minified classes as minified`() { - val classNames = listOf( - "ConstantPoolHelpers", - "FileUtil", - """FileUtil${"$"}Inner""" - ) - - classNames.forEach { - assertFalse(classNameLooksMinified(it, "com/example/$it"), it) - } - } - - @Test - fun `does not consider short class names as minified classes`() { - val classNames = listOf( - Pair("Call", "retrofit2/Call"), - Pair("Call", "okhttp3/Call"), - Pair("Fill", "androidx/compose/ui/graphics/drawscope/Fill"), - Pair("Px", "androidx/annotation/Px"), - Pair("Dp", "androidx/annotation/Dp") - ) - - classNames.forEach { (simpleName, fullName) -> - assertFalse(classNameLooksMinified(simpleName, fullName)) - } + @Test + fun `detects minified class names`() { + val classNames = + listOf( + "l0", + """a${'$'}a""", + "ccc017zz", + """ccc017zz${'$'}a""", + "aa", + "aa${'$'}a", + "ab", + "aa${'$'}ab", + "ab${'$'}a", + ) + + classNames.forEach { assertTrue(classNameLooksMinified(it, "com/example/$it"), it) } + } + + @Test + fun `detects minified class names with minified package name`() { + val classNames = listOf("""a${'$'}""", "aa") + + classNames.forEach { assertTrue(classNameLooksMinified(it, "a/$it"), it) } + } + + @Test + fun `does not consider non minified classes as minified`() { + val classNames = listOf("ConstantPoolHelpers", "FileUtil", """FileUtil${"$"}Inner""") + + classNames.forEach { assertFalse(classNameLooksMinified(it, "com/example/$it"), it) } + } + + @Test + fun `does not consider short class names as minified classes`() { + val classNames = + listOf( + Pair("Call", "retrofit2/Call"), + Pair("Call", "okhttp3/Call"), + Pair("Fill", "androidx/compose/ui/graphics/drawscope/Fill"), + Pair("Px", "androidx/annotation/Px"), + Pair("Dp", "androidx/annotation/Dp"), + ) + + classNames.forEach { (simpleName, fullName) -> + assertFalse(classNameLooksMinified(simpleName, fullName)) } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtilsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtilsTest.kt index 563aed1b..2b792e9b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtilsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/SentryPackageNameUtilsTest.kt @@ -7,21 +7,14 @@ import org.junit.Test class SentryPackageNameUtilsTest { - @Test - fun `test is sentry class or not`() { - assertTrue { TestClassContext("io.sentry.Sentry").isSentryClass() } - assertFalse { - TestClassContext("io.sentry.samples.instrumentation.ui.MainActivity") - .isSentryClass() - } - assertFalse { - TestClassContext("io.sentry.samples.MainActivity").isSentryClass() - } - assertFalse { - TestClassContext("io.sentry.mobile.StartActivity").isSentryClass() - } - assertFalse { - TestClassContext("androidx.room.Dao").isSentryClass() - } + @Test + fun `test is sentry class or not`() { + assertTrue { TestClassContext("io.sentry.Sentry").isSentryClass() } + assertFalse { + TestClassContext("io.sentry.samples.instrumentation.ui.MainActivity").isSentryClass() } + assertFalse { TestClassContext("io.sentry.samples.MainActivity").isSentryClass() } + assertFalse { TestClassContext("io.sentry.mobile.StartActivity").isSentryClass() } + assertFalse { TestClassContext("androidx.room.Dao").isSentryClass() } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt index b057a052..842fe96a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt @@ -14,397 +14,410 @@ import org.objectweb.asm.tree.MethodNode class WrappingVisitorTest { - class Fixture { - val logger = CapturingTestLogger() - val visitor = CapturingMethodVisitor() - - fun getSut( - methodContext: MethodContext = MethodContext( - Opcodes.ACC_PUBLIC, - "test", - "()V", - null, - null - ), - classContext: ClassData = TestClassData("io/sentry/RandomClass"), - replacements: Map = mapOf(), - firstPassVisitor: MethodNode = MethodNode(Opcodes.ASM7) - ) = WrappingVisitor( - Opcodes.ASM7, - visitor, - firstPassVisitor, - classContext, - methodContext, - replacements, - logger + class Fixture { + val logger = CapturingTestLogger() + val visitor = CapturingMethodVisitor() + + fun getSut( + methodContext: MethodContext = MethodContext(Opcodes.ACC_PUBLIC, "test", "()V", null, null), + classContext: ClassData = TestClassData("io/sentry/RandomClass"), + replacements: Map = mapOf(), + firstPassVisitor: MethodNode = MethodNode(Opcodes.ASM7), + ) = + WrappingVisitor( + Opcodes.ASM7, + visitor, + firstPassVisitor, + classContext, + methodContext, + replacements, + logger, + ) + } + + private val fixture = Fixture() + + @Test + fun `invokedynamic is skipped from instrumentation`() { + val context = MethodContext(Opcodes.ACC_PUBLIC, "test", "()V", null, null) + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKEDYNAMIC, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + fixture + .getSut(context) + .visitMethodInsn( + opcode = methodVisit.opcode, + owner = methodVisit.owner, + name = methodVisit.name, + descriptor = methodVisit.descriptor, + isInterface = methodVisit.isInterface, + ) + + assertEquals( + fixture.logger.capturedMessage, + "[sentry] INVOKEDYNAMIC skipped from instrumentation for io.sentry.RandomClass.test", + ) + // method visit should remain unchanged + assertEquals(fixture.visitor.methodVisits.size, 1) + assertEquals(fixture.visitor.methodVisits.first(), methodVisit) + } + + @Test + fun `when no replacements found does not modify method visit`() { + val context = MethodContext(Opcodes.ACC_PUBLIC, "test", "()V", null, null) + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKEVIRTUAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + fixture + .getSut(context) + .visitMethodInsn( + opcode = methodVisit.opcode, + owner = methodVisit.owner, + name = methodVisit.name, + descriptor = methodVisit.descriptor, + isInterface = methodVisit.isInterface, + ) + + // method visit should remain unchanged + assertEquals(fixture.visitor.methodVisits.size, 1) + assertEquals(fixture.visitor.methodVisits.first(), methodVisit) + } + + @Test + fun `when replacement found and super call in override does not modify method visit`() { + val context = + MethodContext( + Opcodes.ACC_PUBLIC, + "openFileInput", + "(Ljava/lang/String;)Ljava/io/FileInputStream;", + null, + null, + ) + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "android/content/Context", + name = "openFileInput", + descriptor = "(Ljava/lang/String;)Ljava/io/FileInputStream;", + isInterface = false, + ) + fixture + .getSut( + context, + classContext = TestClassData("io/sentry/android/sample/LyricsActivity"), + replacements = mapOf(Replacement.Context.OPEN_FILE_INPUT), + ) + .visitMethodInsn( + opcode = methodVisit.opcode, + owner = methodVisit.owner, + name = methodVisit.name, + descriptor = methodVisit.descriptor, + isInterface = methodVisit.isInterface, + ) + + assertEquals( + fixture.logger.capturedMessage, + "[sentry] io.sentry.android.sample.LyricsActivity skipped from instrumentation " + + "in overridden method openFileInput.(Ljava/lang/String;)Ljava/io/FileInputStream;", + ) + // method visit should remain unchanged + assertEquals(fixture.visitor.methodVisits.size, 1) + assertEquals(fixture.visitor.methodVisits.first(), methodVisit) + } + + @Test + fun `when replacement found and super call in constructor does not modify method visit`() { + val context = MethodContext(Opcodes.ACC_PUBLIC, "", "()V", null, null) + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + fixture + .getSut( + context, + classContext = + TestClassData( + "io/sentry/CustomFileInputStream", + superClasses = listOf("java/io/FileInputStream"), + ), + replacements = mapOf(Replacement.FileInputStream.STRING), + ) + .visitMethodInsn( + opcode = methodVisit.opcode, + owner = methodVisit.owner, + name = methodVisit.name, + descriptor = methodVisit.descriptor, + isInterface = methodVisit.isInterface, + ) + + assertEquals( + fixture.logger.capturedMessage, + "[sentry] io.sentry.CustomFileInputStream skipped from instrumentation in " + + "constructor .(Ljava/lang/String;)V", + ) + // method visit should remain unchanged + assertEquals(fixture.visitor.methodVisits.size, 1) + assertEquals(fixture.visitor.methodVisits.first(), methodVisit) + } + + @Test + fun `when replacement found modifies method visit`() { + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + /* ktlint-disable experimental:argument-list-wrapping */ + fixture + .getSut(replacements = mapOf(Replacement.FileInputStream.STRING)) + .visitMethodInsn( + methodVisit.opcode, + methodVisit.owner, + methodVisit.name, + methodVisit.descriptor, + methodVisit.isInterface, + ) + /* ktlint-enable experimental:argument-list-wrapping */ + + assertEquals(fixture.visitor.methodVisits.size, 2) + // store original arguments + assertEquals(fixture.visitor.varVisits[0], VarVisit(Opcodes.ASTORE, 1)) + // load original argument for the original method visit + assertEquals(fixture.visitor.varVisits[1], VarVisit(Opcodes.ALOAD, 1)) + // original method is visited unchanged + assertEquals(fixture.visitor.methodVisits[0], methodVisit) + + // load original argument for the replacement/wrapping method visit + // the target object that we are wrapping will be taken from stack + assertEquals(fixture.visitor.varVisits[2], VarVisit(Opcodes.ALOAD, 1)) + // replacement/wrapping method visited + assertEquals( + fixture.visitor.methodVisits[1], + MethodVisit( + Opcodes.INVOKESTATIC, + "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", + "create", + "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;", + isInterface = false, + ), + ) + } + + @Test + fun `when NEW insn with DUP does not modify operand stack`() { + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + val firstPassVisitor = + MethodNode(Opcodes.ASM7).apply { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitInsn(Opcodes.DUP) + } + + fixture + .getSut( + replacements = mapOf(Replacement.FileInputStream.STRING), + firstPassVisitor = firstPassVisitor, + ) + .run { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitMethodInsn( + methodVisit.opcode, + methodVisit.owner, + methodVisit.name, + methodVisit.descriptor, + methodVisit.isInterface, ) - } - - private val fixture = Fixture() - - @Test - fun `invokedynamic is skipped from instrumentation`() { - val context = MethodContext(Opcodes.ACC_PUBLIC, "test", "()V", null, null) - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKEDYNAMIC, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - fixture.getSut(context).visitMethodInsn( - opcode = methodVisit.opcode, - owner = methodVisit.owner, - name = methodVisit.name, - descriptor = methodVisit.descriptor, - isInterface = methodVisit.isInterface - ) - - assertEquals( - fixture.logger.capturedMessage, - "[sentry] INVOKEDYNAMIC skipped from instrumentation for io.sentry.RandomClass.test" - ) - // method visit should remain unchanged - assertEquals(fixture.visitor.methodVisits.size, 1) - assertEquals(fixture.visitor.methodVisits.first(), methodVisit) - } - - @Test - fun `when no replacements found does not modify method visit`() { - val context = MethodContext(Opcodes.ACC_PUBLIC, "test", "()V", null, null) - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKEVIRTUAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - fixture.getSut(context).visitMethodInsn( - opcode = methodVisit.opcode, - owner = methodVisit.owner, - name = methodVisit.name, - descriptor = methodVisit.descriptor, - isInterface = methodVisit.isInterface - ) - - // method visit should remain unchanged - assertEquals(fixture.visitor.methodVisits.size, 1) - assertEquals(fixture.visitor.methodVisits.first(), methodVisit) - } - - @Test - fun `when replacement found and super call in override does not modify method visit`() { - val context = MethodContext( - Opcodes.ACC_PUBLIC, - "openFileInput", - "(Ljava/lang/String;)Ljava/io/FileInputStream;", - null, - null - ) - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "android/content/Context", - name = "openFileInput", - descriptor = "(Ljava/lang/String;)Ljava/io/FileInputStream;", - isInterface = false - ) - fixture.getSut( - context, - classContext = TestClassData("io/sentry/android/sample/LyricsActivity"), - replacements = mapOf(Replacement.Context.OPEN_FILE_INPUT) - ).visitMethodInsn( - opcode = methodVisit.opcode, - owner = methodVisit.owner, - name = methodVisit.name, - descriptor = methodVisit.descriptor, - isInterface = methodVisit.isInterface - ) - - assertEquals( - fixture.logger.capturedMessage, - "[sentry] io.sentry.android.sample.LyricsActivity skipped from instrumentation " + - "in overridden method openFileInput.(Ljava/lang/String;)Ljava/io/FileInputStream;" - ) - // method visit should remain unchanged - assertEquals(fixture.visitor.methodVisits.size, 1) - assertEquals(fixture.visitor.methodVisits.first(), methodVisit) - } - - @Test - fun `when replacement found and super call in constructor does not modify method visit`() { - val context = MethodContext( - Opcodes.ACC_PUBLIC, - "", - "()V", - null, - null - ) - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - fixture.getSut( - context, - classContext = TestClassData( - "io/sentry/CustomFileInputStream", - superClasses = listOf("java/io/FileInputStream") - ), - replacements = mapOf(Replacement.FileInputStream.STRING) - ).visitMethodInsn( - opcode = methodVisit.opcode, - owner = methodVisit.owner, - name = methodVisit.name, - descriptor = methodVisit.descriptor, - isInterface = methodVisit.isInterface + } + + // DUP was not visited by our visitor + assertEquals(fixture.visitor.insnVisits.size, 0) + // ASTORE was not visited by our visitor in the end (count would be 4 otherwise) + assertEquals(fixture.visitor.varVisits.size, 3) + } + + @Test + fun `when NEW insn without DUP - modifies operand stack with DUP and ASTORE`() { + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + val firstPassVisitor = + MethodNode(Opcodes.ASM7).apply { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitVarInsn(Opcodes.ASTORE, 1) + } + + fixture + .getSut( + replacements = mapOf(Replacement.FileInputStream.STRING), + firstPassVisitor = firstPassVisitor, + ) + .run { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitVarInsn(Opcodes.ASTORE, 1) + visitMethodInsn( + methodVisit.opcode, + methodVisit.owner, + methodVisit.name, + methodVisit.descriptor, + methodVisit.isInterface, ) + } - assertEquals( - fixture.logger.capturedMessage, - "[sentry] io.sentry.CustomFileInputStream skipped from instrumentation in " + - "constructor .(Ljava/lang/String;)V" - ) - // method visit should remain unchanged - assertEquals(fixture.visitor.methodVisits.size, 1) - assertEquals(fixture.visitor.methodVisits.first(), methodVisit) + // DUP was visited by our visitor + assertTrue { + fixture.visitor.insnVisits.size == 1 && + fixture.visitor.insnVisits.first() == InsnVisit(Opcodes.DUP) } - - @Test - fun `when replacement found modifies method visit`() { - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - /* ktlint-disable experimental:argument-list-wrapping */ - fixture.getSut(replacements = mapOf(Replacement.FileInputStream.STRING)) - .visitMethodInsn( - methodVisit.opcode, - methodVisit.owner, - methodVisit.name, - methodVisit.descriptor, - methodVisit.isInterface - ) - /* ktlint-enable experimental:argument-list-wrapping */ - - assertEquals(fixture.visitor.methodVisits.size, 2) - // store original arguments - assertEquals(fixture.visitor.varVisits[0], VarVisit(Opcodes.ASTORE, 1)) - // load original argument for the original method visit - assertEquals(fixture.visitor.varVisits[1], VarVisit(Opcodes.ALOAD, 1)) - // original method is visited unchanged - assertEquals(fixture.visitor.methodVisits[0], methodVisit) - - // load original argument for the replacement/wrapping method visit - // the target object that we are wrapping will be taken from stack - assertEquals(fixture.visitor.varVisits[2], VarVisit(Opcodes.ALOAD, 1)) - // replacement/wrapping method visited - assertEquals( - fixture.visitor.methodVisits[1], - MethodVisit( - Opcodes.INVOKESTATIC, - "io/sentry/instrumentation/file/SentryFileInputStream${'$'}Factory", - "create", - "(Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;", - isInterface = false - ) - ) + // ASTORE was visited by our visitor in the end + assertTrue { + fixture.visitor.varVisits.size == 5 && + fixture.visitor.varVisits.last() == VarVisit(Opcodes.ASTORE, 1) } - - @Test - fun `when NEW insn with DUP does not modify operand stack`() { - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - val firstPassVisitor = MethodNode(Opcodes.ASM7).apply { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitInsn(Opcodes.DUP) - } - - fixture.getSut( - replacements = mapOf(Replacement.FileInputStream.STRING), - firstPassVisitor = firstPassVisitor - ).run { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitMethodInsn( - methodVisit.opcode, - methodVisit.owner, - methodVisit.name, - methodVisit.descriptor, - methodVisit.isInterface - ) - } - - // DUP was not visited by our visitor - assertEquals(fixture.visitor.insnVisits.size, 0) - // ASTORE was not visited by our visitor in the end (count would be 4 otherwise) - assertEquals(fixture.visitor.varVisits.size, 3) + } + + @Test + fun `multiple NEW insns`() { + val firstPassVisitor = + MethodNode(Opcodes.ASM7).apply { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitVarInsn(Opcodes.ASTORE, 1) + + visitTypeInsn(Opcodes.NEW, "some/random/Class") + visitInsn(Opcodes.DUP) + + visitTypeInsn(Opcodes.NEW, "java/io/FileOutputStream") + visitInsn(Opcodes.DUP) + } + + fixture + .getSut( + replacements = + mapOf(Replacement.FileInputStream.STRING, Replacement.FileOutputStream.STRING), + firstPassVisitor = firstPassVisitor, + ) + .run { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitTypeInsn(Opcodes.NEW, "some/random/Class") + visitTypeInsn(Opcodes.NEW, "java/io/FileOutputStream") + } + assertTrue { + fixture.visitor.insnVisits.size == 1 && + fixture.visitor.insnVisits.first() == InsnVisit(Opcodes.DUP) } - - @Test - fun `when NEW insn without DUP - modifies operand stack with DUP and ASTORE`() { - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false + } + + @Test + fun `when NEW insn with DUP followed by ASTORE - modifies operand stack with DUP and ASTORE`() { + val methodVisit = + MethodVisit( + opcode = Opcodes.INVOKESPECIAL, + owner = "java/io/FileInputStream", + name = "", + descriptor = "(Ljava/lang/String;)V", + isInterface = false, + ) + val firstPassVisitor = + MethodNode(Opcodes.ASM7).apply { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitInsn(Opcodes.DUP) + visitVarInsn(Opcodes.ASTORE, 1) + } + + fixture + .getSut( + replacements = mapOf(Replacement.FileInputStream.STRING), + firstPassVisitor = firstPassVisitor, + ) + .run { + visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") + visitInsn(Opcodes.DUP) + visitVarInsn(Opcodes.ASTORE, 1) + visitMethodInsn( + methodVisit.opcode, + methodVisit.owner, + methodVisit.name, + methodVisit.descriptor, + methodVisit.isInterface, ) - val firstPassVisitor = MethodNode(Opcodes.ASM7).apply { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitVarInsn(Opcodes.ASTORE, 1) - } - - fixture.getSut( - replacements = mapOf(Replacement.FileInputStream.STRING), - firstPassVisitor = firstPassVisitor - ).run { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitVarInsn(Opcodes.ASTORE, 1) - visitMethodInsn( - methodVisit.opcode, - methodVisit.owner, - methodVisit.name, - methodVisit.descriptor, - methodVisit.isInterface - ) - } - - // DUP was visited by our visitor - assertTrue { - fixture.visitor.insnVisits.size == 1 && - fixture.visitor.insnVisits.first() == InsnVisit(Opcodes.DUP) - } - // ASTORE was visited by our visitor in the end - assertTrue { - fixture.visitor.varVisits.size == 5 && - fixture.visitor.varVisits.last() == VarVisit(Opcodes.ASTORE, 1) - } - } + } - @Test - fun `multiple NEW insns`() { - val firstPassVisitor = MethodNode(Opcodes.ASM7).apply { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitVarInsn(Opcodes.ASTORE, 1) - - visitTypeInsn(Opcodes.NEW, "some/random/Class") - visitInsn(Opcodes.DUP) - - visitTypeInsn(Opcodes.NEW, "java/io/FileOutputStream") - visitInsn(Opcodes.DUP) - } - - fixture.getSut( - replacements = mapOf( - Replacement.FileInputStream.STRING, - Replacement.FileOutputStream.STRING - ), - firstPassVisitor = firstPassVisitor - ).run { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitTypeInsn(Opcodes.NEW, "some/random/Class") - visitTypeInsn(Opcodes.NEW, "java/io/FileOutputStream") - } - assertTrue { - fixture.visitor.insnVisits.size == 1 && - fixture.visitor.insnVisits.first() == InsnVisit(Opcodes.DUP) - } + // DUP was visited by our visitor + assertTrue { + fixture.visitor.insnVisits.size == 2 && + fixture.visitor.insnVisits.all { it == InsnVisit(Opcodes.DUP) } } - - @Test - fun `when NEW insn with DUP followed by ASTORE - modifies operand stack with DUP and ASTORE`() { - val methodVisit = MethodVisit( - opcode = Opcodes.INVOKESPECIAL, - owner = "java/io/FileInputStream", - name = "", - descriptor = "(Ljava/lang/String;)V", - isInterface = false - ) - val firstPassVisitor = MethodNode(Opcodes.ASM7).apply { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitInsn(Opcodes.DUP) - visitVarInsn(Opcodes.ASTORE, 1) - } - - fixture.getSut( - replacements = mapOf(Replacement.FileInputStream.STRING), - firstPassVisitor = firstPassVisitor - ).run { - visitTypeInsn(Opcodes.NEW, "java/io/FileInputStream") - visitInsn(Opcodes.DUP) - visitVarInsn(Opcodes.ASTORE, 1) - visitMethodInsn( - methodVisit.opcode, - methodVisit.owner, - methodVisit.name, - methodVisit.descriptor, - methodVisit.isInterface - ) - } - - // DUP was visited by our visitor - assertTrue { - fixture.visitor.insnVisits.size == 2 && - fixture.visitor.insnVisits.all { it == InsnVisit(Opcodes.DUP) } - } - // ASTORE was visited by our visitor in the end - assertTrue { - fixture.visitor.varVisits.size == 5 && - fixture.visitor.varVisits.last() == VarVisit(Opcodes.ASTORE, 1) - } + // ASTORE was visited by our visitor in the end + assertTrue { + fixture.visitor.varVisits.size == 5 && + fixture.visitor.varVisits.last() == VarVisit(Opcodes.ASTORE, 1) } + } } data class MethodVisit( - val opcode: Int, - val owner: String, - val name: String, - val descriptor: String, - val isInterface: Boolean + val opcode: Int, + val owner: String, + val name: String, + val descriptor: String, + val isInterface: Boolean, ) -data class VarVisit( - val opcode: Int, - val variable: Int -) +data class VarVisit(val opcode: Int, val variable: Int) -data class InsnVisit( - val opcode: Int -) +data class InsnVisit(val opcode: Int) class CapturingMethodVisitor : MethodVisitor(Opcodes.ASM7) { - val methodVisits = mutableListOf() - val varVisits = mutableListOf() - val insnVisits = mutableListOf() - - override fun visitVarInsn(opcode: Int, variable: Int) { - super.visitVarInsn(opcode, variable) - varVisits += VarVisit(opcode, variable) - } - - override fun visitInsn(opcode: Int) { - super.visitInsn(opcode) - insnVisits += InsnVisit(opcode) - } - - override fun visitMethodInsn( - opcode: Int, - owner: String, - name: String, - descriptor: String, - isInterface: Boolean - ) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - methodVisits += MethodVisit(opcode, owner, name, descriptor, isInterface) - } + val methodVisits = mutableListOf() + val varVisits = mutableListOf() + val insnVisits = mutableListOf() + + override fun visitVarInsn(opcode: Int, variable: Int) { + super.visitVarInsn(opcode, variable) + varVisits += VarVisit(opcode, variable) + } + + override fun visitInsn(opcode: Int) { + super.visitInsn(opcode) + insnVisits += InsnVisit(opcode) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + methodVisits += MethodVisit(opcode, owner, name, descriptor, isInterface) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryNonAndroidPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryNonAndroidPluginTest.kt index 4095801c..ad5aed05 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryNonAndroidPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryNonAndroidPluginTest.kt @@ -14,49 +14,48 @@ import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder -abstract class BaseSentryNonAndroidPluginTest( - private val gradleVersion: String -) { - @get:Rule - val testProjectDir = TemporaryFolder() - - private val outputStream = ByteArrayOutputStream() - private val writer = OutputStreamWriter(SynchronizedOutputStream(outputStream)) - - @get:Rule - val printBuildOutputOnFailureRule = PrintBuildOutputOnFailureRule(outputStream) - - private val projectTemplateFolder = File("src/test/resources/testFixtures/appTestProject") - - private lateinit var rootBuildFile: File - protected lateinit var appBuildFile: File - protected lateinit var moduleBuildFile: File - protected lateinit var sentryPropertiesFile: File - protected lateinit var runner: GradleRunner - - protected open val additionalRootProjectConfig: String = "" - protected open val additionalBuildClasspath: String = "" - - @Before - fun setup() { - projectTemplateFolder.copyRecursively(testProjectDir.root) - - val pluginClasspath = PluginUnderTestMetadataReading.readImplementationClasspath() - .joinToString(separator = ", ") { "\"$it\"" } - .replace(File.separator, "/") - - appBuildFile = File(testProjectDir.root, "app/build.gradle") - appBuildFile.writeText( - appBuildFile.readText() - .replace("id \"com.android.application\"", "") - .replace("id \"io.sentry.android.gradle\"", "id \"io.sentry.jvm.gradle\"") - .replace("android\\s*\\{\\s*namespace\\s*'com\\.example'\\s*\\}".toRegex(), "") - ) - moduleBuildFile = File(testProjectDir.root, "module/build.gradle") - sentryPropertiesFile = File(testProjectDir.root, "sentry.properties") - rootBuildFile = testProjectDir.writeFile("build.gradle") { - // language=Groovy - """ +abstract class BaseSentryNonAndroidPluginTest(private val gradleVersion: String) { + @get:Rule val testProjectDir = TemporaryFolder() + + private val outputStream = ByteArrayOutputStream() + private val writer = OutputStreamWriter(SynchronizedOutputStream(outputStream)) + + @get:Rule val printBuildOutputOnFailureRule = PrintBuildOutputOnFailureRule(outputStream) + + private val projectTemplateFolder = File("src/test/resources/testFixtures/appTestProject") + + private lateinit var rootBuildFile: File + protected lateinit var appBuildFile: File + protected lateinit var moduleBuildFile: File + protected lateinit var sentryPropertiesFile: File + protected lateinit var runner: GradleRunner + + protected open val additionalRootProjectConfig: String = "" + protected open val additionalBuildClasspath: String = "" + + @Before + fun setup() { + projectTemplateFolder.copyRecursively(testProjectDir.root) + + val pluginClasspath = + PluginUnderTestMetadataReading.readImplementationClasspath() + .joinToString(separator = ", ") { "\"$it\"" } + .replace(File.separator, "/") + + appBuildFile = File(testProjectDir.root, "app/build.gradle") + appBuildFile.writeText( + appBuildFile + .readText() + .replace("id \"com.android.application\"", "") + .replace("id \"io.sentry.android.gradle\"", "id \"io.sentry.jvm.gradle\"") + .replace("android\\s*\\{\\s*namespace\\s*'com\\.example'\\s*\\}".toRegex(), "") + ) + moduleBuildFile = File(testProjectDir.root, "module/build.gradle") + sentryPropertiesFile = File(testProjectDir.root, "sentry.properties") + rootBuildFile = + testProjectDir.writeFile("build.gradle") { + // language=Groovy + """ import io.sentry.android.gradle.autoinstall.AutoInstallState import io.sentry.android.gradle.util.GradleVersions @@ -104,42 +103,44 @@ abstract class BaseSentryNonAndroidPluginTest( commandLine 'find', project.gradle.gradleUserHomeDir, '-type', 'f', '-name', 'transforms-3.lock', '-delete' } } - """.trimIndent() - } - - runner = GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments("--stacktrace") - .withPluginClasspath() - .withGradleVersion(gradleVersion) - .forwardStdOutput(writer) - .forwardStdError(writer) - - if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { - // for newer Gradle versions transforms are unlocked at config time instead of a task - runner.appendArguments("unlockTransforms").build() - } + """ + .trimIndent() + } + + runner = + GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments("--stacktrace") + .withPluginClasspath() + .withGradleVersion(gradleVersion) + .forwardStdOutput(writer) + .forwardStdError(writer) + + if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { + // for newer Gradle versions transforms are unlocked at config time instead of a task + runner.appendArguments("unlockTransforms").build() } - - @After - fun teardown() { - try { - runner.appendArguments("app:cleanupAutoInstallState").build() - } catch (ignored: Throwable) { - // may fail if we are relying on BuildFinishesListener, but we don't care here - } + } + + @After + fun teardown() { + try { + runner.appendArguments("app:cleanupAutoInstallState").build() + } catch (ignored: Throwable) { + // may fail if we are relying on BuildFinishesListener, but we don't care here } + } - companion object { + companion object { - internal fun GradleRunner.appendArguments(vararg arguments: String) = - withArguments(this.arguments + arguments) + internal fun GradleRunner.appendArguments(vararg arguments: String) = + withArguments(this.arguments + arguments) - private fun TemporaryFolder.writeFile(fileName: String, text: () -> String): File { - val file = File(root, fileName) - file.parentFile.mkdirs() - file.writeText(text()) - return file - } + private fun TemporaryFolder.writeFile(fileName: String, text: () -> String): File { + val file = File(root, fileName) + file.parentFile.mkdirs() + file.writeText(text()) + return file } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt index b542d19d..1ed2e82a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/BaseSentryPluginTest.kt @@ -16,44 +16,44 @@ import org.junit.rules.TemporaryFolder @Suppress("FunctionName") abstract class BaseSentryPluginTest( - protected val androidGradlePluginVersion: String, - private val gradleVersion: String + protected val androidGradlePluginVersion: String, + private val gradleVersion: String, ) { - @get:Rule - val testProjectDir = TemporaryFolder() + @get:Rule val testProjectDir = TemporaryFolder() - private val outputStream = ByteArrayOutputStream() - private val writer = OutputStreamWriter(SynchronizedOutputStream(outputStream)) + private val outputStream = ByteArrayOutputStream() + private val writer = OutputStreamWriter(SynchronizedOutputStream(outputStream)) - @get:Rule - val printBuildOutputOnFailureRule = PrintBuildOutputOnFailureRule(outputStream) + @get:Rule val printBuildOutputOnFailureRule = PrintBuildOutputOnFailureRule(outputStream) - private val projectTemplateFolder = File("src/test/resources/testFixtures/appTestProject") - private val mavenTestRepoPath = File("./../build/mavenTestRepo") + private val projectTemplateFolder = File("src/test/resources/testFixtures/appTestProject") + private val mavenTestRepoPath = File("./../build/mavenTestRepo") - private lateinit var rootBuildFile: File - protected lateinit var appBuildFile: File - protected lateinit var moduleBuildFile: File - protected lateinit var sentryPropertiesFile: File - protected lateinit var runner: GradleRunner + private lateinit var rootBuildFile: File + protected lateinit var appBuildFile: File + protected lateinit var moduleBuildFile: File + protected lateinit var sentryPropertiesFile: File + protected lateinit var runner: GradleRunner - protected open val additionalRootProjectConfig: String = "" - protected open val additionalBuildClasspath: String = "" + protected open val additionalRootProjectConfig: String = "" + protected open val additionalBuildClasspath: String = "" - @Before - fun setup() { - projectTemplateFolder.copyRecursively(testProjectDir.root) + @Before + fun setup() { + projectTemplateFolder.copyRecursively(testProjectDir.root) - val pluginClasspath = PluginUnderTestMetadataReading.readImplementationClasspath() - .joinToString(separator = ", ") { "\"$it\"" } - .replace(File.separator, "/") + val pluginClasspath = + PluginUnderTestMetadataReading.readImplementationClasspath() + .joinToString(separator = ", ") { "\"$it\"" } + .replace(File.separator, "/") - appBuildFile = File(testProjectDir.root, "app/build.gradle") - moduleBuildFile = File(testProjectDir.root, "module/build.gradle") - sentryPropertiesFile = File(testProjectDir.root, "sentry.properties") - rootBuildFile = testProjectDir.writeFile("build.gradle") { - // language=Groovy - """ + appBuildFile = File(testProjectDir.root, "app/build.gradle") + moduleBuildFile = File(testProjectDir.root, "module/build.gradle") + sentryPropertiesFile = File(testProjectDir.root, "sentry.properties") + rootBuildFile = + testProjectDir.writeFile("build.gradle") { + // language=Groovy + """ import io.sentry.android.gradle.autoinstall.AutoInstallState import io.sentry.android.gradle.util.GradleVersions @@ -122,43 +122,45 @@ abstract class BaseSentryPluginTest( commandLine 'find', project.gradle.gradleUserHomeDir, '-type', 'f', '-name', 'transforms-3.lock', '-delete' } } - """.trimIndent() - } - - runner = GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments("--stacktrace") - .withPluginClasspath() - .withGradleVersion(gradleVersion) -// .withDebug(true) - .forwardStdOutput(writer) - .forwardStdError(writer) - - if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { - // for newer Gradle versions transforms are unlocked at config time instead of a task - runner.appendArguments("unlockTransforms").build() - } + """ + .trimIndent() + } + + runner = + GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments("--stacktrace") + .withPluginClasspath() + .withGradleVersion(gradleVersion) + // .withDebug(true) + .forwardStdOutput(writer) + .forwardStdError(writer) + + if (SemVer.parse(gradleVersion) < GradleVersions.VERSION_7_5) { + // for newer Gradle versions transforms are unlocked at config time instead of a task + runner.appendArguments("unlockTransforms").build() } - - @After - fun teardown() { - try { - runner.appendArguments("app:cleanupAutoInstallState").build() - } catch (ignored: Throwable) { - // may fail if we are relying on BuildFinishesListener, but we don't care here - } + } + + @After + fun teardown() { + try { + runner.appendArguments("app:cleanupAutoInstallState").build() + } catch (ignored: Throwable) { + // may fail if we are relying on BuildFinishesListener, but we don't care here } + } - companion object { + companion object { - internal fun GradleRunner.appendArguments(vararg arguments: String) = - withArguments(this.arguments + arguments) + internal fun GradleRunner.appendArguments(vararg arguments: String) = + withArguments(this.arguments + arguments) - private fun TemporaryFolder.writeFile(fileName: String, text: () -> String): File { - val file = File(root, fileName) - file.parentFile.mkdirs() - file.writeText(text()) - return file - } + private fun TemporaryFolder.writeFile(fileName: String, text: () -> String): File { + val file = File(root, fileName) + file.parentFile.mkdirs() + file.writeText(text()) + return file } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallNonAndroidTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallNonAndroidTest.kt index 1dddac01..0a2ba575 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallNonAndroidTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallNonAndroidTest.kt @@ -9,13 +9,13 @@ import org.junit.Assume.assumeThat import org.junit.Test class SentryPluginAutoInstallNonAndroidTest : - BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { + BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { - @Test - fun `adds sentry dependency`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `adds sentry dependency`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -24,21 +24,20 @@ class SentryPluginAutoInstallNonAndroidTest : sentry { autoInstallation.enabled = true } - """.trimIndent() - ) - - val result = runListDependenciesTask() - assertTrue { - "io.sentry:sentry:$SENTRY_SDK_VERSION" in result.output - } - } - - @Test - fun `does not do anything when autoinstall is disabled`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + assertTrue { "io.sentry:sentry:$SENTRY_SDK_VERSION" in result.output } + } + + @Test + fun `does not do anything when autoinstall is disabled`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -51,30 +50,29 @@ class SentryPluginAutoInstallNonAndroidTest : } sentry.autoInstallation.enabled = false - """.trimIndent() - ) - - val result = runListDependenciesTask() - assertFalse { "io.sentry:sentry:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-spring:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-spring-jakarta:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-spring-boot:$SENTRY_SDK_VERSION" in result.output } - assertFalse { - "io.sentry:sentry-spring-boot-jakarta:$SENTRY_SDK_VERSION" in result.output - } - assertFalse { "io.sentry:sentry-logback:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-log4j2:$SENTRY_SDK_VERSION" in result.output } - - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `uses user-provided sentryVersion when sentry is not available in direct deps`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + assertFalse { "io.sentry:sentry:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-spring:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-spring-jakarta:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-spring-boot:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-spring-boot-jakarta:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-logback:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-log4j2:$SENTRY_SDK_VERSION" in result.output } + + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `uses user-provided sentryVersion when sentry is not available in direct deps`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -90,27 +88,28 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertTrue { "io.sentry:sentry:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-logback:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-log4j2:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-jdbc:6.10.0" in result.output } - - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `logback is added`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertTrue { "io.sentry:sentry:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-logback:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-log4j2:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-jdbc:6.10.0" in result.output } + + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `logback is added`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -121,21 +120,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-logback:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-logback:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `log4j2 is added`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `log4j2 is added`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -146,22 +146,23 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertTrue { "io.sentry:sentry-log4j2:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `jdbc is added for spring-jdbc`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertTrue { "io.sentry:sentry-log4j2:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `jdbc is added for spring-jdbc`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -172,21 +173,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for hsql`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for hsql`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -197,21 +199,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for mysql`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for mysql`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -222,21 +225,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for mariadb`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for mariadb`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -247,21 +251,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for postgres`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for postgres`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -272,21 +277,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for oracle ojdbc5`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for oracle ojdbc5`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -297,21 +303,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `jdbc is added for oracle ojdbc11`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `jdbc is added for oracle ojdbc11`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -322,21 +329,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-jdbc:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `kotlin-extensions is added`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `kotlin-extensions is added`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -347,21 +355,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-kotlin-extensions:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-kotlin-extensions:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `spring is added for Spring 5`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `spring is added for Spring 5`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -372,25 +381,26 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertTrue { "io.sentry:sentry-spring:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `spring-jakarta is added for Spring 6`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertTrue { "io.sentry:sentry-spring:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `spring-jakarta is added for Spring 6`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -401,24 +411,25 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertFalse { "io.sentry:sentry-spring:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `spring-boot-starter is added for Spring Boot 2`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertFalse { "io.sentry:sentry-spring:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `spring-boot-starter is added for Spring Boot 2`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -429,25 +440,26 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertTrue { "io.sentry:sentry-spring:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-boot:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `spring-boot-starter-jakarta is added for Spring Boot 3`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertTrue { "io.sentry:sentry-spring:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-boot:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `spring-boot-starter-jakarta is added for Spring Boot 3`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -458,24 +470,25 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.28.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertFalse { "io.sentry:sentry-spring:6.28.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } - assertTrue { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `quartz is added`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertFalse { "io.sentry:sentry-spring:6.28.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-jakarta:6.28.0" in result.output } + assertTrue { "io.sentry:sentry-spring-boot-jakarta:6.28.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `quartz is added`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -486,21 +499,22 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.30.0" - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-quartz:6.30.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-quartz:6.30.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `spring-boot version is respected and not overridden when a direct dep`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `spring-boot version is respected and not overridden when a direct dep`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -512,22 +526,23 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.34.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertFalse { "io.sentry:sentry-spring-boot:6.30.0 -> 6.34.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `works with spring dependency management`() { - assumeThat(getJavaVersion() >= 17, `is`(true)) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertFalse { "io.sentry:sentry-spring-boot:6.30.0 -> 6.34.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `works with spring dependency management`() { + assumeThat(getJavaVersion() >= 17, `is`(true)) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -540,34 +555,33 @@ class SentryPluginAutoInstallNonAndroidTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "7.12.0" - """.trimIndent() - ) - - val result = runListDependenciesTask() - - assertFalse { "io.sentry:sentry-spring:7.12.0" in result.output } - assertFalse { "io.sentry:sentry-spring-boot:7.12.0" in result.output } - assertTrue { "io.sentry:sentry-spring-jakarta:7.12.0" in result.output } - assertTrue { "io.sentry:sentry-spring-boot-jakarta:7.12.0" in result.output } - assertTrue { "io.sentry:sentry-okhttp:7.12.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - private fun runListDependenciesTask() = runner - .appendArguments("app:dependencies") - .build() - - private fun getJavaVersion(): Int { - var version = System.getProperty("java.version") - if (version.startsWith("1.")) { - version = version.substring(2, 3) - } else { - val dot = version.indexOf(".") - if (dot != -1) { - version = version.substring(0, dot) - } - } - return version.toInt() + """ + .trimIndent() + ) + + val result = runListDependenciesTask() + + assertFalse { "io.sentry:sentry-spring:7.12.0" in result.output } + assertFalse { "io.sentry:sentry-spring-boot:7.12.0" in result.output } + assertTrue { "io.sentry:sentry-spring-jakarta:7.12.0" in result.output } + assertTrue { "io.sentry:sentry-spring-boot-jakarta:7.12.0" in result.output } + assertTrue { "io.sentry:sentry-okhttp:7.12.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + private fun runListDependenciesTask() = runner.appendArguments("app:dependencies").build() + + private fun getJavaVersion(): Int { + var version = System.getProperty("java.version") + if (version.startsWith("1.")) { + version = version.substring(2, 3) + } else { + val dot = version.indexOf(".") + if (dot != -1) { + version = version.substring(0, dot) + } } + return version.toInt() + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt index 03794bf1..e5e19d4a 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt @@ -9,13 +9,13 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginAutoInstallTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `adds sentry-android dependency`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `adds sentry-android dependency`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -29,20 +29,19 @@ class SentryPluginAutoInstallTest : includeProguardMapping = false autoInstallation.enabled = true } - """.trimIndent() - ) - - val result = runListDependenciesTask() - assertTrue { - "io.sentry:sentry-android:$SENTRY_SDK_VERSION" in result.output - } - } - - @Test - fun `adds integrations and overrides directly user-defined versions with what -core has`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + assertTrue { "io.sentry:sentry-android:$SENTRY_SDK_VERSION" in result.output } + } + + @Test + fun `adds integrations and overrides directly user-defined versions with what -core has`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -70,26 +69,27 @@ class SentryPluginAutoInstallTest : autoInstallation.enabled = true includeDependenciesReport = false } - """.trimIndent() - ) - - val result = runListDependenciesTask() - assertFalse { "io.sentry:sentry-android:6.34.0" in result.output } - assertTrue { "io.sentry:sentry-android-timber:6.34.0" in result.output } - assertTrue { "io.sentry:sentry-android-fragment:6.34.0" in result.output } - assertTrue { "io.sentry:sentry-android-okhttp:6.31.0 -> 6.34.0" in result.output } - assertTrue { "io.sentry:sentry-android-sqlite:6.21.0 -> 6.34.0" in result.output } - assertFalse { "io.sentry:sentry-compose-android:6.34.0" in result.output } - - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `does not do anything when autoinstall is disabled`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + assertFalse { "io.sentry:sentry-android:6.34.0" in result.output } + assertTrue { "io.sentry:sentry-android-timber:6.34.0" in result.output } + assertTrue { "io.sentry:sentry-android-fragment:6.34.0" in result.output } + assertTrue { "io.sentry:sentry-android-okhttp:6.31.0 -> 6.34.0" in result.output } + assertTrue { "io.sentry:sentry-android-sqlite:6.21.0 -> 6.34.0" in result.output } + assertFalse { "io.sentry:sentry-compose-android:6.34.0" in result.output } + + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `does not do anything when autoinstall is disabled`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -107,25 +107,26 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = false sentry.includeProguardMapping = false - """.trimIndent() - ) - - val result = runListDependenciesTask() - assertFalse { "io.sentry:sentry-android:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-android-timber:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-android-fragment:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-android-okhttp:$SENTRY_SDK_VERSION" in result.output } - assertFalse { "io.sentry:sentry-android-sqlite:$SENTRY_SDK_VERSION" in result.output } - - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } - - @Test - fun `uses user-provided sentryVersion when sentry-android is not available in direct deps`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runListDependenciesTask() + assertFalse { "io.sentry:sentry-android:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-android-timber:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-android-fragment:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-android-okhttp:$SENTRY_SDK_VERSION" in result.output } + assertFalse { "io.sentry:sentry-android-sqlite:$SENTRY_SDK_VERSION" in result.output } + + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } + + @Test + fun `uses user-provided sentryVersion when sentry-android is not available in direct deps`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -144,25 +145,26 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.31.0" sentry.includeProguardMapping = false - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-android:6.31.0" in result.output } - assertTrue { "io.sentry:sentry-android-timber:6.31.0" in result.output } - assertTrue { "io.sentry:sentry-android-okhttp:6.31.0" in result.output } - assertTrue { "io.sentry:sentry-android-fragment:5.4.0 -> 6.31.0" in result.output } + assertTrue { "io.sentry:sentry-android:6.31.0" in result.output } + assertTrue { "io.sentry:sentry-android-timber:6.31.0" in result.output } + assertTrue { "io.sentry:sentry-android-okhttp:6.31.0" in result.output } + assertTrue { "io.sentry:sentry-android-fragment:5.4.0 -> 6.31.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `compose is not added for lower sentry versions`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `compose is not added for lower sentry versions`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -179,20 +181,21 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.6.0" sentry.includeProguardMapping = false - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertFalse { "io.sentry:sentry-compose-android:6.6.0" in result.output } - assertFalse { "FAILED" in result.output } - } + assertFalse { "io.sentry:sentry-compose-android:6.6.0" in result.output } + assertFalse { "FAILED" in result.output } + } - @Test - fun `compose is added with when sentry version 6_7_0 or above is used`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `compose is added with when sentry version 6_7_0 or above is used`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -209,21 +212,22 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.7.0" sentry.includeProguardMapping = false - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-compose-android:6.7.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-compose-android:6.7.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `sqlite is not added for lower sentry versions`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `sqlite is not added for lower sentry versions`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -240,20 +244,21 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.20.0" sentry.includeProguardMapping = false - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertFalse { "io.sentry:sentry-android-sqlite:6.20.0" in result.output } - assertFalse { "FAILED" in result.output } - } + assertFalse { "io.sentry:sentry-android-sqlite:6.20.0" in result.output } + assertFalse { "FAILED" in result.output } + } - @Test - fun `sqlite is added with when sentry version 6_21_0 or above is used`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `sqlite is added with when sentry version 6_21_0 or above is used`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -270,21 +275,22 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.21.0" sentry.includeProguardMapping = false - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runListDependenciesTask() + val result = runListDependenciesTask() - assertTrue { "io.sentry:sentry-android-sqlite:6.21.0" in result.output } - // ensure all dependencies could be resolved - assertFalse { "FAILED" in result.output } - } + assertTrue { "io.sentry:sentry-android-sqlite:6.21.0" in result.output } + // ensure all dependencies could be resolved + assertFalse { "FAILED" in result.output } + } - @Test - fun `warns about overriding user-defined sentry dependencies`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `warns about overriding user-defined sentry dependencies`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -301,27 +307,32 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.21.0" sentry.includeProguardMapping = false - """.trimIndent() - ) - - moduleBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + + moduleBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'io.sentry:sentry-android-core:6.0.0' } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner.appendArguments("app:assembleDebug").build() - assertTrue { "WARNING: Version of 'io.sentry:sentry-android-core' was overridden from '6.0.0' to '6.21.0' by the Sentry Gradle plugin. If you want to use the older version, you can add `autoInstallation.sentryVersion.set(\"6.0.0\")` in the `sentry {}` plugin configuration block" in result.output } + val result = runner.appendArguments("app:assembleDebug").build() + assertTrue { + "WARNING: Version of 'io.sentry:sentry-android-core' was overridden from '6.0.0' to '6.21.0' by the Sentry Gradle plugin. If you want to use the older version, you can add `autoInstallation.sentryVersion.set(\"6.0.0\")` in the `sentry {}` plugin configuration block" in + result.output } + } - @Test - fun `considers sentry-bom as a core version`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `considers sentry-bom as a core version`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -339,17 +350,19 @@ class SentryPluginAutoInstallTest : sentry.autoInstallation.enabled = true sentry.autoInstallation.sentryVersion = "6.32.0" sentry.includeProguardMapping = false - """.trimIndent() - ) - - val result = runListDependenciesTask() - // since we consider sentry-bom now, the plugin should not add sentry-android:6.32.0 at all - assertFalse { "io.sentry:sentry-android:6.32.0 -> 7.0.0" in result.output } - } - - private fun runListDependenciesTask() = runner - .appendArguments("app:dependencies") - .appendArguments("--configuration") - .appendArguments("debugRuntimeClasspath") - .build() + """ + .trimIndent() + ) + + val result = runListDependenciesTask() + // since we consider sentry-bom now, the plugin should not add sentry-android:6.32.0 at all + assertFalse { "io.sentry:sentry-android:6.32.0 -> 7.0.0" in result.output } + } + + private fun runListDependenciesTask() = + runner + .appendArguments("app:dependencies") + .appendArguments("--configuration") + .appendArguments("debugRuntimeClasspath") + .build() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt index f7b43600..412492d8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt @@ -6,61 +6,57 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginCheckAndroidSdkTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `when tracingInstrumentation is disabled does not check sentry-android sdk state`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `when tracingInstrumentation is disabled does not check sentry-android sdk state`() { + appBuildFile.appendText( + // language=Groovy + """ sentry.tracingInstrumentation.enabled = false ${captureSdkState()} - """.trimIndent() + """ + .trimIndent() + ) + + // we query the SdkStateHolder intentionally so the build fails, which confirms that the + // service was not registered + val result = runner.appendArguments("app:tasks").buildAndFail() + /* ktlint-disable max-line-length */ + assertTrue { + result.output.contains( + Regex( + """[BuildServiceRegistration with name 'io.sentry.android.gradle.services.SentryModulesService_(\w*)' not found]""" ) - - // we query the SdkStateHolder intentionally so the build fails, which confirms that the - // service was not registered - val result = runner - .appendArguments("app:tasks") - .buildAndFail() - /* ktlint-disable max-line-length */ - assertTrue { - result.output.contains( - Regex( - """[BuildServiceRegistration with name 'io.sentry.android.gradle.services.SentryModulesService_(\w*)' not found]""" - ) - ) - } - /* ktlint-enable max-line-length */ + ) } - - @Test - fun `when tracingInstrumentation is enabled checks sentry-android sdk state`() { - appBuildFile.appendText( - // language=Groovy - """ + /* ktlint-enable max-line-length */ + } + + @Test + fun `when tracingInstrumentation is enabled checks sentry-android sdk state`() { + appBuildFile.appendText( + // language=Groovy + """ sentry.tracingInstrumentation.enabled = true sentry.autoInstallation.enabled = false sentry.includeProguardMapping = false ${captureSdkState()} - """.trimIndent() - ) - - val result = runner - .appendArguments("app:assembleDebug") - .build() - assertTrue { - "SENTRY MODULES: [:]" in result.output - } - } - - @Test - fun `respects variant configuration`() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + + val result = runner.appendArguments("app:assembleDebug").build() + assertTrue { "SENTRY MODULES: [:]" in result.output } + } + + @Test + fun `respects variant configuration`() { + appBuildFile.appendText( + // language=Groovy + """ sentry { tracingInstrumentation.enabled = true autoInstallation.enabled = false @@ -78,24 +74,23 @@ class SentryPluginCheckAndroidSdkTest : } ${captureSdkState()} - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleDebug") - .build() + val result = runner.appendArguments("app:assembleDebug").build() - assertTrue { - "SENTRY MODULES: [io.sentry:sentry-android:5.4.0, " + - "io.sentry:sentry-android-core:5.4.0, " + - "io.sentry:sentry:5.4.0, " + - "io.sentry:sentry-android-ndk:5.4.0]" in result.output - } + assertTrue { + "SENTRY MODULES: [io.sentry:sentry-android:5.4.0, " + + "io.sentry:sentry-android-core:5.4.0, " + + "io.sentry:sentry:5.4.0, " + + "io.sentry:sentry-android-ndk:5.4.0]" in result.output } + } - private fun captureSdkState(): String = - // language=Groovy - """ + private fun captureSdkState(): String = + // language=Groovy + """ import io.sentry.android.gradle.autoinstall.BuildFinishedListenerService import io.sentry.android.gradle.util.* import io.sentry.android.gradle.services.* @@ -108,5 +103,6 @@ class SentryPluginCheckAndroidSdkTest : .get().sentryModules ) } - """.trimIndent() + """ + .trimIndent() } 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 c07994f0..6c1ccb41 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 @@ -16,19 +16,19 @@ import org.junit.Assume.assumeThat import org.junit.Test class SentryPluginConfigurationCacheTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - - @Test - fun `dependency collector task respects configuration cache`() { - assumeThat( - "SentryExternalDependenciesReportTask only supports " + - "configuration cache from Gradle 7.5 onwards", - GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, - `is`(true) - ) - appBuildFile.writeText( - // language=Groovy - """ + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + + @Test + fun `dependency collector task respects configuration cache`() { + assumeThat( + "SentryExternalDependenciesReportTask only supports " + + "configuration cache from Gradle 7.5 onwards", + GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, + `is`(true), + ) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -55,33 +55,36 @@ class SentryPluginConfigurationCacheTest : autoInstallation.enabled = false telemetry = false } - """.trimIndent() - ) - runner.appendArguments(":app:assembleDebug") - .appendArguments("--configuration-cache") - .appendArguments("--info") - - val output = runner.build().output - val deps = verifyDependenciesReportAndroid(testProjectDir.root) - assertEquals( """ + .trimIndent() + ) + runner + .appendArguments(":app:assembleDebug") + .appendArguments("--configuration-cache") + .appendArguments("--info") + + val output = runner.build().output + val deps = verifyDependenciesReportAndroid(testProjectDir.root) + assertEquals( + """ com.squareup.okhttp3:okhttp:3.14.9 com.squareup.okio:okio:1.17.2 - """.trimIndent(), - deps, - "$deps\ndo not match expected value" - ) - assertTrue { "Configuration cache entry stored." in output } - - val outputWithConfigCache = runner.build().output - assertTrue { "Configuration cache entry reused." in outputWithConfigCache } - } - - @Test - fun `SentryModulesService is not discarded at configuration phase`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent(), + deps, + "$deps\ndo not match expected value", + ) + assertTrue { "Configuration cache entry stored." in output } + + val outputWithConfigCache = runner.build().output + assertTrue { "Configuration cache entry reused." in outputWithConfigCache } + } + + @Test + fun `SentryModulesService is not discarded at configuration phase`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -102,131 +105,126 @@ class SentryPluginConfigurationCacheTest : autoInstallation.enabled = false telemetry = false } - """.trimIndent() - ) - - runner.appendArguments(":app:assembleDebug") - .appendArguments("--configuration-cache") - .appendArguments("--info") - - val output = runner.build().output - val readSentryModules = output - .lines() - .find { it.startsWith("[sentry] Read sentry modules:") } - ?.substringAfter("[sentry] Read sentry modules:") - ?.trim() - /* ktlint-disable max-line-length */ - assertEquals( - "{io.sentry:sentry-android-core=6.30.0, io.sentry:sentry=6.30.0, io.sentry:sentry-android-okhttp=6.30.0}", - readSentryModules - ) - /* ktlint-enable max-line-length */ - } - - @Test - fun `works well with configuration cache`() { - // configuration cache doesn't seem to work well on older Gradle/AGP combinations - // producing the following output: - // - // 0 problems were found storing the configuration cache. - // Configuration cache entry discarded - // - // so we only run this test on supported versions - assumeThat( - "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", - SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && - GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, - `is`(true) - ) - - val runner = runner.withArguments( - "--configuration-cache", - "--build-cache", - ":app:assembleDebug" - ) - - val run0 = runner.build() - assertFalse( - "Reusing configuration cache." in run0.output || - "Configuration cache entry reused." in run0.output, - run0.output - ) - - val run1 = runner.build() - assertTrue( - "Reusing configuration cache." in run1.output || - "Configuration cache entry reused." in run1.output, - run1.output - ) - } - - @Test - fun `sentry-cli is recovered when deleted during runs and configuration cache is active`() { - // configuration cache doesn't seem to work well on older Gradle/AGP combinations - // producing the following output: - // - // 0 problems were found storing the configuration cache. - // Configuration cache entry discarded - // - // so we only run this test on supported versions - assumeThat( - "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", - SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && - GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, - `is`(true) - ) - - val runner = runner.withArguments( - "--configuration-cache", - "--build-cache", - ":app:assembleDebug" - ) - - val run0 = runner.build() - assertFalse( - "Reusing configuration cache." in run0.output || - "Configuration cache entry reused." in run0.output, - run0.output - ) - - val cliPath = SentryCliProvider.getCliResourcesExtractionPath( - File(runner.projectDir, "build") - ) - - // On Gradle >= 8, the whole build folder is wiped anyway - if (cliPath.exists()) { - cliPath.delete() - } - - // then it should be recovered on the next run - val run1 = runner.build() - assertTrue( - "Reusing configuration cache." in run1.output || - "Configuration cache entry reused." in run1.output, - run1.output - ) - assertTrue(run1.output) { "BUILD SUCCESSFUL" in run1.output } + """ + .trimIndent() + ) + + runner + .appendArguments(":app:assembleDebug") + .appendArguments("--configuration-cache") + .appendArguments("--info") + + val output = runner.build().output + val readSentryModules = + output + .lines() + .find { it.startsWith("[sentry] Read sentry modules:") } + ?.substringAfter("[sentry] Read sentry modules:") + ?.trim() + /* ktlint-disable max-line-length */ + assertEquals( + "{io.sentry:sentry-android-core=6.30.0, io.sentry:sentry=6.30.0, io.sentry:sentry-android-okhttp=6.30.0}", + readSentryModules, + ) + /* ktlint-enable max-line-length */ + } + + @Test + fun `works well with configuration cache`() { + // configuration cache doesn't seem to work well on older Gradle/AGP combinations + // producing the following output: + // + // 0 problems were found storing the configuration cache. + // Configuration cache entry discarded + // + // so we only run this test on supported versions + assumeThat( + "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", + SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && + GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, + `is`(true), + ) + + val runner = + runner.withArguments("--configuration-cache", "--build-cache", ":app:assembleDebug") + + val run0 = runner.build() + assertFalse( + "Reusing configuration cache." in run0.output || + "Configuration cache entry reused." in run0.output, + run0.output, + ) + + val run1 = runner.build() + assertTrue( + "Reusing configuration cache." in run1.output || + "Configuration cache entry reused." in run1.output, + run1.output, + ) + } + + @Test + fun `sentry-cli is recovered when deleted during runs and configuration cache is active`() { + // configuration cache doesn't seem to work well on older Gradle/AGP combinations + // producing the following output: + // + // 0 problems were found storing the configuration cache. + // Configuration cache entry discarded + // + // so we only run this test on supported versions + assumeThat( + "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", + SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && + GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, + `is`(true), + ) + + val runner = + runner.withArguments("--configuration-cache", "--build-cache", ":app:assembleDebug") + + val run0 = runner.build() + assertFalse( + "Reusing configuration cache." in run0.output || + "Configuration cache entry reused." in run0.output, + run0.output, + ) + + val cliPath = SentryCliProvider.getCliResourcesExtractionPath(File(runner.projectDir, "build")) + + // On Gradle >= 8, the whole build folder is wiped anyway + if (cliPath.exists()) { + cliPath.delete() } - @Test - fun `sentry-cli is recovered when clean is executed before assemble`() { - // configuration cache doesn't seem to work well on older Gradle/AGP combinations - // producing the following output: - // - // 0 problems were found storing the configuration cache. - // Configuration cache entry discarded - // - // so we only run this test on supported versions - assumeThat( - "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", - SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && - GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, - `is`(true) - ) - - appBuildFile.writeText( - // language=Groovy - """ + // then it should be recovered on the next run + val run1 = runner.build() + assertTrue( + "Reusing configuration cache." in run1.output || + "Configuration cache entry reused." in run1.output, + run1.output, + ) + assertTrue(run1.output) { "BUILD SUCCESSFUL" in run1.output } + } + + @Test + fun `sentry-cli is recovered when clean is executed before assemble`() { + // configuration cache doesn't seem to work well on older Gradle/AGP combinations + // producing the following output: + // + // 0 problems were found storing the configuration cache. + // Configuration cache entry discarded + // + // so we only run this test on supported versions + assumeThat( + "We only support configuration cache from AGP 7.4.0 and Gradle 8.0.0 onwards", + SemVer.parse(BuildConfig.AgpVersion) >= AgpVersions.VERSION_7_4_0 && + GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, + `is`(true), + ) + + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -244,32 +242,34 @@ class SentryPluginConfigurationCacheTest : autoInstallation.enabled = false telemetry = false } - """.trimIndent() - ) - - val runner = runner.withArguments( - "--configuration-cache", - "--build-cache", - ":app:clean", - ":app:assembleRelease", - "--stacktrace" - ) - - val run = runner.build() - assertTrue(run.output) { "BUILD SUCCESSFUL" in run.output } - } - - @Test - fun `native symbols upload task respects configuration cache`() { - assumeThat( - "SentryUploadNativeSymbolsTask only supports " + - "configuration cache from Gradle 7.5 onwards", - GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, - `is`(true) - ) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val runner = + runner.withArguments( + "--configuration-cache", + "--build-cache", + ":app:clean", + ":app:assembleRelease", + "--stacktrace", + ) + + val run = runner.build() + assertTrue(run.output) { "BUILD SUCCESSFUL" in run.output } + } + + @Test + fun `native symbols upload task respects configuration cache`() { + assumeThat( + "SentryUploadNativeSymbolsTask only supports " + + "configuration cache from Gradle 7.5 onwards", + GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, + `is`(true), + ) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -287,15 +287,15 @@ class SentryPluginConfigurationCacheTest : autoInstallation.enabled = false telemetry = false } - """.trimIndent() - ) - runner.appendArguments(":app:assembleRelease") - .appendArguments("--configuration-cache") + """ + .trimIndent() + ) + runner.appendArguments(":app:assembleRelease").appendArguments("--configuration-cache") - val output = runner.build().output - assertTrue { "Configuration cache entry stored." in output } + val output = runner.build().output + assertTrue { "Configuration cache entry stored." in output } - val outputWithConfigCache = runner.build().output - assertTrue { "Configuration cache entry reused." in outputWithConfigCache } - } + val outputWithConfigCache = runner.build().output + assertTrue { "Configuration cache entry reused." in outputWithConfigCache } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt index ced199f3..1d897390 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt @@ -10,98 +10,88 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginIntegrationTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - - @Test - fun uploadWithoutSentryCliProperties() { - if (System.getenv("SENTRY_URL").isNullOrBlank()) { - return // Don't run test if local test server endpoint is not set - } - sentryPropertiesFile.writeText("") - applyAutoUploadProguardMappingWithCredentials() - - val build = runner - .appendArguments(":app:assembleRelease") - .build() - - assertEquals( - build.task(":app:uploadSentryProguardMappingsRelease")?.outcome, - TaskOutcome.SUCCESS - ) - assertTrue { "> Authorization: Bearer " in build.output } - } + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun uploadSentryProguardMappingsIntegration() { - if (System.getenv("SENTRY_URL").isNullOrBlank()) { - return // Don't run test if local test server endpoint is not set - } - sentryPropertiesFile.appendText("auth.token=") - applyAutoUploadProguardMapping() - - val build = runner - .appendArguments(":app:assembleRelease") - .build() - - assertEquals( - build.task(":app:uploadSentryProguardMappingsRelease")?.outcome, - TaskOutcome.SUCCESS - ) - assertTrue(build.output) { - "Most likely you have to update your self-hosted Sentry version " + - "to get all of the latest features." in build.output - } + @Test + fun uploadWithoutSentryCliProperties() { + if (System.getenv("SENTRY_URL").isNullOrBlank()) { + return // Don't run test if local test server endpoint is not set + } + sentryPropertiesFile.writeText("") + applyAutoUploadProguardMappingWithCredentials() + + val build = runner.appendArguments(":app:assembleRelease").build() + + assertEquals( + build.task(":app:uploadSentryProguardMappingsRelease")?.outcome, + TaskOutcome.SUCCESS, + ) + assertTrue { "> Authorization: Bearer " in build.output } + } + + @Test + fun uploadSentryProguardMappingsIntegration() { + if (System.getenv("SENTRY_URL").isNullOrBlank()) { + return // Don't run test if local test server endpoint is not set + } + sentryPropertiesFile.appendText("auth.token=") + applyAutoUploadProguardMapping() + + val build = runner.appendArguments(":app:assembleRelease").build() + + assertEquals( + build.task(":app:uploadSentryProguardMappingsRelease")?.outcome, + TaskOutcome.SUCCESS, + ) + assertTrue(build.output) { + "Most likely you have to update your self-hosted Sentry version " + + "to get all of the latest features." in build.output } + } - @Test - fun uploadNativeSymbols() { - if (System.getenv("SENTRY_URL").isNullOrBlank()) { - return // Don't run test if local test server endpoint is not set - } - sentryPropertiesFile.appendText("auth.token=") - applyUploadNativeSymbols() - - val build = runner - .appendArguments(":app:assembleRelease") - .build() - - assertEquals( - build.task(":app:uploadSentryNativeSymbolsForRelease")?.outcome, - TaskOutcome.SUCCESS - ) + @Test + fun uploadNativeSymbols() { + if (System.getenv("SENTRY_URL").isNullOrBlank()) { + return // Don't run test if local test server endpoint is not set } + sentryPropertiesFile.appendText("auth.token=") + applyUploadNativeSymbols() + + val build = runner.appendArguments(":app:assembleRelease").build() + + assertEquals( + build.task(":app:uploadSentryNativeSymbolsForRelease")?.outcome, + TaskOutcome.SUCCESS, + ) + } - @Test - fun uploadSourceContexts() { - if (System.getenv("SENTRY_URL").isNullOrBlank()) { - return // Don't run test if local test server endpoint is not set - } - sentryPropertiesFile.appendText("auth.token=") - applyUploadSourceContexts() - - testProjectDir.withDummyComposeFile() - /* ktlint-disable max-line-length */ - val uploadedIdRegex = """\w+":\{"state":"ok","missingChunks":\[],"uploaded_id":"(\w+-\w+-\w+-\w+-\w+)""".toRegex() - /* ktlint-enable max-line-length */ - - val build = runner - .appendArguments(":app:assembleRelease") - .build() - - assertEquals( - build.task(":app:sentryUploadSourceBundleRelease")?.outcome, - TaskOutcome.SUCCESS - ) - - val uploadedId = uploadedIdRegex.find(build.output)?.groupValues?.get(1) - val bundledId = verifySourceContextId(testProjectDir.root).toString() - assertEquals(uploadedId, bundledId) + @Test + fun uploadSourceContexts() { + if (System.getenv("SENTRY_URL").isNullOrBlank()) { + return // Don't run test if local test server endpoint is not set } + sentryPropertiesFile.appendText("auth.token=") + applyUploadSourceContexts() - private fun applyAutoUploadProguardMapping() { - appBuildFile.appendText( - // language=Groovy - """ + testProjectDir.withDummyComposeFile() + /* ktlint-disable max-line-length */ + val uploadedIdRegex = + """\w+":\{"state":"ok","missingChunks":\[],"uploaded_id":"(\w+-\w+-\w+-\w+-\w+)""".toRegex() + /* ktlint-enable max-line-length */ + + val build = runner.appendArguments(":app:assembleRelease").build() + + assertEquals(build.task(":app:sentryUploadSourceBundleRelease")?.outcome, TaskOutcome.SUCCESS) + + val uploadedId = uploadedIdRegex.find(build.output)?.groupValues?.get(1) + val bundledId = verifySourceContextId(testProjectDir.root).toString() + assertEquals(uploadedId, bundledId) + } + + private fun applyAutoUploadProguardMapping() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'androidx.fragment:fragment:1.3.5' } @@ -115,14 +105,15 @@ class SentryPluginIntegrationTest : enabled = false } } - """.trimIndent() - ) - } - - private fun applyAutoUploadProguardMappingWithCredentials() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + } + + private fun applyAutoUploadProguardMappingWithCredentials() { + appBuildFile.appendText( + // language=Groovy + """ sentry { debug = true includeProguardMapping = true @@ -135,14 +126,15 @@ class SentryPluginIntegrationTest : enabled = false } } - """.trimIndent() - ) - } - - private fun applyUploadNativeSymbols() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + } + + private fun applyUploadNativeSymbols() { + appBuildFile.appendText( + // language=Groovy + """ sentry { autoUploadProguardMapping = false uploadNativeSymbols = true @@ -150,14 +142,15 @@ class SentryPluginIntegrationTest : enabled = false } } - """.trimIndent() - ) - } - - private fun applyUploadSourceContexts() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + } + + private fun applyUploadSourceContexts() { + appBuildFile.appendText( + // language=Groovy + """ sentry { debug = true includeSourceContext = true @@ -166,7 +159,8 @@ class SentryPluginIntegrationTest : enabled = false } } - """.trimIndent() - ) - } + """ + .trimIndent() + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginKotlinCompilerTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginKotlinCompilerTest.kt index 421c71dc..3bcf96a7 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginKotlinCompilerTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginKotlinCompilerTest.kt @@ -7,18 +7,19 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginKotlinCompilerTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - override val additionalBuildClasspath: String = - """ + override val additionalBuildClasspath: String = + """ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20" - """.trimIndent() + """ + .trimIndent() - @Test - fun `does not break for simple compose apps`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `does not break for simple compose apps`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "kotlin-android" @@ -56,15 +57,14 @@ class SentryPluginKotlinCompilerTest : sentry { autoUploadProguardMapping = false } - """.trimIndent() - ) + """ + .trimIndent() + ) - testProjectDir.withDummyComposeFile() + testProjectDir.withDummyComposeFile() - val result = runner - .appendArguments("app:assembleRelease") - .build() + val result = runner.appendArguments("app:assembleRelease").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginNonAndroidTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginNonAndroidTest.kt index 1b6f0cd6..d3818d5c 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginNonAndroidTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginNonAndroidTest.kt @@ -5,14 +5,13 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue import org.gradle.util.GradleVersion -class SentryPluginNonAndroidTest : - BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { +class SentryPluginNonAndroidTest : BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { - @Test - fun `telemetry can be disabled`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `telemetry can be disabled`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -30,23 +29,22 @@ class SentryPluginNonAndroidTest : sentry { telemetry = false } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assemble", "--info") - .build() + val result = runner.appendArguments("app:assemble", "--info").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - assertTrue(result.output) { "Sentry telemetry has been disabled." in result.output } - assertFalse(result.output) { "sentry-cli" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + assertTrue(result.output) { "Sentry telemetry has been disabled." in result.output } + assertFalse(result.output) { "sentry-cli" in result.output } + } - @Test - fun `telemetry is enabled by default`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `telemetry is enabled by default`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -60,14 +58,13 @@ class SentryPluginNonAndroidTest : implementation 'org.postgresql:postgresql:42.6.0' implementation 'com.graphql-java:graphql-java:17.3' } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assemble", "--info") - .build() + val result = runner.appendArguments("app:assemble", "--info").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - assertTrue(result.output) { "Sentry telemetry is enabled." in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + assertTrue(result.output) { "Sentry telemetry is enabled." in result.output } + } } 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 0746e096..af4b757b 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 @@ -14,13 +14,13 @@ import org.junit.Assume.assumeThat import org.junit.Test class SentryPluginSourceContextNonAndroidTest : - BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { + BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { - @Test - fun `skips bundle and upload tasks if no sources`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `skips bundle and upload tasks if no sources`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "org.jetbrains.kotlin.jvm" id "io.sentry.jvm.gradle" @@ -29,28 +29,21 @@ class SentryPluginSourceContextNonAndroidTest : sentry { includeSourceContext = true } - """.trimIndent() - ) - val result = runner - .appendArguments("app:assemble") - .build() - - assertEquals( - result.task(":app:sentryUploadSourceBundleJava")?.outcome, - SKIPPED - ) - assertEquals( - result.task(":app:sentryBundleSourcesJava")?.outcome, - SKIPPED - ) - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } - - @Test - fun `skips bundle and upload tasks if disabled`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + val result = runner.appendArguments("app:assemble").build() + + assertEquals(result.task(":app:sentryUploadSourceBundleJava")?.outcome, SKIPPED) + assertEquals(result.task(":app:sentryBundleSourcesJava")?.outcome, SKIPPED) + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } + + @Test + fun `skips bundle and upload tasks if disabled`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "org.jetbrains.kotlin.jvm" id "io.sentry.jvm.gradle" @@ -59,34 +52,27 @@ class SentryPluginSourceContextNonAndroidTest : sentry { includeSourceContext = false } - """.trimIndent() - ) - - sentryPropertiesFile.writeText("") - - testProjectDir.withDummyKtFile() - testProjectDir.withDummyJavaFile() - - val result = runner - .appendArguments("app:assemble") - .build() - - assertEquals( - result.task(":app:sentryUploadSourceBundleJava")?.outcome, - SKIPPED - ) - assertEquals( - result.task(":app:sentryBundleSourcesJava")?.outcome, - SKIPPED - ) - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } - - @Test - fun `bundles source context`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + sentryPropertiesFile.writeText("") + + testProjectDir.withDummyKtFile() + testProjectDir.withDummyJavaFile() + + val result = runner.appendArguments("app:assemble").build() + + assertEquals(result.task(":app:sentryUploadSourceBundleJava")?.outcome, SKIPPED) + assertEquals(result.task(":app:sentryBundleSourcesJava")?.outcome, SKIPPED) + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } + + @Test + fun `bundles source context`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "org.jetbrains.kotlin.jvm" id "io.sentry.jvm.gradle" @@ -102,57 +88,56 @@ class SentryPluginSourceContextNonAndroidTest : projectName = "sentry-android" url = "https://some-host.sentry.io" } - """.trimIndent() - ) - - sentryPropertiesFile.writeText("") - - val ktContents = testProjectDir.withDummyKtFile() - val javaContents = testProjectDir.withDummyJavaFile() - val customContents = testProjectDir.withDummyCustomFile() - - val result = runner - .appendArguments("app:assemble") - .build() - assertTrue(result.output) { "\"--org\" \"sentry-sdks\"" in result.output } - assertTrue(result.output) { "\"--project\" \"sentry-android\"" in result.output } - assertTrue(result.output) { "\"--url\" \"https://some-host.sentry.io\"" in result.output } - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/Example.jvm", - ktContents, - variant = "java", - archivePath = "app/build/libs/app.jar" - ) - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/TestJava.jvm", - javaContents, - variant = "java", - archivePath = "app/build/libs/app.jar" - ) - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/io/other/TestCustom.jvm", - customContents, - variant = "java", - archivePath = "app/build/libs/app.jar" - ) - } - - @Test - fun `respects configuration cache`() { - assumeThat( - "SentryExternalDependenciesReportTask only supports " + - "configuration cache from Gradle 7.5 onwards", - GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, - `is`(true) - ) - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + sentryPropertiesFile.writeText("") + + val ktContents = testProjectDir.withDummyKtFile() + val javaContents = testProjectDir.withDummyJavaFile() + val customContents = testProjectDir.withDummyCustomFile() + + val result = runner.appendArguments("app:assemble").build() + assertTrue(result.output) { "\"--org\" \"sentry-sdks\"" in result.output } + assertTrue(result.output) { "\"--project\" \"sentry-android\"" in result.output } + assertTrue(result.output) { "\"--url\" \"https://some-host.sentry.io\"" in result.output } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/com/example/Example.jvm", + ktContents, + variant = "java", + archivePath = "app/build/libs/app.jar", + ) + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/com/example/TestJava.jvm", + javaContents, + variant = "java", + archivePath = "app/build/libs/app.jar", + ) + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/io/other/TestCustom.jvm", + customContents, + variant = "java", + archivePath = "app/build/libs/app.jar", + ) + } + + @Test + fun `respects configuration cache`() { + assumeThat( + "SentryExternalDependenciesReportTask only supports " + + "configuration cache from Gradle 7.5 onwards", + GradleVersions.CURRENT >= GradleVersions.VERSION_7_5, + `is`(true), + ) + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -166,27 +151,26 @@ class SentryPluginSourceContextNonAndroidTest : org = "sentry-sdks" projectName = "sentry-android" } - """.trimIndent() - ) + """ + .trimIndent() + ) - sentryPropertiesFile.writeText("") + sentryPropertiesFile.writeText("") - val javaContents = testProjectDir.withDummyJavaFile() + val javaContents = testProjectDir.withDummyJavaFile() - val result = runner - .appendArguments("app:assemble") - .appendArguments("--configuration-cache") - .build() + val result = + runner.appendArguments("app:assemble").appendArguments("--configuration-cache").build() - assertTrue(result.output) { "Configuration cache entry stored." in result.output } - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + assertTrue(result.output) { "Configuration cache entry stored." in result.output } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/TestJava.jvm", - javaContents, - variant = "java", - archivePath = "app/build/libs/app.jar" - ) - } + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/com/example/TestJava.jvm", + javaContents, + variant = "java", + archivePath = "app/build/libs/app.jar", + ) + } } 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 2cc0185d..4bc1c7a7 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 @@ -21,13 +21,13 @@ import org.junit.Assume.assumeThat import org.junit.Test class SentryPluginSourceContextTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `skips bundle and upload tasks if no sources`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `skips bundle and upload tasks if no sources`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -44,29 +44,22 @@ class SentryPluginSourceContextTest : sentry { includeSourceContext = true } - """.trimIndent() - ) - val result = runner - .appendArguments("app:assembleRelease") - .build() - - assertEquals( - result.task(":app:sentryUploadSourceBundleRelease")?.outcome, - SKIPPED - ) - assertEquals( - result.task(":app:sentryBundleSourcesRelease")?.outcome, - SKIPPED - ) - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } - - @Test - fun `generateBundleId and collectSources are up-to-date on subsequent builds`() { - runner.appendArguments("app:assembleRelease") - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + val result = runner.appendArguments("app:assembleRelease").build() + + assertEquals(result.task(":app:sentryUploadSourceBundleRelease")?.outcome, SKIPPED) + assertEquals(result.task(":app:sentryBundleSourcesRelease")?.outcome, SKIPPED) + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } + + @Test + fun `generateBundleId and collectSources are up-to-date on subsequent builds`() { + runner.appendArguments("app:assembleRelease") + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -88,41 +81,30 @@ class SentryPluginSourceContextTest : projectName = "sentry-android" url = "https://some-host.sentry.io" } - """.trimIndent() - ) - val firstBuild = runner.build() - - val subsequentBuild = runner.build() - - assertEquals( - firstBuild.task(":app:generateSentryBundleIdRelease")?.outcome, - SUCCESS - ) - - assertEquals( - firstBuild.task(":app:sentryCollectSourcesRelease")?.outcome, - SUCCESS - ) - - assertEquals( - subsequentBuild.task(":app:generateSentryBundleIdRelease")?.outcome, - UP_TO_DATE - ) - - assertEquals( - subsequentBuild.task(":app:sentryCollectSourcesRelease")?.outcome, - UP_TO_DATE - ) - - assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } - } - - @Test - fun `generateBundleId and collectSources are not up-to-date if sources change`() { - runner.appendArguments("app:assembleRelease") - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + val firstBuild = runner.build() + + val subsequentBuild = runner.build() + + assertEquals(firstBuild.task(":app:generateSentryBundleIdRelease")?.outcome, SUCCESS) + + assertEquals(firstBuild.task(":app:sentryCollectSourcesRelease")?.outcome, SUCCESS) + + assertEquals(subsequentBuild.task(":app:generateSentryBundleIdRelease")?.outcome, UP_TO_DATE) + + assertEquals(subsequentBuild.task(":app:sentryCollectSourcesRelease")?.outcome, UP_TO_DATE) + + assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } + } + + @Test + fun `generateBundleId and collectSources are not up-to-date if sources change`() { + runner.appendArguments("app:assembleRelease") + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -144,42 +126,31 @@ class SentryPluginSourceContextTest : projectName = "sentry-android" url = "https://some-host.sentry.io" } - """.trimIndent() - ) - val firstBuild = runner.build() + """ + .trimIndent() + ) + val firstBuild = runner.build() - testProjectDir.withDummyComposeFile() + testProjectDir.withDummyComposeFile() - val subsequentBuild = runner.build() + val subsequentBuild = runner.build() - assertEquals( - firstBuild.task(":app:generateSentryBundleIdRelease")?.outcome, - SUCCESS - ) + assertEquals(firstBuild.task(":app:generateSentryBundleIdRelease")?.outcome, SUCCESS) - assertEquals( - firstBuild.task(":app:sentryCollectSourcesRelease")?.outcome, - SUCCESS - ) + assertEquals(firstBuild.task(":app:sentryCollectSourcesRelease")?.outcome, SUCCESS) - assertEquals( - subsequentBuild.task(":app:generateSentryBundleIdRelease")?.outcome, - SUCCESS - ) + assertEquals(subsequentBuild.task(":app:generateSentryBundleIdRelease")?.outcome, SUCCESS) - assertEquals( - subsequentBuild.task(":app:sentryCollectSourcesRelease")?.outcome, - SUCCESS - ) + assertEquals(subsequentBuild.task(":app:sentryCollectSourcesRelease")?.outcome, SUCCESS) - assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } - } + assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } + } - @Test - fun `bundles source context`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `bundles source context`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -203,60 +174,51 @@ class SentryPluginSourceContextTest : projectName = "sentry-android" url = "https://some-host.sentry.io" } - """.trimIndent() - ) - - sentryPropertiesFile.writeText("") - - val ktContents = testProjectDir.withDummyComposeFile() - val javaContents = testProjectDir.withDummyJavaFile() - val customContents = testProjectDir.withDummyCustomFile() - - val result = runner - .appendArguments("app:assembleRelease") - .build() - - assertTrue(result.output) { "\"--org\" \"sentry-sdks\"" in result.output } - assertTrue(result.output) { "\"--project\" \"sentry-android\"" in result.output } - assertTrue(result.output) { "\"--url\" \"https://some-host.sentry.io\"" in result.output } - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/Example.jvm", - ktContents - ) - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/TestJava.jvm", - javaContents - ) - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/io/other/TestCustom.jvm", - customContents - ) - // do not bundle build config - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/BuildConfig.jvm", - "" - ) - } - - @Test - fun `respects configuration cache`() { - assumeThat( - "Sentry Source Context only supports " + - "configuration cache from Gradle 8.0 onwards due to the bug in gradle " + - "https://github.com/gradle/gradle/issues/19252", - GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, - `is`(true) - ) - - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + sentryPropertiesFile.writeText("") + + val ktContents = testProjectDir.withDummyComposeFile() + val javaContents = testProjectDir.withDummyJavaFile() + val customContents = testProjectDir.withDummyCustomFile() + + val result = runner.appendArguments("app:assembleRelease").build() + + assertTrue(result.output) { "\"--org\" \"sentry-sdks\"" in result.output } + assertTrue(result.output) { "\"--project\" \"sentry-android\"" in result.output } + assertTrue(result.output) { "\"--url\" \"https://some-host.sentry.io\"" in result.output } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + + verifySourceBundleContents(testProjectDir.root, "files/_/_/com/example/Example.jvm", ktContents) + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/com/example/TestJava.jvm", + javaContents, + ) + verifySourceBundleContents( + testProjectDir.root, + "files/_/_/io/other/TestCustom.jvm", + customContents, + ) + // do not bundle build config + verifySourceBundleContents(testProjectDir.root, "files/_/_/com/example/BuildConfig.jvm", "") + } + + @Test + fun `respects configuration cache`() { + assumeThat( + "Sentry Source Context only supports " + + "configuration cache from Gradle 8.0 onwards due to the bug in gradle " + + "https://github.com/gradle/gradle/issues/19252", + GradleVersions.CURRENT >= GradleVersions.VERSION_8_0, + `is`(true), + ) + + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -278,49 +240,36 @@ class SentryPluginSourceContextTest : org = "sentry-sdks" projectName = "sentry-android" } - """.trimIndent() - ) - - sentryPropertiesFile.writeText("") - - val ktContents = testProjectDir.withDummyComposeFile() - - val result = runner - .appendArguments("app:assembleRelease") - .appendArguments("--configuration-cache") - .build() - - assertTrue(result.output) { "Configuration cache entry stored." in result.output } - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/Example.jvm", - ktContents - ) - // do not bundle build config - verifySourceBundleContents( - testProjectDir.root, - "files/_/_/com/example/BuildConfig.jvm", - "" - ) - } - - @Test - fun `uploadSourceBundle task is up-to-date on subsequent builds`() { - val sentryCli = SentryCliProvider.getSentryCliPath( - File(""), - File("build"), - File("") - ) - SentryCliProvider.maybeExtractFromResources(File("build"), sentryCli) - - sentryPropertiesFile.writeText("cli.executable=$sentryCli") - - runner.appendArguments("app:assembleRelease") - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + sentryPropertiesFile.writeText("") + + val ktContents = testProjectDir.withDummyComposeFile() + + val result = + runner.appendArguments("app:assembleRelease").appendArguments("--configuration-cache").build() + + assertTrue(result.output) { "Configuration cache entry stored." in result.output } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + + verifySourceBundleContents(testProjectDir.root, "files/_/_/com/example/Example.jvm", ktContents) + // do not bundle build config + verifySourceBundleContents(testProjectDir.root, "files/_/_/com/example/BuildConfig.jvm", "") + } + + @Test + fun `uploadSourceBundle task is up-to-date on subsequent builds`() { + val sentryCli = SentryCliProvider.getSentryCliPath(File(""), File("build"), File("")) + SentryCliProvider.maybeExtractFromResources(File("build"), sentryCli) + + sentryPropertiesFile.writeText("cli.executable=$sentryCli") + + runner.appendArguments("app:assembleRelease") + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -341,42 +290,33 @@ class SentryPluginSourceContextTest : org = "sentry-sdks" projectName = "sentry-android" } - """.trimIndent() - ) - testProjectDir.withDummyComposeFile() + """ + .trimIndent() + ) + testProjectDir.withDummyComposeFile() - val firstBuild = runner.build() + val firstBuild = runner.build() - val subsequentBuild = runner.build() + val subsequentBuild = runner.build() - assertEquals( - firstBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, - SUCCESS - ) + assertEquals(firstBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, SUCCESS) - assertEquals( - subsequentBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, - UP_TO_DATE - ) + assertEquals(subsequentBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, UP_TO_DATE) - assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } - } + assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } + } - @Test - fun `uploadSourceBundle task is not up-to-date on subsequent builds if cli path changes`() { - val sentryCli = SentryCliProvider.getSentryCliPath( - File(""), - File("build"), - File("") - ) - SentryCliProvider.maybeExtractFromResources(File("build"), sentryCli) + @Test + fun `uploadSourceBundle task is not up-to-date on subsequent builds if cli path changes`() { + val sentryCli = SentryCliProvider.getSentryCliPath(File(""), File("build"), File("")) + SentryCliProvider.maybeExtractFromResources(File("build"), sentryCli) - sentryPropertiesFile.writeText("cli.executable=$sentryCli") + sentryPropertiesFile.writeText("cli.executable=$sentryCli") - runner.appendArguments("app:assembleRelease") - appBuildFile.writeText( - // language=Groovy - """ + runner.appendArguments("app:assembleRelease") + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -397,32 +337,27 @@ class SentryPluginSourceContextTest : org = "sentry-sdks" projectName = "sentry-android" } - """.trimIndent() - ) - testProjectDir.withDummyComposeFile() + """ + .trimIndent() + ) + testProjectDir.withDummyComposeFile() - val firstBuild = runner.build() + val firstBuild = runner.build() - val tempDir = Files.createTempDirectory("sentry-test") - val newCliPath = tempDir.resolve("sentry-cli") - Files.copy(Path.of(sentryCli), tempDir.resolve("sentry-cli")) - newCliPath.toFile().deleteOnExit() - tempDir.toFile().deleteOnExit() + val tempDir = Files.createTempDirectory("sentry-test") + val newCliPath = tempDir.resolve("sentry-cli") + Files.copy(Path.of(sentryCli), tempDir.resolve("sentry-cli")) + newCliPath.toFile().deleteOnExit() + tempDir.toFile().deleteOnExit() - sentryPropertiesFile.writeText("cli.executable=$newCliPath") + sentryPropertiesFile.writeText("cli.executable=$newCliPath") - val subsequentBuild = runner.build() + val subsequentBuild = runner.build() - assertEquals( - firstBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, - SUCCESS - ) + assertEquals(firstBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, SUCCESS) - assertEquals( - subsequentBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, - SUCCESS - ) + assertEquals(subsequentBuild.task(":app:sentryUploadSourceBundleRelease")?.outcome, SUCCESS) - assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } - } + assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTelemetryTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTelemetryTest.kt index d4761f48..69459d15 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTelemetryTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginTelemetryTest.kt @@ -7,33 +7,32 @@ import kotlin.test.assertTrue import org.gradle.util.GradleVersion class SentryPluginTelemetryTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `telemetry can be disabled`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `telemetry can be disabled`() { + appBuildFile.appendText( + // language=Groovy + """ sentry { telemetry = false } - """.trimIndent() - ) - - val result = runner - .appendArguments("app:assembleDebug", "--info") - .build() - - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - assertTrue(result.output) { "Sentry telemetry has been disabled." in result.output } - assertFalse(result.output) { "sentry-cli" in result.output } - } - - @Test - fun `telemetry is enabled by default`() { - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + val result = runner.appendArguments("app:assembleDebug", "--info").build() + + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + assertTrue(result.output) { "Sentry telemetry has been disabled." in result.output } + assertFalse(result.output) { "sentry-cli" in result.output } + } + + @Test + fun `telemetry is enabled by default`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -42,13 +41,12 @@ class SentryPluginTelemetryTest : android { namespace 'com.example' } - """.trimIndent() - ) - val result = runner - .appendArguments("app:assembleDebug", "--info") - .build() - - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - assertTrue(result.output) { "Sentry telemetry is enabled." in result.output } - } + """ + .trimIndent() + ) + val result = runner.appendArguments("app:assembleDebug", "--info").build() + + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + assertTrue(result.output) { "Sentry telemetry is enabled." in result.output } + } } 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 a3a7d765..2599c86b 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 @@ -21,67 +21,69 @@ import org.junit.Assume.assumeThat import org.junit.Test class SentryPluginTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - - @Test - fun `plugin does not configure tasks`() { - val prefix = "task-configured-for-test: " - appBuildFile.appendText( - // language=Groovy - """ + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + + @Test + fun `plugin does not configure tasks`() { + val prefix = "task-configured-for-test: " + appBuildFile.appendText( + // language=Groovy + """ project.tasks.configureEach { Task task -> println("$prefix" + task.path) } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner.withArguments("help").build() - val configuredTasks = result.output.lines() - .filter { it.startsWith(prefix) } - .map { it.removePrefix(prefix) } - .sorted() - .toMutableList() + val result = runner.withArguments("help").build() + val configuredTasks = + result.output + .lines() + .filter { it.startsWith(prefix) } + .map { it.removePrefix(prefix) } + .sorted() + .toMutableList() - // AGP 7.2.x configures the 'clean' task, so ignore it - configuredTasks.remove(":app:clean") + // AGP 7.2.x configures the 'clean' task, so ignore it + configuredTasks.remove(":app:clean") - assertTrue(configuredTasks.isEmpty(), configuredTasks.joinToString("\n")) - } + assertTrue(configuredTasks.isEmpty(), configuredTasks.joinToString("\n")) + } - @Test - fun `does not regenerate UUID every build`() { - runner.appendArguments(":app:assembleRelease") + @Test + fun `does not regenerate UUID every build`() { + runner.appendArguments(":app:assembleRelease") - runner.build() - val uuid1 = verifyProguardUuid(testProjectDir.root) + runner.build() + val uuid1 = verifyProguardUuid(testProjectDir.root) - runner.build() - val uuid2 = verifyProguardUuid(testProjectDir.root) + runner.build() + val uuid2 = verifyProguardUuid(testProjectDir.root) - assertEquals(uuid1, uuid2) - } + assertEquals(uuid1, uuid2) + } - @Test - fun `regenerates UUID if mapping file changes`() { - runner.appendArguments(":app:assembleRelease") + @Test + fun `regenerates UUID if mapping file changes`() { + runner.appendArguments(":app:assembleRelease") - val sourceDir = File(testProjectDir.root, "app/src/main/java/com/example").apply { - mkdirs() - } + 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( - """ + val manifest = File(testProjectDir.root, "app/src/main/AndroidManifest.xml") + manifest.writeText( + """ - """.trimIndent() - ) - - val sourceFile = File(sourceDir, "MainActivity.java") - sourceFile.createNewFile() - sourceFile.writeText( """ + .trimIndent() + ) + + val sourceFile = File(sourceDir, "MainActivity.java") + sourceFile.createNewFile() + sourceFile.writeText( + """ package com.example; import android.app.Activity; import android.os.Bundle; @@ -97,14 +99,15 @@ class SentryPluginTest : System.out.println("Hello"); } } - """.trimIndent() - ) + """ + .trimIndent() + ) - runner.appendArguments(":app:assembleRelease").build() - val uuid1 = verifyProguardUuid(testProjectDir.root) + runner.appendArguments(":app:assembleRelease").build() + val uuid1 = verifyProguardUuid(testProjectDir.root) - sourceFile.writeText( - """ + sourceFile.writeText( + """ package com.example; import android.app.Activity; import android.os.Bundle; @@ -125,38 +128,38 @@ class SentryPluginTest : System.out.println("Hello2"); } } - """.trimIndent() - ) + """ + .trimIndent() + ) - runner.appendArguments(":app:assembleRelease").build() - val uuid2 = verifyProguardUuid(testProjectDir.root) + runner.appendArguments(":app:assembleRelease").build() + val uuid2 = verifyProguardUuid(testProjectDir.root) - assertNotEquals(uuid1, uuid2) - } + assertNotEquals(uuid1, uuid2) + } - @Test - fun `does not regenerate UUID if mapping file stays the same`() { - runner.appendArguments(":app:assembleRelease") + @Test + fun `does not regenerate UUID if mapping file stays the same`() { + runner.appendArguments(":app:assembleRelease") - val sourceDir = File(testProjectDir.root, "app/src/main/java/com/example").apply { - mkdirs() - } + 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( - """ + val manifest = File(testProjectDir.root, "app/src/main/AndroidManifest.xml") + manifest.writeText( + """ - """.trimIndent() - ) - - val sourceFile = File(sourceDir, "MainActivity.java") - sourceFile.createNewFile() - sourceFile.writeText( """ + .trimIndent() + ) + + val sourceFile = File(sourceDir, "MainActivity.java") + sourceFile.createNewFile() + sourceFile.writeText( + """ package com.example; import android.app.Activity; import android.os.Bundle; @@ -172,14 +175,15 @@ class SentryPluginTest : System.out.println("Hello"); } } - """.trimIndent() - ) + """ + .trimIndent() + ) - runner.appendArguments(":app:assembleRelease").build() - val uuid1 = verifyProguardUuid(testProjectDir.root) + runner.appendArguments(":app:assembleRelease").build() + val uuid1 = verifyProguardUuid(testProjectDir.root) - sourceFile.writeText( - """ + sourceFile.writeText( + """ package com.example; import android.app.Activity; import android.os.Bundle; @@ -195,642 +199,583 @@ class SentryPluginTest : System.out.println("Hello2"); } } - """.trimIndent() - ) - - val 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) - } - - @Test - fun `generateSentryDebugMetaProperties task is up-to-date on subsequent builds`() { - runner.appendArguments(":app:assembleRelease") - - val firstBuild = runner.build() - val subsequentBuild = runner.build() - - assertEquals( - TaskOutcome.SUCCESS, - firstBuild.task(":app:generateSentryDebugMetaPropertiesRelease")?.outcome - ?: firstBuild.task(":app:injectSentryDebugMetaPropertiesIntoAssetsRelease") - ?.outcome, - ) - - assertEquals( - TaskOutcome.UP_TO_DATE, - subsequentBuild.task(":app:generateSentryDebugMetaPropertiesRelease")?.outcome - ?: subsequentBuild.task(":app:injectSentryDebugMetaPropertiesIntoAssetsRelease") - ?.outcome, - ) - - assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } - } - - @Test - fun `does not include a UUID in the APK`() { - // isMinifyEnabled is disabled by default in debug builds - runner - .appendArguments(":app:assembleDebug") - .build() - - assertThrows(AssertionError::class.java) { - verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false) - } - } - - @Test - fun `does not include a UUID in the APK if includeProguardMapping is off`() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + + val 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) + } + + @Test + fun `generateSentryDebugMetaProperties task is up-to-date on subsequent builds`() { + runner.appendArguments(":app:assembleRelease") + + val firstBuild = runner.build() + val subsequentBuild = runner.build() + + assertEquals( + TaskOutcome.SUCCESS, + firstBuild.task(":app:generateSentryDebugMetaPropertiesRelease")?.outcome + ?: firstBuild.task(":app:injectSentryDebugMetaPropertiesIntoAssetsRelease")?.outcome, + ) + + assertEquals( + TaskOutcome.UP_TO_DATE, + subsequentBuild.task(":app:generateSentryDebugMetaPropertiesRelease")?.outcome + ?: subsequentBuild.task(":app:injectSentryDebugMetaPropertiesIntoAssetsRelease")?.outcome, + ) + + assertTrue(subsequentBuild.output) { "BUILD SUCCESSFUL" in subsequentBuild.output } + } + + @Test + fun `does not include a UUID in the APK`() { + // isMinifyEnabled is disabled by default in debug builds + runner.appendArguments(":app:assembleDebug").build() + + assertThrows(AssertionError::class.java) { + verifyProguardUuid(testProjectDir.root, variant = "debug", signed = false) + } + } + + @Test + fun `does not include a UUID in the APK if includeProguardMapping is off`() { + appBuildFile.appendText( + // language=Groovy + """ sentry { includeProguardMapping = false } - """.trimIndent() - ) - - runner - .appendArguments(":app:assembleRelease") - .build() - - assertThrows(AssertionError::class.java) { - verifyProguardUuid(testProjectDir.root) - } - } + """ + .trimIndent() + ) - @Test - fun `creates uploadSentryNativeSymbols task if uploadNativeSymbols is enabled`() { - applyUploadNativeSymbols() + runner.appendArguments(":app:assembleRelease").build() - val build = runner - .appendArguments(":app:assembleRelease", "--dry-run") - .build() + assertThrows(AssertionError::class.java) { verifyProguardUuid(testProjectDir.root) } + } - assertTrue(":app:uploadSentryNativeSymbolsForRelease" in build.output) - } + @Test + fun `creates uploadSentryNativeSymbols task if uploadNativeSymbols is enabled`() { + applyUploadNativeSymbols() - @Test - fun `does not create uploadSentryNativeSymbols task if non debuggable app`() { - applyUploadNativeSymbols() + val build = runner.appendArguments(":app:assembleRelease", "--dry-run").build() - val build = runner - .appendArguments(":app:assembleDebug", "--dry-run") - .build() + assertTrue(":app:uploadSentryNativeSymbolsForRelease" in build.output) + } - assertFalse(":app:uploadSentryNativeSymbolsForDebug" in build.output) - } + @Test + fun `does not create uploadSentryNativeSymbols task if non debuggable app`() { + applyUploadNativeSymbols() - @Test - fun `skips variant if set with ignoredVariants`() { - applyIgnores(ignoredVariant = "release") + val build = runner.appendArguments(":app:assembleDebug", "--dry-run").build() - val build = runner - .appendArguments(":app:assembleRelease", "--dry-run") - .build() + assertFalse(":app:uploadSentryNativeSymbolsForDebug" in build.output) + } - assertFalse(":app:uploadSentryProguardMappingsRelease" in build.output) - } + @Test + fun `skips variant if set with ignoredVariants`() { + applyIgnores(ignoredVariant = "release") - @Test - fun `does not skip variant if ignoredVariants specifies another value`() { - applyIgnores(ignoredVariant = "debug") + val build = runner.appendArguments(":app:assembleRelease", "--dry-run").build() - val build = runner - .appendArguments(":app:assembleRelease", "--dry-run") - .build() + assertFalse(":app:uploadSentryProguardMappingsRelease" in build.output) + } - assertTrue(":app:uploadSentryProguardMappingsRelease" in build.output) - } + @Test + fun `does not skip variant if ignoredVariants specifies another value`() { + applyIgnores(ignoredVariant = "debug") - @Test - fun `skips tracing instrumentation if tracingInstrumentation is disabled`() { - applyTracingInstrumentation( - false, - appStart = false, - logcat = false - ) - - val build = runner - .appendArguments(":app:assembleRelease", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleRelease", "--dry-run").build() - assertFalse(":app:transformReleaseClassesWithAsm" in build.output) - } + assertTrue(":app:uploadSentryProguardMappingsRelease" in build.output) + } - @Test - fun `register tracing instrumentation if tracingInstrumentation is enabled`() { - applyTracingInstrumentation() + @Test + fun `skips tracing instrumentation if tracingInstrumentation is disabled`() { + applyTracingInstrumentation(false, appStart = false, logcat = false) - val build = runner - .appendArguments(":app:assembleRelease", "--dry-run") - .build() - - assertTrue(":app:transformReleaseClassesWithAsm" in build.output) - } + val build = runner.appendArguments(":app:assembleRelease", "--dry-run").build() - @Test - fun `applies only DB instrumentables when only DATABASE feature enabled`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.DATABASE), - dependencies = setOf( - "androidx.sqlite:sqlite:2.0.0", - "io.sentry:sentry-android-sqlite:6.21.0" - ), - appStart = false, - logcat = false - ) + assertFalse(":app:transformReleaseClassesWithAsm" in build.output) + } - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() + @Test + fun `register tracing instrumentation if tracingInstrumentation is enabled`() { + applyTracingInstrumentation() - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "AndroidXSQLiteOpenHelper, AndroidXRoomDao)" in build.output - } - } + val build = runner.appendArguments(":app:assembleRelease", "--dry-run").build() - @Test - fun `applies only FILE_IO instrumentables when only FILE_IO feature enabled`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.FILE_IO), - appStart = false, - logcat = false - ) + assertTrue(":app:transformReleaseClassesWithAsm" in build.output) + } - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() + @Test + fun `applies only DB instrumentables when only DATABASE feature enabled`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.DATABASE), + dependencies = + setOf("androidx.sqlite:sqlite:2.0.0", "io.sentry:sentry-android-sqlite:6.21.0"), + appStart = false, + logcat = false, + ) + + val build = runner.appendArguments(":app:assembleDebug", "--info").build() - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "WrappingInstrumentable, RemappingInstrumentable)" in build.output - } + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "AndroidXSQLiteOpenHelper, AndroidXRoomDao)" in build.output } + } - @Test - fun `applies only Compose instrumentable when only Compose feature enabled`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.COMPOSE), - dependencies = setOf( - "androidx.compose.runtime:runtime:1.1.0", - "io.sentry:sentry-compose-android:6.7.0" - ), - appStart = false, - logcat = false - ) + @Test + fun `applies only FILE_IO instrumentables when only FILE_IO feature enabled`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.FILE_IO), + appStart = false, + logcat = false, + ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "ComposeNavigation)" in build.output - } - } + val build = runner.appendArguments(":app:assembleDebug", "--info").build() - @Test - fun `does not apply Compose instrumentable when app does not depend on compose (runtime)`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.COMPOSE), - appStart = false, - logcat = false - ) - - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=)" in build.output - } + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "WrappingInstrumentable, RemappingInstrumentable)" in build.output } + } - @Test - fun `apply old Database instrumentable when app does not depend on sentry-android-sqlite`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.DATABASE), - appStart = false, - logcat = false - ) + @Test + fun `applies only Compose instrumentable when only Compose feature enabled`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.COMPOSE), + dependencies = + setOf("androidx.compose.runtime:runtime:1.1.0", "io.sentry:sentry-compose-android:6.7.0"), + appStart = false, + logcat = false, + ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "AndroidXSQLiteDatabase, AndroidXSQLiteStatement, AndroidXRoomDao)" in build.output - } + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + "ComposeNavigation)" in + build.output } - - @Test - fun `does not apply okhttp listener on older version of sentry-android-okhttp`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.OKHTTP), - dependencies = setOf( - "com.squareup.okhttp3:okhttp:3.14.9", - "io.sentry:sentry-android-okhttp:6.19.0" - ), - appStart = false, - logcat = false - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=OkHttp)" in build.output - } - } - - @Test - fun `apply okhttp listener on sentry-android-okhttp 6_20`() { - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.OKHTTP), - dependencies = setOf( - "com.squareup.okhttp3:okhttp:3.14.9", - "io.sentry:sentry-android-okhttp:6.20.0" - ), - appStart = false, - logcat = false - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "OkHttpEventListener, OkHttp)" in build.output - } - } - - @Test - fun `does not apply app start instrumentations when older SDK version is used`() { - applyTracingInstrumentation( - appStart = true, - sdkVersion = "7.0.0" - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=)" in build.output - } - } - - @Test - fun `applies app start instrumentations when enabled`() { - applyTracingInstrumentation( - appStart = true - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "Application, ContentProvider)" in build.output - } - } - - @Test - fun `applies logcat instrumentation when enabled`() { - applyTracingInstrumentation( - appStart = false, - logcat = true - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "Logcat)" in build.output - } - } - - @Test - fun `applies all instrumentables when all features are enabled`() { - applyTracingInstrumentation( - features = setOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.OKHTTP, - InstrumentationFeature.COMPOSE - ), - dependencies = setOf( - "androidx.sqlite:sqlite:2.0.0", - "com.squareup.okhttp3:okhttp:3.14.9", - "io.sentry:sentry-android-okhttp:6.20.0", - "androidx.compose.runtime:runtime:1.1.0", - "io.sentry:sentry-compose-android:6.7.0", - "io.sentry:sentry-android-sqlite:6.21.0" - ), - appStart = true, - logcat = true - ) - val build = runner - .appendArguments(":app:assembleDebug", "--info") - .build() - - assertTrue { - "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + - "AndroidXSQLiteOpenHelper, AndroidXRoomDao, OkHttpEventListener, " + - "OkHttp, WrappingInstrumentable, RemappingInstrumentable, " + - "ComposeNavigation, Logcat, Application, ContentProvider)" in build.output - } - } - - @Test - fun `instruments okhttp v3`() { - applyTracingInstrumentation(features = setOf(InstrumentationFeature.OKHTTP), debug = true) - appBuildFile.appendText( - // language=Groovy - """ + } + + @Test + fun `does not apply Compose instrumentable when app does not depend on compose (runtime)`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.COMPOSE), + appStart = false, + logcat = false, + ) + + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=)" in build.output + } + } + + @Test + fun `apply old Database instrumentable when app does not depend on sentry-android-sqlite`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.DATABASE), + appStart = false, + logcat = false, + ) + + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "AndroidXSQLiteDatabase, AndroidXSQLiteStatement, AndroidXRoomDao)" in build.output + } + } + + @Test + fun `does not apply okhttp listener on older version of sentry-android-okhttp`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.OKHTTP), + dependencies = + setOf("com.squareup.okhttp3:okhttp:3.14.9", "io.sentry:sentry-android-okhttp:6.19.0"), + appStart = false, + logcat = false, + ) + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=OkHttp)" in build.output + } + } + + @Test + fun `apply okhttp listener on sentry-android-okhttp 6_20`() { + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.OKHTTP), + dependencies = + setOf("com.squareup.okhttp3:okhttp:3.14.9", "io.sentry:sentry-android-okhttp:6.20.0"), + appStart = false, + logcat = false, + ) + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "OkHttpEventListener, OkHttp)" in build.output + } + } + + @Test + fun `does not apply app start instrumentations when older SDK version is used`() { + applyTracingInstrumentation(appStart = true, sdkVersion = "7.0.0") + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=)" in build.output + } + } + + @Test + fun `applies app start instrumentations when enabled`() { + applyTracingInstrumentation(appStart = true) + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "Application, ContentProvider)" in build.output + } + } + + @Test + fun `applies logcat instrumentation when enabled`() { + applyTracingInstrumentation(appStart = false, logcat = true) + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + "Logcat)" in build.output + } + } + + @Test + fun `applies all instrumentables when all features are enabled`() { + applyTracingInstrumentation( + features = + setOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, + InstrumentationFeature.OKHTTP, + InstrumentationFeature.COMPOSE, + ), + dependencies = + setOf( + "androidx.sqlite:sqlite:2.0.0", + "com.squareup.okhttp3:okhttp:3.14.9", + "io.sentry:sentry-android-okhttp:6.20.0", + "androidx.compose.runtime:runtime:1.1.0", + "io.sentry:sentry-compose-android:6.7.0", + "io.sentry:sentry-android-sqlite:6.21.0", + ), + appStart = true, + logcat = true, + ) + val build = runner.appendArguments(":app:assembleDebug", "--info").build() + + assertTrue { + "[sentry] Instrumentable: ChainedInstrumentable(instrumentables=" + + "AndroidXSQLiteOpenHelper, AndroidXRoomDao, OkHttpEventListener, " + + "OkHttp, WrappingInstrumentable, RemappingInstrumentable, " + + "ComposeNavigation, Logcat, Application, ContentProvider)" in build.output + } + } + + @Test + fun `instruments okhttp v3`() { + applyTracingInstrumentation(features = setOf(InstrumentationFeature.OKHTTP), debug = true) + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.14.9' implementation 'io.sentry:sentry-android-okhttp:6.6.0' } - """.trimIndent() - ) + """ + .trimIndent() + ) - runner - .appendArguments(":app:assembleDebug") - .build() + runner.appendArguments(":app:assembleDebug").build() - // since it's an integration test, we just test that the log file was created for the class - // meaning our CommonClassVisitor has visited and instrumented it - val debugOutput = - testProjectDir.root.resolve("app/build/tmp/sentry/RealCall-instrumentation.log") - assertTrue { debugOutput.exists() && debugOutput.length() > 0 } - } + // since it's an integration test, we just test that the log file was created for the class + // meaning our CommonClassVisitor has visited and instrumented it + val debugOutput = + testProjectDir.root.resolve("app/build/tmp/sentry/RealCall-instrumentation.log") + assertTrue { debugOutput.exists() && debugOutput.length() > 0 } + } - @Test - fun `includes flattened list of dependencies into the APK, excluding non-external deps`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `includes flattened list of dependencies into the APK, excluding non-external deps`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.14.9' implementation project(':module') // multi-module project dependency implementation ':asm-9.2' // flat jar } - """.trimIndent() - ) - runner.appendArguments(":app:assembleDebug") - - runner.build() - val deps = verifyDependenciesReportAndroid(testProjectDir.root) - assertEquals( """ + .trimIndent() + ) + runner.appendArguments(":app:assembleDebug") + + runner.build() + val deps = verifyDependenciesReportAndroid(testProjectDir.root) + assertEquals( + """ com.squareup.okhttp3:okhttp:3.14.9 com.squareup.okio:okio:1.17.2 - """.trimIndent(), - deps - ) - } - - @Test - fun `tracks dependency tree changed`() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent(), + deps, + ) + } + + @Test + fun `tracks dependency tree changed`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.14.9' } - """.trimIndent() - ) - runner.appendArguments(":app:assembleDebug") - - runner.build() - val deps = verifyDependenciesReportAndroid(testProjectDir.root) - assertEquals( """ + .trimIndent() + ) + runner.appendArguments(":app:assembleDebug") + + runner.build() + val deps = verifyDependenciesReportAndroid(testProjectDir.root) + assertEquals( + """ com.squareup.okhttp3:okhttp:3.14.9 com.squareup.okio:okio:1.17.2 - """.trimIndent(), - deps - ) - - appBuildFile.appendText( - // language=Groovy """ + .trimIndent(), + deps, + ) + + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.jakewharton.timber:timber:5.0.1' } - """.trimIndent() - ) - runner.build() - val depsAfterChange = verifyDependenciesReportAndroid(testProjectDir.root) - assertEquals( """ + .trimIndent() + ) + runner.build() + val depsAfterChange = verifyDependenciesReportAndroid(testProjectDir.root) + assertEquals( + """ com.jakewharton.timber:timber:5.0.1 com.squareup.okhttp3:okhttp:3.14.9 com.squareup.okio:okio:1.17.2 org.jetbrains.kotlin:kotlin-stdlib-common:1.5.21 org.jetbrains.kotlin:kotlin-stdlib:1.5.21 org.jetbrains:annotations:20.1.0 - """.trimIndent(), - depsAfterChange - ) - } - - @Test - fun `when disabled, skips the task and does not include dependencies report in the APK`() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent(), + depsAfterChange, + ) + } + + @Test + fun `when disabled, skips the task and does not include dependencies report in the APK`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.14.9' } sentry.includeDependenciesReport = false - """.trimIndent() - ) - val output = runner - .appendArguments(":app:assembleDebug") - .build() - .output - - assertTrue { "collectExternalDebugDependenciesForSentry" !in output } - assertThrows(AssertionError::class.java) { - verifyDependenciesReportAndroid(testProjectDir.root) - } - } - - @Test - fun `all integrations are written to manifest`() { - applyTracingInstrumentation( - features = setOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.OKHTTP, - InstrumentationFeature.COMPOSE - ), - logcat = true, - appStart = true, - dependencies = setOf( - "com.squareup.okhttp3:okhttp:3.14.9", - "io.sentry:sentry-android-okhttp:6.6.0", - "androidx.compose.runtime:runtime:1.1.0", - "io.sentry:sentry-compose-android:6.7.0" - ) - ) - - runner.appendArguments(":app:assembleDebug") - runner.build() - - val integrations = verifyIntegrationList( - testProjectDir.root, - variant = "debug", - signed = false - ).sorted() - - val expectedIntegrations = ( - listOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.COMPOSE, - InstrumentationFeature.OKHTTP - ).map { it.integrationName }.toMutableList() + - listOf("LogcatInstrumentation", "AppStartInstrumentation") - ) - .sorted() - - assertEquals(expectedIntegrations, integrations) - } - - @Test - fun `only active integrations are written to manifest`() { - applyTracingInstrumentation( - features = setOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.OKHTTP, - InstrumentationFeature.COMPOSE - ), - dependencies = setOf( - "androidx.compose.runtime:runtime:1.1.0", - "io.sentry:sentry-compose-android:6.7.0" - ) - ) - - runner.appendArguments(":app:assembleDebug") - - runner.build() - val integrations = verifyIntegrationList( - testProjectDir.root, - variant = "debug", - signed = false - ).sorted() - - val expectedIntegrations = listOf( + """ + .trimIndent() + ) + val output = runner.appendArguments(":app:assembleDebug").build().output + + assertTrue { "collectExternalDebugDependenciesForSentry" !in output } + assertThrows(AssertionError::class.java) { + verifyDependenciesReportAndroid(testProjectDir.root) + } + } + + @Test + fun `all integrations are written to manifest`() { + applyTracingInstrumentation( + features = + setOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, + InstrumentationFeature.OKHTTP, + InstrumentationFeature.COMPOSE, + ), + logcat = true, + appStart = true, + dependencies = + setOf( + "com.squareup.okhttp3:okhttp:3.14.9", + "io.sentry:sentry-android-okhttp:6.6.0", + "androidx.compose.runtime:runtime:1.1.0", + "io.sentry:sentry-compose-android:6.7.0", + ), + ) + + runner.appendArguments(":app:assembleDebug") + runner.build() + + val integrations = + verifyIntegrationList(testProjectDir.root, variant = "debug", signed = false).sorted() + + val expectedIntegrations = + (listOf( InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, InstrumentationFeature.COMPOSE, - InstrumentationFeature.FILE_IO - ).map { it.integrationName }.sorted() - - assertEquals(expectedIntegrations, integrations) - } - - @Test - fun `no integrations are written to manifest if not configured`() { - applyTracingInstrumentation( - dependencies = setOf( - "com.squareup.okhttp3:okhttp:3.14.9", - "androidx.compose.runtime:runtime:1.1.0", - "io.sentry:sentry-compose-android:6.7.0" - ) - ) - - runner.appendArguments(":app:assembleDebug") - - runner.build() - - assertThrows(NoSuchElementException::class.java) { - verifyIntegrationList(testProjectDir.root, variant = "debug", signed = false) - } - } - - @Test - fun `no integrations are written to manifest if instrumentation is disabled`() { - applyTracingInstrumentation( - tracingInstrumentation = false, - features = setOf( - InstrumentationFeature.DATABASE, - InstrumentationFeature.FILE_IO, - InstrumentationFeature.OKHTTP, - InstrumentationFeature.COMPOSE - ), - ) - - runner.appendArguments(":app:assembleRelease") - - runner.build() - - assertThrows(NoSuchElementException::class.java) { - verifyIntegrationList(testProjectDir.root) - } - } - - @Test - fun `does not instrument classes that are provided in excludes`() { - assumeThat( - "We only support the 'excludes' option from AGP 7.4.0 onwards", - SemVer.parse(androidGradlePluginVersion) >= AgpVersions.VERSION_7_4_0, - `is`(true) - ) - applyTracingInstrumentation( - features = setOf(InstrumentationFeature.OKHTTP), - debug = true, - excludes = setOf("okhttp3/RealCall") - ) - appBuildFile.appendText( - // language=Groovy - """ + InstrumentationFeature.OKHTTP, + ) + .map { it.integrationName } + .toMutableList() + listOf("LogcatInstrumentation", "AppStartInstrumentation")) + .sorted() + + assertEquals(expectedIntegrations, integrations) + } + + @Test + fun `only active integrations are written to manifest`() { + applyTracingInstrumentation( + features = + setOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, + InstrumentationFeature.OKHTTP, + InstrumentationFeature.COMPOSE, + ), + dependencies = + setOf("androidx.compose.runtime:runtime:1.1.0", "io.sentry:sentry-compose-android:6.7.0"), + ) + + runner.appendArguments(":app:assembleDebug") + + runner.build() + val integrations = + verifyIntegrationList(testProjectDir.root, variant = "debug", signed = false).sorted() + + val expectedIntegrations = + listOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.COMPOSE, + InstrumentationFeature.FILE_IO, + ) + .map { it.integrationName } + .sorted() + + assertEquals(expectedIntegrations, integrations) + } + + @Test + fun `no integrations are written to manifest if not configured`() { + applyTracingInstrumentation( + dependencies = + setOf( + "com.squareup.okhttp3:okhttp:3.14.9", + "androidx.compose.runtime:runtime:1.1.0", + "io.sentry:sentry-compose-android:6.7.0", + ) + ) + + runner.appendArguments(":app:assembleDebug") + + runner.build() + + assertThrows(NoSuchElementException::class.java) { + verifyIntegrationList(testProjectDir.root, variant = "debug", signed = false) + } + } + + @Test + fun `no integrations are written to manifest if instrumentation is disabled`() { + applyTracingInstrumentation( + tracingInstrumentation = false, + features = + setOf( + InstrumentationFeature.DATABASE, + InstrumentationFeature.FILE_IO, + InstrumentationFeature.OKHTTP, + InstrumentationFeature.COMPOSE, + ), + ) + + runner.appendArguments(":app:assembleRelease") + + runner.build() + + assertThrows(NoSuchElementException::class.java) { verifyIntegrationList(testProjectDir.root) } + } + + @Test + fun `does not instrument classes that are provided in excludes`() { + assumeThat( + "We only support the 'excludes' option from AGP 7.4.0 onwards", + SemVer.parse(androidGradlePluginVersion) >= AgpVersions.VERSION_7_4_0, + `is`(true), + ) + applyTracingInstrumentation( + features = setOf(InstrumentationFeature.OKHTTP), + debug = true, + excludes = setOf("okhttp3/RealCall"), + ) + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.14.9' implementation 'io.sentry:sentry-android-okhttp:6.6.0' } - """.trimIndent() - ) - - runner - .appendArguments(":app:assembleDebug") - .build() - - // since it's an integration test, we just test that the log file wasn't created - // for the class meaning our CommonClassVisitor has NOT instrumented it - val debugOutput = - testProjectDir.root.resolve("app/build/tmp/sentry/RealCall-instrumentation.log") - assertTrue { !debugOutput.exists() } - } - - @Test - fun `does not run minify tasks when isIncludeAndroidResources is enabled`() { - assumeThat( - "On AGP 7.4.0 assets are merged right before final packaging", - SemVer.parse(BuildConfig.AgpVersion) < AgpVersions.VERSION_7_4_0, - `is`(true) - ) - - appBuildFile.writeText( - // language=Groovy """ + .trimIndent() + ) + + runner.appendArguments(":app:assembleDebug").build() + + // since it's an integration test, we just test that the log file wasn't created + // for the class meaning our CommonClassVisitor has NOT instrumented it + val debugOutput = + testProjectDir.root.resolve("app/build/tmp/sentry/RealCall-instrumentation.log") + assertTrue { !debugOutput.exists() } + } + + @Test + fun `does not run minify tasks when isIncludeAndroidResources is enabled`() { + assumeThat( + "On AGP 7.4.0 assets are merged right before final packaging", + SemVer.parse(BuildConfig.AgpVersion) < AgpVersions.VERSION_7_4_0, + `is`(true), + ) + + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -852,89 +797,90 @@ class SentryPluginTest : } telemetry = false } - """.trimIndent() - ) - - val result1 = runner.appendArguments(":app:testReleaseUnitTest").build() - val uuid1 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) - assertFalse { "minifyReleaseWithR8" in result1.output } - - val result2 = runner.build() - val uuid2 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) - assertFalse { "minifyReleaseWithR8" in result2.output } - - 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( - "We only wire up the copyFlutterAssets task " + - "if the transform API (AGP >= 7.4.0) is used", - SemVer.parse(androidGradlePluginVersion) >= AgpVersions.VERSION_7_4_0, - `is`(true) - ) - - // when a flutter project is detected - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + + val result1 = runner.appendArguments(":app:testReleaseUnitTest").build() + val uuid1 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) + assertFalse { "minifyReleaseWithR8" in result1.output } + + val result2 = runner.build() + val uuid2 = verifyProguardUuid(testProjectDir.root, inGeneratedFolder = true) + assertFalse { "minifyReleaseWithR8" in result2.output } + + 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( + "We only wire up the copyFlutterAssets task " + "if the transform API (AGP >= 7.4.0) is used", + SemVer.parse(androidGradlePluginVersion) >= AgpVersions.VERSION_7_4_0, + `is`(true), + ) + + // when a flutter project is detected + appBuildFile.appendText( + // language=Groovy + """ tasks.register("copyFlutterAssetsDebug") { doFirst { println("Hello World") } } - """.trimIndent() - ) - - // then InjectSentryMetaPropertiesIntoAssetsTask should depend on it - // and thus copyFlutterAssetsDebug should be executed as part of assembleDebug - val build = runner.withArguments(":app:assembleDebug").build() - - assertEquals( - TaskOutcome.SUCCESS, - build.task(":app:copyFlutterAssetsDebug")?.outcome, - build.output - ) - } - - private fun applyUploadNativeSymbols() { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + + // then InjectSentryMetaPropertiesIntoAssetsTask should depend on it + // and thus copyFlutterAssetsDebug should be executed as part of assembleDebug + val build = runner.withArguments(":app:assembleDebug").build() + + assertEquals( + TaskOutcome.SUCCESS, + build.task(":app:copyFlutterAssetsDebug")?.outcome, + build.output, + ) + } + + private fun applyUploadNativeSymbols() { + appBuildFile.appendText( + // language=Groovy + """ sentry { autoUploadProguardMapping = false uploadNativeSymbols = true @@ -942,14 +888,15 @@ class SentryPluginTest : enabled = false } } - """.trimIndent() - ) - } - - private fun applyIgnores(ignoredVariant: String) { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + } + + private fun applyIgnores(ignoredVariant: String) { + appBuildFile.appendText( + // language=Groovy + """ sentry { autoUploadProguardMapping = true ignoredVariants = ["$ignoredVariant"] @@ -957,23 +904,24 @@ class SentryPluginTest : enabled = false } } - """.trimIndent() - ) - } - - private fun applyTracingInstrumentation( - tracingInstrumentation: Boolean = true, - features: Set = emptySet(), - logcat: Boolean = false, - appStart: Boolean = false, - dependencies: Set = emptySet(), - debug: Boolean = false, - excludes: Set = emptySet(), - sdkVersion: String = "7.1.0" - ) { - appBuildFile.appendText( - // language=Groovy """ + .trimIndent() + ) + } + + private fun applyTracingInstrumentation( + tracingInstrumentation: Boolean = true, + features: Set = emptySet(), + logcat: Boolean = false, + appStart: Boolean = false, + dependencies: Set = emptySet(), + debug: Boolean = false, + excludes: Set = emptySet(), + sdkVersion: String = "7.1.0", + ) { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'io.sentry:sentry-android:$sdkVersion' ${dependencies.joinToString("\n") { "implementation '$it'" }} @@ -995,7 +943,8 @@ class SentryPluginTest : excludes = ["${excludes.joinToString()}"] } } - """.trimIndent() - ) - } + """ + .trimIndent() + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginVariantTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginVariantTest.kt index afd75b37..866f2a4e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginVariantTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginVariantTest.kt @@ -7,11 +7,11 @@ import org.junit.Assert.assertTrue import org.junit.Test class SentryPluginVariantTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - override val additionalRootProjectConfig: String = - // language=Groovy - """ + override val additionalRootProjectConfig: String = + // language=Groovy + """ flavorDimensions "version" productFlavors { create("demo") { @@ -21,92 +21,79 @@ class SentryPluginVariantTest : applicationIdSuffix = ".full" } } - """.trimIndent() + """ + .trimIndent() - @Test - fun `skips variant if set with ignoredVariants`() { - applyIgnores(ignoredVariants = setOf("fullRelease")) + @Test + fun `skips variant if set with ignoredVariants`() { + applyIgnores(ignoredVariants = setOf("fullRelease")) - val build = runner - .appendArguments(":app:assembleFullRelease", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() - assertFalse(":app:uploadSentryProguardMappingsFullRelease" in build.output) - } + assertFalse(":app:uploadSentryProguardMappingsFullRelease" in build.output) + } - @Test - fun `does not skip variant if not included in ignoredVariants`() { - applyIgnores(ignoredVariants = setOf("demoRelease", "fullDebug", "demoDebug")) + @Test + fun `does not skip variant if not included in ignoredVariants`() { + applyIgnores(ignoredVariants = setOf("demoRelease", "fullDebug", "demoDebug")) - val build = runner - .appendArguments(":app:assembleFullRelease", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() - assertTrue(":app:uploadSentryProguardMappingsFullRelease" in build.output) - } + assertTrue(":app:uploadSentryProguardMappingsFullRelease" in build.output) + } - @Test - fun `skips buildType if set with ignoredBuildTypes`() { - applyIgnores(ignoredBuildTypes = setOf("debug")) + @Test + fun `skips buildType if set with ignoredBuildTypes`() { + applyIgnores(ignoredBuildTypes = setOf("debug")) - val build = runner - .appendArguments(":app:assembleFullDebug", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleFullDebug", "--dry-run").build() - assertFalse(":app:uploadSentryProguardMappingsFullDebug" in build.output) - assertFalse(":app:uploadSentryProguardMappingsDemoDebug" in build.output) - } + assertFalse(":app:uploadSentryProguardMappingsFullDebug" in build.output) + assertFalse(":app:uploadSentryProguardMappingsDemoDebug" in build.output) + } - @Test - fun `does not skip buildType if not included in ignoredBuildTypes`() { - applyIgnores(ignoredBuildTypes = setOf("debug")) + @Test + fun `does not skip buildType if not included in ignoredBuildTypes`() { + applyIgnores(ignoredBuildTypes = setOf("debug")) - val build = runner - .appendArguments(":app:assembleFullRelease", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() - assertTrue(":app:uploadSentryProguardMappingsFullRelease" in build.output) - } + assertTrue(":app:uploadSentryProguardMappingsFullRelease" in build.output) + } - @Test - fun `skips flavor if set with ignoredFlavors`() { - applyIgnores(ignoredFlavors = setOf("full")) + @Test + fun `skips flavor if set with ignoredFlavors`() { + applyIgnores(ignoredFlavors = setOf("full")) - var build = runner - .appendArguments(":app:assembleFullDebug", "--dry-run") - .build() + var build = runner.appendArguments(":app:assembleFullDebug", "--dry-run").build() - assertFalse(":app:uploadSentryProguardMappingsFullDebug" in build.output) + assertFalse(":app:uploadSentryProguardMappingsFullDebug" in build.output) - build = runner - .appendArguments(":app:assembleFullRelease", "--dry-run") - .build() + build = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() - assertFalse(":app:uploadSentryProguardMappingsFullRelease" in build.output) - } + assertFalse(":app:uploadSentryProguardMappingsFullRelease" in build.output) + } - @Test - fun `does not skip flavor if not included in ignoredFlavors`() { - applyIgnores(ignoredFlavors = setOf("full")) + @Test + fun `does not skip flavor if not included in ignoredFlavors`() { + applyIgnores(ignoredFlavors = setOf("full")) - val build = runner - .appendArguments(":app:assembleDemoRelease", "--dry-run") - .build() + val build = runner.appendArguments(":app:assembleDemoRelease", "--dry-run").build() - assertTrue(":app:uploadSentryProguardMappingsDemoRelease" in build.output) - } + assertTrue(":app:uploadSentryProguardMappingsDemoRelease" in build.output) + } - private fun applyIgnores( - ignoredVariants: Set = setOf(), - ignoredBuildTypes: Set = setOf(), - ignoredFlavors: Set = setOf() - ) { - val variants = ignoredVariants.joinToString(",") { "\"$it\"" } - val buildTypes = ignoredBuildTypes.joinToString(",") { "\"$it\"" } - val flavors = ignoredFlavors.joinToString(",") { "\"$it\"" } - appBuildFile.appendText( - // language=Groovy - """ + private fun applyIgnores( + ignoredVariants: Set = setOf(), + ignoredBuildTypes: Set = setOf(), + ignoredFlavors: Set = setOf(), + ) { + val variants = ignoredVariants.joinToString(",") { "\"$it\"" } + val buildTypes = ignoredBuildTypes.joinToString(",") { "\"$it\"" } + val flavors = ignoredFlavors.joinToString(",") { "\"$it\"" } + appBuildFile.appendText( + // language=Groovy + """ sentry { autoUploadProguardMapping = false ignoredVariants = [$variants] @@ -116,7 +103,8 @@ class SentryPluginVariantTest : enabled = false } } - """.trimIndent() - ) - } + """ + .trimIndent() + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsNonAndroidTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsNonAndroidTest.kt index d5d74f0b..8c975a8d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsNonAndroidTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsNonAndroidTest.kt @@ -5,13 +5,13 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginWithDependencyCollectorsNonAndroidTest : - BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { + BaseSentryNonAndroidPluginTest(GradleVersion.current().version) { - @Test - fun `does not break when there are plugins that collect dependencies applied`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `does not break when there are plugins that collect dependencies applied`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "java" id "io.sentry.jvm.gradle" @@ -31,18 +31,18 @@ class SentryPluginWithDependencyCollectorsNonAndroidTest : autoUploadProguardMapping = false autoInstallation.enabled = true } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:collectDependencies") - .build() + val result = runner.appendArguments("app:collectDependencies").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - override val additionalBuildClasspath: String = - """ + override val additionalBuildClasspath: String = + """ classpath 'com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:10.6.1' - """.trimIndent() + """ + .trimIndent() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsTest.kt index e64006a0..57712b5f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithDependencyCollectorsTest.kt @@ -6,13 +6,13 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginWithDependencyCollectorsTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `does not break when there are plugins that collect dependencies applied`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `does not break when there are plugins that collect dependencies applied`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -38,19 +38,19 @@ class SentryPluginWithDependencyCollectorsTest : sentry { autoUploadProguardMapping = false } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleRelease") - .build() + val result = runner.appendArguments("app:assembleRelease").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - override val additionalBuildClasspath: String = - """ + override val additionalBuildClasspath: String = + """ classpath 'com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:10.6.1' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' - """.trimIndent() + """ + .trimIndent() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithFirebaseTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithFirebaseTest.kt index 22a4bad9..21f49ec0 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithFirebaseTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithFirebaseTest.kt @@ -6,13 +6,13 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginWithFirebaseTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `does not break when there is a firebase-perf plugin applied`() { - appBuildFile.writeText( - // language=Groovy - """ + @Test + fun `does not break when there is a firebase-perf plugin applied`() { + appBuildFile.writeText( + // language=Groovy + """ plugins { id "com.android.application" id "io.sentry.android.gradle" @@ -39,18 +39,18 @@ class SentryPluginWithFirebaseTest : sentry { autoUploadProguardMapping = false } - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleRelease") - .build() + val result = runner.appendArguments("app:assembleRelease").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - override val additionalBuildClasspath: String = - """ + override val additionalBuildClasspath: String = + """ classpath 'com.google.firebase:perf-plugin:1.4.2' - """.trimIndent() + """ + .trimIndent() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithMinifiedLibsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithMinifiedLibsTest.kt index 4172ce25..a126862e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithMinifiedLibsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginWithMinifiedLibsTest.kt @@ -6,13 +6,13 @@ import org.gradle.util.GradleVersion import org.junit.Test class SentryPluginWithMinifiedLibsTest : - BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { + BaseSentryPluginTest(BuildConfig.AgpVersion, GradleVersion.current().version) { - @Test - fun `does not break when there is a minified jar dependency`() { - appBuildFile.appendText( - // language=Groovy - """ + @Test + fun `does not break when there is a minified jar dependency`() { + appBuildFile.appendText( + // language=Groovy + """ dependencies { implementation 'io.sentry:sentry-android-core:${BuildConfig.SdkVersion}' implementation 'com.google.android.play:core-ktx:1.8.1' @@ -27,15 +27,14 @@ class SentryPluginWithMinifiedLibsTest : } sentry.tracingInstrumentation.forceInstrumentDependencies = true - """.trimIndent() - ) + """ + .trimIndent() + ) - val result = runner - .appendArguments("app:assembleDebug") - .build() + val result = runner.appendArguments("app:assembleDebug").build() - assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } - } + assertTrue(result.output) { "BUILD SUCCESSFUL" in result.output } + } - override val additionalRootProjectConfig: String = "" + override val additionalRootProjectConfig: String = "" } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt index 327b8429..37e9091d 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/BundleSourcesTaskTest.kt @@ -21,272 +21,245 @@ import org.junit.rules.TemporaryFolder class BundleSourcesTaskTest { - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `cli-executable is set correctly`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertTrue("debug-files" in args) - assertTrue("bundle-jvm" in args) - assertTrue(sourceDir.absolutePath in args) - assertTrue("--output=${outDir.absolutePath}" in args) - - assertFalse("--org" in args) - assertFalse("--project" in args) - assertFalse("--log-level=debug" in args) + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `cli-executable is set correctly`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("sentry-cli" in args) + assertTrue("debug-files" in args) + assertTrue("bundle-jvm" in args) + assertTrue(sourceDir.absolutePath in args) + assertTrue("--output=${outDir.absolutePath}" in args) + + assertFalse("--org" in args) + assertFalse("--project" in args) + assertFalse("--log-level=debug" in args) + } + + @Test + fun `--log-level=debug is set correctly`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.debug.set(true) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--log-level=debug" in args) + } + + @Test + fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { + val project = createProject() + val propertiesFile = project.file("dummy/folder/sentry.properties") + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.sentryProperties.set(propertiesFile) + } + + task.get().setSentryPropertiesEnv() + + assertEquals( + propertiesFile.absolutePath, + task.get().environment["SENTRY_PROPERTIES"].toString(), + ) + } + + @Test + fun `with sentryAuthToken env variable is set correctly`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.sentryAuthToken.set("") + } + + task.get().setSentryAuthTokenEnv() + + assertEquals("", task.get().environment["SENTRY_AUTH_TOKEN"].toString()) + } + + @Test + fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + } + + task.get().setSentryPropertiesEnv() + + assertNull(task.get().environment["SENTRY_PROPERTIES"]) + } + + @Test + fun `with sentryOrganization adds --org`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.sentryOrganization.set("dummy-org") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--org" in args) + assertTrue("dummy-org" in args) + } + + @Test + fun `with sentryProject adds --project`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.sentryProject.set("dummy-proj") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--project" in args) + assertTrue("dummy-proj" in args) + } + + @Test + fun `readBundleIdFromFile works correctly`() { + val expected = "8c776014-bb25-11eb-8529-0242ac130003" + val input = tempDir.newFile().apply { writeText("$SENTRY_BUNDLE_ID_PROPERTY=$expected") } + val actual = BundleSourcesTask.readBundleIdFromFile(input) + assertEquals(expected, actual) + } + + @Test + fun `readBundleIdFromFile works correctly with whitespaces`() { + val expected = "8c776014-bb25-11eb-8529-0242ac130003" + val input = tempDir.newFile().apply { writeText(" $SENTRY_BUNDLE_ID_PROPERTY=$expected\n") } + val actual = BundleSourcesTask.readBundleIdFromFile(input) + assertEquals(expected, actual) + } + + @Test + fun `readBundleIdFromFile fails with missing file`() { + assertThrows(IllegalStateException::class.java) { + BundleSourcesTask.readBundleIdFromFile(File("missing")) } + } - @Test - fun `--log-level=debug is set correctly`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.debug.set(true) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--log-level=debug" in args) + @Test + fun `readBundleIdFromFile fails with empty file`() { + assertThrows(IllegalStateException::class.java) { + BundleSourcesTask.readBundleIdFromFile(tempDir.newFile()) } + } - @Test - fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { - val project = createProject() - val propertiesFile = project.file("dummy/folder/sentry.properties") - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.sentryProperties.set(propertiesFile) - } - - task.get().setSentryPropertiesEnv() - - assertEquals( - propertiesFile.absolutePath, - task.get().environment["SENTRY_PROPERTIES"].toString() - ) + @Test + fun `readBundleIdFromFile fails with missing property`() { + assertThrows(IllegalStateException::class.java) { + val inputFile = tempDir.newFile().apply { writeText("a.property=true") } + BundleSourcesTask.readBundleIdFromFile(inputFile) } - - @Test - fun `with sentryAuthToken env variable is set correctly`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.sentryAuthToken.set("") - } - - task.get().setSentryAuthTokenEnv() - - assertEquals( - "", - task.get().environment["SENTRY_AUTH_TOKEN"].toString() - ) - } - - @Test - fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - } - - task.get().setSentryPropertiesEnv() - - assertNull(task.get().environment["SENTRY_PROPERTIES"]) - } - - @Test - fun `with sentryOrganization adds --org`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.sentryOrganization.set("dummy-org") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--org" in args) - assertTrue("dummy-org" in args) - } - - @Test - fun `with sentryProject adds --project`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.sentryProject.set("dummy-proj") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--project" in args) - assertTrue("dummy-proj" in args) + } + + @Test + fun `with sentryUrl --url is set`() { + val project = createProject() + val debugMetaPropertiesFile = createDebugMetaProperties(project) + + val sourceDir = File(project.buildDir, "dummy/source") + val outDir = File(project.buildDir, "dummy/out") + val task: TaskProvider = + project.tasks.register("testBundleSources", BundleSourcesTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceDir.set(sourceDir) + it.bundleIdFile.set(debugMetaPropertiesFile) + it.output.set(outDir) + it.sentryUrl.set("https://some-host.sentry.io") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--url" in args) + assertTrue("https://some-host.sentry.io" in args) + } + + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } - - @Test - fun `readBundleIdFromFile works correctly`() { - val expected = "8c776014-bb25-11eb-8529-0242ac130003" - val input = tempDir.newFile().apply { writeText("$SENTRY_BUNDLE_ID_PROPERTY=$expected") } - val actual = BundleSourcesTask.readBundleIdFromFile(input) - assertEquals(expected, actual) - } - - @Test - fun `readBundleIdFromFile works correctly with whitespaces`() { - val expected = "8c776014-bb25-11eb-8529-0242ac130003" - val input = tempDir.newFile().apply { writeText(" $SENTRY_BUNDLE_ID_PROPERTY=$expected\n") } - val actual = BundleSourcesTask.readBundleIdFromFile(input) - assertEquals(expected, actual) - } - - @Test - fun `readBundleIdFromFile fails with missing file`() { - assertThrows(IllegalStateException::class.java) { - BundleSourcesTask.readBundleIdFromFile(File("missing")) + } + + private fun createDebugMetaProperties( + project: Project, + uuid: UUID = UUID.randomUUID(), + ): Provider { + val file = + tempDir.newFile("sentry-debug-meta.properties").apply { + Properties().also { props -> + props.setProperty(SENTRY_BUNDLE_ID_PROPERTY, uuid.toString()) + this.writer().use { props.store(it, "") } } - } - - @Test - fun `readBundleIdFromFile fails with empty file`() { - assertThrows(IllegalStateException::class.java) { - BundleSourcesTask.readBundleIdFromFile(tempDir.newFile()) - } - } - - @Test - fun `readBundleIdFromFile fails with missing property`() { - assertThrows(IllegalStateException::class.java) { - val inputFile = tempDir.newFile().apply { writeText("a.property=true") } - BundleSourcesTask.readBundleIdFromFile(inputFile) - } - } - - @Test - fun `with sentryUrl --url is set`() { - val project = createProject() - val debugMetaPropertiesFile = createDebugMetaProperties(project) - - val sourceDir = File(project.buildDir, "dummy/source") - val outDir = File(project.buildDir, "dummy/out") - val task: TaskProvider = - project.tasks.register( - "testBundleSources", - BundleSourcesTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceDir.set(sourceDir) - it.bundleIdFile.set(debugMetaPropertiesFile) - it.output.set(outDir) - it.sentryUrl.set("https://some-host.sentry.io") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--url" in args) - assertTrue("https://some-host.sentry.io" in args) - } - - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } - } - - private fun createDebugMetaProperties( - project: Project, - uuid: UUID = UUID.randomUUID() - ): Provider { - val file = tempDir.newFile("sentry-debug-meta.properties").apply { - Properties().also { props -> - props.setProperty(SENTRY_BUNDLE_ID_PROPERTY, uuid.toString()) - this.writer().use { props.store(it, "") } - } - } - return project.layout.file(project.provider { file }) - } + } + return project.layout.file(project.provider { file }) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/CollectSourcesTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/CollectSourcesTaskTest.kt index 09cb9416..cc757456 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/CollectSourcesTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/CollectSourcesTaskTest.kt @@ -9,39 +9,39 @@ import org.junit.Test class CollectSourcesTaskTest { - @Test - fun `cli-executable is set correctly`() { - val project = createProject() - - project.file("dummy/src/a/TestFile1.java").also { - it.parentFile.mkdirs() - it.writeText("TestFile1") - } - project.file("dummy/src/a/child/TestFile2.java").also { - it.parentFile.mkdirs() - it.writeText("TestFile2") - } - project.file("dummy/src/b/TestFile3.java").also { - it.parentFile.mkdirs() - it.writeText("TestFile3") - } - - val sourceDirs = project.files() - sourceDirs.from("dummy/src/a") - sourceDirs.from("dummy/src/b") - - val outDir = File(project.buildDir, "dummy/out") - - SourceCollector().collectSources(outDir, sourceDirs) - - val outSources = outDir.walk().filter { it.isFile }.toList() - assertEquals(3, outSources.size) + @Test + fun `cli-executable is set correctly`() { + val project = createProject() + + project.file("dummy/src/a/TestFile1.java").also { + it.parentFile.mkdirs() + it.writeText("TestFile1") + } + project.file("dummy/src/a/child/TestFile2.java").also { + it.parentFile.mkdirs() + it.writeText("TestFile2") } + project.file("dummy/src/b/TestFile3.java").also { + it.parentFile.mkdirs() + it.writeText("TestFile3") + } + + val sourceDirs = project.files() + sourceDirs.from("dummy/src/a") + sourceDirs.from("dummy/src/b") + + val outDir = File(project.buildDir, "dummy/out") + + SourceCollector().collectSources(outDir, sourceDirs) + + val outSources = outDir.walk().filter { it.isFile }.toList() + assertEquals(3, outSources.size) + } - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/GenerateBundleIdTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/GenerateBundleIdTaskTest.kt index 0e2ee410..c5c4dde3 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/GenerateBundleIdTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/GenerateBundleIdTaskTest.kt @@ -15,64 +15,64 @@ import org.junit.Test class GenerateBundleIdTaskTest { - @Test - fun `generate bundleId generates ID correctly`() { - val project = createProject() - val task: TaskProvider = - GenerateBundleIdTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - project.objects.property(Boolean::class.java).convention(true), - "test" - ) + @Test + fun `generate bundleId generates ID correctly`() { + val project = createProject() + val task: TaskProvider = + GenerateBundleIdTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + project.objects.property(Boolean::class.java).convention(true), + "test", + ) - task.get().generateProperties() + task.get().generateProperties() - val expectedFile = File(project.buildDir, "dummy/folder/sentry-bundle-id.properties") - assertTrue(expectedFile.exists()) + val expectedFile = File(project.buildDir, "dummy/folder/sentry-bundle-id.properties") + assertTrue(expectedFile.exists()) - val props = PropertiesUtil.load(expectedFile) - val bundleId = props.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - assertNotNull(bundleId) - } + val props = PropertiesUtil.load(expectedFile) + val bundleId = props.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + assertNotNull(bundleId) + } - @Test - fun `generate bundleId overrides the ID on subsequent calls`() { - val project = createProject() - val task: TaskProvider = - GenerateBundleIdTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - project.objects.property(Boolean::class.java).convention(true), - "test" - ) - val expectedFile = File(project.buildDir, "dummy/folder/sentry-bundle-id.properties") + @Test + fun `generate bundleId overrides the ID on subsequent calls`() { + val project = createProject() + val task: TaskProvider = + GenerateBundleIdTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + project.objects.property(Boolean::class.java).convention(true), + "test", + ) + val expectedFile = File(project.buildDir, "dummy/folder/sentry-bundle-id.properties") - task.get().generateProperties() + task.get().generateProperties() - val props1 = PropertiesUtil.load(expectedFile) - val bundleId1 = props1.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + val props1 = PropertiesUtil.load(expectedFile) + val bundleId1 = props1.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - task.get().generateProperties() + task.get().generateProperties() - val props2 = PropertiesUtil.load(expectedFile) - val bundleId2 = props2.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + val props2 = PropertiesUtil.load(expectedFile) + val bundleId2 = props2.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - assertNotNull(bundleId1) - assertNotNull(bundleId2) - assertNotEquals(bundleId1, bundleId2) - } + assertNotNull(bundleId1) + assertNotNull(bundleId2) + assertNotEquals(bundleId1, bundleId2) + } - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt index a542385d..360cb746 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryCliExecTaskTest.kt @@ -15,192 +15,161 @@ import org.junit.rules.TemporaryFolder class SentryCliExecTaskTest { - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `cli-executable is set correctly`() { - val project = createProject() - - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertFalse("--org" in args) - assertFalse("--project" in args) - assertFalse("--log-level=debug" in args) + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `cli-executable is set correctly`() { + val project = createProject() + + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("sentry-cli" in args) + assertFalse("--org" in args) + assertFalse("--project" in args) + assertFalse("--log-level=debug" in args) + } + + @Test + fun `cli-executable is extracted from resources if required`() { + val project = createProject() + + val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.buildDir) + + assertTrue(!cliPath.exists()) + + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set(cliPath.absolutePath) + } + + // when the args are computed (usually during task execution) + val args = task.get().computeCommandLineArgs() + + // then the CLI should be extracted and set + assertTrue(cliPath.exists()) + assertEquals(cliPath.absolutePath, File(args[0]).absolutePath) + } + + @Test + fun `--log-level=debug is set correctly`() { + val project = createProject() + + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.debug.set(true) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--log-level=debug" in args) + } + + @Test + fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { + val project = createProject() + val propertiesFile = project.file("dummy/folder/sentry.properties") + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sentryProperties.set(propertiesFile) + } + + task.get().setSentryPropertiesEnv() + + assertEquals( + propertiesFile.absolutePath, + task.get().environment["SENTRY_PROPERTIES"].toString(), + ) + } + + @Test + fun `with sentryAuthToken env variable is set correctly`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sentryAuthToken.set("") + } + + task.get().setSentryAuthTokenEnv() + + assertEquals("", task.get().environment["SENTRY_AUTH_TOKEN"].toString()) + } + + @Test + fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + } + + task.get().setSentryPropertiesEnv() + + assertNull(task.get().environment["SENTRY_PROPERTIES"]) + } + + @Test + fun `with sentryOrganization adds --org`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sentryOrganization.set("dummy-org") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--org" in args) + assertTrue("dummy-org" in args) + } + + @Test + fun `with sentryProject adds --project`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sentryProject.set("dummy-proj") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--project" in args) + assertTrue("dummy-proj" in args) + } + + @Test + fun `with sentryUrl --url is set`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register("testTask", TestTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sentryUrl.set("https://some-host.sentry.io") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--url" in args) + assertTrue("https://some-host.sentry.io" in args) + } + + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } - @Test - fun `cli-executable is extracted from resources if required`() { - val project = createProject() - - val cliPath = SentryCliProvider.getCliResourcesExtractionPath(project.buildDir) - - assertTrue(!cliPath.exists()) - - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set(cliPath.absolutePath) - } - - // when the args are computed (usually during task execution) - val args = task.get().computeCommandLineArgs() - - // then the CLI should be extracted and set - assertTrue(cliPath.exists()) - assertEquals(cliPath.absolutePath, File(args[0]).absolutePath) - } - - @Test - fun `--log-level=debug is set correctly`() { - val project = createProject() - - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.debug.set(true) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--log-level=debug" in args) - } - - @Test - fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { - val project = createProject() - val propertiesFile = project.file("dummy/folder/sentry.properties") - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sentryProperties.set(propertiesFile) - } - - task.get().setSentryPropertiesEnv() - - assertEquals( - propertiesFile.absolutePath, - task.get().environment["SENTRY_PROPERTIES"].toString() - ) - } - - @Test - fun `with sentryAuthToken env variable is set correctly`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sentryAuthToken.set("") - } - - task.get().setSentryAuthTokenEnv() - - assertEquals( - "", - task.get().environment["SENTRY_AUTH_TOKEN"].toString() - ) - } - - @Test - fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - } - - task.get().setSentryPropertiesEnv() - - assertNull(task.get().environment["SENTRY_PROPERTIES"]) - } - - @Test - fun `with sentryOrganization adds --org`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sentryOrganization.set("dummy-org") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--org" in args) - assertTrue("dummy-org" in args) - } - - @Test - fun `with sentryProject adds --project`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sentryProject.set("dummy-proj") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--project" in args) - assertTrue("dummy-proj" in args) - } - - @Test - fun `with sentryUrl --url is set`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testTask", - TestTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sentryUrl.set("https://some-host.sentry.io") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--url" in args) - assertTrue("https://some-host.sentry.io" in args) - } - - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } - } - - abstract class TestTask : SentryCliExecTask() { - override fun getArguments(args: MutableList) { - // no-op - } + abstract class TestTask : SentryCliExecTask() { + override fun getArguments(args: MutableList) { + // no-op } + } } 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 39c54e68..a9a9ee3a 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 @@ -15,118 +15,116 @@ import org.junit.Test class SentryGenerateDebugMetaPropertiesTaskTest { - @Test - fun `generate debug-meta properties generates proguard mapping UUID and bundle id correctly`() { - val project = createProject() - val sourceDirs = project.files() - sourceDirs.from("dummy/src/a") - val bundleIdTask = GenerateBundleIdTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - project.objects.property(Boolean::class.java).convention(true), - "test" - ) - val proguardIdTask = SentryGenerateProguardUuidTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - null, - "test" - ) - val idGenerationTasks = listOf( - bundleIdTask, - proguardIdTask - ) - val task: TaskProvider = - SentryGenerateDebugMetaPropertiesTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - idGenerationTasks, - project.layout.buildDirectory.dir("dummy/folder/"), - "test" - ) + @Test + fun `generate debug-meta properties generates proguard mapping UUID and bundle id correctly`() { + val project = createProject() + val sourceDirs = project.files() + sourceDirs.from("dummy/src/a") + val bundleIdTask = + GenerateBundleIdTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + project.objects.property(Boolean::class.java).convention(true), + "test", + ) + val proguardIdTask = + SentryGenerateProguardUuidTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + null, + "test", + ) + val idGenerationTasks = listOf(bundleIdTask, proguardIdTask) + val task: TaskProvider = + SentryGenerateDebugMetaPropertiesTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + idGenerationTasks, + project.layout.buildDirectory.dir("dummy/folder/"), + "test", + ) - bundleIdTask.get().generateProperties() - proguardIdTask.get().generateProperties() - task.get().generateProperties() + bundleIdTask.get().generateProperties() + proguardIdTask.get().generateProperties() + task.get().generateProperties() - val expectedFile = File(project.buildDir, "dummy/folder/sentry-debug-meta.properties") - assertTrue(expectedFile.exists()) + val expectedFile = File(project.buildDir, "dummy/folder/sentry-debug-meta.properties") + assertTrue(expectedFile.exists()) - val props = PropertiesUtil.load(expectedFile) - assertNotNull(props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY)) - assertNotNull(props.getProperty(SENTRY_BUNDLE_ID_PROPERTY)) - } + val props = PropertiesUtil.load(expectedFile) + assertNotNull(props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY)) + assertNotNull(props.getProperty(SENTRY_BUNDLE_ID_PROPERTY)) + } - @Test - fun `generate proguard UUID overrides the UUID on subsequent calls`() { - val project = createProject() - val bundleIdTask = GenerateBundleIdTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - project.objects.property(Boolean::class.java).convention(true), - "test" - ) - val proguardIdTask = SentryGenerateProguardUuidTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - project.layout.buildDirectory.dir("dummy/folder/"), - null, - "test" - ) - val idGenerationTasks = listOf( - bundleIdTask, - proguardIdTask - ) - val task: TaskProvider = - SentryGenerateDebugMetaPropertiesTask.register( - project, - project.extensions.findByName("sentry") as SentryPluginExtension, - null, - idGenerationTasks, - project.layout.buildDirectory.dir("dummy/folder/"), - "test" - ) + @Test + fun `generate proguard UUID overrides the UUID on subsequent calls`() { + val project = createProject() + val bundleIdTask = + GenerateBundleIdTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + project.objects.property(Boolean::class.java).convention(true), + "test", + ) + val proguardIdTask = + SentryGenerateProguardUuidTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + project.layout.buildDirectory.dir("dummy/folder/"), + null, + "test", + ) + val idGenerationTasks = listOf(bundleIdTask, proguardIdTask) + val task: TaskProvider = + SentryGenerateDebugMetaPropertiesTask.register( + project, + project.extensions.findByName("sentry") as SentryPluginExtension, + null, + idGenerationTasks, + project.layout.buildDirectory.dir("dummy/folder/"), + "test", + ) - bundleIdTask.get().generateProperties() - proguardIdTask.get().generateProperties() - task.get().generateProperties() + bundleIdTask.get().generateProperties() + proguardIdTask.get().generateProperties() + task.get().generateProperties() - val expectedFile = File(project.buildDir, "dummy/folder/sentry-debug-meta.properties") - assertTrue(expectedFile.exists()) + val expectedFile = File(project.buildDir, "dummy/folder/sentry-debug-meta.properties") + assertTrue(expectedFile.exists()) - val props1 = PropertiesUtil.load(expectedFile) - val proguardId1 = props1.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - assertNotNull(proguardId1) - val bundleId1 = props1.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - assertNotNull(bundleId1) + val props1 = PropertiesUtil.load(expectedFile) + val proguardId1 = props1.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) + assertNotNull(proguardId1) + val bundleId1 = props1.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + assertNotNull(bundleId1) - bundleIdTask.get().generateProperties() - proguardIdTask.get().generateProperties() - task.get().generateProperties() + bundleIdTask.get().generateProperties() + proguardIdTask.get().generateProperties() + task.get().generateProperties() - assertTrue(expectedFile.exists()) + assertTrue(expectedFile.exists()) - val props2 = PropertiesUtil.load(expectedFile) - val proguardId2 = props2.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - assertNotNull(proguardId2) - val bundleId2 = props2.getProperty(SENTRY_BUNDLE_ID_PROPERTY) - assertNotNull(bundleId2) - } + val props2 = PropertiesUtil.load(expectedFile) + val proguardId2 = props2.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) + assertNotNull(proguardId2) + val bundleId2 = props2.getProperty(SENTRY_BUNDLE_ID_PROPERTY) + assertNotNull(bundleId2) + } - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } } 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 22f65698..d3ea4de0 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 @@ -13,58 +13,58 @@ import org.junit.Test class SentryGenerateProguardUuidTaskTest { - @Test - fun `generate proguard UUID generates the UUID correctly`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testGenerateProguardUuid", - SentryGenerateProguardUuidTask::class.java - ) { - it.output.set(project.layout.buildDirectory.dir("dummy/folder/")) - } + @Test + fun `generate proguard UUID generates the UUID correctly`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register( + "testGenerateProguardUuid", + SentryGenerateProguardUuidTask::class.java, + ) { + it.output.set(project.layout.buildDirectory.dir("dummy/folder/")) + } - task.get().generateProperties() + task.get().generateProperties() - val expectedFile = File(project.buildDir, "dummy/folder/sentry-proguard-uuid.properties") - assertTrue(expectedFile.exists()) + val expectedFile = File(project.buildDir, "dummy/folder/sentry-proguard-uuid.properties") + assertTrue(expectedFile.exists()) - val props = PropertiesUtil.load(expectedFile) - val uuid = props.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - assertNotNull(uuid) - } + 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") + @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() + task.get().generateProperties() - val props1 = PropertiesUtil.load(expectedFile) - val uuid1 = props1.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - assertNotNull(uuid1) + val props1 = PropertiesUtil.load(expectedFile) + val uuid1 = props1.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) + assertNotNull(uuid1) - task.get().generateProperties() + task.get().generateProperties() - val props2 = PropertiesUtil.load(expectedFile) - val uuid2 = props2.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) - assertNotNull(uuid2) + val props2 = PropertiesUtil.load(expectedFile) + val uuid2 = props2.getProperty(SENTRY_PROGUARD_MAPPING_UUID_PROPERTY) + assertNotNull(uuid2) - assertNotEquals(uuid1, uuid2) - } + assertNotEquals(uuid1, uuid2) + } - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt index a92c4a5f..920c8a92 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadNativeSymbolsTaskTest.kt @@ -11,178 +11,182 @@ import org.junit.Test class SentryUploadNativeSymbolsTaskTest { - @Test - fun `cli-executable is set correctly`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.includeNativeSources.set(false) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - val sep = File.separator - - assertTrue("sentry-cli" in args) - assertTrue("debug-files" in args) - assertTrue("upload" in args) - val path = "${project.buildDir}${sep}intermediates" + - "${sep}merged_native_libs${sep}debug" - assertTrue(path in args) - assertFalse("--include-sources" in args) - assertFalse("--log-level=debug" in args) + @Test + fun `cli-executable is set correctly`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.includeNativeSources.set(false) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + val sep = File.separator + + assertTrue("sentry-cli" in args) + assertTrue("debug-files" in args) + assertTrue("upload" in args) + val path = "${project.buildDir}${sep}intermediates" + "${sep}merged_native_libs${sep}debug" + assertTrue(path in args) + assertFalse("--include-sources" in args) + assertFalse("--log-level=debug" in args) + } + + @Test + fun `--auto-upload is set correctly`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.includeNativeSources.set(false) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(false) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--no-upload" in args) + } + + @Test + fun `--log-level=debug is set correctly`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.includeNativeSources.set(false) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(false) + it.debug.set(true) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--log-level=debug" in args) + } + + @Test + fun `--include-sources is set correctly`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.includeNativeSources.set(true) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--include-sources" in args) + } + + @Test + fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { + val project = createProject() + val propertiesFile = project.file("dummy/folder/sentry.properties") + val task = createTestTask(project) { it.sentryProperties.set(propertiesFile) } + + task.setSentryPropertiesEnv() + + assertEquals(propertiesFile.absolutePath, task.environment["SENTRY_PROPERTIES"].toString()) + } + + @Test + fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { + val project = createProject() + val task = createTestTask(project) + + task.setSentryPropertiesEnv() + + assertNull(task.environment["SENTRY_PROPERTIES"]) + } + + @Test + fun `with sentryOrganization adds --org`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.sentryOrganization.set("dummy-org") + it.includeNativeSources.set(true) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--org" in args) + assertTrue("dummy-org" in args) + } + + @Test + fun `with sentryProject adds --project`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.sentryProject.set("dummy-proj") + it.includeNativeSources.set(true) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--project" in args) + assertTrue("dummy-proj" in args) + } + + @Test + fun `with sentryUrl adds --url`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.sentryUrl.set("https://some-host.sentry.io") + it.includeNativeSources.set(true) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + + assertTrue("--url" in args) + assertTrue("https://some-host.sentry.io" in args) + } + + @Test + fun `the --url parameter is placed as the first argument`() { + val project = createProject() + val task = + createTestTask(project) { + it.cliExecutable.set("sentry-cli") + it.sentryUrl.set("https://some-host.sentry.io") + it.includeNativeSources.set(true) + it.variantName.set("debug") + it.autoUploadNativeSymbol.set(true) + } + + val args = task.computeCommandLineArgs() + + assertEquals(1, args.indexOf("--url")) + } + + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } - - @Test - fun `--auto-upload is set correctly`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.includeNativeSources.set(false) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(false) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--no-upload" in args) - } - - @Test - fun `--log-level=debug is set correctly`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.includeNativeSources.set(false) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(false) - it.debug.set(true) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--log-level=debug" in args) - } - - @Test - fun `--include-sources is set correctly`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.includeNativeSources.set(true) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--include-sources" in args) - } - - @Test - fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { - val project = createProject() - val propertiesFile = project.file("dummy/folder/sentry.properties") - val task = createTestTask(project) { - it.sentryProperties.set(propertiesFile) - } - - task.setSentryPropertiesEnv() - - assertEquals(propertiesFile.absolutePath, task.environment["SENTRY_PROPERTIES"].toString()) - } - - @Test - fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { - val project = createProject() - val task = createTestTask(project) - - task.setSentryPropertiesEnv() - - assertNull(task.environment["SENTRY_PROPERTIES"]) - } - - @Test - fun `with sentryOrganization adds --org`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.sentryOrganization.set("dummy-org") - it.includeNativeSources.set(true) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--org" in args) - assertTrue("dummy-org" in args) - } - - @Test - fun `with sentryProject adds --project`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.sentryProject.set("dummy-proj") - it.includeNativeSources.set(true) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--project" in args) - assertTrue("dummy-proj" in args) - } - - @Test - fun `with sentryUrl adds --url`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.sentryUrl.set("https://some-host.sentry.io") - it.includeNativeSources.set(true) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - - assertTrue("--url" in args) - assertTrue("https://some-host.sentry.io" in args) - } - - @Test - fun `the --url parameter is placed as the first argument`() { - val project = createProject() - val task = createTestTask(project) { - it.cliExecutable.set("sentry-cli") - it.sentryUrl.set("https://some-host.sentry.io") - it.includeNativeSources.set(true) - it.variantName.set("debug") - it.autoUploadNativeSymbol.set(true) - } - - val args = task.computeCommandLineArgs() - - assertEquals(1, args.indexOf("--url")) - } - - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } - } - - private fun createTestTask( - project: Project, - block: (SentryUploadNativeSymbolsTask) -> Unit = {} - ): SentryUploadNativeSymbolsTask = - project.tasks.register( - "testUploadNativeSymbols", - SentryUploadNativeSymbolsTask::class.java - ) { block(it) }.get() + } + + private fun createTestTask( + project: Project, + block: (SentryUploadNativeSymbolsTask) -> Unit = {}, + ): SentryUploadNativeSymbolsTask = + project.tasks + .register("testUploadNativeSymbols", SentryUploadNativeSymbolsTask::class.java) { block(it) } + .get() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt index 3b230846..b0a88f74 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingTaskTest.kt @@ -20,348 +20,347 @@ import org.junit.rules.TemporaryFolder class SentryUploadProguardMappingTaskTest { - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `cli-executable is set correctly`() { - val randomUuid = UUID.randomUUID() - val project = createProject() - val uuidFileProvider = createFakeUuid(project, randomUuid) - val releaseInfo = ReleaseInfo("com.test", "1.0.0", 1) - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(true) - it.releaseInfo.set(releaseInfo) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertTrue("upload-proguard" in args) - assertTrue("--uuid" in args) - assertTrue(randomUuid.toString() in args) - assertTrue(mappingFile.get().first().toString() in args) - assertTrue("--app-id" in args) - assertTrue(releaseInfo.applicationId in args) - assertTrue("--version" in args) - assertTrue(releaseInfo.versionName in args) - assertTrue("--version-code" in args) - assertTrue(releaseInfo.versionCode.toString() in args) - assertFalse("--no-upload" in args) + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `cli-executable is set correctly`() { + val randomUuid = UUID.randomUUID() + val project = createProject() + val uuidFileProvider = createFakeUuid(project, randomUuid) + val releaseInfo = ReleaseInfo("com.test", "1.0.0", 1) + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(true) + it.releaseInfo.set(releaseInfo) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("sentry-cli" in args) + assertTrue("upload-proguard" in args) + assertTrue("--uuid" in args) + assertTrue(randomUuid.toString() in args) + assertTrue(mappingFile.get().first().toString() in args) + assertTrue("--app-id" in args) + assertTrue(releaseInfo.applicationId in args) + assertTrue("--version" in args) + assertTrue(releaseInfo.versionName in args) + assertTrue("--version-code" in args) + assertTrue(releaseInfo.versionCode.toString() in args) + assertFalse("--no-upload" in args) + } + + @Test + fun `with no version code cli-executable is set correctly`() { + val randomUuid = UUID.randomUUID() + val project = createProject() + val uuidFileProvider = createFakeUuid(project, randomUuid) + val releaseInfo = ReleaseInfo("com.test", "1.0.0") + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(true) + it.releaseInfo.set(releaseInfo) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("sentry-cli" in args) + assertTrue("upload-proguard" in args) + assertTrue("--uuid" in args) + assertTrue(randomUuid.toString() in args) + assertTrue(mappingFile.get().first().toString() in args) + assertTrue("--app-id" in args) + assertTrue(releaseInfo.applicationId in args) + assertTrue("--version" in args) + assertTrue(releaseInfo.versionName in args) + assertFalse("--version-code" in args) + assertFalse("--no-upload" in args) + assertFalse("--log-level=debug" in args) + } + + @Test + fun `with multiple mappingFiles picks the first existing file`() { + val randomUuid = UUID.randomUUID() + val project = createProject() + val uuidFileProvider = createFakeUuid(project, randomUuid) + val releaseInfo = ReleaseInfo("com.test", "1.0.0") + + val mappingFiles = + createMappingFileProvider( + project, + "dummy/folder/missing-mapping.txt", + "dummy/folder/existing-mapping.txt", + ) + val existingFile = + project.file("dummy/folder/existing-mapping.txt").apply { + parentFile.mkdirs() + writeText("dummy-file") + } + + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFiles + it.autoUploadProguardMapping.set(true) + it.releaseInfo.set(releaseInfo) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue(existingFile.toString() in args, "Observed args: $args") + } + + @Test + fun `--auto-upload is set correctly`() { + val project = createProject() + val uuidFileProvider = createFakeUuid(project) + val releaseInfo = ReleaseInfo("com.test", "1.0.0") + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(false) + it.releaseInfo.set(releaseInfo) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--no-upload" in args) + } + + @Test + fun `--log-level=debug is set correctly`() { + val project = createProject() + val uuidFileProvider = createFakeUuid(project) + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(false) + it.debug.set(true) + it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0")) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--log-level=debug" in args) + } + + @Test + fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { + val project = createProject() + val propertiesFile = project.file("dummy/folder/sentry.properties") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.sentryProperties.set(propertiesFile) + } + + task.get().setSentryPropertiesEnv() + + assertEquals( + propertiesFile.absolutePath, + task.get().environment["SENTRY_PROPERTIES"].toString(), + ) + } + + @Test + fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) + + task.get().setSentryPropertiesEnv() + + assertNull(task.get().environment["SENTRY_PROPERTIES"]) + } + + @Test + fun `with sentryAuthToken env variable is set correctly`() { + val project = createProject() + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.sentryAuthToken.set("") + } + + task.get().setSentryAuthTokenEnv() + + assertEquals("", task.get().environment["SENTRY_AUTH_TOKEN"].toString()) + } + + @Test + fun `with sentryUrl sets --url`() { + val project = createProject() + val uuidFileProvider = createFakeUuid(project) + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val releaseInfo = ReleaseInfo("com.test", "1.0.0") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.sentryUrl.set("https://some-host.sentry.io") + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(false) + it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--url" in args) + assertTrue("https://some-host.sentry.io" in args) + } + + @Test + fun `with sentryOrganization adds --org`() { + val project = createProject() + val uuidFileProvider = createFakeUuid(project) + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(false) + it.sentryOrganization.set("dummy-org") + it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--org" in args) + assertTrue("dummy-org" in args) + } + + @Test + fun `with sentryProject adds --project`() { + val project = createProject() + val uuidFileProvider = createFakeUuid(project) + + val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") + val task: TaskProvider = + project.tasks.register( + "testUploadProguardMapping", + SentryUploadProguardMappingsTask::class.java, + ) { + it.cliExecutable.set("sentry-cli") + it.uuidFile.set(uuidFileProvider) + it.mappingsFiles = mappingFile + it.autoUploadProguardMapping.set(false) + it.sentryProject.set("dummy-proj") + it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--project" in args) + assertTrue("dummy-proj" in args) + } + + @Test + fun `readUuidFromFile works correctly`() { + val expected = "8c776014-bb25-11eb-8529-0242ac130003" + val input = tempDir.newFile().apply { writeText("io.sentry.ProguardUuids=$expected") } + val actual = SentryUploadProguardMappingsTask.readUuidFromFile(input) + assertEquals(expected, actual) + } + + @Test + fun `readUuidFromFile works correctly with whitespaces`() { + val expected = "8c776014-bb25-11eb-8529-0242ac130003" + val input = tempDir.newFile().apply { writeText(" io.sentry.ProguardUuids=$expected\n") } + val actual = SentryUploadProguardMappingsTask.readUuidFromFile(input) + assertEquals(expected, actual) + } + + @Test + fun `readUuidFromFile fails with missing file`() { + assertThrows(IllegalStateException::class.java) { + SentryUploadProguardMappingsTask.readUuidFromFile(File("missing")) } + } - @Test - fun `with no version code cli-executable is set correctly`() { - val randomUuid = UUID.randomUUID() - val project = createProject() - val uuidFileProvider = createFakeUuid(project, randomUuid) - val releaseInfo = ReleaseInfo("com.test", "1.0.0") - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(true) - it.releaseInfo.set(releaseInfo) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertTrue("upload-proguard" in args) - assertTrue("--uuid" in args) - assertTrue(randomUuid.toString() in args) - assertTrue(mappingFile.get().first().toString() in args) - assertTrue("--app-id" in args) - assertTrue(releaseInfo.applicationId in args) - assertTrue("--version" in args) - assertTrue(releaseInfo.versionName in args) - assertFalse("--version-code" in args) - assertFalse("--no-upload" in args) - assertFalse("--log-level=debug" in args) + @Test + fun `readUuidFromFile fails with empty file`() { + assertThrows(IllegalStateException::class.java) { + SentryUploadProguardMappingsTask.readUuidFromFile(tempDir.newFile()) } + } - @Test - fun `with multiple mappingFiles picks the first existing file`() { - val randomUuid = UUID.randomUUID() - val project = createProject() - val uuidFileProvider = createFakeUuid(project, randomUuid) - val releaseInfo = ReleaseInfo("com.test", "1.0.0") - - val mappingFiles = createMappingFileProvider( - project, - "dummy/folder/missing-mapping.txt", - "dummy/folder/existing-mapping.txt" - ) - val existingFile = project.file("dummy/folder/existing-mapping.txt").apply { - parentFile.mkdirs() - writeText("dummy-file") - } - - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFiles - it.autoUploadProguardMapping.set(true) - it.releaseInfo.set(releaseInfo) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue(existingFile.toString() in args, "Observed args: $args") + @Test + fun `readUuidFromFile fails with missing property`() { + assertThrows(IllegalStateException::class.java) { + val inputFile = tempDir.newFile().apply { writeText("a.property=true") } + SentryUploadProguardMappingsTask.readUuidFromFile(inputFile) } + } - @Test - fun `--auto-upload is set correctly`() { - val project = createProject() - val uuidFileProvider = createFakeUuid(project) - val releaseInfo = ReleaseInfo("com.test", "1.0.0") - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(false) - it.releaseInfo.set(releaseInfo) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--no-upload" in args) + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } - - @Test - fun `--log-level=debug is set correctly`() { - val project = createProject() - val uuidFileProvider = createFakeUuid(project) - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(false) - it.debug.set(true) - it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0")) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--log-level=debug" in args) - } - - @Test - fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { - val project = createProject() - val propertiesFile = project.file("dummy/folder/sentry.properties") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.sentryProperties.set(propertiesFile) - } - - task.get().setSentryPropertiesEnv() - - assertEquals( - propertiesFile.absolutePath, - task.get().environment["SENTRY_PROPERTIES"].toString() - ) - } - - @Test - fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) - - task.get().setSentryPropertiesEnv() - - assertNull(task.get().environment["SENTRY_PROPERTIES"]) - } - - @Test - fun `with sentryAuthToken env variable is set correctly`() { - val project = createProject() - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.sentryAuthToken.set("") - } - - task.get().setSentryAuthTokenEnv() - - assertEquals( - "", - task.get().environment["SENTRY_AUTH_TOKEN"].toString() - ) - } - - @Test - fun `with sentryUrl sets --url`() { - val project = createProject() - val uuidFileProvider = createFakeUuid(project) - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val releaseInfo = ReleaseInfo("com.test", "1.0.0") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.sentryUrl.set("https://some-host.sentry.io") - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(false) - it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--url" in args) - assertTrue("https://some-host.sentry.io" in args) - } - - @Test - fun `with sentryOrganization adds --org`() { - val project = createProject() - val uuidFileProvider = createFakeUuid(project) - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(false) - it.sentryOrganization.set("dummy-org") - it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--org" in args) - assertTrue("dummy-org" in args) - } - - @Test - fun `with sentryProject adds --project`() { - val project = createProject() - val uuidFileProvider = createFakeUuid(project) - - val mappingFile = createMappingFileProvider(project, "dummy/folder/mapping.txt") - val task: TaskProvider = - project.tasks.register( - "testUploadProguardMapping", - SentryUploadProguardMappingsTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.uuidFile.set(uuidFileProvider) - it.mappingsFiles = mappingFile - it.autoUploadProguardMapping.set(false) - it.sentryProject.set("dummy-proj") - it.releaseInfo.set(ReleaseInfo("com.test", "1.0.0", 1)) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--project" in args) - assertTrue("dummy-proj" in args) - } - - @Test - fun `readUuidFromFile works correctly`() { - val expected = "8c776014-bb25-11eb-8529-0242ac130003" - val input = tempDir.newFile().apply { writeText("io.sentry.ProguardUuids=$expected") } - val actual = SentryUploadProguardMappingsTask.readUuidFromFile(input) - assertEquals(expected, actual) - } - - @Test - fun `readUuidFromFile works correctly with whitespaces`() { - val expected = "8c776014-bb25-11eb-8529-0242ac130003" - val input = tempDir.newFile().apply { writeText(" io.sentry.ProguardUuids=$expected\n") } - val actual = SentryUploadProguardMappingsTask.readUuidFromFile(input) - assertEquals(expected, actual) - } - - @Test - fun `readUuidFromFile fails with missing file`() { - assertThrows(IllegalStateException::class.java) { - SentryUploadProguardMappingsTask.readUuidFromFile(File("missing")) - } - } - - @Test - fun `readUuidFromFile fails with empty file`() { - assertThrows(IllegalStateException::class.java) { - SentryUploadProguardMappingsTask.readUuidFromFile(tempDir.newFile()) - } - } - - @Test - fun `readUuidFromFile fails with missing property`() { - assertThrows(IllegalStateException::class.java) { - val inputFile = tempDir.newFile().apply { writeText("a.property=true") } - SentryUploadProguardMappingsTask.readUuidFromFile(inputFile) - } - } - - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } - } - - private fun createFakeUuid( - project: Project, - uuid: UUID = UUID.randomUUID() - ): Provider { - val file = tempDir.newFile("sentry-debug-meta.properties").apply { - writeText("io.sentry.ProguardUuids=$uuid") - } - return project.layout.file(project.provider { file }) - } - - private fun createMappingFileProvider( - project: Project, - vararg path: String - ): Provider = project.providers.provider { project.files(*path) } + } + + private fun createFakeUuid( + project: Project, + uuid: UUID = UUID.randomUUID(), + ): Provider { + val file = + tempDir.newFile("sentry-debug-meta.properties").apply { + writeText("io.sentry.ProguardUuids=$uuid") + } + return project.layout.file(project.provider { file }) + } + + private fun createMappingFileProvider( + project: Project, + vararg path: String, + ): Provider = project.providers.provider { project.files(*path) } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt index 0883630e..9f7457d8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/UploadSourceBundleTaskTest.kt @@ -15,218 +15,187 @@ import org.junit.rules.TemporaryFolder class UploadSourceBundleTaskTest { - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `cli-executable is set correctly`() { - val project = createProject() - - val sourceBundleDir = File(project.buildDir, "dummy/folder") - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("sentry-cli" in args) - assertTrue("debug-files" in args) - assertTrue("upload" in args) - assertTrue("--type=jvm" in args) - assertTrue(sourceBundleDir.absolutePath in args) - - assertFalse("--no-upload" in args) - assertFalse("--org" in args) - assertFalse("--project" in args) - assertFalse("--log-level=debug" in args) - } - - @Test - fun `--auto-upload is set correctly`() { - val project = createProject() - - val sourceBundleDir = File(project.buildDir, "dummy/folder") - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(false) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--no-upload" in args) - } - - @Test - fun `--log-level=debug is set correctly`() { - val project = createProject() - - val sourceBundleDir = File(project.buildDir, "dummy/folder") - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.debug.set(true) - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--log-level=debug" in args) - } - - @Test - fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { - val project = createProject() - val propertiesFile = project.file("dummy/folder/sentry.properties") - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.sentryProperties.set(propertiesFile) - } - - task.get().setSentryPropertiesEnv() - - assertEquals( - propertiesFile.absolutePath, - task.get().environment["SENTRY_PROPERTIES"].toString() - ) - } - - @Test - fun `with sentryAuthToken env variable is set correctly`() { - val project = createProject() - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.sentryAuthToken.set("") - } - - task.get().setSentryAuthTokenEnv() - - assertEquals( - "", - task.get().environment["SENTRY_AUTH_TOKEN"].toString() - ) - } - - @Test - fun `with sentryUrl --url is set`() { - val project = createProject() - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.sentryUrl.set("https://some-host.sentry.io") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--url" in args) - assertTrue("https://some-host.sentry.io" in args) - } - - @Test - fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { - val project = createProject() - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - } - - task.get().setSentryPropertiesEnv() - - assertNull(task.get().environment["SENTRY_PROPERTIES"]) - } - - @Test - fun `with sentryOrganization adds --org`() { - val project = createProject() - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.sentryOrganization.set("dummy-org") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--org" in args) - assertTrue("dummy-org" in args) - } - - @Test - fun `with sentryProject adds --project`() { - val project = createProject() - val sourceBundleDir = File(project.buildDir, "dummy/folder") - - val task: TaskProvider = - project.tasks.register( - "testUploadSourceBundle", - UploadSourceBundleTask::class.java - ) { - it.cliExecutable.set("sentry-cli") - it.sourceBundleDir.set(sourceBundleDir) - it.autoUploadSourceContext.set(true) - it.sentryProject.set("dummy-proj") - } - - val args = task.get().computeCommandLineArgs() - - assertTrue("--project" in args) - assertTrue("dummy-proj" in args) - } - - private fun createProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("io.sentry.android.gradle") - return this - } + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `cli-executable is set correctly`() { + val project = createProject() + + val sourceBundleDir = File(project.buildDir, "dummy/folder") + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("sentry-cli" in args) + assertTrue("debug-files" in args) + assertTrue("upload" in args) + assertTrue("--type=jvm" in args) + assertTrue(sourceBundleDir.absolutePath in args) + + assertFalse("--no-upload" in args) + assertFalse("--org" in args) + assertFalse("--project" in args) + assertFalse("--log-level=debug" in args) + } + + @Test + fun `--auto-upload is set correctly`() { + val project = createProject() + + val sourceBundleDir = File(project.buildDir, "dummy/folder") + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(false) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--no-upload" in args) + } + + @Test + fun `--log-level=debug is set correctly`() { + val project = createProject() + + val sourceBundleDir = File(project.buildDir, "dummy/folder") + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.debug.set(true) + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--log-level=debug" in args) + } + + @Test + fun `with sentryProperties file SENTRY_PROPERTIES is set correctly`() { + val project = createProject() + val propertiesFile = project.file("dummy/folder/sentry.properties") + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.sentryProperties.set(propertiesFile) + } + + task.get().setSentryPropertiesEnv() + + assertEquals( + propertiesFile.absolutePath, + task.get().environment["SENTRY_PROPERTIES"].toString(), + ) + } + + @Test + fun `with sentryAuthToken env variable is set correctly`() { + val project = createProject() + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.sentryAuthToken.set("") + } + + task.get().setSentryAuthTokenEnv() + + assertEquals("", task.get().environment["SENTRY_AUTH_TOKEN"].toString()) + } + + @Test + fun `with sentryUrl --url is set`() { + val project = createProject() + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.sentryUrl.set("https://some-host.sentry.io") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--url" in args) + assertTrue("https://some-host.sentry.io" in args) + } + + @Test + fun `without sentryProperties file SENTRY_PROPERTIES is not set`() { + val project = createProject() + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + } + + task.get().setSentryPropertiesEnv() + + assertNull(task.get().environment["SENTRY_PROPERTIES"]) + } + + @Test + fun `with sentryOrganization adds --org`() { + val project = createProject() + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.sentryOrganization.set("dummy-org") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--org" in args) + assertTrue("dummy-org" in args) + } + + @Test + fun `with sentryProject adds --project`() { + val project = createProject() + val sourceBundleDir = File(project.buildDir, "dummy/folder") + + val task: TaskProvider = + project.tasks.register("testUploadSourceBundle", UploadSourceBundleTask::class.java) { + it.cliExecutable.set("sentry-cli") + it.sourceBundleDir.set(sourceBundleDir) + it.autoUploadSourceContext.set(true) + it.sentryProject.set("dummy-proj") + } + + val args = task.get().computeCommandLineArgs() + + assertTrue("--project" in args) + assertTrue("dummy-proj" in args) + } + + private fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("io.sentry.android.gradle") + return this } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskTest.kt index ddf60edc..8daeffd8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/dependencies/SentryExternalDependenciesReportTaskTest.kt @@ -12,71 +12,73 @@ import org.junit.rules.TemporaryFolder class SentryExternalDependenciesReportTaskTest { - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `flattens transitive dependencies into a single sorted list`() { - val project = createRegularProject() - val output = tempDir.newFolder("dependencies") - - val task: TaskProvider = project.tasks.register( - "testDependenciesReport", - SentryExternalDependenciesReportTask::class.java - ) { - it.includeReport.set(true) - it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) - it.output.set(project.layout.dir(project.provider { output })) - } - - task.get().action() - - output.verifyContents() - } - - @Test - fun `skips flat jars`() { - val project = createProjectWithFlatJars() - val output = tempDir.newFolder("dependencies") - - val task: TaskProvider = project.tasks.register( - "testDependenciesReport", - SentryExternalDependenciesReportTask::class.java - ) { - it.includeReport.set(true) - it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) - it.output.set(project.layout.dir(project.provider { output })) - } - - task.get().action() - - val outputFile = File(output, SENTRY_DEPENDENCIES_REPORT_OUTPUT) - assertEquals("", outputFile.readText()) - } - - @Test - fun `skips local modules and projects`() { - val project = createMultiModuleProject() - val output = tempDir.newFolder("dependencies") - - val task: TaskProvider = project.tasks.register( - "testDependenciesReport", - SentryExternalDependenciesReportTask::class.java - ) { - it.includeReport.set(true) - it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) - it.output.set(project.layout.dir(project.provider { output })) - } - - task.get().action() - - val outputFile = File(output, SENTRY_DEPENDENCIES_REPORT_OUTPUT) - assertEquals("", outputFile.readText()) - } - - private fun File.verifyContents() { - assertEquals( - """ + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `flattens transitive dependencies into a single sorted list`() { + val project = createRegularProject() + val output = tempDir.newFolder("dependencies") + + val task: TaskProvider = + project.tasks.register( + "testDependenciesReport", + SentryExternalDependenciesReportTask::class.java, + ) { + it.includeReport.set(true) + it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) + it.output.set(project.layout.dir(project.provider { output })) + } + + task.get().action() + + output.verifyContents() + } + + @Test + fun `skips flat jars`() { + val project = createProjectWithFlatJars() + val output = tempDir.newFolder("dependencies") + + val task: TaskProvider = + project.tasks.register( + "testDependenciesReport", + SentryExternalDependenciesReportTask::class.java, + ) { + it.includeReport.set(true) + it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) + it.output.set(project.layout.dir(project.provider { output })) + } + + task.get().action() + + val outputFile = File(output, SENTRY_DEPENDENCIES_REPORT_OUTPUT) + assertEquals("", outputFile.readText()) + } + + @Test + fun `skips local modules and projects`() { + val project = createMultiModuleProject() + val output = tempDir.newFolder("dependencies") + + val task: TaskProvider = + project.tasks.register( + "testDependenciesReport", + SentryExternalDependenciesReportTask::class.java, + ) { + it.includeReport.set(true) + it.setRuntimeConfiguration(project.configurations.getByName("runtimeClasspath")) + it.output.set(project.layout.dir(project.provider { output })) + } + + task.get().action() + + val outputFile = File(output, SENTRY_DEPENDENCIES_REPORT_OUTPUT) + assertEquals("", outputFile.readText()) + } + + private fun File.verifyContents() { + assertEquals( + """ androidx.annotation:annotation:1.1.0 androidx.arch.core:core-common:2.1.0 androidx.collection:collection:1.0.0 @@ -88,64 +90,64 @@ class SentryExternalDependenciesReportTaskTest { androidx.versionedparcelable:versionedparcelable:1.1.0 io.sentry:sentry-android-core:6.5.0 io.sentry:sentry:6.5.0 - """.trimIndent(), - File(this, SENTRY_DEPENDENCIES_REPORT_OUTPUT).readText() - ) - } - - private fun createRegularProject(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("java") - plugins.apply("io.sentry.android.gradle") - - repositories.mavenCentral() - repositories.google() - - dependencies.add("implementation", "androidx.activity:activity:1.2.0") - dependencies.add("implementation", "io.sentry:sentry-android-core:6.5.0") - return this - } + """ + .trimIndent(), + File(this, SENTRY_DEPENDENCIES_REPORT_OUTPUT).readText(), + ) + } + + private fun createRegularProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("java") + plugins.apply("io.sentry.android.gradle") + + repositories.mavenCentral() + repositories.google() + + dependencies.add("implementation", "androidx.activity:activity:1.2.0") + dependencies.add("implementation", "io.sentry:sentry-android-core:6.5.0") + return this } + } - private fun createProjectWithFlatJars(): Project { - with(ProjectBuilder.builder().build()) { - plugins.apply("java") - plugins.apply("io.sentry.android.gradle") + private fun createProjectWithFlatJars(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("java") + plugins.apply("io.sentry.android.gradle") - mkdir("libs") - file("libs/local.jar").apply { createNewFile() } + mkdir("libs") + file("libs/local.jar").apply { createNewFile() } - repositories.flatDir { it.dir("libs") } + repositories.flatDir { it.dir("libs") } - dependencies.add("implementation", ":local") - return this - } + dependencies.add("implementation", ":local") + return this } - - private fun createMultiModuleProject(): Project { - with(ProjectBuilder.builder().build()) { - mkdir("module") - val module = ProjectBuilder.builder() - .withName("module") - .withProjectDir(file("module")) - .withParent(this) - .build() - - mkdir("app") - val app = ProjectBuilder.builder() - .withName("app") - .withProjectDir(file("app")) - .withParent(this) - .build() - app.plugins.apply("java") - app.plugins.apply("io.sentry.android.gradle") - - app.dependencies.add( - "implementation", - app.dependencies.project(mapOf("path" to ":module")) - ) - - return app - } + } + + private fun createMultiModuleProject(): Project { + with(ProjectBuilder.builder().build()) { + mkdir("module") + val module = + ProjectBuilder.builder() + .withName("module") + .withProjectDir(file("module")) + .withParent(this) + .build() + + mkdir("app") + val app = + ProjectBuilder.builder() + .withName("app") + .withProjectDir(file("app")) + .withParent(this) + .build() + app.plugins.apply("java") + app.plugins.apply("io.sentry.android.gradle") + + app.dependencies.add("implementation", app.dependencies.project(mapOf("path" to ":module"))) + + return app } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt index 0bf39467..67528a18 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/telemetry/SentryTelemetryServiceTest.kt @@ -9,27 +9,26 @@ import org.junit.rules.TemporaryFolder class SentryTelemetryServiceTest { - @get:Rule - val testProjectDir = TemporaryFolder() + @get:Rule val testProjectDir = TemporaryFolder() - @Suppress("UnstableApiUsage") - @Test - fun `SentryCliInfoValueSource returns empty string when no auth token is present`() { - val project = ProjectBuilder - .builder() - .withProjectDir(testProjectDir.root) - .build() + @Suppress("UnstableApiUsage") + @Test + fun `SentryCliInfoValueSource returns empty string when no auth token is present`() { + val project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() - val cliPath = SentryCliProvider - .getCliResourcesExtractionPath(project.layout.buildDirectory.asFile.get()) + val cliPath = + SentryCliProvider.getCliResourcesExtractionPath(project.layout.buildDirectory.asFile.get()) - val infoOutput = project.providers.of(SentryCliInfoValueSource::class.java) { cliVS -> - cliVS.parameters.buildDirectory.set(project.buildDir) - cliVS.parameters.cliExecutable.set(cliPath.absolutePath) - // sets an empty/invalid auth token - cliVS.parameters.authToken.set("") - }.get() + val infoOutput = + project.providers + .of(SentryCliInfoValueSource::class.java) { cliVS -> + cliVS.parameters.buildDirectory.set(project.buildDir) + cliVS.parameters.cliExecutable.set(cliPath.absolutePath) + // sets an empty/invalid auth token + cliVS.parameters.authToken.set("") + } + .get() - assertEquals("", infoOutput) - } + assertEquals("", infoOutput) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/testutil/ProjectTestUtil.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/testutil/ProjectTestUtil.kt index 61befbaf..ab028835 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/testutil/ProjectTestUtil.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/testutil/ProjectTestUtil.kt @@ -6,51 +6,45 @@ import org.gradle.testfixtures.ProjectBuilder import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension fun createTestAndroidProject( - parent: Project? = null, - forceEvaluate: Boolean = true, - block: AppExtension.() -> Unit = {} + parent: Project? = null, + forceEvaluate: Boolean = true, + block: AppExtension.() -> Unit = {}, ): Pair { - val project = ProjectBuilder - .builder() - .apply { parent?.let { withParent(parent) } } - .build() - project.plugins.apply("com.android.application") - val appExtension = project.extensions.getByType(AppExtension::class.java).apply { - compileSdkVersion(30) - this.block() + val project = ProjectBuilder.builder().apply { parent?.let { withParent(parent) } }.build() + project.plugins.apply("com.android.application") + val appExtension = + project.extensions.getByType(AppExtension::class.java).apply { + compileSdkVersion(30) + this.block() } - if (forceEvaluate) { - project.forceEvaluate() - } - return project to appExtension + if (forceEvaluate) { + project.forceEvaluate() + } + return project to appExtension } fun createTestProguardProject( - parent: Project? = null, - forceEvaluate: Boolean = true, - block: AppExtension.() -> Unit = {} + parent: Project? = null, + forceEvaluate: Boolean = true, + block: AppExtension.() -> Unit = {}, ): Pair { - val project = ProjectBuilder - .builder() - .apply { parent?.let { withParent(parent) } } - .build() - project.plugins.apply("com.android.application") - val appExtension = project.extensions.getByType(AppExtension::class.java).apply { - compileSdkVersion(30) - this.block() - } - project.plugins.apply("com.guardsquare.proguard") - project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { - configurations.create("release") { - it.defaultConfiguration("proguard-android-optimize.txt") - } - } - if (forceEvaluate) { - project.forceEvaluate() + val project = ProjectBuilder.builder().apply { parent?.let { withParent(parent) } }.build() + project.plugins.apply("com.android.application") + val appExtension = + project.extensions.getByType(AppExtension::class.java).apply { + compileSdkVersion(30) + this.block() } - return project to appExtension + project.plugins.apply("com.guardsquare.proguard") + project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { + configurations.create("release") { it.defaultConfiguration("proguard-android-optimize.txt") } + } + if (forceEvaluate) { + project.forceEvaluate() + } + return project to appExtension } fun Project.forceEvaluate() { - getTasksByName("assembleDebug", false) + getTasksByName("assembleDebug", false) } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransformTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransformTest.kt index f9b2f058..13bb61fc 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransformTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/MetaInfStripTransformTest.kt @@ -24,223 +24,233 @@ import org.junit.rules.TemporaryFolder class MetaInfStripTransformTest { - class Fixture { - val provider = mock>() - - fun getSut( - tmpDir: TemporaryFolder, - jarName: String = "test.jar", - multiRelease: Boolean = true, - includeSupportedVersion: Boolean = false, - signed: Boolean = false - ): MetaInfStripTransform { - val file = tmpDir.newFile(jarName) - val jar = file.toJar( - multiRelease = multiRelease, - includeSupportedVersion = includeSupportedVersion, - signed = signed - ) + class Fixture { + val provider = mock>() + + fun getSut( + tmpDir: TemporaryFolder, + jarName: String = "test.jar", + multiRelease: Boolean = true, + includeSupportedVersion: Boolean = false, + signed: Boolean = false, + ): MetaInfStripTransform { + val file = tmpDir.newFile(jarName) + val jar = + file.toJar( + multiRelease = multiRelease, + includeSupportedVersion = includeSupportedVersion, + signed = signed, + ) - whenever(provider.get()).thenReturn(RegularFile { jar }) + whenever(provider.get()).thenReturn(RegularFile { jar }) - return TestMetaInfStripTransform(provider) - } + return TestMetaInfStripTransform(provider) + } - private fun File.toJar( - multiRelease: Boolean, - includeSupportedVersion: Boolean, - signed: Boolean - ): File { - JarOutputStream(outputStream()).use { - // normal classpath - it.putNextEntry(ZipEntry("com/squareup/moshi/Types.class")) - it.write( - """ + private fun File.toJar( + multiRelease: Boolean, + includeSupportedVersion: Boolean, + signed: Boolean, + ): File { + JarOutputStream(outputStream()).use { + // normal classpath + it.putNextEntry(ZipEntry("com/squareup/moshi/Types.class")) + it.write( + """ public final class Types { } - """.trimIndent().toByteArray() - ) - - if (multiRelease) { - // META-INF/versions/16 - it.putNextEntry( - ZipEntry("META-INF/versions/16/com/squareup/moshi/RecordJsonAdapter.class") - ) - it.write( - """ + """ + .trimIndent() + .toByteArray() + ) + + if (multiRelease) { + // META-INF/versions/16 + it.putNextEntry( + ZipEntry("META-INF/versions/16/com/squareup/moshi/RecordJsonAdapter.class") + ) + it.write( + """ import com.squareup.moshi.RecordJsonAdapter.1; final class RecordJsonAdapter extends JsonAdapter { //... } - """.trimIndent().toByteArray() - ) - it.closeEntry() - - it.putNextEntry( - ZipEntry( - "META-INF/versions/16/com/squareup/moshi/RecordJsonAdapter$1.class" - ) - ) - it.write( """ + .trimIndent() + .toByteArray() + ) + it.closeEntry() + + it.putNextEntry( + ZipEntry("META-INF/versions/16/com/squareup/moshi/RecordJsonAdapter$1.class") + ) + it.write( + """ import com.squareup.moshi.JsonAdapter.Factory; final class RecordJsonAdapter$1 implements JsonAdapter.Factory { //... } - """.trimIndent().toByteArray() - ) - it.closeEntry() - - // META-INF/versions/9 - if (includeSupportedVersion) { - it.putNextEntry(ZipEntry("META-INF/versions/9/module-info.class")) - it.write( - """ + """ + .trimIndent() + .toByteArray() + ) + it.closeEntry() + + // META-INF/versions/9 + if (includeSupportedVersion) { + it.putNextEntry(ZipEntry("META-INF/versions/9/module-info.class")) + it.write( + """ class module-info { } - """.trimIndent().toByteArray() - ) - it.closeEntry() - } + """ + .trimIndent() + .toByteArray() + ) + it.closeEntry() + } - if (signed) { - it.putNextEntry(ZipEntry("META-INF/SIGNING_FILE.SF")) - it.putNextEntry(ZipEntry("META-INF/SIGNING_FILE.DSA")) - } - } - - // manifest - it.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME)) - val manifest = Manifest().apply { - mainAttributes[Attributes.Name.MULTI_RELEASE] = multiRelease.toString() - mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" - } - manifest.write(it) - it.closeEntry() - } - return this + if (signed) { + it.putNextEntry(ZipEntry("META-INF/SIGNING_FILE.SF")) + it.putNextEntry(ZipEntry("META-INF/SIGNING_FILE.DSA")) + } } - private class TestMetaInfStripTransform( - override val inputArtifact: Provider - ) : MetaInfStripTransform() { - override fun getParameters(): Parameters = object : Parameters { - override val invalidate: Property - get() = DefaultProperty(PropertyHost.NO_OP, Long::class.java).convention(0L) - } + // manifest + it.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME)) + val manifest = + Manifest().apply { + mainAttributes[Attributes.Name.MULTI_RELEASE] = multiRelease.toString() + mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" + } + manifest.write(it) + it.closeEntry() + } + return this + } + + private class TestMetaInfStripTransform( + override val inputArtifact: Provider + ) : MetaInfStripTransform() { + override fun getParameters(): Parameters = + object : Parameters { + override val invalidate: Property + get() = DefaultProperty(PropertyHost.NO_OP, Long::class.java).convention(0L) } } + } - @get:Rule - val tmp = TemporaryFolder() + @get:Rule val tmp = TemporaryFolder() - private val fixture = Fixture() + private val fixture = Fixture() - @Test - fun `when not multi-release jar, keeps input unchanged`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when not multi-release jar, keeps input unchanged`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp, multiRelease = false) - sut.transform(outputs) + val sut = fixture.getSut(tmp, multiRelease = false) + sut.transform(outputs) - assertTrue { outputs.outputFile.name == "test.jar" } - } + assertTrue { outputs.outputFile.name == "test.jar" } + } - @Test - fun `when multi-release jar, renames input with meta-inf-stripped suffix`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when multi-release jar, renames input with meta-inf-stripped suffix`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp) - sut.transform(outputs) + val sut = fixture.getSut(tmp) + sut.transform(outputs) - assertTrue { outputs.outputFile.name == "test-meta-inf-stripped.jar" } - } + assertTrue { outputs.outputFile.name == "test-meta-inf-stripped.jar" } + } - @Test - fun `when multi-release jar, strips out unsupported java classes`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when multi-release jar, strips out unsupported java classes`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp) + val sut = fixture.getSut(tmp) - sut.transform(outputs) + sut.transform(outputs) - val jar = JarFile(outputs.outputFile) - assertFalse { jar.read().any { it.key.startsWith("META-INF/versions/16") } } - } + val jar = JarFile(outputs.outputFile) + assertFalse { jar.read().any { it.key.startsWith("META-INF/versions/16") } } + } - @Test - fun `when multi-release jar contains only unsupported classes, changes to single-release`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when multi-release jar contains only unsupported classes, changes to single-release`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp) - sut.transform(outputs) + val sut = fixture.getSut(tmp) + sut.transform(outputs) - val jar = JarFile(outputs.outputFile) - assertFalse { jar.isMultiRelease } - } + val jar = JarFile(outputs.outputFile) + assertFalse { jar.isMultiRelease } + } - @Test - fun `when multi-release jar with supported classes, keeps them and multi-release flag`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when multi-release jar with supported classes, keeps them and multi-release flag`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp, includeSupportedVersion = true) - sut.transform(outputs) + val sut = fixture.getSut(tmp, includeSupportedVersion = true) + sut.transform(outputs) - val jar = JarFile(outputs.outputFile) - assertEquals( - jar.read()["META-INF/versions/9/module-info.class"], - """ + val jar = JarFile(outputs.outputFile) + assertEquals( + jar.read()["META-INF/versions/9/module-info.class"], + """ class module-info { } - """.trimIndent() - ) - assertTrue { jar.isMultiRelease } - } - - @Test - fun `when multi-release jar, keeps unrelated entries unchanged`() { - val outputs = FakeTransformOutputs(tmp) - - val sut = fixture.getSut(tmp) - sut.transform(outputs) - - val jar = JarFile(outputs.outputFile) - assertEquals( - jar.read()["com/squareup/moshi/Types.class"], """ + .trimIndent(), + ) + assertTrue { jar.isMultiRelease } + } + + @Test + fun `when multi-release jar, keeps unrelated entries unchanged`() { + val outputs = FakeTransformOutputs(tmp) + + val sut = fixture.getSut(tmp) + sut.transform(outputs) + + val jar = JarFile(outputs.outputFile) + assertEquals( + jar.read()["com/squareup/moshi/Types.class"], + """ public final class Types { } - """.trimIndent() - ) - assertTrue { jar.manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] == "1.0" } - } - - private fun JarFile.read(): Map { - val entries = mutableMapOf() // entry.name to entry.content - val iter = entries() - while (iter.hasMoreElements()) { - val jarEntry = iter.nextElement() - entries[jarEntry.name] = getInputStream(jarEntry).reader().readText() - } - return entries + """ + .trimIndent(), + ) + assertTrue { jar.manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] == "1.0" } + } + + private fun JarFile.read(): Map { + val entries = mutableMapOf() // entry.name to entry.content + val iter = entries() + while (iter.hasMoreElements()) { + val jarEntry = iter.nextElement() + entries[jarEntry.name] = getInputStream(jarEntry).reader().readText() } + return entries + } - @Test - fun `when multi-release signed-jar, skip transform`() { - val outputs = FakeTransformOutputs(tmp) + @Test + fun `when multi-release signed-jar, skip transform`() { + val outputs = FakeTransformOutputs(tmp) - val sut = fixture.getSut(tmp, includeSupportedVersion = true, signed = true) - sut.transform(outputs) + val sut = fixture.getSut(tmp, includeSupportedVersion = true, signed = true) + sut.transform(outputs) - val jar = JarFile(outputs.outputFile) + val jar = JarFile(outputs.outputFile) - assertTrue { jar.read().any { it.key.startsWith("META-INF/versions/16") } } - assertTrue { outputs.outputFile.name == "test.jar" } - } + assertTrue { jar.read().any { it.key.startsWith("META-INF/versions/16") } } + assertTrue { outputs.outputFile.name == "test.jar" } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/fakes/FakeTransformOutputs.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/fakes/FakeTransformOutputs.kt index 3e8f5771..6b56fafd 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/fakes/FakeTransformOutputs.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/transforms/fakes/FakeTransformOutputs.kt @@ -7,32 +7,35 @@ import org.gradle.api.provider.Provider import org.junit.rules.TemporaryFolder class FakeTransformOutputs(temporaryFolder: TemporaryFolder) : TransformOutputs { - val rootDir: File = temporaryFolder.newFolder() + val rootDir: File = temporaryFolder.newFolder() - lateinit var outputDirectory: File - private set - lateinit var outputFile: File - private set + lateinit var outputDirectory: File + private set - override fun file(path: Any): File { - outputFile = if (path is Provider<*>) { - (path.get() as FileSystemLocation).asFile - } else if (path is File && path.isAbsolute) { - path - } else { - path as String - rootDir.resolve(path) - } - return outputFile - } + lateinit var outputFile: File + private set - override fun dir(path: Any): File { - outputDirectory = if (path is File && path.isAbsolute) { - path - } else { - path as String - rootDir.resolve(path).also { it.mkdirs() } - } - return outputDirectory - } + override fun file(path: Any): File { + outputFile = + if (path is Provider<*>) { + (path.get() as FileSystemLocation).asFile + } else if (path is File && path.isAbsolute) { + path + } else { + path as String + rootDir.resolve(path) + } + return outputFile + } + + override fun dir(path: Any): File { + outputDirectory = + if (path is File && path.isAbsolute) { + path + } else { + path as String + rootDir.resolve(path).also { it.mkdirs() } + } + return outputDirectory + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/AgpVersionsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/AgpVersionsTest.kt index ec64acb5..86531bfc 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/AgpVersionsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/AgpVersionsTest.kt @@ -5,11 +5,11 @@ import org.junit.Test class AgpVersionsTest { - @Test - fun `agp versions`() { - assertTrue { SemVer.parse("7.1.0") < AgpVersions.VERSION_7_1_2 } - assertTrue { SemVer.parse("7.2.0-alpha07") > AgpVersions.VERSION_7_1_2 } - assertTrue { SemVer.parse("7.2.0") > AgpVersions.VERSION_7_1_2 } - assertTrue { SemVer.parse("7.4.0-rc01") >= AgpVersions.VERSION_7_4_0 } - } + @Test + fun `agp versions`() { + assertTrue { SemVer.parse("7.1.0") < AgpVersions.VERSION_7_1_2 } + assertTrue { SemVer.parse("7.2.0-alpha07") > AgpVersions.VERSION_7_1_2 } + assertTrue { SemVer.parse("7.2.0") > AgpVersions.VERSION_7_1_2 } + assertTrue { SemVer.parse("7.4.0-rc01") >= AgpVersions.VERSION_7_4_0 } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/CliFailureReasonTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/CliFailureReasonTest.kt index f1223afa..90d405e1 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/CliFailureReasonTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/CliFailureReasonTest.kt @@ -7,31 +7,29 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) class CliFailureReasonTest( - private val stdErr: String, - private val expectedReason: CliFailureReason + private val stdErr: String, + private val expectedReason: CliFailureReason, ) { - @Test - fun `parses reason from stderr`() { - val message = "somethingsomething\n$stdErr" + @Test + fun `parses reason from stderr`() { + val message = "somethingsomething\n$stdErr" - val reason = CliFailureReason.fromErrOut(message) + val reason = CliFailureReason.fromErrOut(message) - assertEquals(expectedReason, reason) - } + assertEquals(expectedReason, reason) + } - companion object { - @Parameterized.Parameters(name = "{1}") - @JvmStatic - fun parameters() = listOf( - arrayOf("error: resource not found", CliFailureReason.OUTDATED), - arrayOf("error: An organization slug is required", CliFailureReason.ORG_SLUG), - arrayOf("error: A project slug is required", CliFailureReason.PROJECT_SLUG), - arrayOf( - "error: Failed to parse org auth token", - CliFailureReason.INVALID_ORG_AUTH_TOKEN - ), - arrayOf("error: we don't do that here", CliFailureReason.UNKNOWN) - ) - } + companion object { + @Parameterized.Parameters(name = "{1}") + @JvmStatic + fun parameters() = + listOf( + arrayOf("error: resource not found", CliFailureReason.OUTDATED), + arrayOf("error: An organization slug is required", CliFailureReason.ORG_SLUG), + arrayOf("error: A project slug is required", CliFailureReason.PROJECT_SLUG), + arrayOf("error: Failed to parse org auth token", CliFailureReason.INVALID_ORG_AUTH_TOKEN), + arrayOf("error: we don't do that here", CliFailureReason.UNKNOWN), + ) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/PrintBuildOutputOnFailureRule.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/PrintBuildOutputOnFailureRule.kt index 9099d760..5b53c9e8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/PrintBuildOutputOnFailureRule.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/PrintBuildOutputOnFailureRule.kt @@ -5,20 +5,18 @@ import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -/** - * A rule that prints integration test build logs on any failure from the provided [out] stream. - */ +/** A rule that prints integration test build logs on any failure from the provided [out] stream. */ class PrintBuildOutputOnFailureRule(private val out: ByteArrayOutputStream) : TestRule { - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { - override fun evaluate() { - try { - base.evaluate() - } catch (e: Throwable) { - print(out.toString()) - throw e - } - } + override fun apply(base: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + try { + base.evaluate() + } catch (e: Throwable) { + print(out.toString()) + throw e } + } } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt index 96a1dc89..c2f728d8 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt @@ -26,249 +26,226 @@ import org.junit.rules.TemporaryFolder class SentryModulesCollectorTest { - class Fixture { - - private val configuration = mock() - val resolvableDependencies = mock() - private val resolutionResult = mock() - - val configurationName: String = "debugRuntimeClasspath" - val variantName: String = "debug" - - lateinit var sentryModulesServiceProvider: Provider - val logger = CapturingTestLogger() - - fun getSut( - tmpDir: File, - dependencies: Set = emptySet() - ): Project { - whenever(configuration.name).thenReturn(configurationName) - whenever(configuration.incoming).thenReturn(resolvableDependencies) - whenever(resolvableDependencies.resolutionResult).thenReturn(resolutionResult) - whenever(resolutionResult.allComponents).thenReturn(dependencies) - doAnswer { - // trigger the callback registered in tests - (it.arguments[0] as Action).execute(resolvableDependencies) - } - .whenever(resolvableDependencies) - .afterResolve(any>()) - - val fakeProject = ProjectBuilder - .builder() - .withProjectDir(tmpDir) - .build() - - val featureProvider = fakeProject.provider { emptySet() } - val logcatEnabled = fakeProject.provider { true } - val sourceContextEnabled = fakeProject.provider { true } - val dexguardEnabled = fakeProject.provider { true } - val appStartEnabled = fakeProject.provider { true } - - val project = spy(fakeProject) - whenever(project.logger).thenReturn(logger) - project.configurations.add(configuration) - sentryModulesServiceProvider = SentryModulesService.register( - project, - featureProvider, - logcatEnabled, - sourceContextEnabled, - dexguardEnabled, - appStartEnabled - ) - - return project - } - - fun getSentryModules() = sentryModulesServiceProvider.get().sentryModules + class Fixture { + + private val configuration = mock() + val resolvableDependencies = mock() + private val resolutionResult = mock() + + val configurationName: String = "debugRuntimeClasspath" + val variantName: String = "debug" + + lateinit var sentryModulesServiceProvider: Provider + val logger = CapturingTestLogger() + + fun getSut(tmpDir: File, dependencies: Set = emptySet()): Project { + whenever(configuration.name).thenReturn(configurationName) + whenever(configuration.incoming).thenReturn(resolvableDependencies) + whenever(resolvableDependencies.resolutionResult).thenReturn(resolutionResult) + whenever(resolutionResult.allComponents).thenReturn(dependencies) + doAnswer { + // trigger the callback registered in tests + (it.arguments[0] as Action).execute(resolvableDependencies) + } + .whenever(resolvableDependencies) + .afterResolve(any>()) + + val fakeProject = ProjectBuilder.builder().withProjectDir(tmpDir).build() + + val featureProvider = fakeProject.provider { emptySet() } + val logcatEnabled = fakeProject.provider { true } + val sourceContextEnabled = fakeProject.provider { true } + val dexguardEnabled = fakeProject.provider { true } + val appStartEnabled = fakeProject.provider { true } + + val project = spy(fakeProject) + whenever(project.logger).thenReturn(logger) + project.configurations.add(configuration) + sentryModulesServiceProvider = + SentryModulesService.register( + project, + featureProvider, + logcatEnabled, + sourceContextEnabled, + dexguardEnabled, + appStartEnabled, + ) - fun getExternalModules() = sentryModulesServiceProvider.get().externalModules + return project } - @get:Rule - val testProjectDir = TemporaryFolder() + fun getSentryModules() = sentryModulesServiceProvider.get().sentryModules - private val fixture = Fixture() + fun getExternalModules() = sentryModulesServiceProvider.get().externalModules + } - @Test - fun `configuration cannot be found - logs a warning and the modules list is empty`() { - val project = fixture.getSut(testProjectDir.root) - project.collectModules( - "releaseRuntimeClasspath", - "release", - fixture.sentryModulesServiceProvider - ) - assertTrue { fixture.getSentryModules().isEmpty() } - assertTrue { - fixture.logger.capturedMessage == - "[sentry] Unable to find configuration releaseRuntimeClasspath for variant release." - } - } + @get:Rule val testProjectDir = TemporaryFolder() - @Test - fun `sentry-android is not in the dependencies - modules list is empty`() { - val sqliteDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId("androidx.sqlite", "sqlite", "2.1.0") - ) - } - - val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sqliteDep)) - project.collectModules( - fixture.configurationName, - fixture.variantName, - fixture.sentryModulesServiceProvider - ) + private val fixture = Fixture() - assertTrue { fixture.getSentryModules().isEmpty() } + @Test + fun `configuration cannot be found - logs a warning and the modules list is empty`() { + val project = fixture.getSut(testProjectDir.root) + project.collectModules( + "releaseRuntimeClasspath", + "release", + fixture.sentryModulesServiceProvider, + ) + assertTrue { fixture.getSentryModules().isEmpty() } + assertTrue { + fixture.logger.capturedMessage == + "[sentry] Unable to find configuration releaseRuntimeClasspath for variant release." } - - @Test - fun `sentry-android as a local dependency - module's version is omitted`() { - val sentryAndroidDep = mock { - whenever(mock.moduleVersion).thenReturn( - // this is the case when sentry-android is a local dependency - DefaultModuleVersionIdentifier.newId( - "io.sentry", - "sentry-android-core", - "unspecified" - ) - ) - } - - val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sentryAndroidDep)) - project.collectModules( - fixture.configurationName, - fixture.variantName, - fixture.sentryModulesServiceProvider - ) - - val moduleIdentifier = DefaultModuleIdentifier - .newId("io.sentry", "sentry-android-core") - assertTrue { - fixture.getSentryModules().size == 1 && - fixture.getSentryModules()[moduleIdentifier] == SemVer() - } - assertTrue { - fixture.logger.capturedMessage == - "[sentry] Detected Sentry modules {io.sentry:sentry-android-core=0.0.0} " + - "for variant: debug, config: debugRuntimeClasspath" - } + } + + @Test + fun `sentry-android is not in the dependencies - modules list is empty`() { + val sqliteDep = + mock { + whenever(mock.moduleVersion) + .thenReturn(DefaultModuleVersionIdentifier.newId("androidx.sqlite", "sqlite", "2.1.0")) + } + + val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sqliteDep)) + project.collectModules( + fixture.configurationName, + fixture.variantName, + fixture.sentryModulesServiceProvider, + ) + + assertTrue { fixture.getSentryModules().isEmpty() } + } + + @Test + fun `sentry-android as a local dependency - module's version is omitted`() { + val sentryAndroidDep = + mock { + whenever(mock.moduleVersion) + .thenReturn( + // this is the case when sentry-android is a local dependency + DefaultModuleVersionIdentifier.newId("io.sentry", "sentry-android-core", "unspecified") + ) + } + + val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sentryAndroidDep)) + project.collectModules( + fixture.configurationName, + fixture.variantName, + fixture.sentryModulesServiceProvider, + ) + + val moduleIdentifier = DefaultModuleIdentifier.newId("io.sentry", "sentry-android-core") + assertTrue { + fixture.getSentryModules().size == 1 && + fixture.getSentryModules()[moduleIdentifier] == SemVer() } - - @Test - fun `sentry-android performance version - logs a info and persists module in build service`() { - val sentryAndroidDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId( - "io.sentry", - "sentry-android-core", - "4.1.0" - ) - ) - } - - val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sentryAndroidDep)) - project.collectModules( - fixture.configurationName, - fixture.variantName, - fixture.sentryModulesServiceProvider - ) - - val moduleIdentifier = DefaultModuleIdentifier - .newId("io.sentry", "sentry-android-core") - assertTrue { - fixture.getSentryModules().size == 1 && - fixture.getSentryModules()[moduleIdentifier] == SemVer(4, 1, 0) - } - assertTrue { - fixture.logger.capturedMessage == - "[sentry] Detected Sentry modules {io.sentry:sentry-android-core=4.1.0} " + - "for variant: debug, config: debugRuntimeClasspath" - } + assertTrue { + fixture.logger.capturedMessage == + "[sentry] Detected Sentry modules {io.sentry:sentry-android-core=0.0.0} " + + "for variant: debug, config: debugRuntimeClasspath" } - - @Test - fun `non sentry dependencies are provided via the modules provider`() { - val group = "androidx.sqlite" - val name = "sqlite-framework" - val moduleIdentifier = DefaultModuleIdentifier.newId(group, name) - val version = "2.3.0" - - val sqliteDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId(group, name, version) - ) - } - - val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sqliteDep)) - project.collectModules( - fixture.configurationName, - fixture.variantName, - fixture.sentryModulesServiceProvider - ) - - assertTrue { - fixture.getExternalModules()[moduleIdentifier]!! == SemVer.parse(version) - } + } + + @Test + fun `sentry-android performance version - logs a info and persists module in build service`() { + val sentryAndroidDep = + mock { + whenever(mock.moduleVersion) + .thenReturn( + DefaultModuleVersionIdentifier.newId("io.sentry", "sentry-android-core", "4.1.0") + ) + } + + val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sentryAndroidDep)) + project.collectModules( + fixture.configurationName, + fixture.variantName, + fixture.sentryModulesServiceProvider, + ) + + val moduleIdentifier = DefaultModuleIdentifier.newId("io.sentry", "sentry-android-core") + assertTrue { + fixture.getSentryModules().size == 1 && + fixture.getSentryModules()[moduleIdentifier] == SemVer(4, 1, 0) } - - @Test - fun `sentry-android transitive - logs a info and persists both modules in build service`() { - val firstLevelDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId( - "io.sentry", - "sentry-android", - "5.5.0" - ) - ) - } - val transitiveSentryDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId( - "io.sentry", - "sentry-android-core", - "5.5.0" - ) - ) - } - val okHttpDep = mock { - whenever(mock.moduleVersion).thenReturn( - DefaultModuleVersionIdentifier.newId( - "io.sentry", - "sentry-android-okhttp", - "6.0.0" - ) - ) - } - - val project = fixture.getSut( - testProjectDir.root, - dependencies = setOf(firstLevelDep, transitiveSentryDep, okHttpDep) - ) - project.collectModules( - fixture.configurationName, - fixture.variantName, - fixture.sentryModulesServiceProvider - ) - - val sentryAndroidModule = DefaultModuleIdentifier - .newId("io.sentry", "sentry-android") - val sentryAndroidCoreModule = DefaultModuleIdentifier - .newId("io.sentry", "sentry-android-core") - val sentryAndroidOkHttpModule = DefaultModuleIdentifier - .newId("io.sentry", "sentry-android-okhttp") - assertTrue { - fixture.getSentryModules().size == 3 && - fixture.getSentryModules()[sentryAndroidModule] == SemVer(5, 5, 0) && - fixture.getSentryModules()[sentryAndroidCoreModule] == SemVer(5, 5, 0) && - fixture.getSentryModules()[sentryAndroidOkHttpModule] == SemVer(6, 0, 0) - } - assertTrue { - fixture.logger.capturedMessage == - "[sentry] Detected Sentry modules {io.sentry:sentry-android=5.5.0, " + - "io.sentry:sentry-android-core=5.5.0, io.sentry:sentry-android-okhttp=6.0.0} " + - "for variant: debug, config: debugRuntimeClasspath" - } + assertTrue { + fixture.logger.capturedMessage == + "[sentry] Detected Sentry modules {io.sentry:sentry-android-core=4.1.0} " + + "for variant: debug, config: debugRuntimeClasspath" + } + } + + @Test + fun `non sentry dependencies are provided via the modules provider`() { + val group = "androidx.sqlite" + val name = "sqlite-framework" + val moduleIdentifier = DefaultModuleIdentifier.newId(group, name) + val version = "2.3.0" + + val sqliteDep = + mock { + whenever(mock.moduleVersion) + .thenReturn(DefaultModuleVersionIdentifier.newId(group, name, version)) + } + + val project = fixture.getSut(testProjectDir.root, dependencies = setOf(sqliteDep)) + project.collectModules( + fixture.configurationName, + fixture.variantName, + fixture.sentryModulesServiceProvider, + ) + + assertTrue { fixture.getExternalModules()[moduleIdentifier]!! == SemVer.parse(version) } + } + + @Test + fun `sentry-android transitive - logs a info and persists both modules in build service`() { + val firstLevelDep = + mock { + whenever(mock.moduleVersion) + .thenReturn(DefaultModuleVersionIdentifier.newId("io.sentry", "sentry-android", "5.5.0")) + } + val transitiveSentryDep = + mock { + whenever(mock.moduleVersion) + .thenReturn( + DefaultModuleVersionIdentifier.newId("io.sentry", "sentry-android-core", "5.5.0") + ) + } + val okHttpDep = + mock { + whenever(mock.moduleVersion) + .thenReturn( + DefaultModuleVersionIdentifier.newId("io.sentry", "sentry-android-okhttp", "6.0.0") + ) + } + + val project = + fixture.getSut( + testProjectDir.root, + dependencies = setOf(firstLevelDep, transitiveSentryDep, okHttpDep), + ) + project.collectModules( + fixture.configurationName, + fixture.variantName, + fixture.sentryModulesServiceProvider, + ) + + val sentryAndroidModule = DefaultModuleIdentifier.newId("io.sentry", "sentry-android") + val sentryAndroidCoreModule = DefaultModuleIdentifier.newId("io.sentry", "sentry-android-core") + val sentryAndroidOkHttpModule = + DefaultModuleIdentifier.newId("io.sentry", "sentry-android-okhttp") + assertTrue { + fixture.getSentryModules().size == 3 && + fixture.getSentryModules()[sentryAndroidModule] == SemVer(5, 5, 0) && + fixture.getSentryModules()[sentryAndroidCoreModule] == SemVer(5, 5, 0) && + fixture.getSentryModules()[sentryAndroidOkHttpModule] == SemVer(6, 0, 0) + } + assertTrue { + fixture.logger.capturedMessage == + "[sentry] Detected Sentry modules {io.sentry:sentry-android=5.5.0, " + + "io.sentry:sentry-android-core=5.5.0, io.sentry:sentry-android-okhttp=6.0.0} " + + "for variant: debug, config: debugRuntimeClasspath" } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryPluginUtilsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryPluginUtilsTest.kt index 96c1ef4f..be8b6956 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryPluginUtilsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryPluginUtilsTest.kt @@ -17,101 +17,77 @@ import org.junit.runners.Parameterized import proguard.gradle.plugin.android.dsl.ProGuardAndroidExtension @RunWith(Parameterized::class) -class SentryPluginUtilsTest( - private val agpVersion: SemVer -) { - - @get:Rule - val tempDir = TemporaryFolder() - - @Test - fun `capitalizes string first letter uppercase`() { - assertEquals("Kotlin", "kotlin".capitalizeUS()) +class SentryPluginUtilsTest(private val agpVersion: SemVer) { + + @get:Rule val tempDir = TemporaryFolder() + + @Test + fun `capitalizes string first letter uppercase`() { + assertEquals("Kotlin", "kotlin".capitalizeUS()) + } + + @Test + fun `capitalizes string does nothing on already capitalized string`() { + assertEquals("Kotlin", "Kotlin".capitalizeUS()) + } + + @Test + fun `capitalizes string returns empty on empty string`() { + assertEquals("", "".capitalizeUS()) + } + + @Test + fun `isMinificationEnabled returns false for standalone Proguard`() { + val (project, _) = createTestProguardProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + val variant = project.retrieveAndroidVariant(agpVersion, "debug") + + assertEquals(false, isMinificationEnabled(project, variant, dexguardEnabled = true)) + } + + @Test + fun `isMinificationEnabled returns true for standalone Proguard and valid config`() { + val (project, _) = createTestProguardProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { + configurations.create("debug") { it.defaultConfiguration("proguard-android-optimize.txt") } } + val variant = project.retrieveAndroidVariant(agpVersion, "debug") - @Test - fun `capitalizes string does nothing on already capitalized string`() { - assertEquals("Kotlin", "Kotlin".capitalizeUS()) - } + assertEquals(true, isMinificationEnabled(project, variant, dexguardEnabled = true)) + } - @Test - fun `capitalizes string returns empty on empty string`() { - assertEquals("", "".capitalizeUS()) + @Test + fun `isMinificationEnabled returns false for standalone Proguard without opt-in`() { + val (project, _) = createTestProguardProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { + configurations.create("debug") { it.defaultConfiguration("proguard-android-optimize.txt") } } + val variant = project.retrieveAndroidVariant(agpVersion, "debug") - @Test - fun `isMinificationEnabled returns false for standalone Proguard`() { - val (project, _) = createTestProguardProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - val variant = project.retrieveAndroidVariant(agpVersion, "debug") - - assertEquals( - false, - isMinificationEnabled(project, variant, dexguardEnabled = true) - ) - } + assertEquals(false, isMinificationEnabled(project, variant, dexguardEnabled = false)) + } - @Test - fun `isMinificationEnabled returns true for standalone Proguard and valid config`() { - val (project, _) = createTestProguardProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { - configurations.create("debug") { - it.defaultConfiguration("proguard-android-optimize.txt") - } - } - val variant = project.retrieveAndroidVariant(agpVersion, "debug") - - assertEquals( - true, - isMinificationEnabled(project, variant, dexguardEnabled = true) - ) - } + @Test + fun `isMinificationEnabled returns false for debug builds`() { + val (project, _) = createTestAndroidProject(forceEvaluate = !AgpVersions.isAGP74(agpVersion)) + val variant = project.retrieveAndroidVariant(agpVersion, "debug") - @Test - fun `isMinificationEnabled returns false for standalone Proguard without opt-in`() { - val (project, _) = createTestProguardProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - project.extensions.getByType(ProGuardAndroidExtension::class.java).apply { - configurations.create("debug") { - it.defaultConfiguration("proguard-android-optimize.txt") - } - } - val variant = project.retrieveAndroidVariant(agpVersion, "debug") - - assertEquals( - false, - isMinificationEnabled(project, variant, dexguardEnabled = false) - ) - } + assertEquals(false, isMinificationEnabled(project, variant)) + } - @Test - fun `isMinificationEnabled returns false for debug builds`() { - val (project, _) = createTestAndroidProject( - forceEvaluate = !AgpVersions.isAGP74(agpVersion) - ) - val variant = project.retrieveAndroidVariant(agpVersion, "debug") + @Test + fun `getAndDelete deletes the file`() { + val (project, _) = createTestAndroidProject() + val file = tempDir.newFile("temp-file.txt") - assertEquals(false, isMinificationEnabled(project, variant)) - } - - @Test - fun `getAndDelete deletes the file`() { - val (project, _) = createTestAndroidProject() - val file = tempDir.newFile("temp-file.txt") + assertTrue { file.exists() } - assertTrue { file.exists() } + getAndDeleteFile(project.layout.file(project.provider { file })) + assertFalse { file.exists() } + } - getAndDeleteFile(project.layout.file(project.provider { file })) - assertFalse { file.exists() } - } - - companion object { - @Parameterized.Parameters(name = "AGP {0}") - @JvmStatic - fun parameters() = listOf(AgpVersions.VERSION_7_0_0, AgpVersions.VERSION_7_4_0) - } + companion object { + @Parameterized.Parameters(name = "AGP {0}") + @JvmStatic + fun parameters() = listOf(AgpVersions.VERSION_7_0_0, AgpVersions.VERSION_7_4_0) + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SystemPropertyRule.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SystemPropertyRule.kt index 587ca5ac..6cf4ce33 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SystemPropertyRule.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SystemPropertyRule.kt @@ -10,30 +10,24 @@ import org.junit.runners.model.Statement */ class SystemPropertyRule : TestRule { - private val retain = mutableMapOf() + private val retain = mutableMapOf() - override fun apply(statement: Statement, description: Description): Statement { - return object : Statement() { - override fun evaluate() { - val annotation = description - .annotations - .filterIsInstance() - .firstOrNull() + override fun apply(statement: Statement, description: Description): Statement { + return object : Statement() { + override fun evaluate() { + val annotation = + description.annotations.filterIsInstance().firstOrNull() - annotation?.keys?.forEachIndexed { index, key -> - System.getProperty(key).let { oldProperty -> - retain[key] = oldProperty - } - System.setProperty(key, annotation.values[index]) - } - try { - statement.evaluate() - } finally { - retain.forEach { (key, value) -> - System.setProperty(key, value) - } - } - } + annotation?.keys?.forEachIndexed { index, key -> + System.getProperty(key).let { oldProperty -> retain[key] = oldProperty } + System.setProperty(key, annotation.values[index]) } + try { + statement.evaluate() + } finally { + retain.forEach { (key, value) -> System.setProperty(key, value) } + } + } } + } } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/WithSystemProperty.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/WithSystemProperty.kt index fe46d7c6..edf5d616 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/WithSystemProperty.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/WithSystemProperty.kt @@ -1,11 +1,8 @@ package io.sentry.android.gradle.util /** - * Annotation to specify arrays of key-values to override - * [System.getProperties] with [SystemPropertyRule] + * Annotation to specify arrays of key-values to override [System.getProperties] with + * [SystemPropertyRule] */ @Retention(AnnotationRetention.RUNTIME) -annotation class WithSystemProperty( - val keys: Array, - val values: Array -) +annotation class WithSystemProperty(val keys: Array, val values: Array) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/roomsample/data/Entities.kt b/plugin-build/src/test/kotlin/io/sentry/android/roomsample/data/Entities.kt index d281b29e..d32087f5 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/roomsample/data/Entities.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/roomsample/data/Entities.kt @@ -1,33 +1,23 @@ package io.sentry.android.roomsample.data -// these need to be on the classpath, so the ASM verifier can resolve superclasses and control flows properly +// these need to be on the classpath, so the ASM verifier can resolve superclasses and control flows +// properly data class Track( - val id: Long = 0, - val name: String, - val albumId: Long?, - val composer: String?, - val mediaTypeId: Long?, - val genreId: Long?, - val millis: Long, - val bytes: Long?, - val price: Float + val id: Long = 0, + val name: String, + val albumId: Long?, + val composer: String?, + val mediaTypeId: Long?, + val genreId: Long?, + val millis: Long, + val bytes: Long?, + val price: Float, ) -open class Album( - val id: Long, - val title: String, - val artistId: Long -) +open class Album(val id: Long, val title: String, val artistId: Long) -data class MultiPKeyEntity( - val id: Long, - val name: String -) +data class MultiPKeyEntity(val id: Long, val name: String) -class SubAlbum( - id: Long, - title: String, - artistId: Long -) : Album(id, title, artistId) +class SubAlbum(id: Long, title: String, artistId: Long) : Album(id, title, artistId) data class NameWithComposer(val id: Int, val nameWithComposer: String) diff --git a/plugin-build/src/test/kotlin/okhttp3/Interceptor.kt b/plugin-build/src/test/kotlin/okhttp3/Interceptor.kt index bd703cd7..1491ac6b 100644 --- a/plugin-build/src/test/kotlin/okhttp3/Interceptor.kt +++ b/plugin-build/src/test/kotlin/okhttp3/Interceptor.kt @@ -1,5 +1,5 @@ package okhttp3 interface Interceptor { - interface Chain + interface Chain } diff --git a/sentry-kotlin-compiler-plugin/build.gradle.kts b/sentry-kotlin-compiler-plugin/build.gradle.kts index 081e5def..40f96ada 100644 --- a/sentry-kotlin-compiler-plugin/build.gradle.kts +++ b/sentry-kotlin-compiler-plugin/build.gradle.kts @@ -1,79 +1,78 @@ plugins { - kotlin("jvm") version "1.9.24" - kotlin("kapt") version "1.9.24" - id("distribution") - id("com.vanniktech.maven.publish") version "0.17.0" - id("com.diffplug.spotless") version "7.0.0.BETA1" + kotlin("jvm") version "1.9.24" + kotlin("kapt") version "1.9.24" + id("distribution") + id("com.vanniktech.maven.publish") version "0.17.0" + id("com.diffplug.spotless") version "7.0.0.BETA1" } allprojects { - repositories { - google() - mavenCentral() - } + repositories { + google() + mavenCentral() + } } spotless { - kotlin { - ktfmt("0.51").googleStyle() - targetExclude("**/generated/**") - } + kotlin { + ktfmt("0.51").googleStyle() + targetExclude("**/generated/**") + } + kotlinGradle { + ktfmt("0.51").googleStyle() + targetExclude("**/generated/**") + } } val sep = File.separator + distributions { - main { - contents { - from("build${sep}libs") - from("build${sep}publications${sep}maven") - } + main { + contents { + from("build${sep}libs") + from("build${sep}publications${sep}maven") } + } } -val publish = extensions.getByType( - com.vanniktech.maven.publish.MavenPublishPluginExtension::class.java -) +val publish = + extensions.getByType(com.vanniktech.maven.publish.MavenPublishPluginExtension::class.java) + // signing is done when uploading files to MC // via gpg:sign-and-deploy-file (release.kts) publish.releaseSigningEnabled = false tasks.named("distZip") { - dependsOn("publishToMavenLocal") - onlyIf { - inputs.sourceFiles.isEmpty.not().also { - require(it) { "No distribution to zip." } - } - } + dependsOn("publishToMavenLocal") + onlyIf { inputs.sourceFiles.isEmpty.not().also { require(it) { "No distribution to zip." } } } } repositories { - google() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } dependencies { - compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") + compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable") - kapt("com.google.auto.service:auto-service:1.0.1") - compileOnly("com.google.auto.service:auto-service-annotations:1.0.1") + kapt("com.google.auto.service:auto-service:1.0.1") + compileOnly("com.google.auto.service:auto-service-annotations:1.0.1") - testImplementation(kotlin("test-junit")) - testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable") - testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.6.0") - testImplementation("org.jetbrains.compose.desktop:desktop:1.6.10") + testImplementation(kotlin("test-junit")) + testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable") + testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.6.0") + testImplementation("org.jetbrains.compose.desktop:desktop:1.6.10") } -kapt { - correctErrorTypes = true -} +kapt { correctErrorTypes = true } plugins.withId("com.vanniktech.maven.publish.base") { - configure { - repositories { - maven { - name = "mavenTestRepo" - url = file("${rootProject.projectDir}/../build/mavenTestRepo").toURI() - } - } + configure { + repositories { + maven { + name = "mavenTestRepo" + url = file("${rootProject.projectDir}/../build/mavenTestRepo").toURI() + } } + } } diff --git a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPlugin.kt b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPlugin.kt index 0f81d3a5..9bb51c8c 100644 --- a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPlugin.kt +++ b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPlugin.kt @@ -13,16 +13,14 @@ import org.jetbrains.kotlin.config.CompilerConfiguration @AutoService(CompilerPluginRegistrar::class) class SentryKotlinCompilerPlugin : CompilerPluginRegistrar() { - override val supportsK2: Boolean - get() = true + override val supportsK2: Boolean + get() = true - override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { - val messageCollector = configuration.get( - CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, - MessageCollector.NONE - ) - IrGenerationExtension.registerExtension( - extension = JetpackComposeTracingIrExtension(messageCollector) - ) - } + override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) { + val messageCollector = + configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) + IrGenerationExtension.registerExtension( + extension = JetpackComposeTracingIrExtension(messageCollector) + ) + } } diff --git a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPluginCommandLineProcessor.kt b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPluginCommandLineProcessor.kt index 0cfb0c04..3b7bddae 100644 --- a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPluginCommandLineProcessor.kt +++ b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/SentryKotlinCompilerPluginCommandLineProcessor.kt @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi @AutoService(CommandLineProcessor::class) class SentryKotlinCompilerPluginCommandLineProcessor : CommandLineProcessor { - override val pluginId: String = "io.sentry.kotlin.compiler" + override val pluginId: String = "io.sentry.kotlin.compiler" - override val pluginOptions: Collection = emptyList() + override val pluginOptions: Collection = emptyList() } diff --git a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/compose/JetpackComposeTracingIrExtension.kt b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/compose/JetpackComposeTracingIrExtension.kt index 9d646c75..804ac496 100644 --- a/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/compose/JetpackComposeTracingIrExtension.kt +++ b/sentry-kotlin-compiler-plugin/src/main/kotlin/io/sentry/compose/JetpackComposeTracingIrExtension.kt @@ -28,238 +28,224 @@ import org.jetbrains.kotlin.name.SpecialNames // required only for Kotlin 2.0.0 // @UnsafeDuringIrConstructionAPI -class JetpackComposeTracingIrExtension( - private val messageCollector: MessageCollector -) : IrGenerationExtension { - - override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - val composableAnnotation = FqName("androidx.compose.runtime.Composable") - val kotlinNothing = FqName("kotlin.Nothing") - - val modifierClassFqName = FqName("androidx.compose.ui.Modifier") - - val modifierClassId = FqName("androidx.compose.ui").classId("Modifier") - val modifierClassSymbol = pluginContext.referenceClass(modifierClassId) - if (modifierClassSymbol == null) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "No class definition of androidx.compose.ui.Modifier found, " + - "Sentry Kotlin Compiler plugin won't run. " + - "Please ensure you're applying the plugin to a compose-enabled project." - ) - return - } - - val modifierType = modifierClassSymbol.owner.defaultType - val modifierCompanionClass = - pluginContext.referenceClass(modifierClassId)?.owner?.companionObject() - val modifierCompanionClassRef = modifierCompanionClass?.symbol - - if (modifierCompanionClass == null || modifierCompanionClassRef == null) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "No type definition of androidx.compose.ui.Modifier found, " + - "Sentry Kotlin Compiler plugin won't run. " + - "Please ensure you're applying to plugin to a compose-enabled project." - ) - return - } - - val modifierThenRefs = pluginContext - .referenceFunctions(modifierClassId.callableId("then")) - if (modifierThenRefs.isEmpty()) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "No definition of androidx.compose.ui.Modifier.then() found, " + - "Sentry Kotlin Compiler plugin won't run. " + - "Please ensure you're applying to plugin to a compose-enabled project." - ) - return - } else if (modifierThenRefs.size != 1) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "Multiple definitions androidx.compose.ui.Modifier.then() found, " + - "which is not supported by Sentry Kotlin Compiler plugin won't run. " + - "Please file an issue under " + - "https://github.com/getsentry/sentry-android-gradle-plugin" - ) - return - } - val modifierThen = modifierThenRefs.single() - - val sentryModifierTagFunction = FqName("io.sentry.compose") - .classId("SentryModifier") - .callableId("sentryTag") - - val sentryModifierTagFunctionRefs = pluginContext - .referenceFunctions(sentryModifierTagFunction) - - if (sentryModifierTagFunctionRefs.isEmpty()) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "io.sentry.compose.Modifier.sentryTag() not found, " + - "Sentry Kotlin Compiler plugin won't run. " + - "Please ensure you're using " + - "'io.sentry:sentry-compose-android' as a dependency." - ) - return - } else if (sentryModifierTagFunctionRefs.size != 1) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "Multiple definitions io.sentry.compose.Modifier.sentryTag() found, " + - "Sentry Kotlin Compiler plugin won't run. " + - "Please ensure your versions of 'io.sentry:sentry-compose-android' " + - "and the sentry Android Gradle plugin match." - ) - return - } - val sentryModifierTagFunctionRef = sentryModifierTagFunctionRefs.single() +class JetpackComposeTracingIrExtension(private val messageCollector: MessageCollector) : + IrGenerationExtension { + + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + val composableAnnotation = FqName("androidx.compose.runtime.Composable") + val kotlinNothing = FqName("kotlin.Nothing") + + val modifierClassFqName = FqName("androidx.compose.ui.Modifier") + + val modifierClassId = FqName("androidx.compose.ui").classId("Modifier") + val modifierClassSymbol = pluginContext.referenceClass(modifierClassId) + if (modifierClassSymbol == null) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "No class definition of androidx.compose.ui.Modifier found, " + + "Sentry Kotlin Compiler plugin won't run. " + + "Please ensure you're applying the plugin to a compose-enabled project.", + ) + return + } - val transformer = object : IrElementTransformerVoidWithContext() { + val modifierType = modifierClassSymbol.owner.defaultType + val modifierCompanionClass = + pluginContext.referenceClass(modifierClassId)?.owner?.companionObject() + val modifierCompanionClassRef = modifierCompanionClass?.symbol + + if (modifierCompanionClass == null || modifierCompanionClassRef == null) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "No type definition of androidx.compose.ui.Modifier found, " + + "Sentry Kotlin Compiler plugin won't run. " + + "Please ensure you're applying to plugin to a compose-enabled project.", + ) + return + } - // a stack of the function names - private var visitingFunctionNames = ArrayDeque() - private var visitingDeclarationIrBuilder = ArrayDeque() + val modifierThenRefs = pluginContext.referenceFunctions(modifierClassId.callableId("then")) + if (modifierThenRefs.isEmpty()) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "No definition of androidx.compose.ui.Modifier.then() found, " + + "Sentry Kotlin Compiler plugin won't run. " + + "Please ensure you're applying to plugin to a compose-enabled project.", + ) + return + } else if (modifierThenRefs.size != 1) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "Multiple definitions androidx.compose.ui.Modifier.then() found, " + + "which is not supported by Sentry Kotlin Compiler plugin won't run. " + + "Please file an issue under " + + "https://github.com/getsentry/sentry-android-gradle-plugin", + ) + return + } + val modifierThen = modifierThenRefs.single() + + val sentryModifierTagFunction = + FqName("io.sentry.compose").classId("SentryModifier").callableId("sentryTag") + + val sentryModifierTagFunctionRefs = pluginContext.referenceFunctions(sentryModifierTagFunction) + + if (sentryModifierTagFunctionRefs.isEmpty()) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "io.sentry.compose.Modifier.sentryTag() not found, " + + "Sentry Kotlin Compiler plugin won't run. " + + "Please ensure you're using " + + "'io.sentry:sentry-compose-android' as a dependency.", + ) + return + } else if (sentryModifierTagFunctionRefs.size != 1) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "Multiple definitions io.sentry.compose.Modifier.sentryTag() found, " + + "Sentry Kotlin Compiler plugin won't run. " + + "Please ensure your versions of 'io.sentry:sentry-compose-android' " + + "and the sentry Android Gradle plugin match.", + ) + return + } + val sentryModifierTagFunctionRef = sentryModifierTagFunctionRefs.single() - override fun visitFunctionNew(declaration: IrFunction): IrStatement { - val anonymous = declaration.name == SpecialNames.ANONYMOUS + val transformer = + object : IrElementTransformerVoidWithContext() { - // in case of an anonymous, let's try to fallback to it's enclosing function name - val name = if (!anonymous) declaration.name.toString() else { - visitingFunctionNames.lastOrNull() ?: declaration.name.toString() - } + // a stack of the function names + private var visitingFunctionNames = ArrayDeque() + private var visitingDeclarationIrBuilder = ArrayDeque() - val isComposable = declaration.symbol.owner.hasAnnotation(composableAnnotation) + override fun visitFunctionNew(declaration: IrFunction): IrStatement { + val anonymous = declaration.name == SpecialNames.ANONYMOUS - val packageName = declaration.symbol.owner.parent.kotlinFqName.asString() + // in case of an anonymous, let's try to fallback to it's enclosing function name + val name = + if (!anonymous) declaration.name.toString() + else { + visitingFunctionNames.lastOrNull() ?: declaration.name.toString() + } - val isAndroidXPackage = packageName.startsWith("androidx") - val isSentryPackage = packageName.startsWith("io.sentry.compose") + val isComposable = declaration.symbol.owner.hasAnnotation(composableAnnotation) - if (isComposable && !isAndroidXPackage && !isSentryPackage) { - visitingFunctionNames.add(name) - visitingDeclarationIrBuilder.add( - DeclarationIrBuilder(pluginContext, declaration.symbol) - ) - } else { - visitingFunctionNames.add(null) - visitingDeclarationIrBuilder.add(null) - } - val irStatement = super.visitFunctionNew(declaration) + val packageName = declaration.symbol.owner.parent.kotlinFqName.asString() - visitingFunctionNames.removeLast() - visitingDeclarationIrBuilder.removeLast() - return irStatement - } + val isAndroidXPackage = packageName.startsWith("androidx") + val isSentryPackage = packageName.startsWith("io.sentry.compose") - override fun visitCall(expression: IrCall): IrExpression { - val composableName = visitingFunctionNames.lastOrNull() ?: return super.visitCall( - expression - ) - val builder = visitingDeclarationIrBuilder.lastOrNull() ?: return super.visitCall( - expression - ) + if (isComposable && !isAndroidXPackage && !isSentryPackage) { + visitingFunctionNames.add(name) + visitingDeclarationIrBuilder.add( + DeclarationIrBuilder(pluginContext, declaration.symbol) + ) + } else { + visitingFunctionNames.add(null) + visitingDeclarationIrBuilder.add(null) + } + val irStatement = super.visitFunctionNew(declaration) + + visitingFunctionNames.removeLast() + visitingDeclarationIrBuilder.removeLast() + return irStatement + } - // avoid infinite recursion by instrumenting ourselves - val dispatchReceiver = expression.dispatchReceiver - if (dispatchReceiver is IrCall && - dispatchReceiver.symbol == sentryModifierTagFunctionRef - ) { - return super.visitCall(expression) - } - - for (idx in 0 until expression.symbol.owner.valueParameters.size) { - val valueParameter = expression.symbol.owner.valueParameters[idx] - if (valueParameter.type.classFqName == modifierClassFqName) { - val argument = expression.getValueArgument(idx) - expression.putValueArgument( - idx, - wrapExpression(argument, composableName, builder) - ) - } - } - return super.visitCall(expression) + override fun visitCall(expression: IrCall): IrExpression { + val composableName = + visitingFunctionNames.lastOrNull() ?: return super.visitCall(expression) + val builder = + visitingDeclarationIrBuilder.lastOrNull() ?: return super.visitCall(expression) + + // avoid infinite recursion by instrumenting ourselves + val dispatchReceiver = expression.dispatchReceiver + if ( + dispatchReceiver is IrCall && dispatchReceiver.symbol == sentryModifierTagFunctionRef + ) { + return super.visitCall(expression) + } + + for (idx in 0 until expression.symbol.owner.valueParameters.size) { + val valueParameter = expression.symbol.owner.valueParameters[idx] + if (valueParameter.type.classFqName == modifierClassFqName) { + val argument = expression.getValueArgument(idx) + expression.putValueArgument(idx, wrapExpression(argument, composableName, builder)) } + } + return super.visitCall(expression) + } - private fun wrapExpression( - expression: IrExpression?, - composableName: String, - builder: DeclarationIrBuilder - ): - IrExpression { - val overwriteModifier = expression == null || - (expression is IrComposite && expression.type.classFqName == kotlinNothing) - - if (overwriteModifier) { - // Case A: modifier is not supplied - // -> simply set our modifier as param - // e.g. BasicText(text = "abc") - // into BasicText(text = "abc", modifier = Modifier.sentryTag("") - - // we can safely set the sentryModifier if there's no value parameter provided - // but in case the Jetpack Compose Compiler plugin runs before us, - // it will inject all default value parameters as actual parameters using IrComposite - // hence we need to cover this case and overwrite the composite default/null value with sentryModifier - // see https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt;l=287-298;drc=f0b820e062ac34044b43144a87617e90d74657f3 - - // Modifier.sentryTag() - return generateSentryTagCall(builder, composableName) - } else { - // Case B: modifier is already supplied - // -> chain the modifiers - // e.g. BasicText(text = "abc", modifier = Modifier.fillMaxSize()) - // into BasicText(text = "abc", modifier = Modifier.sentryTag("<>").then(Modifier.fillMaxSize()) - - // wrap the call with the sentryTag modifier - - // Modifier.sentryTag() - val sentryTagCall = generateSentryTagCall(builder, composableName) - - // Modifier.then() - val thenCall = builder.irCall( - modifierThen, - modifierType, - 1, - 0, - null - ) - thenCall.putValueArgument(0, expression) - thenCall.dispatchReceiver = sentryTagCall - - return thenCall - } - } + private fun wrapExpression( + expression: IrExpression?, + composableName: String, + builder: DeclarationIrBuilder, + ): IrExpression { + val overwriteModifier = + expression == null || + (expression is IrComposite && expression.type.classFqName == kotlinNothing) + + if (overwriteModifier) { + // Case A: modifier is not supplied + // -> simply set our modifier as param + // e.g. BasicText(text = "abc") + // into BasicText(text = "abc", modifier = Modifier.sentryTag("") + + // we can safely set the sentryModifier if there's no value parameter provided + // but in case the Jetpack Compose Compiler plugin runs before us, + // it will inject all default value parameters as actual parameters using IrComposite + // hence we need to cover this case and overwrite the composite default/null value with + // sentryModifier + // see + // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerParamTransformer.kt;l=287-298;drc=f0b820e062ac34044b43144a87617e90d74657f3 + + // Modifier.sentryTag() + return generateSentryTagCall(builder, composableName) + } else { + // Case B: modifier is already supplied + // -> chain the modifiers + // e.g. BasicText(text = "abc", modifier = Modifier.fillMaxSize()) + // into BasicText(text = "abc", modifier = + // Modifier.sentryTag("<>").then(Modifier.fillMaxSize()) + + // wrap the call with the sentryTag modifier + + // Modifier.sentryTag() + val sentryTagCall = generateSentryTagCall(builder, composableName) + + // Modifier.then() + val thenCall = builder.irCall(modifierThen, modifierType, 1, 0, null) + thenCall.putValueArgument(0, expression) + thenCall.dispatchReceiver = sentryTagCall + + return thenCall + } + } - private fun generateSentryTagCall( - builder: DeclarationIrBuilder, - composableName: String - ): IrCall { - val sentryTagCall = builder.irCall( - sentryModifierTagFunctionRef, - modifierType, - 1, - 0, - null - ).also { - it.extensionReceiver = builder.irGetObjectValue( - type = modifierCompanionClassRef - .createType(false, emptyList()), - classSymbol = modifierCompanionClassRef - ) - it.putValueArgument(0, builder.irString(composableName)) - } - return sentryTagCall + private fun generateSentryTagCall( + builder: DeclarationIrBuilder, + composableName: String, + ): IrCall { + val sentryTagCall = + builder.irCall(sentryModifierTagFunctionRef, modifierType, 1, 0, null).also { + it.extensionReceiver = + builder.irGetObjectValue( + type = modifierCompanionClassRef.createType(false, emptyList()), + classSymbol = modifierCompanionClassRef, + ) + it.putValueArgument(0, builder.irString(composableName)) } + return sentryTagCall } + } - moduleFragment.transform(transformer, null) - } + moduleFragment.transform(transformer, null) + } } fun FqName.classId(name: String): ClassId { - return ClassId(this, org.jetbrains.kotlin.name.Name.identifier(name)) + return ClassId(this, org.jetbrains.kotlin.name.Name.identifier(name)) } fun ClassId.callableId(name: String): CallableId { - return CallableId(this, org.jetbrains.kotlin.name.Name.identifier(name)) + return CallableId(this, org.jetbrains.kotlin.name.Name.identifier(name)) } diff --git a/sentry-kotlin-compiler-plugin/src/test/kotlin/io/sentry/compose/JetpackComposeInstrumentationTest.kt b/sentry-kotlin-compiler-plugin/src/test/kotlin/io/sentry/compose/JetpackComposeInstrumentationTest.kt index 05d30328..18943cd7 100644 --- a/sentry-kotlin-compiler-plugin/src/test/kotlin/io/sentry/compose/JetpackComposeInstrumentationTest.kt +++ b/sentry-kotlin-compiler-plugin/src/test/kotlin/io/sentry/compose/JetpackComposeInstrumentationTest.kt @@ -12,15 +12,16 @@ import org.junit.Test @OptIn(ExperimentalCompilerApi::class) class JetpackComposeInstrumentationTest { - class Fixture { - - // A Fake modifier, which provides hooks so we can not only verify that our code compiles, - // but also execute it and ensure our tags are set correctly. - private val fakeSentryModifier = SourceFile.kotlin( - name = "SentryModifier.kt", - contents = - // language=kotlin - """ + class Fixture { + + // A Fake modifier, which provides hooks so we can not only verify that our code compiles, + // but also execute it and ensure our tags are set correctly. + private val fakeSentryModifier = + SourceFile.kotlin( + name = "SentryModifier.kt", + contents = + // language=kotlin + """ package io.sentry.compose import androidx.compose.ui.Modifier @@ -56,11 +57,14 @@ class JetpackComposeInstrumentationTest { ) } } - """.trimIndent() - ) - private val fakeComposeFunction = SourceFile.kotlin( - name = "ComposableFunction.kt", - contents = """ + """ + .trimIndent(), + ) + private val fakeComposeFunction = + SourceFile.kotlin( + name = "ComposableFunction.kt", + contents = + """ package io.sentry.compose import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -73,76 +77,74 @@ class JetpackComposeInstrumentationTest { ) { // no-op } - """.trimIndent() - ) - - fun compileFile( - file: SourceFile, - includeFakeSentryModifier: Boolean = true - ): KotlinCompilation.Result { - val result = KotlinCompilation().apply { - sources = if (includeFakeSentryModifier) { - listOf( - fakeSentryModifier, - fakeComposeFunction, - file - ) - } else { - listOf(fakeComposeFunction, file) - } - compilerPluginRegistrars = listOf(SentryKotlinCompilerPlugin()) - commandLineProcessors = listOf(SentryKotlinCompilerPluginCommandLineProcessor()) - inheritClassPath = true - messageOutputStream = System.out // see diagnostics in real time - }.compile() - - return result - } - - /** - * Executes the compiled code. - * Also registers a hook in our fake SentryModifier and collects all calls to it. - * This way we can ensure the Compiler Plugin actually added the correct .sentryTag - * calls, and they don't fail during execution - */ - fun execute( - compilation: KotlinCompilation.Result, - className: String = "io.sentry.samples.Example", - method: String, - methodArgTypes: List = emptyList(), - methodArgs: List = emptyList() - ): List { - // inject a callback into our fake modifier - val tags = mutableListOf() - try { - val fakeModifierClass = - compilation.classLoader.loadClass("io.sentry.compose.SentryModifier") - val setCallbackMethod = - fakeModifierClass.getMethod("setCallback", Function1::class.java) - setCallbackMethod.invoke(fakeModifierClass, { tag: String -> - tags.add(tag) - }) - } catch (ex: ClassNotFoundException) { - // no-op - } - - val kClazz = compilation.classLoader.loadClass(className) - val exampleObj = kClazz.getDeclaredConstructor().newInstance() - - val argClasses = - methodArgTypes.map { compilation.classLoader.loadClass(it) }.toTypedArray() - val args = methodArgs.toTypedArray() - kClazz.getMethod(method, *argClasses).invoke(exampleObj, *args) - - return tags - } + """ + .trimIndent(), + ) + + fun compileFile( + file: SourceFile, + includeFakeSentryModifier: Boolean = true, + ): KotlinCompilation.Result { + val result = + KotlinCompilation() + .apply { + sources = + if (includeFakeSentryModifier) { + listOf(fakeSentryModifier, fakeComposeFunction, file) + } else { + listOf(fakeComposeFunction, file) + } + compilerPluginRegistrars = listOf(SentryKotlinCompilerPlugin()) + commandLineProcessors = listOf(SentryKotlinCompilerPluginCommandLineProcessor()) + inheritClassPath = true + messageOutputStream = System.out // see diagnostics in real time + } + .compile() + + return result } - @Test - fun `When no modifier is present, inject sentry modifier`() { - val kotlinSource = SourceFile.kotlin( - name = "Example.kt", - contents = """ + /** + * Executes the compiled code. Also registers a hook in our fake SentryModifier and collects all + * calls to it. This way we can ensure the Compiler Plugin actually added the correct .sentryTag + * calls, and they don't fail during execution + */ + fun execute( + compilation: KotlinCompilation.Result, + className: String = "io.sentry.samples.Example", + method: String, + methodArgTypes: List = emptyList(), + methodArgs: List = emptyList(), + ): List { + // inject a callback into our fake modifier + val tags = mutableListOf() + try { + val fakeModifierClass = + compilation.classLoader.loadClass("io.sentry.compose.SentryModifier") + val setCallbackMethod = fakeModifierClass.getMethod("setCallback", Function1::class.java) + setCallbackMethod.invoke(fakeModifierClass, { tag: String -> tags.add(tag) }) + } catch (ex: ClassNotFoundException) { + // no-op + } + + val kClazz = compilation.classLoader.loadClass(className) + val exampleObj = kClazz.getDeclaredConstructor().newInstance() + + val argClasses = methodArgTypes.map { compilation.classLoader.loadClass(it) }.toTypedArray() + val args = methodArgs.toTypedArray() + kClazz.getMethod(method, *argClasses).invoke(exampleObj, *args) + + return tags + } + } + + @Test + fun `When no modifier is present, inject sentry modifier`() { + val kotlinSource = + SourceFile.kotlin( + name = "Example.kt", + contents = + """ package io.sentry.samples import androidx.compose.runtime.Composable import io.sentry.compose.ComposableFunction @@ -157,24 +159,27 @@ class JetpackComposeInstrumentationTest { ) } } - """.trimIndent() - ) - - val fixture = Fixture() - - val compilation = fixture.compileFile(kotlinSource) - assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) - - val tags = fixture.execute(compilation, method = "NoModifier") - assertEquals(1, tags.size) - assertEquals("NoModifier", tags[0]) - } - - @Test - fun `Modifier Companion calls are replaced with sentry modifier`() { - val kotlinSource = SourceFile.kotlin( - name = "Example.kt", - contents = """ + """ + .trimIndent(), + ) + + val fixture = Fixture() + + val compilation = fixture.compileFile(kotlinSource) + assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) + + val tags = fixture.execute(compilation, method = "NoModifier") + assertEquals(1, tags.size) + assertEquals("NoModifier", tags[0]) + } + + @Test + fun `Modifier Companion calls are replaced with sentry modifier`() { + val kotlinSource = + SourceFile.kotlin( + name = "Example.kt", + contents = + """ package io.sentry.samples import androidx.compose.foundation.layout.fillMaxSize @@ -195,25 +200,28 @@ class JetpackComposeInstrumentationTest { ) } } - """.trimIndent() - ) - - val fixture = Fixture() - - val compilation = fixture.compileFile(kotlinSource) - assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) - - val tags = fixture.execute(compilation, method = "ExistingModifier") - assertEquals(1, tags.size) - assertEquals("ExistingModifier", tags[0]) - } - - @Test - fun `modifier arguments are enriched with sentry modifier`() { - // when a modifier gets passed as a function argument - val kotlinSource = SourceFile.kotlin( - name = "Example.kt", - contents = """ + """ + .trimIndent(), + ) + + val fixture = Fixture() + + val compilation = fixture.compileFile(kotlinSource) + assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) + + val tags = fixture.execute(compilation, method = "ExistingModifier") + assertEquals(1, tags.size) + assertEquals("ExistingModifier", tags[0]) + } + + @Test + fun `modifier arguments are enriched with sentry modifier`() { + // when a modifier gets passed as a function argument + val kotlinSource = + SourceFile.kotlin( + name = "Example.kt", + contents = + """ package io.sentry.samples import androidx.compose.foundation.layout.fillMaxSize @@ -234,33 +242,37 @@ class JetpackComposeInstrumentationTest { ) } } - """.trimIndent() - ) - - val fixture = Fixture() - - // then it should compile fine - val compilation = fixture.compileFile(kotlinSource) - assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) - - // and - val tags = fixture.execute( - compilation, - className = "io.sentry.samples.Example", - method = "ModifierAsParam", - methodArgTypes = listOf("androidx.compose.ui.Modifier"), - methodArgs = listOf(Modifier) - ) - assertEquals(1, tags.size) - assertEquals("ModifierAsParam", tags[0]) - } - - @Test - fun `when sentry modifier does not exist, still compiles`() { - // when an example is compiled without our sentryModifier - val kotlinSource = SourceFile.kotlin( - name = "Example.kt", - contents = """ + """ + .trimIndent(), + ) + + val fixture = Fixture() + + // then it should compile fine + val compilation = fixture.compileFile(kotlinSource) + assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) + + // and + val tags = + fixture.execute( + compilation, + className = "io.sentry.samples.Example", + method = "ModifierAsParam", + methodArgTypes = listOf("androidx.compose.ui.Modifier"), + methodArgs = listOf(Modifier), + ) + assertEquals(1, tags.size) + assertEquals("ModifierAsParam", tags[0]) + } + + @Test + fun `when sentry modifier does not exist, still compiles`() { + // when an example is compiled without our sentryModifier + val kotlinSource = + SourceFile.kotlin( + name = "Example.kt", + contents = + """ package io.sentry.samples import androidx.compose.runtime.Composable @@ -274,30 +286,23 @@ class JetpackComposeInstrumentationTest { ) } } - """.trimIndent() - ) - - val fixture = Fixture() - - val compilation = fixture.compileFile( - kotlinSource, - includeFakeSentryModifier = false - ) - - // then it should still compile fine - assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) - - // emit a compiler warning - assert( - compilation.messages.contains("io.sentry.compose.Modifier.sentryTag() not found") - ) - - // and still execute fine - val tags = fixture.execute( - compilation, - className = "io.sentry.samples.Example", - method = "Example" - ) - assertEquals(0, tags.size) - } + """ + .trimIndent(), + ) + + val fixture = Fixture() + + val compilation = fixture.compileFile(kotlinSource, includeFakeSentryModifier = false) + + // then it should still compile fine + assert(compilation.exitCode == KotlinCompilation.ExitCode.OK) + + // emit a compiler warning + assert(compilation.messages.contains("io.sentry.compose.Modifier.sentryTag() not found")) + + // and still execute fine + val tags = + fixture.execute(compilation, className = "io.sentry.samples.Example", method = "Example") + assertEquals(0, tags.size) + } } From a0bb2d0693deb13a2a93516bbe470cd1a7536c2a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 2 Aug 2024 12:23:49 +0200 Subject: [PATCH 17/17] remove redundant ktlint ignores --- .../io/sentry/samples/instrumentation/ui/list/TrackAdapter.kt | 2 -- .../compose/visitor/RememberNavControllerMethodVisitor.kt | 2 -- .../room/visitor/InstrumentableMethodsCollectingVisitor.kt | 2 -- .../gradle/instrumentation/util/ConstantPoolHelpers.kt | 2 -- .../android/gradle/instrumentation/wrap/Replacements.kt | 1 - .../sentry/android/gradle/sourcecontext/CollectSourcesTask.kt | 2 -- .../src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt | 2 -- .../src/test/kotlin/io/sentry/android/gradle/TestUtils.kt | 4 ---- .../gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt | 1 - .../instrumentation/wrap/visitor/WrappingVisitorTest.kt | 2 -- .../android/gradle/integration/SentryPluginAutoInstallTest.kt | 1 - .../gradle/integration/SentryPluginCheckAndroidSdkTest.kt | 2 -- .../gradle/integration/SentryPluginConfigurationCacheTest.kt | 2 -- .../android/gradle/integration/SentryPluginIntegrationTest.kt | 2 -- 14 files changed, 27 deletions(-) diff --git a/examples/android-instrumentation-sample/src/main/java/io/sentry/samples/instrumentation/ui/list/TrackAdapter.kt b/examples/android-instrumentation-sample/src/main/java/io/sentry/samples/instrumentation/ui/list/TrackAdapter.kt index 0f23e505..50eceedf 100644 --- a/examples/android-instrumentation-sample/src/main/java/io/sentry/samples/instrumentation/ui/list/TrackAdapter.kt +++ b/examples/android-instrumentation-sample/src/main/java/io/sentry/samples/instrumentation/ui/list/TrackAdapter.kt @@ -67,7 +67,6 @@ class TrackAdapter : RecyclerView.Adapter() { val context = holder.row.context val track = data[holder.bindingAdapterPosition] - /* ktlint-disable experimental:argument-list-wrapping */ AlertDialog.Builder(context) .setTitle("Choose File API") .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } @@ -91,7 +90,6 @@ class TrackAdapter : RecyclerView.Adapter() { ) dialog.dismiss() }.show() - /* ktlint-enable experimental:argument-list-wrapping */ } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt index fc00684a..423c6d15 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/compose/visitor/RememberNavControllerMethodVisitor.kt @@ -19,7 +19,6 @@ class RememberNavControllerMethodVisitor( instrumentableContext.name, instrumentableContext.descriptor, ) { - /* ktlint-disable max-line-length */ private val replacement = Replacement( "Lio/sentry/compose/SentryNavigationIntegrationKt;", @@ -27,7 +26,6 @@ class RememberNavControllerMethodVisitor( "(Landroidx/navigation/NavHostController;Landroidx/compose/runtime/Composer;I)Landroidx/navigation/NavHostController;", ) - /* ktlint-enable max-line-length */ override fun onMethodExit(opcode: Int) { // NavHostController is the return value; diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt index 83ab7423..0c1a5132 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/androidx/room/visitor/InstrumentableMethodsCollectingVisitor.kt @@ -52,7 +52,6 @@ class InstrumentableMethodsCollectingVisitor( // this means that either it's a SELECT query wrapped into a transaction // or some unknown to us usecase for instrumentation and we rather skip it if (methodNode in methodsToInstrument) { - /* ktlint-disable max-line-length */ val prevType = methodsToInstrument[methodNode] type = when { @@ -69,7 +68,6 @@ class InstrumentableMethodsCollectingVisitor( null } } - /* ktlint-enable max-line-length */ } if (type != null) { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt index 4056b8f2..ea20d5ca 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt @@ -80,7 +80,6 @@ private fun isR8Minified(classReader: ClassReader): Boolean { * See https://github.com/getsentry/sentry-android-gradle-plugin/issues/360 and * https://github.com/getsentry/sentry-android-gradle-plugin/issues/359#issuecomment-1193782500 */ -/* ktlint-disable max-line-length */ private val MINIFIED_CLASSNAME_REGEX = """^(((([a-zA-z])\4{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\7{1,})?)|([a-zA-Z]([0-9])?))(${'\\'}${'$'}((((\w)\14{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\17{1,})?)|(\w([0-9])?)))*${'$'}""" .toRegex() @@ -93,7 +92,6 @@ private val MINIFIED_CLASSNAME_SENTRY_REGEX = """^(([\w\${'$'}]\/[\w\${'$'}]{1,2})|([\w\${'$'}]{2}\/[\w\${'$'}]\/[\w\${'$'}]))(\/|${'$'})""" .toRegex() -/* ktlint-enable max-line-length */ fun classNameLooksMinified(simpleClassName: String, fullClassName: String): Boolean { return simpleClassName.isNotEmpty() && diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt index c2fbd303..bcb80989 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/wrap/Replacements.kt @@ -1,4 +1,3 @@ -// ktlint-disable filename package io.sentry.android.gradle.instrumentation.wrap data class Replacement(val owner: String, val name: String, val descriptor: String) { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt index 94b35840..cd414a7a 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/sourcecontext/CollectSourcesTask.kt @@ -83,11 +83,9 @@ internal class SourceCollector { val targetFile = outDir.resolve(File(relativePath)) if (sourceFile.isFile) { if (relativePath.isBlank()) { - /* ktlint-disable max-line-length */ SentryPlugin.logger.debug { "Skipping ${sourceFile.absolutePath} as the plugin was unable to determine a relative path for it." } - /* ktlint-enable max-line-length */ } else { SentryPlugin.logger.debug { "Copying file ${sourceFile.absolutePath} " + "to ${targetFile.absolutePath}" diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt index b111da36..d966e8c9 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/SemVer.kt @@ -39,13 +39,11 @@ data class SemVer( ) : Comparable, Serializable { companion object { - /* ktlint-disable max-line-length */ val pattern = Regex( """(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:\.)?(0|[1-9]\d*)?(?:-([\dA-z\-]+(?:\.[\dA-z\-]+)*))?(?:\+([\dA-z\-]+(?:\.[\dA-z\-]+)*))?""" ) - /* ktlint-enable max-line-length */ /** * Parse the version string to [SemVer] data object. diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt index 7566c874..4b97f508 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/TestUtils.kt @@ -23,7 +23,6 @@ import net.lingala.zip4j.io.inputstream.ZipInputStream import org.gradle.api.Project import org.junit.rules.TemporaryFolder -/* ktlint-disable max-line-length */ private val ASSET_PATTERN_PROGUARD = Regex( """^io\.sentry\.ProguardUuids=([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$""" @@ -36,7 +35,6 @@ private val ASSET_PATTERN_SOURCE_CONTEXT = .trimMargin() ) -/* ktlint-enable max-line-length */ internal fun verifyProguardUuid( rootFile: File, @@ -48,7 +46,6 @@ internal fun verifyProguardUuid( val apk = rootFile.resolve("app/build/outputs/apk/$variant/app-$variant$signedStr.apk") val sentryProperties = if (inGeneratedFolder) { - /* ktlint-disable max-line-length experimental:argument-list-wrapping */ val propsFile = if (AgpVersions.isAGP74) { rootFile.resolve( @@ -62,7 +59,6 @@ internal fun verifyProguardUuid( "/sentry/debug-meta-properties/$variant/sentry-debug-meta.properties" ) } - /* ktlint-enable max-line-length experimental:argument-list-wrapping */ if (propsFile.exists()) propsFile.readText() else "" } else { extractZip(apk, "assets/sentry-debug-meta.properties") diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt index 1e289e6b..559122cd 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/autoinstall/override/WarnOnOverrideStrategyTest.kt @@ -1,4 +1,3 @@ -// ktlint-disable max-line-length package io.sentry.android.gradle.autoinstall.override import com.nhaarman.mockitokotlin2.doReturn diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt index 842fe96a..79db351f 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/wrap/visitor/WrappingVisitorTest.kt @@ -184,7 +184,6 @@ class WrappingVisitorTest { descriptor = "(Ljava/lang/String;)V", isInterface = false, ) - /* ktlint-disable experimental:argument-list-wrapping */ fixture .getSut(replacements = mapOf(Replacement.FileInputStream.STRING)) .visitMethodInsn( @@ -194,7 +193,6 @@ class WrappingVisitorTest { methodVisit.descriptor, methodVisit.isInterface, ) - /* ktlint-enable experimental:argument-list-wrapping */ assertEquals(fixture.visitor.methodVisits.size, 2) // store original arguments diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt index e5e19d4a..4a7eb90e 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginAutoInstallTest.kt @@ -1,4 +1,3 @@ -// ktlint-disable max-line-length package io.sentry.android.gradle.integration import io.sentry.BuildConfig diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt index 412492d8..4417328b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginCheckAndroidSdkTest.kt @@ -23,7 +23,6 @@ class SentryPluginCheckAndroidSdkTest : // we query the SdkStateHolder intentionally so the build fails, which confirms that the // service was not registered val result = runner.appendArguments("app:tasks").buildAndFail() - /* ktlint-disable max-line-length */ assertTrue { result.output.contains( Regex( @@ -31,7 +30,6 @@ class SentryPluginCheckAndroidSdkTest : ) ) } - /* ktlint-enable max-line-length */ } @Test 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 6c1ccb41..06995e0a 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 @@ -121,12 +121,10 @@ class SentryPluginConfigurationCacheTest : .find { it.startsWith("[sentry] Read sentry modules:") } ?.substringAfter("[sentry] Read sentry modules:") ?.trim() - /* ktlint-disable max-line-length */ assertEquals( "{io.sentry:sentry-android-core=6.30.0, io.sentry:sentry=6.30.0, io.sentry:sentry-android-okhttp=6.30.0}", readSentryModules, ) - /* ktlint-enable max-line-length */ } @Test diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt index 1d897390..ca3d119b 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/integration/SentryPluginIntegrationTest.kt @@ -74,10 +74,8 @@ class SentryPluginIntegrationTest : applyUploadSourceContexts() testProjectDir.withDummyComposeFile() - /* ktlint-disable max-line-length */ val uploadedIdRegex = """\w+":\{"state":"ok","missingChunks":\[],"uploaded_id":"(\w+-\w+-\w+-\w+-\w+)""".toRegex() - /* ktlint-enable max-line-length */ val build = runner.appendArguments(":app:assembleRelease").build()