diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 6989e01f3..092f6c8a5 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -34,7 +34,7 @@ jobs: # upload_artifacts defaults to true, so no need to explicitly set secrets: GRADLE_ENCRYPTION_KEY: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - + check-workflow-status: name: Check Workflow Status runs-on: ubuntu-latest diff --git a/.github/workflows/reusable-android-build.yml b/.github/workflows/reusable-android-build.yml index d6077542e..5c2b4b92e 100644 --- a/.github/workflows/reusable-android-build.yml +++ b/.github/workflows/reusable-android-build.yml @@ -9,6 +9,8 @@ on: required: false DATADOG_CLIENT_TOKEN: required: false + CODECOV_TOKEN: + required: false inputs: upload_artifacts: description: 'Whether to upload build and Detekt artifacts' @@ -67,9 +69,17 @@ jobs: echo "datadogApplicationId=$DATADOG_APPLICATION_ID" >> ./secrets.properties echo "datadogClientToken=$DATADOG_CLIENT_TOKEN" >> ./secrets.properties - name: Run Spotless, Detekt, Build, Lint, and Local Tests - run: ./gradlew :app:spotlessCheck :app:detekt :app:lintFdroidDebug :app:lintGoogleDebug :app:assembleDebug :app:testFdroidDebug :app:testGoogleDebug --configuration-cache --scan + run: ./gradlew :app:spotlessCheck :app:detekt :app:lintFdroidDebug :app:lintGoogleDebug :app:assembleDebug koverXmlReport --configuration-cache --scan env: VERSION_CODE: ${{ env.VERSION_CODE }} + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: meshtastic/Meshtastic-Android + files: app/build/reports/kover/xml/report.xml + - name: Upload F-Droid debug artifact if: ${{ inputs.upload_artifacts }} uses: actions/upload-artifact@v4 diff --git a/.github/workflows/reusable-android-test.yml b/.github/workflows/reusable-android-test.yml index a105c191d..c41ff3abf 100644 --- a/.github/workflows/reusable-android-test.yml +++ b/.github/workflows/reusable-android-test.yml @@ -70,4 +70,4 @@ jobs: with: name: android-test-reports-api-${{ matrix.api-level }} path: app/build/outputs/androidTest-results/ - retention-days: 14 \ No newline at end of file + retention-days: 14 diff --git a/README.md b/README.md index dfeb79a23..6aa7f1877 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ![GitHub all releases](https://img.shields.io/github/downloads/meshtastic/meshtastic-android/total) [![Android CI](https://github.com/meshtastic/Meshtastic-Android/actions/workflows/pull-request.yml/badge.svg?branch=main)](https://github.com/meshtastic/Meshtastic-Android/actions/workflows/pull-request.yml) +[![codecov](https://codecov.io/gh/meshtastic/Meshtastic-Android/graph/badge.svg)](https://codecov.io/gh/meshtastic/Meshtastic-Android) [![Crowdin](https://badges.crowdin.net/e/f440f1a5e094a5858dd86deb1adfe83d/localized.svg)](https://crowdin.meshtastic.org/android) [![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/Meshtastic-Android)](https://cla-assistant.io/meshtastic/Meshtastic-Android) [![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 264ebdd2b..68261f72a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,6 +34,7 @@ plugins { alias(libs.plugins.secrets) alias(libs.plugins.spotless) alias(libs.plugins.dokka) + alias(libs.plugins.kover) } val keystorePropertiesFile = rootProject.file("keystore.properties") @@ -62,12 +63,18 @@ android { minSdk = Configs.MIN_SDK targetSdk = Configs.TARGET_SDK // Prioritize injected props, then ENV, then fallback to git commit count - versionCode = (project.findProperty("android.injected.version.code")?.toString()?.toInt() - ?: System.getenv("VERSION_CODE")?.toInt() - ?: gitVersionProvider.get().toInt()) - versionName = (project.findProperty("android.injected.version.name")?.toString() - ?: System.getenv("VERSION_NAME") - ?: Configs.VERSION_NAME_BASE) + versionCode = + ( + project.findProperty("android.injected.version.code")?.toString()?.toInt() + ?: System.getenv("VERSION_CODE")?.toInt() + ?: gitVersionProvider.get().toInt() + ) + versionName = + ( + project.findProperty("android.injected.version.name")?.toString() + ?: System.getenv("VERSION_NAME") + ?: Configs.VERSION_NAME_BASE + ) testInstrumentationRunner = "com.geeksville.mesh.TestRunner" buildConfigField("String", "MIN_FW_VERSION", "\"${Configs.MIN_FW_VERSION}\"") buildConfigField("String", "ABS_MIN_FW_VERSION", "\"${Configs.ABS_MIN_FW_VERSION}\"") diff --git a/build.gradle.kts b/build.gradle.kts index 8ba691d94..4843239aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,44 @@ plugins { alias(libs.plugins.protobuf) apply false alias(libs.plugins.secrets) apply false alias(libs.plugins.dokka) apply false + alias(libs.plugins.kover) +} + +kover { + reports { + total { + filters { + excludes { + // Exclude generated classes + classes("*_Impl") + classes("*Binding") + classes("*Factory") + classes("*.BuildConfig") + classes("*.R") + classes("*.R$*") + + // Exclude UI components + annotatedBy("*Preview") + + // Exclude declarations + annotatedBy( + "*.HiltAndroidApp", + "*.AndroidEntryPoint", + "*.Module", + "*.Provides", + "*.Binds", + "*.Composable", + ) + } + } + } + } +} + +dependencies { + kover(project(":app")) + kover(project(":network")) + kover(project(":mesh_service_example")) } tasks.register("clean") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b1a9eb555..afcbd1dd5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ kotlin = "2.2.20" kotlinx-collections-immutable = "0.4.0" kotlinx-coroutines-android = "1.10.2" kotlinx-serialization-json = "1.9.0" +kover = "0.9.1" lifecycle = "2.9.3" location-services = "21.3.0" maps-compose = "6.10.0" @@ -229,6 +230,7 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf-gradle-plugin" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets-gradle-plugin" } spotless = { id = "com.diffplug.spotless", version .ref= "spotless" } diff --git a/mesh_service_example/build.gradle.kts b/mesh_service_example/build.gradle.kts index d2f7ca26a..49684c5fa 100644 --- a/mesh_service_example/build.gradle.kts +++ b/mesh_service_example/build.gradle.kts @@ -23,6 +23,7 @@ plugins { alias(libs.plugins.compose) alias(libs.plugins.protobuf) alias(libs.plugins.detekt) + alias(libs.plugins.kover) } android { diff --git a/network/build.gradle.kts b/network/build.gradle.kts index 392b3420a..e6b38c49f 100644 --- a/network/build.gradle.kts +++ b/network/build.gradle.kts @@ -25,6 +25,7 @@ plugins { alias(libs.plugins.detekt) id("kotlinx-serialization") alias(libs.plugins.dokka) + alias(libs.plugins.kover) } android {