├── .github └── workflows │ ├── build.yml │ ├── deploy.yml │ ├── release.yml │ └── websitedeploy.yml ├── .gitignore ├── CrashlyticsUploadScript.png ├── LICENSE.txt ├── README.md ├── THE_PROBLEM.md ├── bugsnag-ios-link ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── co │ └── touchlab │ └── crashkios │ └── BugsnagLinkPlugin.kt ├── bugsnag ├── build.gradle.kts └── src │ ├── androidMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── bugsnag │ │ └── BugsnagCallsActual.kt │ ├── appleMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── bugsnag │ │ ├── BugsnagCallsActual.kt │ │ └── BugsnagConfig.kt │ ├── commonMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── bugsnag │ │ ├── BugsnagCalls.kt │ │ └── BugsnagKotlin.kt │ ├── include │ ├── Bugsnag-old.h │ ├── Bugsnag.h │ ├── BugsnagConfiguration+NSExceptionKt.h │ ├── BugsnagConfiguration.h │ ├── BugsnagError.h │ ├── BugsnagEvent.h │ ├── BugsnagFeatureFlag.h │ ├── BugsnagFeatureFlagStore.h │ ├── BugsnagStackframe.h │ └── Private │ │ ├── BugsnagHandledState+NSExceptionKt.h │ │ └── BugsnagHandledState.h │ └── nativeInterop │ └── cinterop │ └── bugsnag.def ├── build.gradle.kts ├── core ├── build.gradle.kts └── src │ ├── androidMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── core │ │ └── ThreadSafeVar.kt │ ├── appleMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── core │ │ └── ThreadSafeVar.kt │ └── commonMain │ └── kotlin │ └── co │ └── touchlab │ └── crashkios │ └── core │ └── ThreadSafeVar.kt ├── crashlytics-ios-link ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── co │ └── touchlab │ └── crashkios │ └── CrashlyticsLinkPlugin.kt ├── crashlytics ├── build.gradle.kts └── src │ ├── androidMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── crashlytics │ │ └── CrashlyticsCallsActual.kt │ ├── appleMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── crashlytics │ │ ├── Crashlytics.kt │ │ └── CrashlyticsCallsActual.kt │ ├── commonMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkios │ │ └── crashlytics │ │ ├── CrashlyticsCalls.kt │ │ └── CrashlyticsKotlin.kt │ └── nativeInterop │ └── cinterop │ └── crashlytics.def ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlinabort.png ├── kotlinlines.png ├── samples ├── sample-bugsnag │ ├── CrashKiOSSampleIOS │ │ ├── .gitignore │ │ ├── CrashKiOSSampleIOS.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ │ └── IDETemplateMacros.plist │ │ ├── CrashKiOSSampleIOS.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── CrashKiOSSampleIOS │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.storyboard │ │ │ ├── ContentView.swift │ │ │ ├── Info.plist │ │ │ ├── Preview Content │ │ │ │ └── Preview Assets.xcassets │ │ │ │ │ └── Contents.json │ │ │ └── SceneDelegate.swift │ │ ├── Podfile │ │ └── Podfile.lock │ ├── app │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── co │ │ │ │ └── touchlab │ │ │ │ └── crashkiossamplecrashlog │ │ │ │ ├── MainActivity.kt │ │ │ │ └── SampleApp.kt │ │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── values-night │ │ │ └── themes.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── shared │ │ ├── build.gradle.kts │ │ ├── shared.podspec │ │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── co │ │ │ └── touchlab │ │ │ └── crashkiossample │ │ │ ├── CrashBot.kt │ │ │ └── SampleCommon.kt │ │ └── iosMain │ │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkiossample │ │ └── Helper.kt └── sample-crashlytics │ ├── app │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── co │ │ │ └── touchlab │ │ │ └── crashkiossamplecrashlog │ │ │ ├── MainActivity.kt │ │ │ └── SampleApp.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── ios-spm │ ├── .gitignore │ ├── ios.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ios.xcscheme │ └── ios │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── GoogleService-Info.plist │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── iosApp.swift │ ├── ios │ ├── .gitignore │ ├── Podfile │ ├── Podfile.lock │ ├── ios.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ios.xcscheme │ ├── ios.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── ios │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── GoogleService-Info.plist │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── iosApp.swift │ ├── settings.gradle.kts │ └── shared │ ├── build.gradle.kts │ ├── shared.podspec │ └── src │ ├── commonMain │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkiossample │ │ ├── CrashBot.kt │ │ └── SampleCommon.kt │ ├── commonTest │ └── kotlin │ │ └── co │ │ └── touchlab │ │ └── crashkiossample │ │ └── BasicTest.kt │ └── iosMain │ └── kotlin │ └── co │ └── touchlab │ └── crashkiossample │ └── Helper.kt ├── settings.gradle.kts └── website ├── .gitignore ├── README.md ├── WEB-INF └── web.xml ├── babel.config.js ├── blog └── 2022-12-01-first.md ├── docs ├── BugsnagTutorial.md ├── CrashlyticsTutorial.md ├── add_build_phase.png ├── index.md └── misc │ └── THE_PROBLEM.md ├── docusaurus.config.js ├── intellijstyle.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ ├── FeatureIcons.jsx │ ├── FeaturesBlocks.jsx │ ├── FeaturesZigzag.jsx │ ├── HeroAbout.jsx │ ├── HeroHome.jsx │ ├── Newsletter.jsx │ ├── NewsletterDoc.jsx │ ├── Process.jsx │ ├── Stats.jsx │ └── TouchlabPro.jsx ├── css │ ├── additional-styles │ │ ├── range-slider.css │ │ ├── theme.css │ │ ├── toggle-switch.css │ │ └── utility-patterns.css │ └── custom.css ├── pages │ ├── index.module.css │ └── index.tsx ├── theme │ └── DocPaginator │ │ └── index.js └── utils │ ├── Modal.jsx │ └── Transition.jsx ├── static ├── .nojekyll ├── CNAME ├── componentimg │ ├── bink.jpg │ ├── features-03-image-01.png │ ├── features-03-image-02.png │ ├── features-03-image-03.png │ └── hero.jpg ├── docimages │ ├── crashtest2.jpg │ ├── frog.jpg │ ├── kmmbridge-hero.png │ ├── kotlinabort.png │ ├── kotlinlines.png │ ├── skie-hero1.jpg │ ├── skie-hero2.jpg │ ├── skie-hero3.jpg │ └── stackbig.png └── img │ ├── 6D6750ED-C39C-47B7-991A-1A35A33CBE51.png │ ├── Touchlab_Gradient.png │ ├── blog_icon.svg │ ├── books.svg │ ├── docusaurus.png │ ├── factory_icon.svg │ ├── favicon.ico │ ├── hero-image-01.jpg │ ├── logo.svg │ ├── map_icon.svg │ ├── shovel_icon.svg │ ├── tools_icon.svg │ ├── towel_icon.svg │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── staticwebapp.config.json ├── tailwind.config.js ├── touchlabconfig.js ├── tsconfig.json └── yarn.lock /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | #on: workflow_dispatch 3 | on: 4 | pull_request: 5 | branches: 6 | - "**" 7 | push: 8 | branches: 9 | - "**" 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | os: [macOS-latest] 16 | runs-on: ${{matrix.os}} 17 | steps: 18 | - name: Checkout the repo 19 | uses: actions/checkout@v2 20 | - uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: "17" 24 | - name: Validate Gradle Wrapper 25 | uses: gradle/wrapper-validation-action@v1 26 | - name: Cache gradle 27 | uses: actions/cache@v2 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 31 | restore-keys: | 32 | ${{ runner.os }}-gradle- 33 | - name: Cache konan 34 | uses: actions/cache@v2 35 | with: 36 | path: ~/.konan 37 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 38 | restore-keys: | 39 | ${{ runner.os }}-gradle- 40 | - name: Build 41 | run: ./gradlew build --no-daemon --stacktrace 42 | 43 | env: 44 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" 45 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | # find-release: 6 | # runs-on: ubuntu-latest 7 | # steps: 8 | # - name: Checkout the repo 9 | # uses: actions/checkout@v2 10 | # - name: Read version from gradle.properties 11 | # id: read_version 12 | # uses: christian-draeger/read-properties@1.0.1 13 | # with: 14 | # path: './gradle.properties' 15 | # property: 'VERSION_NAME' 16 | 17 | deploy: 18 | runs-on: macOS-latest 19 | steps: 20 | - name: Checkout the repo 21 | uses: actions/checkout@v2 22 | 23 | - uses: actions/setup-java@v2 24 | with: 25 | distribution: "adopt" 26 | java-version: "17" 27 | 28 | - name: Validate Gradle Wrapper 29 | uses: gradle/wrapper-validation-action@v1 30 | 31 | - name: Cache gradle 32 | uses: actions/cache@v2 33 | with: 34 | path: ~/.gradle/caches 35 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 36 | restore-keys: | 37 | ${{ runner.os }}-gradle- 38 | 39 | - name: Cache konan 40 | uses: actions/cache@v2 41 | with: 42 | path: ~/.konan 43 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 44 | restore-keys: | 45 | ${{ runner.os }}-gradle- 46 | 47 | - name: Publish 48 | run: ./gradlew publish --no-daemon --stacktrace --no-build-cache 49 | env: 50 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 51 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 52 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 53 | 54 | env: 55 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | release: 6 | runs-on: macos-latest 7 | steps: 8 | - name: Checkout the repo 9 | uses: actions/checkout@v2 10 | 11 | - uses: touchlab/read-property@0.1 12 | id: version-name 13 | with: 14 | file: ./gradle.properties 15 | property: VERSION_NAME 16 | 17 | - name: Echo Version 18 | run: echo "${{ steps.version-name.outputs.propVal }}" 19 | 20 | - uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: "17" 24 | - name: Validate Gradle Wrapper 25 | uses: gradle/wrapper-validation-action@v1 26 | - name: Cache gradle 27 | uses: actions/cache@v2 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 31 | restore-keys: | 32 | ${{ runner.os }}-gradle- 33 | 34 | - name: Cache konan 35 | uses: actions/cache@v2 36 | with: 37 | path: ~/.konan 38 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 39 | restore-keys: | 40 | ${{ runner.os }}-gradle- 41 | 42 | - name: Finish Maven Central Release 43 | run: ./gradlew closeAndReleaseRepository --no-daemon --stacktrace --no-build-cache 44 | env: 45 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 46 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 47 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 48 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 49 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 50 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 51 | 52 | - name: Create Release 53 | if: ${{ contains(steps.version-match.outputs.group1, 'SNAPSHOT') == false }} 54 | uses: touchlab/release-action@v1.10.0 55 | with: 56 | tag: ${{ steps.version-name.outputs.propVal }} 57 | 58 | env: 59 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" 60 | -------------------------------------------------------------------------------- /.github/workflows/websitedeploy.yml: -------------------------------------------------------------------------------- 1 | name: Publish the website 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | docusaurus-deploy: 6 | runs-on: macos-latest 7 | steps: 8 | - name: Checkout the repo 9 | uses: actions/checkout@v3 10 | 11 | - uses: actions/setup-java@v2 12 | with: 13 | distribution: "adopt" 14 | java-version: "17" 15 | 16 | - name: Validate Gradle Wrapper 17 | uses: gradle/wrapper-validation-action@v1 18 | 19 | - name: Find Last Release 20 | id: last_release 21 | uses: InsonusK/get-latest-release@v1.0.1 22 | with: 23 | myToken: ${{ github.token }} 24 | view_top: 1 25 | 26 | - name: Build Main 27 | run: ./gradlew replaceValuesDocusaurus -PLATEST_GITHUB_VERSION=${{ steps.last_release.outputs.tag_name }} --no-daemon --stacktrace 28 | env: 29 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" 30 | 31 | - name: Publish website 32 | run: | 33 | git config --global user.email "kgalligan@gmail.com" 34 | git config --global user.name "Kevin Galligan" 35 | cd website 36 | npm install 37 | GIT_USER=ciuser GIT_PASS=${{ secrets.GITHUB_TOKEN }} yarn deploy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Android Studio 36 | *.iws 37 | *.ipr 38 | *.iml 39 | .gradle 40 | /local.properties 41 | .idea 42 | .DS_Store 43 | /build 44 | /captures 45 | .externalNativeBuild 46 | .cxx 47 | 48 | # Keystore files 49 | *.jks 50 | 51 | # Eclipse project files 52 | .classpath 53 | .project 54 | 55 | ## Playgrounds 56 | timeline.xctimeline 57 | playground.xcworkspace 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | 71 | buildboth.sh 72 | Pods 73 | -------------------------------------------------------------------------------- /CrashlyticsUploadScript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlab/CrashKiOS/8141216696297cb2aede28fedad0fbbcead8be0f/CrashlyticsUploadScript.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrashKiOS - Crash reporting for Kotlin/iOS 2 | 3 | See [crashkios.touchlab.co](https://crashkios.touchlab.co/) -------------------------------------------------------------------------------- /THE_PROBLEM.md: -------------------------------------------------------------------------------- 1 | ## The Problem 2 | 3 | Crash reporter clients on iOS take the stack of active threads at the moment of crash. Kotlin on iOS, like Kotlin on the JVM, bubbles exceptions up until they are caught or reach the top of the call stack, at which point an unhandled exception hook is called. Because this differs from how iOS works, the crash report shows the point at which we call into Kotlin from Swift/Objc. 4 | 5 | Abort report 6 | 7 | We want to get the caught exception and record that instead: 8 | 9 | Abort report 10 | 11 | That's what this library is for. -------------------------------------------------------------------------------- /bugsnag-ios-link/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Touchlab. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations under 11 | * the License. 12 | */ 13 | 14 | plugins { 15 | `kotlin-dsl` 16 | kotlin("jvm") 17 | id("java-gradle-plugin") 18 | id("com.vanniktech.maven.publish.base") 19 | id("com.gradle.plugin-publish") 20 | } 21 | 22 | repositories { 23 | gradlePluginPortal() 24 | mavenCentral() 25 | } 26 | 27 | gradlePlugin { 28 | website.set("https://github.com/touchlab/CrashKiOS") 29 | vcsUrl.set("https://github.com/touchlab/CrashKiOS.git") 30 | 31 | description = "CrashKiOS linker params for Bugsnag on iOS" 32 | plugins { 33 | register("bugsnag-ios-link-plugin") { 34 | id = "co.touchlab.crashkios.bugsnaglink" 35 | implementationClass = "co.touchlab.crashkios.BugsnagLinkPlugin" 36 | displayName = "Bugsnag iOS Link Plugin" 37 | tags.set(listOf("kmm", "kotlin", "multiplatform", "mobile", "ios")) 38 | } 39 | } 40 | } 41 | 42 | dependencies { 43 | compileOnly(gradleApi()) 44 | compileOnly(kotlin("gradle-plugin")) 45 | testImplementation(kotlin("test")) 46 | } 47 | 48 | java { 49 | toolchain { 50 | languageVersion.set(JavaLanguageVersion.of(8)) 51 | } 52 | } 53 | 54 | val GROUP: String by project 55 | val VERSION_NAME: String by project 56 | 57 | group = GROUP 58 | version = VERSION_NAME 59 | 60 | mavenPublishing { 61 | publishToMavenCentral() 62 | val releaseSigningEnabled = 63 | project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true 64 | if (releaseSigningEnabled) signAllPublications() 65 | pomFromGradleProperties() 66 | } -------------------------------------------------------------------------------- /bugsnag-ios-link/src/main/kotlin/co/touchlab/crashkios/BugsnagLinkPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Touchlab. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations under 11 | * the License. 12 | */ 13 | 14 | package co.touchlab.crashkios 15 | 16 | import org.gradle.api.Plugin 17 | import org.gradle.api.Project 18 | import org.gradle.kotlin.dsl.getByType 19 | import org.jetbrains.kotlin.gradle.dsl.KotlinArtifactsExtension 20 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 21 | import org.jetbrains.kotlin.gradle.dsl.KotlinNativeFrameworkConfig 22 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 23 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 24 | import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.kotlinArtifactsExtension 25 | 26 | internal val Project.kotlinExtension: KotlinMultiplatformExtension get() = extensions.getByType() 27 | 28 | @Suppress("unused") 29 | class BugsnagLinkPlugin : Plugin { 30 | override fun apply(project: Project): Unit = project.withKotlinMultiplatformPlugin { 31 | val linkerArgs = "-U _OBJC_CLASS_\$_BugsnagHandledState " + 32 | "-U _OBJC_CLASS_\$_Bugsnag " + 33 | "-U _OBJC_CLASS_\$_BugsnagStackframe " + 34 | "-U _OBJC_CLASS_\$_FIRStackFrame " + 35 | "-U _OBJC_CLASS_\$_BugsnagFeatureFlag " + 36 | "-U _OBJC_CLASS_\$_BugsnagError" 37 | afterEvaluate { 38 | project.kotlinExtension.crashLinkerConfig(linkerArgs) 39 | project.kotlinArtifactsExtension.crashLinkerConfigArtifacts(linkerArgs) 40 | } 41 | } 42 | } 43 | 44 | private fun Project.withKotlinMultiplatformPlugin(action: Project.() -> Unit) { 45 | pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") { 46 | action() 47 | } 48 | } 49 | 50 | private fun KotlinArtifactsExtension.crashLinkerConfigArtifacts(linkerOpts: String) { 51 | artifactConfigs.withType(KotlinNativeFrameworkConfig::class.java).configureEach { 52 | if (!isStatic) { 53 | toolOptions { 54 | freeCompilerArgs.add("-linker-options") 55 | freeCompilerArgs.add(linkerOpts) 56 | } 57 | } 58 | } 59 | } 60 | 61 | private fun KotlinMultiplatformExtension.crashLinkerConfig(linkerOpts: String) { 62 | targets.withType(KotlinNativeTarget::class.java).configureEach { 63 | val hasDynamicFrameworks = binaries.any { it is Framework && !it.isStatic } 64 | 65 | if (hasDynamicFrameworks) { 66 | compilations.getByName("main").kotlinOptions.freeCompilerArgs += listOf("-linker-options", linkerOpts) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /bugsnag/src/androidMain/kotlin/co/touchlab/crashkios/bugsnag/BugsnagCallsActual.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.bugsnag 2 | 3 | import com.bugsnag.android.Bugsnag 4 | 5 | actual class BugsnagCallsActual : BugsnagCalls { 6 | override fun logMessage(message: String) { 7 | Bugsnag.leaveBreadcrumb(message) 8 | } 9 | 10 | override fun sendHandledException(throwable: Throwable) { 11 | Bugsnag.notify(throwable) 12 | } 13 | 14 | override fun sendFatalException(throwable: Throwable) { 15 | Bugsnag.notify(throwable) 16 | } 17 | 18 | override fun setCustomValue(section: String, key: String, value: Any) { 19 | Bugsnag.addMetadata(section, key, value) 20 | } 21 | } -------------------------------------------------------------------------------- /bugsnag/src/appleMain/kotlin/co/touchlab/crashkios/bugsnag/BugsnagCallsActual.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.bugsnag 2 | 3 | import com.rickclephas.kmp.nsexceptionkt.core.asNSException 4 | import com.rickclephas.kmp.nsexceptionkt.core.causes 5 | import kotlinx.cinterop.ExperimentalForeignApi 6 | 7 | @OptIn(ExperimentalForeignApi::class) 8 | actual class BugsnagCallsActual : BugsnagCalls { 9 | 10 | override fun logMessage(message: String) { 11 | Bugsnag.leaveBreadcrumbWithMessage(message) 12 | } 13 | 14 | override fun sendHandledException(throwable: Throwable) { 15 | sendException(throwable, true) 16 | } 17 | 18 | override fun sendFatalException(throwable: Throwable) { 19 | sendException(throwable, false) 20 | } 21 | 22 | override fun setCustomValue(section: String, key: String, value: Any) { 23 | Bugsnag.addMetadata(value, key, section) 24 | } 25 | 26 | private fun sendException(throwable: Throwable, handled: Boolean) { 27 | val exception = throwable.asNSException() 28 | val causes = throwable.causes.map { it.asNSException() } 29 | // Notify will persist unhandled events, so we can safely terminate afterwards. 30 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/Client/BugsnagClient.m#L744 31 | Bugsnag.notify(exception) { event -> 32 | if (event == null) return@notify true 33 | 34 | if (handled) { 35 | event.severity = BSGSeverity.BSGSeverityWarning 36 | } else { 37 | event.unhandled = true 38 | event.severity = BSGSeverity.BSGSeverityError 39 | } 40 | 41 | if (causes.isNotEmpty()) { 42 | event.errors += causes.map { it.asBugsnagError() } 43 | } 44 | 45 | true 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /bugsnag/src/appleMain/kotlin/co/touchlab/crashkios/bugsnag/BugsnagConfig.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalForeignApi::class) 2 | 3 | package co.touchlab.crashkios.bugsnag 4 | 5 | import com.rickclephas.kmp.nsexceptionkt.core.asNSException 6 | import com.rickclephas.kmp.nsexceptionkt.core.causes 7 | import com.rickclephas.kmp.nsexceptionkt.core.wrapUnhandledExceptionHook 8 | import kotlinx.cinterop.ExperimentalForeignApi 9 | import platform.Foundation.NSException 10 | 11 | public fun startBugsnag(config: BugsnagConfiguration){ 12 | configureBugsnag(config) 13 | Bugsnag.startWithConfiguration(config) 14 | setBugsnagUnhandledExceptionHook() 15 | enableBugsnag() 16 | } 17 | 18 | /** 19 | * Configures Bugsnag to ignore the Kotlin termination crash. 20 | */ 21 | public fun configureBugsnag(config: BugsnagConfiguration) { 22 | NSExceptionKt_OverrideBugsnagHandledStateOriginalUnhandledValue() 23 | NSExceptionKt_BugsnagConfigAddOnSendErrorBlock(config) { event -> 24 | if (event == null) return@NSExceptionKt_BugsnagConfigAddOnSendErrorBlock true 25 | !event.unhandled || event.featureFlags.none { (it as BugsnagFeatureFlag).name == kotlinCrashedFeatureFlag } 26 | } 27 | config.clearFeatureFlagWithName(kotlinCrashedFeatureFlag) 28 | } 29 | 30 | /** 31 | * Sets the unhandled exception hook such that all unhandled exceptions are logged to Bugsnag as fatal exceptions. 32 | * If an unhandled exception hook was already set, that hook will be invoked after the exception is logged. 33 | * Note: once the exception is logged the program will be terminated. 34 | * @see wrapUnhandledExceptionHook 35 | */ 36 | public fun setBugsnagUnhandledExceptionHook(): Unit = wrapUnhandledExceptionHook { throwable -> 37 | val exception = throwable.asNSException() 38 | val causes = throwable.causes.map { it.asNSException() } 39 | // Notify will persist unhandled events, so we can safely terminate afterwards. 40 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/Client/BugsnagClient.m#L744 41 | Bugsnag.notify(exception) { event -> 42 | if (event == null) return@notify true 43 | event.unhandled = true 44 | event.severity = BSGSeverity.BSGSeverityError 45 | if (causes.isNotEmpty()) { 46 | event.errors += causes.map { it.asBugsnagError() } 47 | } 48 | true 49 | } 50 | Bugsnag.addFeatureFlagWithName(kotlinCrashedFeatureFlag) 51 | } 52 | 53 | /** 54 | * Feature flag used to mark the Kotlin termination crash. 55 | */ 56 | private const val kotlinCrashedFeatureFlag = "crashkios.kotlin_crashed" 57 | 58 | /** 59 | * Converts `this` [NSException] to a [BugsnagError]. 60 | */ 61 | internal fun NSException.asBugsnagError(): BugsnagError = BugsnagError().apply { 62 | errorClass = name 63 | errorMessage = reason 64 | stacktrace = BugsnagStackframe.stackframesWithCallStackReturnAddresses(callStackReturnAddresses) 65 | type = BSGErrorType.BSGErrorTypeCocoa 66 | } 67 | -------------------------------------------------------------------------------- /bugsnag/src/commonMain/kotlin/co/touchlab/crashkios/bugsnag/BugsnagCalls.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.bugsnag 2 | 3 | interface BugsnagCalls { 4 | fun logMessage(message: String) 5 | fun sendHandledException(throwable: Throwable) 6 | fun sendFatalException(throwable: Throwable) 7 | fun setCustomValue(section: String, key: String, value: Any) 8 | } 9 | 10 | expect class BugsnagCallsActual() : BugsnagCalls -------------------------------------------------------------------------------- /bugsnag/src/commonMain/kotlin/co/touchlab/crashkios/bugsnag/BugsnagKotlin.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.bugsnag 2 | 3 | import co.touchlab.crashkios.core.ThreadSafeVar 4 | 5 | object BugsnagKotlin { 6 | var implementation: BugsnagCalls by ThreadSafeVar(EmptyCalls) 7 | 8 | fun logMessage(message: String) { 9 | implementation.logMessage(message) 10 | } 11 | 12 | fun sendHandledException(throwable: Throwable) { 13 | implementation.sendHandledException(throwable) 14 | } 15 | 16 | fun sendFatalException(throwable: Throwable) { 17 | implementation.sendFatalException(throwable) 18 | } 19 | 20 | fun setCustomValue(section: String, key: String, value: Any) { 21 | implementation.setCustomValue(section, key, value) 22 | } 23 | } 24 | 25 | /** 26 | * Call in startup code in an actual app. Tests should generally skip this. In Kotlin/Native, not calling this 27 | * for tests avoids linker issues. 28 | */ 29 | fun enableBugsnag(){ 30 | BugsnagKotlin.implementation = BugsnagCallsActual() 31 | } 32 | 33 | internal object EmptyCalls : BugsnagCalls { 34 | override fun logMessage(message: String) { 35 | 36 | } 37 | 38 | override fun sendHandledException(throwable: Throwable) { 39 | 40 | } 41 | 42 | override fun sendFatalException(throwable: Throwable) { 43 | 44 | } 45 | 46 | override fun setCustomValue(section: String, key: String, value: Any) { 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagConfiguration+NSExceptionKt.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | // We need the following wrapper function because of 6 | // https://github.com/JetBrains/kotlin/blob/7bc0132cca92464344ded194f2273c56699f99ca/kotlin-native/runtime/src/main/cpp/ObjCExport.mm#L515 7 | void NSExceptionKt_BugsnagConfigAddOnSendErrorBlock(BugsnagConfiguration* config, BugsnagOnSendErrorBlock block) { 8 | [config addOnSendErrorBlock:^BOOL(BugsnagEvent * _Nonnull event) { 9 | return block(event); 10 | }]; 11 | } 12 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagConfiguration.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/include/Bugsnag/BugsnagConfiguration.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | #import 19 | 20 | typedef BOOL (^BugsnagOnErrorBlock)(BugsnagEvent *_Nonnull event); 21 | 22 | typedef BOOL (^BugsnagOnSendErrorBlock)(BugsnagEvent *_Nonnull event); 23 | 24 | typedef id BugsnagOnSendErrorRef; 25 | 26 | @interface BugsnagConfiguration : NSObject 27 | 28 | - (BugsnagOnSendErrorRef)addOnSendErrorBlock:(BugsnagOnSendErrorBlock)block; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagError.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/include/Bugsnag/BugsnagError.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | #import 19 | 20 | typedef NS_OPTIONS(NSUInteger, BSGErrorType) { 21 | BSGErrorTypeCocoa, 22 | BSGErrorTypeC, 23 | BSGErrorTypeReactNativeJs 24 | }; 25 | 26 | @interface BugsnagError : NSObject 27 | 28 | @property (copy, nullable, nonatomic) NSString *errorClass; 29 | @property (copy, nullable, nonatomic) NSString *errorMessage; 30 | @property (copy, nonnull, nonatomic) NSArray *stacktrace; 31 | @property (nonatomic) BSGErrorType type; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagEvent.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/include/Bugsnag/BugsnagEvent.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | #import 19 | #import 20 | 21 | typedef NS_ENUM(NSUInteger, BSGSeverity) { 22 | BSGSeverityError, 23 | BSGSeverityWarning, 24 | BSGSeverityInfo, 25 | }; 26 | 27 | @interface BugsnagEvent : NSObject 28 | 29 | @property (readwrite, nonatomic) BSGSeverity severity; 30 | @property (readwrite, copy, nonnull, nonatomic) NSArray *errors; 31 | @property (readonly, strong, nonnull, nonatomic) NSArray *featureFlags; 32 | @property (readwrite, nonatomic) BOOL unhandled; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagFeatureFlag.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/include/Bugsnag/BugsnagFeatureFlag.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | 19 | @interface BugsnagFeatureFlag : NSObject 20 | 21 | @property (readonly, nonatomic, nonnull) NSString *name; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagFeatureFlagStore.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/include/Bugsnag/BugsnagFeatureFlagStore.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | #import 19 | 20 | @interface BugsnagConfiguration () 21 | 22 | - (void)clearFeatureFlagWithName:(NSString *_Nullable)name; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /bugsnag/src/include/BugsnagStackframe.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/bd0465cd0e753ca42eef59fef4d5ceda80da1222/Bugsnag/include/Bugsnag/BugsnagStackframe.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | 19 | @interface BugsnagStackframe : NSObject 20 | 21 | + (NSArray *_Nonnull)stackframesWithCallStackReturnAddresses:(NSArray *_Nonnull)callStackReturnAddresses; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /bugsnag/src/include/Private/BugsnagHandledState+NSExceptionKt.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | BOOL NSExceptionKt_BugsnagHandledStateOriginalUnhandledValue(BugsnagHandledState* self, SEL _cmd) { 5 | return self.unhandled; 6 | } 7 | 8 | // In Bugsnag 6.26.2 and above we need to override the originalUnhandledValue property. 9 | // By default it will prevent our exceptions from being stored to disk. 10 | // https://github.com/bugsnag/bugsnag-cocoa/pull/1549 11 | void NSExceptionKt_OverrideBugsnagHandledStateOriginalUnhandledValue() { 12 | Method method = class_getInstanceMethod([BugsnagHandledState class], @selector(originalUnhandledValue)); 13 | method_setImplementation(method, (IMP)NSExceptionKt_BugsnagHandledStateOriginalUnhandledValue); 14 | } -------------------------------------------------------------------------------- /bugsnag/src/include/Private/BugsnagHandledState.h: -------------------------------------------------------------------------------- 1 | // The following are snippets from the Bugsnag Cocoa SDK used to generate Kotlin stubs. 2 | // 3 | // https://github.com/bugsnag/bugsnag-cocoa/blob/6bcd46f5f8dc06ac26537875d501f02b27d219a9/Bugsnag/Payload/BugsnagHandledState.h 4 | // 5 | // Copyright (c) 2012 Bugsnag, https://bugsnag.com/ 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the "Software"), 9 | // to deal in the Software without restriction, including without limitation 10 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | // and/or sell copies of the Software, and to permit persons to whom the Software 12 | // is furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | 17 | #import 18 | 19 | @interface BugsnagHandledState : NSObject 20 | 21 | @property(nonatomic) BOOL unhandled; 22 | 23 | @end -------------------------------------------------------------------------------- /bugsnag/src/nativeInterop/cinterop/bugsnag.def: -------------------------------------------------------------------------------- 1 | package = co.touchlab.crashkios.bugsnag 2 | language = Objective-C 3 | headers = Bugsnag.h BugsnagConfiguration.h BugsnagConfiguration+NSExceptionKt.h BugsnagError.h BugsnagEvent.h \ 4 | BugsnagFeatureFlag.h BugsnagFeatureFlagStore.h BugsnagStackframe.h Private/BugsnagHandledState.h \ 5 | Private/BugsnagHandledState+NSExceptionKt.h 6 | 7 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) apply false 3 | alias(libs.plugins.maven.publish) apply false 4 | alias(libs.plugins.android.library) apply false 5 | alias(libs.plugins.touchlab.docusaurus.template) 6 | alias(libs.plugins.gradle.publish) apply false 7 | } 8 | 9 | allprojects { 10 | repositories { 11 | mavenCentral() 12 | google() 13 | } 14 | } -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Touchlab 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations under 11 | * the License. 12 | */ 13 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 14 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 15 | 16 | plugins { 17 | id("com.android.library") 18 | kotlin("multiplatform") 19 | } 20 | 21 | val GROUP: String by project 22 | val VERSION_NAME: String by project 23 | 24 | group = GROUP 25 | version = VERSION_NAME 26 | 27 | kotlin { 28 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 29 | compilerOptions { 30 | freeCompilerArgs.add("-Xexpect-actual-classes") 31 | } 32 | androidTarget { 33 | publishAllLibraryVariants() 34 | } 35 | 36 | macosX64() 37 | macosArm64() 38 | iosX64() 39 | iosArm64() 40 | // iosArm32() 41 | iosSimulatorArm64() 42 | watchosArm32() 43 | watchosArm64() 44 | watchosSimulatorArm64() 45 | // watchosX86() 46 | watchosX64() 47 | watchosDeviceArm64() 48 | tvosArm64() 49 | tvosSimulatorArm64() 50 | tvosX64() 51 | 52 | sourceSets { 53 | commonTest { 54 | dependencies { 55 | implementation(kotlin("test")) 56 | } 57 | } 58 | } 59 | } 60 | 61 | android { 62 | namespace = "co.touchlab.crashkios.core" 63 | compileSdk = libs.versions.compileSdk.get().toInt() 64 | defaultConfig { 65 | minSdk = libs.versions.minSdk.get().toInt() 66 | } 67 | compileOptions { 68 | sourceCompatibility = JavaVersion.VERSION_1_8 69 | targetCompatibility = JavaVersion.VERSION_1_8 70 | } 71 | } 72 | 73 | tasks.withType { 74 | kotlinOptions.jvmTarget = "1.8" 75 | } 76 | 77 | apply(plugin = "com.vanniktech.maven.publish") 78 | -------------------------------------------------------------------------------- /core/src/androidMain/kotlin/co/touchlab/crashkios/core/ThreadSafeVar.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.core 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | actual class ThreadSafeVar actual constructor(@Volatile private var target: T) { 6 | actual operator fun getValue(thisRef: Any?, property: KProperty<*>): T = target 7 | actual operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 8 | target = value 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/appleMain/kotlin/co/touchlab/crashkios/core/ThreadSafeVar.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.core 2 | 3 | import kotlin.experimental.ExperimentalNativeApi 4 | import kotlin.concurrent.AtomicReference 5 | import kotlin.native.concurrent.freeze 6 | import kotlin.reflect.KProperty 7 | 8 | @OptIn(FreezingIsDeprecated::class, ExperimentalNativeApi::class) 9 | actual class ThreadSafeVar actual constructor(target: T) { 10 | private val atom = AtomicReference(target) 11 | actual operator fun getValue(thisRef: Any?, property: KProperty<*>): T = atom.value 12 | actual operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 13 | if (Platform.memoryModel == MemoryModel.STRICT) { 14 | value.freeze() 15 | } 16 | atom.value = value 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/co/touchlab/crashkios/core/ThreadSafeVar.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.core 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | expect class ThreadSafeVar(target:T) { 6 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T 7 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) 8 | } 9 | -------------------------------------------------------------------------------- /crashlytics-ios-link/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Touchlab. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations under 11 | * the License. 12 | */ 13 | plugins { 14 | `kotlin-dsl` 15 | kotlin("jvm") 16 | id("java-gradle-plugin") 17 | id("com.vanniktech.maven.publish.base") 18 | id("com.gradle.plugin-publish") 19 | } 20 | 21 | repositories { 22 | gradlePluginPortal() 23 | mavenCentral() 24 | } 25 | 26 | gradlePlugin { 27 | website.set("https://github.com/touchlab/CrashKiOS") 28 | vcsUrl.set("https://github.com/touchlab/CrashKiOS.git") 29 | 30 | description = "CrashKiOS linker params for Crashlytics on iOS" 31 | plugins { 32 | register("crashlytics-ios-link-plugin") { 33 | id = "co.touchlab.crashkios.crashlyticslink" 34 | implementationClass = "co.touchlab.crashkios.CrashlyticsLinkPlugin" 35 | displayName = "Crashlytics iOS Link Plugin" 36 | tags.set(listOf("kmm", "kotlin", "multiplatform", "mobile", "ios")) 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | compileOnly(gradleApi()) 43 | compileOnly(kotlin("gradle-plugin")) 44 | testImplementation(kotlin("test")) 45 | } 46 | 47 | java { 48 | toolchain { 49 | languageVersion.set(JavaLanguageVersion.of(8)) 50 | } 51 | } 52 | 53 | val GROUP: String by project 54 | val VERSION_NAME: String by project 55 | 56 | group = GROUP 57 | version = VERSION_NAME 58 | 59 | mavenPublishing { 60 | publishToMavenCentral() 61 | val releaseSigningEnabled = 62 | project.properties["RELEASE_SIGNING_ENABLED"]?.toString()?.equals("false", ignoreCase = true) != true 63 | if (releaseSigningEnabled) signAllPublications() 64 | pomFromGradleProperties() 65 | } -------------------------------------------------------------------------------- /crashlytics-ios-link/src/main/kotlin/co/touchlab/crashkios/CrashlyticsLinkPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Touchlab. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations under 11 | * the License. 12 | */ 13 | 14 | package co.touchlab.crashkios 15 | 16 | import org.gradle.api.Plugin 17 | import org.gradle.api.Project 18 | import org.gradle.kotlin.dsl.getByType 19 | import org.jetbrains.kotlin.gradle.dsl.KotlinArtifactsExtension 20 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 21 | import org.jetbrains.kotlin.gradle.dsl.KotlinNativeFrameworkConfig 22 | import org.jetbrains.kotlin.gradle.plugin.mpp.Framework 23 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 24 | import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.kotlinArtifactsExtension 25 | 26 | internal val Project.kotlinExtension: KotlinMultiplatformExtension get() = extensions.getByType() 27 | 28 | @Suppress("unused") 29 | class CrashlyticsLinkPlugin : Plugin { 30 | 31 | override fun apply(project: Project): Unit = project.withKotlinMultiplatformPlugin { 32 | val linkerArgs = "-U _FIRCLSExceptionRecordNSException" 33 | afterEvaluate { 34 | project.kotlinExtension.crashLinkerConfig(linkerArgs) 35 | project.kotlinArtifactsExtension.crashLinkerConfigArtifacts(linkerArgs) 36 | } 37 | } 38 | } 39 | 40 | private fun Project.withKotlinMultiplatformPlugin(action: Project.() -> Unit) { 41 | pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") { 42 | action() 43 | } 44 | } 45 | 46 | private fun KotlinArtifactsExtension.crashLinkerConfigArtifacts(linkerOpts: String) { 47 | artifactConfigs.withType(KotlinNativeFrameworkConfig::class.java).configureEach { 48 | if (!isStatic) { 49 | toolOptions { 50 | freeCompilerArgs.add("-linker-options") 51 | freeCompilerArgs.add(linkerOpts) 52 | } 53 | } 54 | } 55 | } 56 | 57 | private fun KotlinMultiplatformExtension.crashLinkerConfig(linkerOpts: String) { 58 | targets.withType(KotlinNativeTarget::class.java).configureEach { 59 | val hasDynamicFrameworks = binaries.any { it is Framework && !it.isStatic } 60 | 61 | if (hasDynamicFrameworks) { 62 | compilations.getByName("main").kotlinOptions.freeCompilerArgs += listOf("-linker-options", linkerOpts) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crashlytics/src/androidMain/kotlin/co/touchlab/crashkios/crashlytics/CrashlyticsCallsActual.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.crashlytics 2 | 3 | import com.google.firebase.crashlytics.FirebaseCrashlytics 4 | 5 | actual class CrashlyticsCallsActual : CrashlyticsCalls { 6 | override fun logMessage(message: String) { 7 | FirebaseCrashlytics.getInstance().log(message) 8 | } 9 | 10 | override fun sendHandledException(throwable: Throwable) { 11 | FirebaseCrashlytics.getInstance().recordException(throwable) 12 | } 13 | 14 | override fun sendFatalException(throwable: Throwable) { 15 | FirebaseCrashlytics.getInstance().recordException(throwable) 16 | } 17 | 18 | override fun setCustomValue(key: String, value: Any) { 19 | when(value){ 20 | is Boolean -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 21 | is Double -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 22 | is Float -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 23 | is Int -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 24 | is Long -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 25 | is String -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) 26 | else -> { 27 | throw IllegalArgumentException("Custom value must be of type [Boolean, Double, Float, Int, Long, String]") 28 | } 29 | } 30 | } 31 | 32 | override fun setUserId(identifier: String) { 33 | FirebaseCrashlytics.getInstance().setUserId(identifier) 34 | } 35 | } -------------------------------------------------------------------------------- /crashlytics/src/appleMain/kotlin/co/touchlab/crashkios/crashlytics/Crashlytics.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.crashlytics 2 | 3 | import com.rickclephas.kmp.nsexceptionkt.core.asNSException 4 | import com.rickclephas.kmp.nsexceptionkt.core.causes 5 | import com.rickclephas.kmp.nsexceptionkt.core.wrapUnhandledExceptionHook 6 | import kotlinx.cinterop.UnsafeNumber 7 | import platform.Foundation.NSException 8 | import platform.Foundation.NSNumber 9 | 10 | /** 11 | * Sets the unhandled exception hook such that all unhandled exceptions are logged to Crashlytics as fatal exceptions. 12 | * If an unhandled exception hook was already set, that hook will be invoked after the exception is logged. 13 | * Note: once the exception is logged the program will be terminated. 14 | * @see wrapUnhandledExceptionHook 15 | */ 16 | public fun setCrashlyticsUnhandledExceptionHook(): Unit = wrapUnhandledExceptionHook { throwable -> 17 | CrashlyticsKotlin.sendFatalException(throwable) 18 | } 19 | -------------------------------------------------------------------------------- /crashlytics/src/appleMain/kotlin/co/touchlab/crashkios/crashlytics/CrashlyticsCallsActual.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.crashlytics 2 | 3 | import com.rickclephas.kmp.nsexceptionkt.core.asNSException 4 | import com.rickclephas.kmp.nsexceptionkt.core.getFilteredStackTraceAddresses 5 | import kotlinx.cinterop.UnsafeNumber 6 | import kotlinx.cinterop.convert 7 | 8 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) 9 | actual class CrashlyticsCallsActual : CrashlyticsCalls { 10 | 11 | init { 12 | FIRCheckLinkDependencies() 13 | } 14 | 15 | override fun logMessage(message: String) { 16 | FIRCrashlyticsLog(message) 17 | } 18 | 19 | @OptIn(UnsafeNumber::class) 20 | override fun sendHandledException(throwable: Throwable) { 21 | val exceptionClassName = throwable::class.qualifiedName ?: throwable::class.simpleName ?: "kotlin.Throwable" 22 | FIRCrashlyticsRecordHandledException(exceptionClassName, throwable.message ?: "", throwable.getFilteredStackTraceAddresses().map { 23 | FIRStackFrameWithAddress(it.convert()) 24 | }) 25 | } 26 | 27 | override fun sendFatalException(throwable: Throwable) { 28 | val exception = throwable.asNSException(true) 29 | // The recorded exception is persisted, so we can safely terminate afterwards. 30 | // https://github.com/firebase/firebase-ios-sdk/blob/82f163bd86566f83c5d7572a1c2c0024a04eb4dc/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm#L227 31 | tryFIRCLSExceptionRecordNSException(exception) 32 | } 33 | 34 | override fun setCustomValue(key: String, value: Any) { 35 | FIRCrashlyticsSetCustomValue(key, value) 36 | } 37 | 38 | override fun setUserId(identifier: String) { 39 | FIRCrashlyticsSetUserID(identifier) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crashlytics/src/commonMain/kotlin/co/touchlab/crashkios/crashlytics/CrashlyticsCalls.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.crashlytics 2 | 3 | interface CrashlyticsCalls { 4 | fun logMessage(message: String) 5 | fun sendHandledException(throwable: Throwable) 6 | fun sendFatalException(throwable: Throwable) 7 | fun setCustomValue(key: String, value: Any) 8 | fun setUserId(identifier: String) 9 | } 10 | 11 | expect class CrashlyticsCallsActual() : CrashlyticsCalls 12 | -------------------------------------------------------------------------------- /crashlytics/src/commonMain/kotlin/co/touchlab/crashkios/crashlytics/CrashlyticsKotlin.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.crashkios.crashlytics 2 | 3 | import co.touchlab.crashkios.core.ThreadSafeVar 4 | 5 | object CrashlyticsKotlin { 6 | var implementation: CrashlyticsCalls by ThreadSafeVar(EmptyCalls) 7 | 8 | fun logMessage(message: String) { 9 | implementation.logMessage(message) 10 | } 11 | 12 | fun sendHandledException(throwable: Throwable) { 13 | implementation.sendHandledException(throwable) 14 | } 15 | 16 | fun sendFatalException(throwable: Throwable) { 17 | implementation.sendFatalException(throwable) 18 | } 19 | 20 | fun setCustomValue(key: String, value: Any) { 21 | implementation.setCustomValue(key, value) 22 | } 23 | 24 | fun setUserId(identifier: String) { 25 | implementation.setUserId(identifier) 26 | } 27 | } 28 | 29 | /** 30 | * Call in startup code in an actual app. Tests should generally skip this. In Kotlin/Native, not calling this 31 | * for tests avoids linker issues. 32 | */ 33 | fun enableCrashlytics() { 34 | CrashlyticsKotlin.implementation = CrashlyticsCallsActual() 35 | } 36 | 37 | internal object EmptyCalls : CrashlyticsCalls { 38 | override fun logMessage(message: String) { 39 | 40 | } 41 | 42 | override fun sendHandledException(throwable: Throwable) { 43 | 44 | } 45 | 46 | override fun sendFatalException(throwable: Throwable) { 47 | 48 | } 49 | 50 | override fun setCustomValue(key: String, value: Any) { 51 | 52 | } 53 | 54 | override fun setUserId(identifier: String) { 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4g 2 | android.useAndroidX=true 3 | kotlin.code.style=official 4 | #kotlin.native.home=/Users/kgalligan/temp/kotlin-native-master-hold/dist 5 | SONATYPE_HOST=DEFAULT 6 | RELEASE_SIGNING_ENABLED=true 7 | GROUP=co.touchlab.crashkios 8 | VERSION_NAME=0.9.0 9 | 10 | POM_URL=https://github.com/touchlab/CrashKios 11 | POM_DESCRIPTION=Kotlin Native iOS Crash Report Library 12 | POM_NAME=CrashKios 13 | POM_SCM_URL=https://github.com/touchlab/CrashKios 14 | POM_SCM_CONNECTION=scm:git:git://github.com/touchlab/CrashKios.git 15 | POM_SCM_DEV_CONNECTION=scm:git:git://github.com/touchlab/CrashKios.git 16 | 17 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 18 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 19 | POM_LICENCE_DIST=repo 20 | 21 | POM_DEVELOPER_ID=kpgalligan 22 | POM_DEVELOPER_NAME=Kevin Galligan 23 | POM_DEVELOPER_ORG=Kevin Galligan 24 | POM_DEVELOPER_URL=https://touchlab.co/ 25 | 26 | kotlin.mpp.enableCInteropCommonization=true 27 | kotlin.mpp.commonizerLogLevel=info 28 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | ## SDK Versions 3 | minSdk = "21" 4 | targetSdk = "34" 5 | compileSdk = "34" 6 | 7 | # Dependencies 8 | kotlin = "1.9.24" 9 | android-gradle-plugin = "8.2.1" 10 | mavenPublish = "0.29.0" 11 | touchlab-docusaurus-template = "0.1.10" 12 | gradlePublish = "1.2.1" 13 | nsexceptionKt = "0.1.10" 14 | firebase-crashlytics = "18.4.1" 15 | bugsnag = "5.31.1" 16 | crashkios = "0.8.7" 17 | 18 | # Sample Apps 19 | androidx-core = "1.12.0" 20 | androidx-appcompat = "1.6.1" 21 | androidx-constraintLayout = "2.1.4" 22 | androidx-navigationFragment = "2.7.2" 23 | androidx-navigationUI = "2.7.2" 24 | androidx-coordinatorLayout = "1.2.0" 25 | 26 | [libraries] 27 | nsexceptionKt-core = { module = "com.rickclephas.kmp:nsexception-kt-core", version.ref = "nsexceptionKt" } 28 | firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx",version.ref = "firebase-crashlytics" } 29 | bugsnag-android = { module = "com.bugsnag:bugsnag-android", version.ref = "bugsnag" } 30 | 31 | androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } 32 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } 33 | androidx-constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintLayout" } 34 | androidx-navigationFragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigationFragment" } 35 | androidx-navigationUI = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigationUI" } 36 | androidx-coordinatorLayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorLayout" } 37 | 38 | [plugins] 39 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 40 | maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } 41 | android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } 42 | touchlab-docusaurus-template = { id = "co.touchlab.touchlabtools.docusaurusosstemplate", version.ref = "touchlab-docusaurus-template" } 43 | gradle-publish = { id = "com.gradle.plugin-publish", version.ref = "gradlePublish" } 44 | 45 | # For Samples 46 | crashkios-crashlyticslink = { id = "co.touchlab.crashkios.crashlyticslink", version.ref = "crashkios" } 47 | crashkios-bugsnaglink = { id = "co.touchlab.crashkios.bugsnaglink", version.ref = "crashkios" } 48 | 49 | [bundles] 50 | android = [ 51 | "androidx-core", 52 | "androidx-appcompat", 53 | "androidx-constraintLayout", 54 | "androidx-navigationFragment", 55 | "androidx-navigationUI", 56 | "androidx-coordinatorLayout", 57 | ] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlab/CrashKiOS/8141216696297cb2aede28fedad0fbbcead8be0f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /kotlinabort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlab/CrashKiOS/8141216696297cb2aede28fedad0fbbcead8be0f/kotlinabort.png -------------------------------------------------------------------------------- /kotlinlines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touchlab/CrashKiOS/8141216696297cb2aede28fedad0fbbcead8be0f/kotlinlines.png -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // ___FILENAME___ 8 | // ___PACKAGENAME___ 9 | 10 | // Copyright (c) 2020 Touchlab 11 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // 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. 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CrashKiOSSampleIOS 4 | 5 | // Copyright (c) 2021 Touchlab 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // 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. 12 | 13 | import UIKit 14 | import shared 15 | import Bugsnag 16 | 17 | @UIApplicationMain 18 | class AppDelegate: UIResponder, UIApplicationDelegate { 19 | 20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 21 | // Override point for customization after application launch. 22 | let config = BugsnagConfiguration.loadConfig() 23 | BugsnagConfigKt.startBugsnag(config: config) 24 | 25 | return true 26 | } 27 | 28 | // MARK: UISceneSession Lifecycle 29 | 30 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 31 | // Called when a new scene session is being created. 32 | // Use this method to select a configuration to create the new scene with. 33 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 34 | } 35 | 36 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 37 | // Called when the user discards a scene session. 38 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 39 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // CrashKiOSSampleIOS 4 | 5 | // Copyright (c) 2020 Touchlab 6 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // 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. 12 | 13 | import SwiftUI 14 | import shared 15 | 16 | struct ContentView: View { 17 | 18 | let common: SampleCommon 19 | let cb = CrashBot() 20 | 21 | init() { 22 | self.common = SampleCommon() 23 | } 24 | 25 | var body: some View { 26 | VStack(spacing: 50){ 27 | Button(action: { 28 | self.common.onClick() 29 | }){ 30 | Text("Click Count").padding() 31 | .background(Color.blue) 32 | .foregroundColor(.white) 33 | .font(.title) 34 | } 35 | Button(action: { 36 | self.common.logException() 37 | }){ 38 | Text("Log Exception").padding() 39 | .background(Color.blue) 40 | .foregroundColor(.white) 41 | .font(.title) 42 | } 43 | Button(action: { 44 | self.cb.goCrash() 45 | }){ 46 | Text("Kotlin Crash").padding() 47 | .background(Color.blue) 48 | .foregroundColor(.white) 49 | .font(.title) 50 | } 51 | 52 | Button(action: { 53 | realCrash() 54 | }){ 55 | Text("Swift Crash").padding() 56 | .background(Color.blue) 57 | .foregroundColor(.white) 58 | .font(.title) 59 | } 60 | } 61 | } 62 | } 63 | 64 | func realCrash() { 65 | let numbers = [0] 66 | let _ = numbers[1] 67 | } 68 | 69 | struct ContentView_Previews: PreviewProvider { 70 | static var previews: some View { 71 | ContentView() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | bugsnag 60 | 61 | apiKey 62 | c928b2fb07591784a0b775ff8ea6ba3d 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/CrashKiOSSampleIOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'CrashKiOSSampleIOS' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | pod 'Bugsnag' 9 | plugin 'cocoapods-bugsnag' 10 | 11 | # Pods for CrashKiOSSampleIOS 12 | pod 'shared', :path => '../shared' 13 | 14 | end 15 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/CrashKiOSSampleIOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Bugsnag (6.27.2) 3 | - shared (0.0.1) 4 | 5 | DEPENDENCIES: 6 | - Bugsnag 7 | - shared (from `../shared`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - Bugsnag 12 | 13 | EXTERNAL SOURCES: 14 | shared: 15 | :path: "../shared" 16 | 17 | SPEC CHECKSUMS: 18 | Bugsnag: d7b2e5e78eaf66246d635240c61a6c8a18e3922c 19 | shared: db2bfa2f9309d3a03ed92c95da4d0b121dfc7d02 20 | 21 | PODFILE CHECKSUM: 334109894bf80203c54092dcc680f36bc85b527c 22 | 23 | COCOAPODS: 1.12.1 24 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /google-services.json 3 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Touchlab 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * 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. 9 | */ 10 | 11 | plugins { 12 | id("com.android.application") 13 | kotlin("android") 14 | id("com.bugsnag.android.gradle") 15 | } 16 | 17 | android { 18 | namespace = "co.touchlab.crashkiossamplecrashlog" 19 | compileSdk = projectLibs.versions.compileSdk.get().toInt() 20 | defaultConfig { 21 | applicationId = "co.touchlab.crashkiossamplebugsnag" 22 | minSdk = projectLibs.versions.minSdk.get().toInt() 23 | targetSdk = projectLibs.versions.targetSdk.get().toInt() 24 | versionCode = 1 25 | versionName = "0.0.1" 26 | } 27 | packaging { 28 | resources.excludes.add("META-INF/*.kotlin_module") 29 | } 30 | buildTypes { 31 | getByName("release") { 32 | isMinifyEnabled = false 33 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | } 43 | buildFeatures { 44 | viewBinding = true 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation(project(":shared")) 50 | implementation(projectLibs.bundles.android) 51 | implementation("com.bugsnag:bugsnag-android:5.+") 52 | // implementation(projectLibs.bugsnag) 53 | } 54 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 22 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/src/main/java/co/touchlab/crashkiossamplecrashlog/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Touchlab 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * 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. 9 | */ 10 | 11 | package co.touchlab.crashkiossamplecrashlog 12 | 13 | import androidx.appcompat.app.AppCompatActivity 14 | import android.os.Bundle 15 | import co.touchlab.crashkiossample.CrashBot 16 | import co.touchlab.crashkiossample.SampleCommon 17 | import co.touchlab.crashkiossamplecrashlog.databinding.ActivityMainBinding 18 | import com.bugsnag.android.Bugsnag 19 | 20 | class MainActivity : AppCompatActivity() { 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | val binding = ActivityMainBinding.inflate(layoutInflater) 25 | setContentView(binding.root) 26 | 27 | val sampleCommon = SampleCommon() 28 | binding.clickCount.setOnClickListener{ 29 | sampleCommon.onClick() 30 | } 31 | binding.logException.setOnClickListener{ 32 | sampleCommon.logException() 33 | } 34 | binding.crash.setOnClickListener { 35 | CrashBot().goCrash() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/src/main/java/co/touchlab/crashkiossamplecrashlog/SampleApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Touchlab 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * 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. 9 | */ 10 | 11 | package co.touchlab.crashkiossamplecrashlog 12 | 13 | import android.app.Application 14 | import co.touchlab.crashkios.bugsnag.enableBugsnag 15 | import com.bugsnag.android.Bugsnag 16 | 17 | class SampleApp : Application() { 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | Bugsnag.start(this) 23 | enableBugsnag() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 19 | 25 | 28 | 31 | 32 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /samples/sample-bugsnag/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 |