Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: Okhttp-integration version check #327

Merged
merged 6 commits into from Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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))
romtsn marked this conversation as resolved.
Show resolved Hide resolved

## 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