From 8908056d5f5d607bc58dd0b443725a159a4d7efd Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Thu, 14 Aug 2025 16:49:02 +0300 Subject: [PATCH] Add Sentry to `MapLibreAndroidTestApp` (#3716) Co-authored-by: Bart Louwers Co-authored-by: Bart Louwers --- .github/actions/setup-android-ci/action.yml | 87 +++++++++++++++++++ .github/workflows/android-ci.yml | 76 +--------------- .github/workflows/android-testapp-release.yml | 61 +++++++++++++ .../MapLibreAndroidTestApp/build.gradle.kts | 15 +++- .../src/main/AndroidManifest.xml | 62 +++++++++++++ platform/android/buildSrc/build.gradle.kts | 1 + .../src/main/kotlin/SentryConfigPlugin.kt | 62 +++++++++++++ 7 files changed, 288 insertions(+), 76 deletions(-) create mode 100644 .github/actions/setup-android-ci/action.yml create mode 100644 .github/workflows/android-testapp-release.yml create mode 100644 platform/android/buildSrc/src/main/kotlin/SentryConfigPlugin.kt diff --git a/.github/actions/setup-android-ci/action.yml b/.github/actions/setup-android-ci/action.yml new file mode 100644 index 000000000..597370999 --- /dev/null +++ b/.github/actions/setup-android-ci/action.yml @@ -0,0 +1,87 @@ +name: setup-android-ci +description: "Sets up Android CI environment" +runs: + using: "composite" + steps: + - name: Free Disk Space (Ubuntu) + if: startsWith(runner.name, 'GitHub Actions') + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: false + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false + + - name: Validate VERSION + run: .github/scripts/validate-version.sh platform/android/VERSION + working-directory: . + shell: bash + + - run: echo "cmake.dir=$(dirname "$(dirname "$(command -v cmake)")")" >> local.properties + shell: bash + + - uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Get CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.24.1 + ninjaVersion: latest + + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + + - name: npm install + run: npm install --ignore-scripts + working-directory: . + shell: bash + + - name: run platform/android/scripts/generate-style-code.mjs + run: node platform/android/scripts/generate-style-code.mjs + working-directory: . + shell: bash + + - run: | + python3 -m venv venv + source venv/bin/activate + pip3 install pre-commit + shell: bash + + - run: | + source venv/bin/activate + pre-commit run clang-format --all-files + continue-on-error: true # this can mean files are modified, which is not an error + shell: bash + + - run: | + source venv/bin/activate + pre-commit run clang-format --all-files + rm -rf venv + shell: bash + + - uses: infotroph/tree-is-clean@v1 + with: + check_untracked: true + + - uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + append-timestamp: false + max-size: 5G + + - name: restore-gradle-cache + uses: actions/cache@v4 + env: + cache-name: gradle-v1 + with: + path: ~/.gradle + key: ${{ env.cache-name }}-${{ hashFiles('platform/android/buildSrc/src/main/kotlin/maplibre.dependencies.gradle.kts') }}-${{ hashFiles('platform/android/build.gradle.kts') }}-${{ hashFiles('platform/android/local.properties') }}-${{ hashFiles('platform/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + - ${{ env.cache-name }} diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index c158711d4..bd3abc732 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -51,86 +51,12 @@ jobs: IS_LOCAL_DEVELOPMENT: false MLN_ANDROID_STL: c++_static steps: - - name: Free Disk Space (Ubuntu) - if: startsWith(runner.name, 'GitHub Actions') - uses: jlumbroso/free-disk-space@main - with: - tool-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - docker-images: true - swap-storage: false - - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - - name: Validate VERSION - run: .github/scripts/validate-version.sh platform/android/VERSION - working-directory: . - - - run: echo "cmake.dir=$(dirname "$(dirname "$(command -v cmake)")")" >> local.properties - - - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "17" - - - name: Get CMake and Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: 3.24.1 - ninjaVersion: latest - - - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - - - name: npm install - run: npm install --ignore-scripts - working-directory: . - - - name: run platform/android/scripts/generate-style-code.mjs - run: node platform/android/scripts/generate-style-code.mjs - working-directory: . - - - run: | - python3 -m venv venv - source venv/bin/activate - pip3 install pre-commit - - - run: | - source venv/bin/activate - pre-commit run clang-format --all-files - continue-on-error: true # this can mean files are modified, which is not an error - - - run: | - source venv/bin/activate - pre-commit run clang-format --all-files - rm -rf venv - - - uses: infotroph/tree-is-clean@v1 - with: - check_untracked: true - - - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: ${{ github.job }} - append-timestamp: false - max-size: 5G - - - name: restore-gradle-cache - uses: actions/cache@v4 - env: - cache-name: gradle-v1 - with: - path: ~/.gradle - key: ${{ env.cache-name }}-${{ hashFiles('platform/android/buildSrc/src/main/kotlin/maplibre.dependencies.gradle.kts') }}-${{ hashFiles('platform/android/build.gradle.kts') }}-${{ hashFiles('platform/android/local.properties') }}-${{ hashFiles('platform/android/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - - ${{ env.cache-name }} + - uses: ./.github/actions/setup-android-ci - name: Check code style if: matrix.renderer == 'opengl' diff --git a/.github/workflows/android-testapp-release.yml b/.github/workflows/android-testapp-release.yml new file mode 100644 index 000000000..2e089e234 --- /dev/null +++ b/.github/workflows/android-testapp-release.yml @@ -0,0 +1,61 @@ +name: android-testapp-release + +permissions: + contents: write + +on: + workflow_dispatch: + +jobs: + create-android-testapp-release: + runs-on: ubuntu-latest + steps: + - name: Create Release + run: | + gh release create android-testapp-${{ github.sha }} \ + --draft=false \ + --prerelease=false \ + --latest=false \ + --title android-testapp-${{ github.sha }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + android-build-testapp: + needs: [create-android-testapp-release] + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + renderer: [opengl, vulkan] + defaults: + run: + working-directory: platform/android + env: + BUILDTYPE: Debug + IS_LOCAL_DEVELOPMENT: false + MLN_ANDROID_STL: c++_static + SENTRY_ORG: maplibre + SENTRY_PROJECT: maplibre-android + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_ANDROID }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - uses: ./.github/actions/setup-android-ci + + - run: ./gradlew assemble${{ matrix.renderer }}Release + + - name: Install sentry-cli + run: curl -sL https://sentry.io/get-cli/ | sh + + - name: Upload debug symbols to sentry + run: sentry-cli debug-files upload MapLibreAndroidTestApp + + - name: Upload Linux artifact + run: | + gh release upload android-testapp-${{ github.sha }} MapLibreAndroidTestApp/build/outputs/apk/${{ matrix.renderer }}/release/MapLibreAndroidTestApp-${{ matrix.renderer }}-release.apk + env: + GH_TOKEN: ${{ github.token }} diff --git a/platform/android/MapLibreAndroidTestApp/build.gradle.kts b/platform/android/MapLibreAndroidTestApp/build.gradle.kts index a3e8e1996..dbfde37d1 100644 --- a/platform/android/MapLibreAndroidTestApp/build.gradle.kts +++ b/platform/android/MapLibreAndroidTestApp/build.gradle.kts @@ -9,7 +9,6 @@ plugins { id("maplibre.gradle-lint") } - fun obtainTestBuildType(): String { return if (project.hasProperty("testBuildType")) { project.properties["testBuildType"] as String @@ -31,6 +30,7 @@ android { versionName = "6.0.1" testInstrumentationRunner = "org.maplibre.android.InstrumentationRunner" multiDexEnabled = true + manifestPlaceholders["SENTRY_DSN"] = "" } nativeBuild(listOf("example-custom-layer")) @@ -47,6 +47,14 @@ android { isMinifyEnabled = false isShrinkResources = false proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + + packaging { + jniLibs { + keepDebugSymbols += "**/*.so" + } + } + buildConfigField("String", "SENTRY_DSN", "\"" + (System.getenv("SENTRY_DSN") ?: "") + "\"") + manifestPlaceholders["SENTRY_DSN"] = System.getenv("SENTRY_DSN") ?: "" } getByName("release") { isMinifyEnabled = true @@ -54,6 +62,9 @@ android { proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") testProguardFiles("test-proguard-rules.pro") signingConfig = signingConfigs.getByName("debug") + + buildConfigField("String", "SENTRY_DSN", "\"" + (System.getenv("SENTRY_DSN") ?: "") + "\"") + manifestPlaceholders["SENTRY_DSN"] = System.getenv("SENTRY_DSN") ?: "" } } @@ -119,3 +130,5 @@ dependencies { androidTestImplementation(libs.androidxTestCoreKtx) androidTestImplementation(libs.kotlinxCoroutinesTest) } + +apply() diff --git a/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml b/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml index 348e3557e..eeab0b226 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapLibreAndroidTestApp/src/main/AndroidManifest.xml @@ -1283,6 +1283,68 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/buildSrc/build.gradle.kts b/platform/android/buildSrc/build.gradle.kts index c2b851aba..4cdaa0204 100644 --- a/platform/android/buildSrc/build.gradle.kts +++ b/platform/android/buildSrc/build.gradle.kts @@ -14,4 +14,5 @@ dependencies { implementation("com.android.tools.build:gradle:8.6.0") implementation("com.android.application:com.android.application.gradle.plugin:8.6.0") implementation("commons-collections:commons-collections:3.2.2") + implementation("io.sentry:sentry-android-gradle-plugin:5.9.0") } diff --git a/platform/android/buildSrc/src/main/kotlin/SentryConfigPlugin.kt b/platform/android/buildSrc/src/main/kotlin/SentryConfigPlugin.kt new file mode 100644 index 000000000..2659a1828 --- /dev/null +++ b/platform/android/buildSrc/src/main/kotlin/SentryConfigPlugin.kt @@ -0,0 +1,62 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project + +class SentryConditionalPlugin : Plugin { + override fun apply(project: Project) { + val sentryProject = System.getenv("SENTRY_PROJECT") + + if (sentryProject.isNullOrBlank()) { + println("SENTRY_PROJECT environment variable not set. Skipping Sentry plugin application.") + return + } + + // Apply the Sentry plugin via plugin id + val sentryPluginId = "io.sentry.android.gradle" + try { + project.pluginManager.apply(sentryPluginId) + } catch (t: Throwable) { + project.logger.warn("Could not apply Sentry plugin ($sentryPluginId): ${t.message}") + return + } + + // Configure Sentry extension using reflection to avoid compile-time dependency + val sentryExt = project.extensions.findByName("sentry") + if (sentryExt == null) { + project.logger.warn("Sentry extension not found after applying the plugin. Skipping configuration.") + return + } + + fun setProperty(ext: Any, propertyGetterName: String, value: Any?) { + try { + val getter = ext.javaClass.methods.firstOrNull { it.name == propertyGetterName && it.parameterCount == 0 } + ?: return + val propObj = getter.invoke(ext) ?: return + val setMethod = propObj.javaClass.methods.firstOrNull { it.name == "set" && it.parameterCount == 1 } + ?: return + setMethod.invoke(propObj, value) + } catch (_: Throwable) { + // Ignore if property isn't present on current plugin version + } + } + + val propertiesToSet = mapOf( + "getDebug" to true, + "getOrg" to System.getenv("SENTRY_ORG"), + "getProjectName" to System.getenv("SENTRY_PROJECT"), + "getAuthToken" to System.getenv("SENTRY_AUTH_TOKEN"), + "getIncludeProguardMapping" to true, + "getAutoUploadProguardMapping" to true, + "getDexguardEnabled" to false, + "getUploadNativeSymbols" to true, + "getAutoUploadNativeSymbols" to false, + "getIncludeNativeSources" to true, + "getIncludeSourceContext" to true, + "getIncludeDependenciesReport" to true, + "getTelemetry" to false + ) + + propertiesToSet.forEach { (getterName, value) -> + setProperty(sentryExt, getterName, value) + } + } +}