-
Notifications
You must be signed in to change notification settings - Fork 17
Linking issue fix proposal #93
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
Open
wellingtoncosta
wants to merge
10
commits into
main
Choose a base branch
from
wp/linking-issue
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
64c694e
Shared sink registry
wellingtoncosta 72eb306
Add CrashKiOS Swift package
wellingtoncosta b94b9f4
Rewrite bugsnag module
wellingtoncosta 098fedb
Rewrite crashlytics module
wellingtoncosta 1db8953
Remove linker-flag gradle plugins
wellingtoncosta ccdf2c4
Update samples
wellingtoncosta fd80639
Implement a single API for configuration
wellingtoncosta e6465ca
Drop remaining references to the removed linker plugins
wellingtoncosta 89204c7
Address PR review feedback
wellingtoncosta 2f1580c
Substitute :core in the sample composite builds
wellingtoncosta File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,3 +71,6 @@ fastlane/test_output | |
|
|
||
| buildboth.sh | ||
| Pods | ||
| .build/ | ||
| Package.resolved | ||
| .swiftpm/ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // swift-tools-version: 5.5 | ||
|
|
||
| import PackageDescription | ||
|
|
||
| let package = Package( | ||
| name: "CrashKiOS", | ||
| platforms: [.iOS(.v15), .macOS(.v10_15), .tvOS(.v15), .watchOS(.v7)], | ||
| products: [ | ||
| .library( | ||
| name: "CrashKiOSCrashlytics", | ||
| targets: ["CrashKiOSCrashlytics"] | ||
| ), | ||
| .library( | ||
| name: "CrashKiOSBugsnag", | ||
| targets: ["CrashKiOSBugsnag"] | ||
| ), | ||
| ], | ||
| dependencies: [ | ||
| .package( | ||
| url: "https://github.com/firebase/firebase-ios-sdk.git", | ||
| "11.0.0"..<"13.0.0" | ||
| ), | ||
| .package( | ||
| url: "https://github.com/bugsnag/bugsnag-cocoa.git", | ||
| "6.22.1"..<"7.0.0" | ||
| ), | ||
| ], | ||
| targets: [ | ||
| // Header-only targets: they own the sink protocols (cinterop'd by the Kotlin | ||
| // modules) so Swift sinks compile without importing the app's Kotlin framework. | ||
| .target( | ||
| name: "CrashKiOSCrashlyticsObjC", | ||
| path: "Sources/CrashKiOSCrashlyticsObjC" | ||
| ), | ||
| .target( | ||
| name: "CrashKiOSCrashlytics", | ||
| dependencies: [ | ||
| .target(name: "CrashKiOSCrashlyticsObjC"), | ||
| .product(name: "FirebaseCrashlytics", package: "firebase-ios-sdk"), | ||
| ], | ||
| path: "Sources/CrashKiOSCrashlytics" | ||
| ), | ||
| .target( | ||
| name: "CrashKiOSBugsnagObjC", | ||
| path: "Sources/CrashKiOSBugsnagObjC" | ||
| ), | ||
| .target( | ||
| name: "CrashKiOSBugsnag", | ||
| dependencies: [ | ||
| .target(name: "CrashKiOSBugsnagObjC"), | ||
| .product(name: "Bugsnag", package: "bugsnag-cocoa"), | ||
| ], | ||
| path: "Sources/CrashKiOSBugsnag" | ||
| ), | ||
| ] | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import Foundation | ||
| import ObjectiveC | ||
| import CrashKiOSBugsnagObjC | ||
| import Bugsnag | ||
|
|
||
| /// Feature flag used to mark the Kotlin termination crash. | ||
| /// Public so custom `CrashKiOSBugsnagSink` implementations can honor the same contract. | ||
| public let kotlinCrashedFeatureFlag = "crashkios.kotlin_crashed" | ||
|
|
||
| /// Prepares `config` for Kotlin crash handling: installs the OnSendError filter that | ||
| /// suppresses the duplicate termination crash following a recorded Kotlin fatal, and | ||
| /// works around Bugsnag 6.26.2+ refusing to persist synthetic unhandled events. | ||
| /// | ||
| /// Custom sink implementations MUST call this before `Bugsnag.start(with: config)` — | ||
| /// otherwise every fatal Kotlin exception is reported twice. | ||
| public func configureBugsnagForKotlin(_ config: BugsnagConfiguration) { | ||
| overrideOriginalUnhandledValue() | ||
| config.addOnSendError { event in | ||
| // Drop Bugsnag's own report of the abort that terminates the app after a | ||
| // Kotlin fatal was already recorded (flagged via markFatalCrashRecorded). | ||
| !event.unhandled || !event.featureFlags.contains { $0.name == kotlinCrashedFeatureFlag } | ||
| } | ||
| config.clearFeatureFlag(name: kotlinCrashedFeatureFlag) | ||
| } | ||
|
|
||
| /// Reference `CrashKiOSBugsnagSink` implementation. | ||
| /// | ||
| /// Start Bugsnag through this class so the configuration is prepared for Kotlin | ||
| /// crash handling, then configure CrashKiOS with the returned sink: | ||
| /// ```swift | ||
| /// let sink = BugsnagSink.start(BugsnagConfiguration.loadConfig()) | ||
| /// CrashKiOS.shared.configure(crashReporting: BugsnagCrashReporting(sink: sink)) | ||
| /// ``` | ||
| /// (Add `export("co.touchlab.crashkios:bugsnag")` AND `export("co.touchlab.crashkios:core")` | ||
| /// to the Kotlin framework for clean names.) | ||
| public final class BugsnagSink: NSObject, CrashKiOSBugsnagSink { | ||
|
|
||
| /// Configures `config` via `configureBugsnagForKotlin(_:)`, starts Bugsnag, | ||
| /// and returns the sink. | ||
| public static func start(_ config: BugsnagConfiguration) -> BugsnagSink { | ||
| configureBugsnagForKotlin(config) | ||
| Bugsnag.start(with: config) | ||
| return BugsnagSink() | ||
| } | ||
|
|
||
| public func leaveBreadcrumb(_ message: String) { | ||
| Bugsnag.leaveBreadcrumb(withMessage: message) | ||
| } | ||
|
|
||
| public func notify(with exceptions: [NSException], handled: Bool) { | ||
| guard let exception = exceptions.first else { return } | ||
| // Notify persists unhandled events, so the caller can safely terminate afterwards. | ||
| // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/Client/BugsnagClient.m#L744 | ||
| Bugsnag.notify(exception) { event in | ||
| if handled { | ||
| event.severity = .warning | ||
| } else { | ||
| event.unhandled = true | ||
| event.severity = .error | ||
| } | ||
| event.errors += exceptions.dropFirst().map(BugsnagError.init) | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| public func addMetadata(_ value: Any, key: String, section: String) { | ||
| Bugsnag.addMetadata(value, key: key, section: section) | ||
| } | ||
|
|
||
| public func markFatalCrashRecorded() { | ||
| // Called only by the Kotlin unhandled-exception hook, right before termination — | ||
| // never on direct sendFatalException() calls, which would poison the session and | ||
| // make the OnSendError filter discard later genuine crashes. | ||
| Bugsnag.addFeatureFlag(name: kotlinCrashedFeatureFlag) | ||
| } | ||
| } | ||
|
|
||
| /// In Bugsnag 6.26.2+ the `originalUnhandledValue` property prevents our synthetic | ||
| /// unhandled exceptions from being stored to disk; alias it to `unhandled`. | ||
| /// https://github.com/bugsnag/bugsnag-cocoa/pull/1549 | ||
| private func overrideOriginalUnhandledValue() { | ||
| guard let handledStateClass = NSClassFromString("BugsnagHandledState"), | ||
| let originalMethod = class_getInstanceMethod(handledStateClass, NSSelectorFromString("originalUnhandledValue")), | ||
| let method = class_getInstanceMethod(handledStateClass, NSSelectorFromString("unhandled")) | ||
| else { | ||
| // Loud, not silent: if Bugsnag renames these internals, fatal Kotlin crashes | ||
| // would silently stop reaching the dashboard (>=6.26.2 refuses to persist them). | ||
| assertionFailure("CrashKiOS: BugsnagHandledState internals changed — Kotlin fatal crashes may not be persisted. Update CrashKiOSBugsnag.") | ||
| return | ||
| } | ||
| method_setImplementation(originalMethod, method_getImplementation(method)) | ||
| } | ||
|
|
||
| private extension BugsnagError { | ||
| /// Creates a BugsnagError from a (Kotlin-synthesized) NSException. | ||
| convenience init(_ exception: NSException) { | ||
| self.init() | ||
| errorClass = exception.name.rawValue | ||
| errorMessage = exception.reason | ||
| stacktrace = BugsnagStackframe.stackframes(withCallStackReturnAddresses: exception.callStackReturnAddresses) | ||
| type = .cocoa | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| // We need this empty file, else SPM won't build this header-only target. |
42 changes: 42 additions & 0 deletions
42
Sources/CrashKiOSBugsnagObjC/include/CrashKiOSBugsnagSink.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #ifndef CrashKiOSBugsnagSink_h | ||
| #define CrashKiOSBugsnagSink_h | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| /// Receives crash-reporting events from Kotlin and forwards them to Bugsnag. | ||
| /// | ||
| /// Implemented in Swift, inside the app's build graph — where Bugsnag is already | ||
| /// linked — and handed to the Kotlin side with | ||
| /// `CrashKiOS.shared.configure(crashReporting: BugsnagCrashReporting(sink:))`. | ||
| /// | ||
| /// Contract for implementations (the shipped `BugsnagSink` does all of this): | ||
| /// - MUST NOT throw: Kotlin cannot catch ObjC exceptions thrown across this | ||
| /// boundary (KT-53004), and the fatal path runs inside the unhandled-exception hook. | ||
| /// - MUST prepare the Bugsnag configuration with `configureBugsnagForKotlin(_:)` | ||
| /// (CrashKiOSBugsnag package) before `Bugsnag.start`, otherwise every fatal Kotlin | ||
| /// exception is reported twice (the Kotlin notify plus Bugsnag's own report of the | ||
| /// termination abort). | ||
| @protocol CrashKiOSBugsnagSink <NSObject> | ||
|
|
||
| /// Bugsnag `leaveBreadcrumbWithMessage:`. | ||
| - (void)leaveBreadcrumb:(NSString * _Nonnull)message; | ||
|
|
||
| /// Notify Bugsnag of a Kotlin exception. `exceptions` is `[main, cause, causeOfCause, ...]`; | ||
| /// each element's `callStackReturnAddresses` carries its Kotlin stack trace. | ||
| /// When `handled` is NO the event must be persisted synchronously before returning | ||
| /// (`Bugsnag.notify` does) — the process may terminate right after. | ||
| - (void)notifyWithExceptions:(NSArray<NSException *> * _Nonnull)exceptions handled:(BOOL)handled; | ||
|
|
||
| /// Bugsnag `addMetadata:key:section:`. | ||
| - (void)addMetadata:(id _Nonnull)value key:(NSString * _Nonnull)key section:(NSString * _Nonnull)section; | ||
|
|
||
| /// Called by the Kotlin unhandled-exception hook — and ONLY that hook — right after | ||
| /// the fatal notify, just before the process terminates. Implementations flag the | ||
| /// session (Bugsnag feature flag `crashkios.kotlin_crashed`) so the OnSendError filter | ||
| /// installed by `configureBugsnagForKotlin(_:)` drops Bugsnag's own report of the | ||
| /// termination abort. Direct `sendFatalException()` calls do NOT trigger this. | ||
| - (void)markFatalCrashRecorded; | ||
|
|
||
| @end | ||
|
|
||
| #endif /* CrashKiOSBugsnagSink_h */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import Foundation | ||
| import CrashKiOSCrashlyticsObjC | ||
| import FirebaseCrashlytics | ||
|
|
||
| /// Reference `CrashKiOSCrashlyticsSink` implementation. | ||
| /// | ||
| /// Configure from your `AppDelegate`, right after Firebase is configured and before | ||
| /// any Kotlin code runs: | ||
| /// ```swift | ||
| /// FirebaseApp.configure() | ||
| /// CrashKiOS.shared.configure(crashReporting: CrashlyticsCrashReporting(sink: CrashlyticsSink())) | ||
| /// ``` | ||
| /// (Add `export("co.touchlab.crashkios:crashlytics")` AND `export("co.touchlab.crashkios:core")` | ||
| /// to the framework for clean names.) | ||
| public final class CrashlyticsSink: NSObject, CrashKiOSCrashlyticsSink { | ||
|
|
||
| private let crashlytics: Crashlytics | ||
|
|
||
| public init(_ crashlytics: Crashlytics = Crashlytics.crashlytics()) { | ||
| self.crashlytics = crashlytics | ||
| } | ||
|
|
||
| public func logMessage(_ message: String) { | ||
| crashlytics.log(message) | ||
| } | ||
|
|
||
| public func recordHandledException(withName name: String, reason: String, stackAddresses: [NSNumber]) { | ||
| let model = ExceptionModel(name: name, reason: reason) | ||
| model.stackTrace = stackAddresses.map { StackFrame(address: $0.uintValue) } | ||
| crashlytics.record(exceptionModel: model) | ||
| } | ||
|
|
||
| public func recordFatalException(_ exception: NSException) { | ||
| // Persists synchronously; Crashlytics stores a single fatal per session, so the | ||
| // termination abort that follows is not double-reported. | ||
| FIRCLSExceptionRecordNSException(exception) | ||
| } | ||
|
|
||
| public func setCustomValue(_ value: Any?, forKey key: String) { | ||
| crashlytics.setCustomValue(value, forKey: key) | ||
| } | ||
|
|
||
| public func setUserId(_ identifier: String) { | ||
| crashlytics.setUserID(identifier) | ||
| } | ||
|
|
||
| public func setCollectionEnabled(_ enabled: Bool) { | ||
| crashlytics.setCrashlyticsCollectionEnabled(enabled) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| // We need this empty file, else SPM won't build this header-only target. |
41 changes: 41 additions & 0 deletions
41
Sources/CrashKiOSCrashlyticsObjC/include/CrashKiOSCrashlyticsSink.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #ifndef CrashKiOSCrashlyticsSink_h | ||
| #define CrashKiOSCrashlyticsSink_h | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| /// Receives crash-reporting events from Kotlin and forwards them to Firebase Crashlytics. | ||
| /// | ||
| /// Implemented in Swift, inside the app's build graph — where FirebaseCrashlytics is | ||
| /// already linked — and handed to the Kotlin side with | ||
| /// `CrashKiOS.shared.configure(crashReporting: CrashlyticsCrashReporting(sink:))`. | ||
| /// | ||
| /// Implementations MUST NOT throw: Kotlin cannot catch ObjC exceptions thrown across | ||
| /// this boundary (KT-53004), and the fatal path runs inside the unhandled-exception hook. | ||
| @protocol CrashKiOSCrashlyticsSink <NSObject> | ||
|
|
||
| /// Crashlytics `log:`. | ||
| - (void)logMessage:(NSString * _Nonnull)message; | ||
|
|
||
| /// A non-fatal exception. `addresses` are Kotlin stack-trace return addresses, | ||
| /// mappable to `FIRStackFrame stackFrameWithAddress:`. | ||
| - (void)recordHandledExceptionWithName:(NSString * _Nonnull)name | ||
| reason:(NSString * _Nonnull)reason | ||
| stackAddresses:(NSArray<NSNumber *> * _Nonnull)addresses; | ||
|
|
||
| /// A fatal exception. The process terminates right after this returns — | ||
| /// the record must be persisted synchronously (FIRCLSExceptionRecordNSException does). | ||
| /// `exception.callStackReturnAddresses` carries the Kotlin stack trace. | ||
| - (void)recordFatalException:(NSException * _Nonnull)exception; | ||
|
|
||
| /// Crashlytics `setCustomValue:forKey:`. | ||
| - (void)setCustomValue:(id _Nullable)value forKey:(NSString * _Nonnull)key; | ||
|
|
||
| /// Crashlytics `setUserID:`. | ||
| - (void)setUserId:(NSString * _Nonnull)identifier; | ||
|
|
||
| /// Crashlytics `setCrashlyticsCollectionEnabled:`. | ||
| - (void)setCollectionEnabled:(BOOL)enabled; | ||
|
|
||
| @end | ||
|
|
||
| #endif /* CrashKiOSCrashlyticsSink_h */ |
30 changes: 30 additions & 0 deletions
30
Sources/CrashKiOSCrashlyticsObjC/include/FIRCLSException.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #ifndef CrashKiOSFIRCLSException_h | ||
| #define CrashKiOSFIRCLSException_h | ||
|
|
||
| // Declaration copied from firebase-ios-sdk's private header: | ||
| // https://github.com/firebase/firebase-ios-sdk/blob/main/Crashlytics/Crashlytics/Handlers/FIRCLSException.h | ||
| // | ||
| // Copyright 2019-2020 Google LLC | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
|
|
||
| // Records `exception` as the session's fatal exception and persists it synchronously. | ||
| // Private Crashlytics SPI, resolved HARD at app link time via the FirebaseCrashlytics | ||
| // product this target depends on — nothing dynamic is left for the App Store's | ||
| // deployment-processing strip pass to break (the CrashKiOS <= 0.9.0 failure mode). | ||
| // Fallback if Firebase ever removes it: -[FIRCrashlytics recordOnDemandExceptionModel:]. | ||
| extern void FIRCLSExceptionRecordNSException(NSException *exception); | ||
|
|
||
| #endif /* CrashKiOSFIRCLSException_h */ |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.