diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 98c8ff1c..8f81a5dd 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -25,6 +25,13 @@ ], "groupName": "kotlin" }, + { + "matchPackagePatterns": [ + "^io.sentry", + "^sentry", + ], + "groupName": "sentry" + }, { "managers": [ "gradle" @@ -81,6 +88,17 @@ ], "datasourceTemplate": "docker", "depNameTemplate": "jetbrains/qodana-jvm-android", + }, + { + "fileMatch": [ + "gradle/libs.versions.toml" + ], + "matchStrings": [ + "sentry-sdk = \"(?.*)\"" + ], + "datasourceTemplate": "maven", + "depNameTemplate": "io.sentry:sentry-android", + "registryUrlTemplate": "https://repo1.maven.org/maven2/", } ] } diff --git a/.github/workflows/baseline-profile.yml b/.github/workflows/baseline-profile.yml index 1ecbaaf9..7bc11a61 100644 --- a/.github/workflows/baseline-profile.yml +++ b/.github/workflows/baseline-profile.yml @@ -61,6 +61,8 @@ jobs: # This allows us to build most of what we need without the emulator running # and using resources - name: Build app and benchmark + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} run: ./gradlew :benchmark:assembleBenchmark :android:assembleBenchmark # Now use reactivecircus/android-emulator-runner to spin up an emulator. We're gonna use it again diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 539cbe8f..6067f81f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,8 @@ jobs: - name: Build release app uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0 + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} with: arguments: --no-configuration-cache --stacktrace collectReleaseApks gradle-home-cache-cleanup: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77876afe..0703d101 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,8 +57,10 @@ jobs: - name: Build release assets shell: bash + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} run: | - ./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle + ./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle -PsentryUploadMappings - name: Clean secrets run: scripts/signing-cleanup.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f4d982..c9892012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +* Introduce [Sentry](https://sentry.io) for error reporting and performance monitoring + ## [1.22.0] - 2023-03-02 ### Changed diff --git a/android/build.gradle.kts b/android/build.gradle.kts index f729938d..a95bb29a 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -11,6 +11,7 @@ plugins { id("dev.msfjarvis.claw.rename-artifacts") id("dev.msfjarvis.claw.kotlin-android") id("dev.msfjarvis.claw.kotlin-kapt") + id("dev.msfjarvis.claw.sentry") id("dev.msfjarvis.claw.versioning-plugin") alias(libs.plugins.anvil) alias(libs.plugins.whetstone) diff --git a/android/src/release/AndroidManifest.xml b/android/src/release/AndroidManifest.xml new file mode 100644 index 00000000..855b262e --- /dev/null +++ b/android/src/release/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 45b5a81f..09e5daba 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Harsh Shandilya. + * Copyright © 2022-2023 Harsh Shandilya. * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT. @@ -59,6 +59,10 @@ gradlePlugin { id = "dev.msfjarvis.claw.rename-artifacts" implementationClass = "dev.msfjarvis.claw.gradle.RenameArtifactsPlugin" } + register("sentry") { + id = "dev.msfjarvis.claw.sentry" + implementationClass = "dev.msfjarvis.claw.gradle.SentryPlugin" + } register("spotless") { id = "dev.msfjarvis.claw.spotless" implementationClass = "dev.msfjarvis.claw.gradle.SpotlessPlugin" @@ -80,6 +84,7 @@ dependencies { implementation(libs.build.detekt) implementation(libs.build.kotlin.gradle) implementation(libs.build.semver) + implementation(libs.build.sentry) implementation(libs.build.spotless) implementation(libs.build.vcu) implementation(libs.build.versions) diff --git a/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt b/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt new file mode 100644 index 00000000..af8f342d --- /dev/null +++ b/build-logic/src/main/kotlin/dev/msfjarvis/claw/gradle/SentryPlugin.kt @@ -0,0 +1,71 @@ +/* + * Copyright © 2023 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.gradle + +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import io.sentry.android.gradle.extensions.InstrumentationFeature +import io.sentry.android.gradle.extensions.SentryPluginExtension +import java.util.EnumSet +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.getByType + +@Suppress("Unused") +class SentryPlugin : Plugin { + + override fun apply(project: Project) { + project.pluginManager.withPlugin("com.android.application") { + val catalog = project.extensions.getByType() + val libs = catalog.named("libs") + project.extensions.configure { + onVariants(selector()) { variant -> + val sentryDsn = project.providers.environmentVariable(SENTRY_DSN_PROPERTY) + if (sentryDsn.isPresent) { + variant.manifestPlaceholders.put("sentryDsn", sentryDsn.get()) + } + } + } + project.plugins.apply(io.sentry.android.gradle.SentryPlugin::class) + project.extensions.configure { + val enableMappings = + project.providers.gradleProperty(SENTRY_UPLOAD_MAPPINGS_PROPERTY).isPresent + includeProguardMapping.set(enableMappings) + autoUploadProguardMapping.set(enableMappings) + uploadNativeSymbols.set(false) + autoUploadNativeSymbols.set(false) + includeNativeSources.set(false) + ignoredVariants.set(emptySet()) + ignoredBuildTypes.set(setOf("debug")) + ignoredFlavors.set(emptySet()) + tracingInstrumentation { + enabled.set(true) + debug.set(false) + forceInstrumentDependencies.set(false) + features.set(EnumSet.allOf(InstrumentationFeature::class.java)) + } + experimentalGuardsquareSupport.set(false) + autoInstallation { + enabled.set(true) + sentryVersion.set(libs.findVersion("sentry-sdk").get().requiredVersion) + } + includeDependenciesReport.set(true) + } + with(project.dependencies) { + addProvider("implementation", platform(libs.findLibrary("sentry-bom").get())) + } + } + } + + private companion object { + + private const val SENTRY_DSN_PROPERTY = "SENTRY_DSN" + private const val SENTRY_UPLOAD_MAPPINGS_PROPERTY = "sentryUploadMappings" + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b1ac7fd..596b7842 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ kotlin = "1.8.10" okhttp = "3.14.9" retrofit = "2.9.0" richtext = "0.16.0" +sentry-sdk = "6.15.0" serialization = "1.5.0" sqldelight = "2.0.0-alpha05" whetstone = "0.6.0-SNAPSHOT" @@ -52,6 +53,7 @@ build-cachefix = "org.gradle.android.cache-fix:org.gradle.android.cache-fix.grad build-detekt = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.22.0" build-kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } build-semver = "com.vdurmont:semver4j:3.1.0" +build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:3.4.2" build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.16.0" build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin:0.7.0" build-versions = "com.github.ben-manes:gradle-versions-plugin:0.46.0" @@ -78,6 +80,7 @@ okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-kotlinxSerializationConverter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" +sentry-bom = { module = "io.sentry:sentry-bom", version.ref = "sentry-sdk" } sqldelight-androidDriver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-extensions-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } sqldelight-jvmDriver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }