diff --git a/CHANGELOG.md b/CHANGELOG.md index d091e4915..e1f4977dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Allow tracing instrumentation and runtime optimizations to be disabled for specific variants, build types, or flavors without disabling other Sentry plugin features ([#1409](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1409)) + ## 6.19.0 ### Fixes 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 ad44625a6..a67c118fe 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 @@ -179,8 +179,22 @@ fun ApplicationAndroidComponentsExtension.configure( } } - val runtimeOptimizationsEnabled = extension.runtimeOptimizations.enabled.get() - val tracingInstrumentationEnabled = extension.tracingInstrumentation.enabled.get() + val runtimeOptimizations = extension.runtimeOptimizations + val runtimeOptimizationsEnabled = + variant.isInstrumentationEnabled( + runtimeOptimizations.enabled.get(), + runtimeOptimizations.ignoredVariants, + runtimeOptimizations.ignoredBuildTypes, + runtimeOptimizations.ignoredFlavors, + ) + val tracingInstrumentation = extension.tracingInstrumentation + val tracingInstrumentationEnabled = + variant.isInstrumentationEnabled( + tracingInstrumentation.enabled.get(), + tracingInstrumentation.ignoredVariants, + tracingInstrumentation.ignoredBuildTypes, + tracingInstrumentation.ignoredFlavors, + ) // Both visitor factories need the resolved dependency graph. val modulesService = if (runtimeOptimizationsEnabled || tracingInstrumentationEnabled) { @@ -581,6 +595,17 @@ fun Variant.configureUploadAppTasks( return uploadBundleTask to uploadApkTask } +private fun Variant.isInstrumentationEnabled( + enabled: Boolean, + ignoredVariants: SetProperty, + ignoredBuildTypes: SetProperty, + ignoredFlavors: SetProperty, +): Boolean = + enabled && + name !in ignoredVariants.get() && + flavorName !in ignoredFlavors.get() && + buildType !in ignoredBuildTypes.get() + private fun Variant.configureInstrumentation( classVisitorFactoryImplClass: Class>, scope: InstrumentationScope, diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/RuntimeOptimizationsExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/RuntimeOptimizationsExtension.kt index e22fef4f3..b6b15126c 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/RuntimeOptimizationsExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/RuntimeOptimizationsExtension.kt @@ -3,10 +3,23 @@ package io.sentry.android.gradle.extensions import javax.inject.Inject import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty open class RuntimeOptimizationsExtension @Inject constructor(objects: ObjectFactory) { /** * Enables runtime optimizations of the Sentry SDK at the cost of build time. Defaults to true. */ val enabled: Property = objects.property(Boolean::class.java).convention(true) + + /** Android build variants for which runtime optimizations should be disabled. */ + val ignoredVariants: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** Android build types for which runtime optimizations should be disabled. */ + val ignoredBuildTypes: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** Android build flavors for which runtime optimizations should be disabled. */ + val ignoredFlavors: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) } 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 f23176c34..2918b1a0b 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 @@ -13,6 +13,18 @@ open class TracingInstrumentationExtension @Inject constructor(objects: ObjectFa */ val enabled: Property = objects.property(Boolean::class.java).convention(true) + /** Android build variants for which tracing instrumentation should be disabled. */ + val ignoredVariants: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** Android build types for which tracing instrumentation should be disabled. */ + val ignoredBuildTypes: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + + /** Android build flavors for which tracing instrumentation should be disabled. */ + val ignoredFlavors: SetProperty = + objects.setProperty(String::class.java).convention(emptySet()) + /** * Enabled debug output of the plugin. Useful when there are issues with code instrumentation, * shows the modified bytecode. Defaults to false. 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 866f2a4e5..69e3baa92 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 @@ -1,5 +1,6 @@ package io.sentry.android.gradle.integration +import com.google.common.truth.Truth.assertThat import io.sentry.BuildConfig import org.gradle.util.GradleVersion import org.junit.Assert.assertFalse @@ -83,6 +84,107 @@ class SentryPluginVariantTest : assertTrue(":app:uploadSentryProguardMappingsDemoRelease" in build.output) } + @Test + fun `skips tracing for ignored variant while preserving mapping upload`() { + applyTracingIgnores(ignoredVariants = setOf("fullRelease")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleDemoRelease", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformDemoReleaseClassesWithAsm") + } + + @Test + fun `skips tracing for ignored build type while preserving mapping upload`() { + applyTracingIgnores(ignoredBuildTypes = setOf("release")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleFullDebug", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformFullDebugClassesWithAsm") + } + + @Test + fun `skips tracing for ignored flavor while preserving mapping upload`() { + applyTracingIgnores(ignoredFlavors = setOf("full")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleDemoRelease", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformDemoReleaseClassesWithAsm") + } + + @Test + fun `skips runtime optimizations for ignored variant while preserving mapping upload`() { + applyRuntimeOptimizationIgnores(ignoredVariants = setOf("fullRelease")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleDemoRelease", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformDemoReleaseClassesWithAsm") + } + + @Test + fun `skips runtime optimizations for ignored build type while preserving mapping upload`() { + applyRuntimeOptimizationIgnores(ignoredBuildTypes = setOf("release")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleFullDebug", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformFullDebugClassesWithAsm") + } + + @Test + fun `skips runtime optimizations for ignored flavor while preserving mapping upload`() { + applyRuntimeOptimizationIgnores(ignoredFlavors = setOf("full")) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleDemoRelease", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformDemoReleaseClassesWithAsm") + } + + @Test + fun `skips both instrumentation visitors when both extensions ignore a build type`() { + applyRuntimeOptimizationIgnores( + ignoredBuildTypes = setOf("release"), + tracingInstrumentation = true, + ) + + val ignoredBuild = runner.appendArguments(":app:assembleFullRelease", "--dry-run").build() + + assertThat(ignoredBuild.output).doesNotContain(":app:transformFullReleaseClassesWithAsm") + assertThat(ignoredBuild.output).contains(":app:uploadSentryProguardMappingsFullRelease") + + val allowedBuild = runner.appendArguments(":app:assembleFullDebug", "--dry-run").build() + + assertThat(allowedBuild.output).contains(":app:transformFullDebugClassesWithAsm") + } + private fun applyIgnores( ignoredVariants: Set = setOf(), ignoredBuildTypes: Set = setOf(), @@ -107,4 +209,64 @@ class SentryPluginVariantTest : .trimIndent() ) } + + private fun applyTracingIgnores( + ignoredVariants: Set = emptySet(), + ignoredBuildTypes: Set = emptySet(), + ignoredFlavors: Set = emptySet(), + ) { + val variants = ignoredVariants.joinToString(",") { "\"$it\"" } + val buildTypes = ignoredBuildTypes.joinToString(",") { "\"$it\"" } + val flavors = ignoredFlavors.joinToString(",") { "\"$it\"" } + appBuildFile.appendText( + // language=Groovy + """ + sentry { + autoUploadProguardMapping = false + runtimeOptimizations { + enabled = false + } + tracingInstrumentation { + enabled = true + ignoredVariants = [$variants] + ignoredBuildTypes = [$buildTypes] + ignoredFlavors = [$flavors] + } + } + """ + .trimIndent() + ) + } + + private fun applyRuntimeOptimizationIgnores( + ignoredVariants: Set = emptySet(), + ignoredBuildTypes: Set = emptySet(), + ignoredFlavors: Set = emptySet(), + tracingInstrumentation: Boolean = false, + ) { + val variants = ignoredVariants.joinToString(",") { "\"$it\"" } + val buildTypes = ignoredBuildTypes.joinToString(",") { "\"$it\"" } + val flavors = ignoredFlavors.joinToString(",") { "\"$it\"" } + appBuildFile.appendText( + // language=Groovy + """ + sentry { + autoUploadProguardMapping = false + runtimeOptimizations { + enabled = true + ignoredVariants = [$variants] + ignoredBuildTypes = [$buildTypes] + ignoredFlavors = [$flavors] + } + tracingInstrumentation { + enabled = $tracingInstrumentation + ignoredVariants = [$variants] + ignoredBuildTypes = [$buildTypes] + ignoredFlavors = [$flavors] + } + } + """ + .trimIndent() + ) + } }