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

feat: override native options #221

Merged
merged 31 commits into from
May 15, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@

## Unreleased

### Features

- Allow initializing the KMP SDK with native options ([#221](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/221))
- This allows you to initialize the SDK with platform-specific options that may not be available in the common code of the KMP SDK yet.

Usage:
```kotlin
// build.gradle.kts
kotlin {
sourceSets {
all {
languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
}
}
}

// commonMain
fun init() {
Sentry.initWithPlatformOptions(createPlatformOptions())
}

expect fun platformOptionsConfiguration(): PlatformOptionsConfiguration

// iOS
actual fun createPlatformOptions(): PlatformOptionsConfiguration = {
dsn = "your_dsn"
release = "1.0.0"
// ...
}

// Android
actual fun createPlatformOptions(): PlatformOptionsConfiguration = {
dsn = "your_dsn"
release = "1.0.0"
// ...
}
```

### Dependencies

- Bump Java SDK from v7.8.0 to v7.9.0 ([#219](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/219))
Expand Down
1 change: 1 addition & 0 deletions scripts/build-apple.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ PROJECT_NAME="$1"
"iosX64Test" \
"watchosX64Test" \
"tvosX64Test" \
"iosSimulatorArm64Test" \
"publishKotlinMultiplatformPublicationToMavenLocal" \
"publishIosArm64PublicationToMavenLocal" \
"publishIosSimulatorArm64PublicationToMavenLocal" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public final class io/sentry/kotlin/multiplatform/Sentry {
public final fun crash ()V
public final fun init (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public final fun init (Lkotlin/jvm/functions/Function1;)V
public final fun initWithPlatformOptions (Lkotlin/jvm/functions/Function1;)V
public final fun isCrashedLastRun ()Z
public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public final class io/sentry/kotlin/multiplatform/Sentry {
public final fun crash ()V
public final fun init (Lio/sentry/kotlin/multiplatform/Context;Lkotlin/jvm/functions/Function1;)V
public final fun init (Lkotlin/jvm/functions/Function1;)V
public final fun initWithPlatformOptions (Lkotlin/jvm/functions/Function1;)V
public final fun isCrashedLastRun ()Z
public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V
}
Expand Down
10 changes: 8 additions & 2 deletions sentry-kotlin-multiplatform/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

Expand Down Expand Up @@ -64,6 +65,11 @@ kotlin {
macosArm64()

sourceSets {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}

all {
languageSettings.apply {
optIn("kotlinx.cinterop.ExperimentalForeignApi")
Expand All @@ -87,7 +93,7 @@ kotlin {
}

androidMain.dependencies {
implementation(Config.Libs.sentryAndroid)
api(Config.Libs.sentryAndroid)
}

// androidUnitTest.dependencies doesn't exist
Expand All @@ -102,7 +108,7 @@ kotlin {
val commonJvmMain by creating {
dependsOn(commonMain.get())
dependencies {
implementation(Config.Libs.sentryJava)
api(Config.Libs.sentryJava)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ Pod::Spec.new do |spec|
}
]

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,12 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import io.sentry.android.core.SentryAndroid
import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback

internal actual fun initSentry(configuration: OptionsConfiguration) {
val options = SentryOptions()
configuration.invoke(options)

val context = applicationContext ?: run {
// TODO: add logging later
return
}

SentryAndroid.init(context) { sentryOptions ->
options.toAndroidSentryOptionsCallback().invoke(sentryOptions)
}
}
public actual typealias Context = Context

internal var applicationContext: Context? = null
private set

public actual typealias Context = Context

/**
* A ContentProvider that does NOT store or provide any data for read or write operations.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.sentry.kotlin.multiplatform

import io.sentry.android.core.SentryAndroid

internal actual class SentryPlatformInstance : SentryInstance {
override fun init(configuration: PlatformOptionsConfiguration) {
val context = applicationContext ?: run {
// TODO: add logging later
return
}

SentryAndroid.init(context, configuration)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.sentry.kotlin.multiplatform

import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback

public actual typealias SentryPlatformOptions = io.sentry.android.core.SentryAndroidOptions

internal actual fun SentryPlatformOptions.prepareForInit() {
sdkVersion?.name = BuildKonfig.SENTRY_KMP_ANDROID_SDK_NAME
sdkVersion?.version = BuildKonfig.VERSION_NAME
if (sdkVersion?.packageSet?.none { it.name == BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME } == true) {
sdkVersion?.addPackage(BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME, BuildKonfig.SENTRY_ANDROID_VERSION)
}
}

internal actual fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration =
toAndroidSentryOptionsCallback()

internal actual fun SentryPlatformOptions.prepareForInitBridge() {
prepareForInit()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.sentry.kotlin.multiplatform.extensions

import io.sentry.android.core.SentryAndroidOptions
import io.sentry.kotlin.multiplatform.BuildKonfig
import io.sentry.kotlin.multiplatform.SentryOptions
import kotlin.collections.forEach as kForEach

Expand All @@ -15,16 +14,9 @@ internal fun SentryOptions.toAndroidSentryOptionsCallback(): (SentryAndroidOptio
it.isAnrEnabled = this.isAnrEnabled
it.anrTimeoutIntervalMillis = this.anrTimeoutIntervalMillis

it.sdkVersion?.name = this.sdk?.name ?: BuildKonfig.SENTRY_KMP_ANDROID_SDK_NAME
it.sdkVersion?.version = this.sdk?.version ?: BuildKonfig.VERSION_NAME

// kForEach solves an issue with linter where it thinks forEach is the Java version
// see here: https://stackoverflow.com/questions/44751469/kotlin-extension-functions-suddenly-require-api-level-24/68897591#68897591
this.sdk?.packages?.kForEach { sdkPackage ->
it.sdkVersion?.addPackage(sdkPackage.name, sdkPackage.version)
}

if (it.sdkVersion?.packages?.none { it.name == BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME } == true) {
it.sdkVersion?.addPackage(BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME, BuildKonfig.SENTRY_ANDROID_VERSION)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ actual abstract class BaseSentryTest {
Sentry.init(optionsConfiguration)
}

actual fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) {
Sentry.initWithPlatformOptions(platformOptionsConfiguration)
}

@BeforeTest
open fun setUp() {
// Set up the provider needed for Sentry.init on Android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.sentry.kotlin.multiplatform

import io.sentry.android.core.SentryAndroidOptions
import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback
import io.sentry.kotlin.multiplatform.utils.fakeDsn
import kotlin.test.assertEquals

actual interface PlatformOptions : CommonPlatformOptions {
Expand Down Expand Up @@ -75,3 +76,7 @@ actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions)
assertEquals(isAnrEnabled, options.isAnrEnabled)
assertEquals(anrTimeoutIntervalMillis, options.anrTimeoutIntervalMillis)
}

actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = {
it.dsn = fakeDsn
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import io.sentry.kotlin.multiplatform.nsexception.setSentryUnhandledExceptionHoo

/** Convenience extension to setup unhandled exception hook */
internal fun SentrySDK.Companion.start(configuration: (CocoaSentryOptions?) -> Unit) {
this.startWithConfigureOptions(configuration)
startWithConfigureOptions(configuration)
setSentryUnhandledExceptionHook()
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package io.sentry.kotlin.multiplatform

import NSException.Sentry.SentryEvent
import PrivateSentrySDKOnly.Sentry.PrivateSentrySDKOnly
import cocoapods.Sentry.SentrySDK
import io.sentry.kotlin.multiplatform.extensions.toCocoaBreadcrumb
import io.sentry.kotlin.multiplatform.extensions.toCocoaUser
import io.sentry.kotlin.multiplatform.extensions.toCocoaUserFeedback
import io.sentry.kotlin.multiplatform.nsexception.asNSException
import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent
import io.sentry.kotlin.multiplatform.protocol.Breadcrumb
import io.sentry.kotlin.multiplatform.protocol.SentryId
import io.sentry.kotlin.multiplatform.protocol.User
Expand All @@ -14,16 +17,53 @@ import platform.Foundation.NSException

public actual abstract class Context

internal expect fun initSentry(configuration: OptionsConfiguration)
// Since the function is the same on all apple platforms, we don't split it into expect/actual
// like on JVM and Android, we may do that later on if needed.
internal actual fun SentryPlatformOptions.prepareForInit() {
val cocoa = this as? CocoaSentryOptions
val existingBeforeSend = cocoa?.beforeSend
val modifiedBeforeSend: (CocoaSentryEvent?) -> CocoaSentryEvent? = beforeSend@{ event ->
// Return early if the user's beforeSend returns null
if (existingBeforeSend?.invoke(event) == null) {
return@beforeSend null
}

val cocoaName = BuildKonfig.SENTRY_COCOA_PACKAGE_NAME
val cocoaVersion = BuildKonfig.SENTRY_COCOA_VERSION

val sdk = event?.sdk?.toMutableMap() ?: mutableMapOf()
val packages = sdk["packages"] as? MutableList<Map<String, String>> ?: mutableListOf()

packages.add(mapOf("name" to cocoaName, "version" to cocoaVersion))
sdk["packages"] = packages
event?.sdk = sdk

dropKotlinCrashEvent(event as SentryEvent?) as CocoaSentryEvent?
}

internal actual object SentryBridge {
cocoa?.setBeforeSend(modifiedBeforeSend)

PrivateSentrySDKOnly.setSdkName(BuildKonfig.SENTRY_KMP_COCOA_SDK_NAME, BuildKonfig.VERSION_NAME)
}

internal actual class SentryBridge actual constructor(private val sentryInstance: SentryInstance) {
actual fun init(context: Context, configuration: OptionsConfiguration) {
initSentry(configuration)
init(configuration)
}

actual fun init(configuration: OptionsConfiguration) {
initSentry(configuration)
val options = SentryOptions()
configuration.invoke(options)
initWithPlatformOptions(options.toPlatformOptionsConfiguration())
}

actual fun initWithPlatformOptions(configuration: PlatformOptionsConfiguration) {
val finalConfiguration: PlatformOptionsConfiguration = {
configuration(it)
// We modify beforeSend so we need this to run after the user's configuration
it.prepareForInit()
}
sentryInstance.init(finalConfiguration)
}

actual fun captureMessage(message: String): SentryId {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package io.sentry.kotlin.multiplatform.extensions

import PrivateSentrySDKOnly.Sentry.PrivateSentrySDKOnly
import cocoapods.Sentry.SentryHttpStatusCodeRange
import io.sentry.kotlin.multiplatform.BuildKonfig
import io.sentry.kotlin.multiplatform.CocoaSentryEvent
import io.sentry.kotlin.multiplatform.CocoaSentryOptions
import io.sentry.kotlin.multiplatform.SentryEvent
import io.sentry.kotlin.multiplatform.SentryOptions
import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent
import kotlinx.cinterop.convert
import platform.Foundation.NSNumber
import NSException.Sentry.SentryEvent as NSExceptionSentryEvent

internal fun SentryOptions.toCocoaOptionsConfiguration(): (CocoaSentryOptions?) -> Unit = {
it?.applyCocoaBaseOptions(this)
Expand Down Expand Up @@ -42,39 +37,26 @@ internal fun CocoaSentryOptions.applyCocoaBaseOptions(options: SentryOptions) {
tracesSampleRate = NSNumber(double = it)
}
beforeSend = { event ->
val cocoaName = BuildKonfig.SENTRY_COCOA_PACKAGE_NAME
val cocoaVersion = BuildKonfig.SENTRY_COCOA_VERSION

val sdk = event?.sdk?.toMutableMap()

val packages = options.sdk?.packages?.map {
mapOf("name" to it.name, "version" to it.version)
}?.toMutableList() ?: mutableListOf()

val names = packages.map { it["name"] }
if (!names.contains(cocoaName)) {
packages.add(mapOf("name" to cocoaName, "version" to cocoaVersion))
}

sdk?.set("packages", packages)

event?.sdk = sdk

if (options.beforeSend == null) {
dropKotlinCrashEvent(event as NSExceptionSentryEvent?) as CocoaSentryEvent?
event
} else {
val modifiedEvent = event?.let { SentryEvent(it) }?.let { unwrappedEvent ->
event?.let { SentryEvent(it) }?.let { unwrappedEvent ->
val result = options.beforeSend?.invoke(unwrappedEvent)
result?.let { event.applyKmpEvent(it) }
}
dropKotlinCrashEvent(modifiedEvent as NSExceptionSentryEvent?) as CocoaSentryEvent?
}
}

val sdkName = options.sdk?.name ?: BuildKonfig.SENTRY_KMP_COCOA_SDK_NAME
val sdkVersion = options.sdk?.version ?: BuildKonfig.VERSION_NAME
PrivateSentrySDKOnly.setSdkName(sdkName, sdkVersion)

beforeBreadcrumb = { cocoaBreadcrumb ->
if (options.beforeBreadcrumb == null) {
cocoaBreadcrumb
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ actual abstract class BaseSentryTest {
actual fun sentryInit(optionsConfiguration: OptionsConfiguration) {
Sentry.init(optionsConfiguration)
}

actual fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) {
Sentry.initWithPlatformOptions(platformOptionsConfiguration)
}
}
Loading
Loading