Skip to content

Commit

Permalink
feat: override native options (#221)
Browse files Browse the repository at this point in the history
* Add native options

* Update

* Update

* Update

* Format

* Update

* Update

* Format code

* Update

* Update

* Update changelog

* Format code

* update

* Update api

* Update

* Format code

* Update

* Update

* Update comments

* Fix detekt

* Update

* Add review

* Update

* Fix test

* Update samples

* Update

* Update

* Format code

* Update

---------

Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
  • Loading branch information
buenaflor and getsentry-bot authored May 15, 2024
1 parent dc19da0 commit 1c5183c
Show file tree
Hide file tree
Showing 50 changed files with 628 additions and 121 deletions.
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

0 comments on commit 1c5183c

Please sign in to comment.