Skip to content

Commit

Permalink
fix: Okhttp-integration version check (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn committed Jun 14, 2022
1 parent 01ad443 commit 04303e1
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 132 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Fix `OkHttp` auto-instrumentation crash, when `sentry-android-okhttp` is not present on classpath ([#327](https://github.com/getsentry/sentry-android-gradle-plugin/pull/327))

## 3.1.0

### Features
Expand Down
Expand Up @@ -19,7 +19,7 @@ import io.sentry.android.gradle.SentryTasksProvider.getTransformerTask
import io.sentry.android.gradle.autoinstall.installDependencies
import io.sentry.android.gradle.extensions.SentryPluginExtension
import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory
import io.sentry.android.gradle.services.SentrySdkStateHolder
import io.sentry.android.gradle.services.SentryModulesService
import io.sentry.android.gradle.tasks.SentryGenerateProguardUuidTask
import io.sentry.android.gradle.tasks.SentryUploadNativeSymbolsTask
import io.sentry.android.gradle.tasks.SentryUploadProguardMappingsTask
Expand Down Expand Up @@ -97,11 +97,11 @@ class SentryPlugin : Plugin<Project> {
* the state between builds and also during a single build, because transforms
* are run in parallel.
*/
val sdkStateHolderProvider = SentrySdkStateHolder.register(project)
val sentryModulesService = SentryModulesService.register(project)
project.detectSentryAndroidSdk(
"${variant.name}RuntimeClasspath",
variant.name,
sdkStateHolderProvider
sentryModulesService
)

variant.transformClassesWith(
Expand All @@ -117,7 +117,7 @@ class SentryPlugin : Plugin<Project> {
params.features.setDisallowChanges(
extension.tracingInstrumentation.features.get()
)
params.sdkStateHolder.setDisallowChanges(sdkStateHolderProvider)
params.sentryModulesService.setDisallowChanges(sentryModulesService)
params.tmpDir.set(tmpDir)
}
variant.setAsmFramesComputationMode(
Expand Down
Expand Up @@ -12,11 +12,10 @@ import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.Androi
import io.sentry.android.gradle.instrumentation.okhttp.OkHttp
import io.sentry.android.gradle.instrumentation.remap.RemappingInstrumentable
import io.sentry.android.gradle.instrumentation.wrap.WrappingInstrumentable
import io.sentry.android.gradle.services.SentrySdkStateHolder
import io.sentry.android.gradle.util.SentryAndroidSdkState
import io.sentry.android.gradle.util.SentryAndroidSdkState.FILE_IO
import io.sentry.android.gradle.util.SentryAndroidSdkState.OKHTTP
import io.sentry.android.gradle.util.SentryAndroidSdkState.PERFORMANCE
import io.sentry.android.gradle.services.SentryModulesService
import io.sentry.android.gradle.util.SemVer
import io.sentry.android.gradle.util.SentryModules
import io.sentry.android.gradle.util.SentryVersions
import io.sentry.android.gradle.util.debug
import io.sentry.android.gradle.util.info
import io.sentry.android.gradle.util.warn
Expand Down Expand Up @@ -50,7 +49,7 @@ abstract class SpanAddingClassVisitorFactory :
val features: SetProperty<InstrumentationFeature>

@get:Internal
val sdkStateHolder: Property<SentrySdkStateHolder>
val sentryModulesService: Property<SentryModulesService>

@get:Internal
val tmpDir: Property<File>
Expand All @@ -69,52 +68,63 @@ abstract class SpanAddingClassVisitorFactory :
return memoized
}

val sdkState = parameters.get().sdkStateHolder.get().sdkState
SentryPlugin.logger.info { "Read sentry-android sdk state: $sdkState" }
val sentryModules = parameters.get().sentryModulesService.get().modules
SentryPlugin.logger.info { "Read sentry modules: $sentryModules" }
/**
* When adding a new instrumentable to the list below, do not forget to add a new
* version range to [SentryAndroidSdkState.from], if it involves runtime classes
* version to [SentryVersions] if it involves runtime classes
* from the sentry-android SDK.
*/
val instrumentables = listOfNotNull(
AndroidXSQLiteDatabase().takeIf {
isDatabaseInstrEnabled(sdkState, parameters.get())
isDatabaseInstrEnabled(sentryModules, parameters.get())
},
AndroidXSQLiteStatement().takeIf {
isDatabaseInstrEnabled(sdkState, parameters.get())
isDatabaseInstrEnabled(sentryModules, parameters.get())
},
AndroidXRoomDao().takeIf { isDatabaseInstrEnabled(sdkState, parameters.get()) },
OkHttp().takeIf { isOkHttpInstrEnabled(sdkState, parameters.get()) },
AndroidXRoomDao().takeIf {
isDatabaseInstrEnabled(sentryModules, parameters.get())
},
OkHttp().takeIf { isOkHttpInstrEnabled(sentryModules, parameters.get()) },
ChainedInstrumentable(
listOf(WrappingInstrumentable(), RemappingInstrumentable())
).takeIf { isFileIOInstrEnabled(sdkState, parameters.get()) }
).takeIf { isFileIOInstrEnabled(sentryModules, parameters.get()) }
)
SentryPlugin.logger.debug {
SentryPlugin.logger.info {
"Instrumentables: ${instrumentables.joinToString { it::class.java.simpleName }}"
}
parameters.get()._instrumentables = ArrayList(instrumentables)
return instrumentables
}

private fun isDatabaseInstrEnabled(
sdkState: SentryAndroidSdkState,
sentryModules: Map<String, SemVer>,
parameters: SpanAddingParameters
): Boolean =
sdkState.isAtLeast(PERFORMANCE) &&
parameters.features.get().contains(InstrumentationFeature.DATABASE)
sentryModules.isAtLeast(
SentryModules.SENTRY_ANDROID_CORE,
SentryVersions.VERSION_PERFORMANCE
) && parameters.features.get().contains(InstrumentationFeature.DATABASE)

private fun isFileIOInstrEnabled(
sdkState: SentryAndroidSdkState,
sentryModules: Map<String, SemVer>,
parameters: SpanAddingParameters
): Boolean =
sdkState.isAtLeast(FILE_IO) &&
parameters.features.get().contains(InstrumentationFeature.FILE_IO)
sentryModules.isAtLeast(
SentryModules.SENTRY_ANDROID_CORE,
SentryVersions.VERSION_FILE_IO
) && parameters.features.get().contains(InstrumentationFeature.FILE_IO)

private fun isOkHttpInstrEnabled(
sdkState: SentryAndroidSdkState,
sentryModules: Map<String, SemVer>,
parameters: SpanAddingParameters
): Boolean = sdkState.isAtLeast(OKHTTP) &&
parameters.features.get().contains(InstrumentationFeature.OKHTTP)
): Boolean = sentryModules.isAtLeast(
SentryModules.SENTRY_ANDROID_OKHTTP,
SentryVersions.VERSION_OKHTTP
) && parameters.features.get().contains(InstrumentationFeature.OKHTTP)

private fun Map<String, SemVer>.isAtLeast(module: String, minVersion: SemVer): Boolean =
getOrDefault(module, SentryVersions.VERSION_DEFAULT) >= minVersion

override fun createClassVisitor(
classContext: ClassContext,
Expand Down
Expand Up @@ -2,24 +2,24 @@

package io.sentry.android.gradle.services

import io.sentry.android.gradle.util.SentryAndroidSdkState
import io.sentry.android.gradle.util.SemVer
import io.sentry.android.gradle.util.getBuildServiceName
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

abstract class SentrySdkStateHolder : BuildService<BuildServiceParameters.None> {
abstract class SentryModulesService : BuildService<BuildServiceParameters.None> {

@get:Synchronized
@set:Synchronized
var sdkState: SentryAndroidSdkState = SentryAndroidSdkState.MISSING
var modules: Map<String, SemVer> = emptyMap()

companion object {
fun register(project: Project): Provider<SentrySdkStateHolder> {
fun register(project: Project): Provider<SentryModulesService> {
return project.gradle.sharedServices.registerIfAbsent(
getBuildServiceName(SentrySdkStateHolder::class.java),
SentrySdkStateHolder::class.java
getBuildServiceName(SentryModulesService::class.java),
SentryModulesService::class.java
) {}
}
}
Expand Down
@@ -1,55 +1,52 @@
package io.sentry.android.gradle.util

import io.sentry.android.gradle.services.SentrySdkStateHolder
import io.sentry.android.gradle.services.SentryModulesService
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider

fun Project.detectSentryAndroidSdk(
configurationName: String,
variantName: String,
sdkStateHolder: Provider<SentrySdkStateHolder>
sentryModulesService: Provider<SentryModulesService>
) {
val configProvider = try {
configurations.named(configurationName)
} catch (e: UnknownDomainObjectException) {
logger.warn {
"Unable to find configuration $configurationName for variant $variantName."
}
sdkStateHolder.get().sdkState = SentryAndroidSdkState.MISSING
sentryModulesService.get().modules = emptyMap()
return
}

configProvider.configure { configuration ->
configuration.incoming.afterResolve {
val version = it.resolutionResult.allComponents.findSentryAndroidSdk()
if (version == null) {
logger.warn { "sentry-android dependency was not found." }
sdkStateHolder.get().sdkState = SentryAndroidSdkState.MISSING
return@afterResolve
val sentryModules = it.resolutionResult.allComponents.filterSentryModules(logger)
logger.info {
"Detected Sentry modules $sentryModules " +
"for variant: $variantName, config: $configurationName"
}

val state = try {
val sdkState = SentryAndroidSdkState.from(version)
logger.info {
"Detected sentry-android $sdkState for version: $version, " +
"variant: $variantName, config: $configurationName"
}
sdkState
} catch (e: IllegalStateException) {
logger.warn { e.localizedMessage }
SentryAndroidSdkState.MISSING
}
sdkStateHolder.get().sdkState = state
sentryModulesService.get().modules = sentryModules
}
}
}

private fun Set<ResolvedComponentResult>.findSentryAndroidSdk(): String? {
val sentryDep = find { resolvedComponent: ResolvedComponentResult ->
val moduleVersion = resolvedComponent.moduleVersion ?: return@find false
moduleVersion.group == "io.sentry" && moduleVersion.name == "sentry-android-core"
private fun Set<ResolvedComponentResult>.filterSentryModules(logger: Logger): Map<String, SemVer> {
return filter { resolvedComponent: ResolvedComponentResult ->
val moduleVersion = resolvedComponent.moduleVersion ?: return@filter false
moduleVersion.group == "io.sentry"
}.associate {
val name = it.moduleVersion?.name ?: ""
val version = it.moduleVersion?.version ?: ""
val semver = try {
SemVer.parse(version)
} catch (e: Throwable) {
logger.info { "Unable to parse version $version of $name" }
SemVer()
}
name to semver
}
return sentryDep?.moduleVersion?.version
}

This file was deleted.

Expand Up @@ -10,6 +10,18 @@ internal object AgpVersions {
val VERSION_7_0_0: SemVer = SemVer.parse("7.0.0")
}

internal object SentryVersions {
internal val VERSION_DEFAULT = SemVer()
internal val VERSION_PERFORMANCE = SemVer(4, 0, 0)
internal val VERSION_OKHTTP = SemVer(5, 0, 0)
internal val VERSION_FILE_IO = SemVer(5, 5, 0)
}

internal object SentryModules {
internal const val SENTRY_ANDROID_CORE = "sentry-android-core"
internal const val SENTRY_ANDROID_OKHTTP = "sentry-android-okhttp"
}

/**
* Adapted from https://github.com/swiftzer/semver/blob/master/src/main/java/net/swiftzer/semver/SemVer.kt
*/
Expand Down
Expand Up @@ -37,7 +37,7 @@ class SentryPluginCheckAndroidSdkTest(
assertTrue {
result.output.contains(
Regex(
"""[BuildServiceRegistration with name 'io.sentry.android.gradle.services.SentrySdkStateHolder_(\w*)' not found]"""
"""[BuildServiceRegistration with name 'io.sentry.android.gradle.services.SentryModulesService_(\w*)' not found]"""
)
)
}
Expand All @@ -55,17 +55,18 @@ class SentryPluginCheckAndroidSdkTest(
}
sentry.tracingInstrumentation.enabled = true
sentry.autoInstallation.enabled = false
sentry.includeProguardMapping = false
${captureSdkState()}
""".trimIndent()
)

val result = runner
.appendArguments("app:tasks")
.appendArguments("app:assembleDebug")
.build()
assertTrue {
"SDK STATE: MISSING" in result.output
"SENTRY MODULES: [:]" in result.output
}
}

Expand All @@ -81,6 +82,7 @@ class SentryPluginCheckAndroidSdkTest(
sentry {
tracingInstrumentation.enabled = true
autoInstallation.enabled = false
includeProguardMapping = false
}
Expand All @@ -102,7 +104,10 @@ class SentryPluginCheckAndroidSdkTest(
.appendArguments("app:assembleDebug")
.build()

print(result.output)
assertTrue {
"SENTRY MODULES: [sentry-android:5.4.0, sentry-android-core:5.4.0, " +
"sentry:5.4.0, sentry-android-ndk:5.4.0]" in result.output
}
}

private fun captureSdkState(): String =
Expand All @@ -112,9 +117,9 @@ class SentryPluginCheckAndroidSdkTest(
import io.sentry.android.gradle.services.*
project.gradle.buildFinished {
println(
"SDK STATE: " + BuildServicesKt
.getBuildService(project.gradle.sharedServices, SentrySdkStateHolder.class)
.get().sdkState
"SENTRY MODULES: " + BuildServicesKt
.getBuildService(project.gradle.sharedServices, SentryModulesService.class)
.get().modules
)
}
""".trimIndent()
Expand Down
Expand Up @@ -3,7 +3,7 @@ package io.sentry.android.gradle.instrumentation.fakes
import io.sentry.android.gradle.extensions.InstrumentationFeature
import io.sentry.android.gradle.instrumentation.ClassInstrumentable
import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory
import io.sentry.android.gradle.services.SentrySdkStateHolder
import io.sentry.android.gradle.services.SentryModulesService
import java.io.File
import org.gradle.api.internal.provider.DefaultProperty
import org.gradle.api.internal.provider.DefaultSetProperty
Expand All @@ -27,7 +27,7 @@ class TestSpanAddingParameters(
get() = DefaultSetProperty(PropertyHost.NO_OP, InstrumentationFeature::class.java)
.convention(setOf(InstrumentationFeature.FILE_IO, InstrumentationFeature.DATABASE))

override val sdkStateHolder: Property<SentrySdkStateHolder>
override val sentryModulesService: Property<SentryModulesService>
get() = TODO()

override val tmpDir: Property<File>
Expand Down

0 comments on commit 04303e1

Please sign in to comment.