From 132a8d6c9e30bf650c4fc796ff6fa43391e8dc2a Mon Sep 17 00:00:00 2001 From: Julien Cristau Date: Fri, 15 May 2026 15:23:42 +0100 Subject: [PATCH 1/2] Bug 1923139 - generate ProGuard UUID in Android builds and embed in manifest. r?#android-reviewers Generate a deterministic UUID from the build ID and variant name, write it to an artifact so it can be used when uploading to sentry in a downstream task, and embed it in AndroidManifest.xml as io.sentry.proguard-uuid so the Sentry SDK picks it up automatically. Differential Revision: https://phabricator.services.mozilla.com/D294704 --- mobile/android/fenix/app/build.gradle | 33 +++++++++++++++++ .../fenix/app/src/main/AndroidManifest.xml | 3 ++ mobile/android/focus-android/app/build.gradle | 35 +++++++++++++++++++ .../app/src/main/AndroidManifest.xml | 4 +++ .../transforms/build_android_app.py | 24 +++++++++++++ 5 files changed, 99 insertions(+) diff --git a/mobile/android/fenix/app/build.gradle b/mobile/android/fenix/app/build.gradle index e81a50f5197bd..0ccb36b181f7d 100644 --- a/mobile/android/fenix/app/build.gradle +++ b/mobile/android/fenix/app/build.gradle @@ -333,6 +333,10 @@ android.defaultConfig.with { def buildDate = LocalDateTime.parse(getBuildId(gradle.mozconfig.topobjdir, providers.environmentVariable("MOZ_BUILD_DATE").getOrNull()), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toString() buildConfigField 'String', 'BUILD_DATE', '"' + buildDate + '"' + // Default for the manifest merges that onVariants does not reach, notably the unit test and + // android test components. Real variants override this below. + manifestPlaceholders.put('sentryProguardUuid', '') + try { def token = new File("${rootDir}/.adjust_token").text.trim() buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"' @@ -415,6 +419,35 @@ android.buildTypes.debug.with { buildConfigField 'String', 'SECRET_SETTINGS_OVERRIDES', '"' + secretSettingsOverrides + '"' } +// ------------------------------------------------------------------------------------------------- +// Sentry: Generate the ProGuard UUID used to match crash reports with their mapping file +// +// The same UUID is embedded in the manifest, for the SDK to report, and written to a file, for the +// sentry-upload task to pass to sentry-cli. Debug builds are not minified, so they get no UUID. +// ------------------------------------------------------------------------------------------------- + +androidComponents { + onVariants(selector().all()) { variant -> + def proguardUuid = variant.buildType == 'debug' ? '' : UUID.nameUUIDFromBytes( + (getBuildId(gradle.mozconfig.topobjdir, providers.environmentVariable("MOZ_BUILD_DATE").getOrNull()) + variant.name).bytes + ).toString() + variant.manifestPlaceholders.put('sentryProguardUuid', proguardUuid) + + def variantNameCap = variant.name.capitalize() + def uuidOutputFile = layout.buildDirectory.file("sentry/${variant.name}/proguard-uuid.txt") + def writeUuid = tasks.register("writeSentryProguardUuid${variantNameCap}") { + outputs.file(uuidOutputFile) + doLast { + def destFile = uuidOutputFile.get().asFile + destFile.parentFile.mkdirs() + destFile.text = proguardUuid + } + } + tasks.matching { it.name == "assemble${variantNameCap}" || it.name == "bundle${variantNameCap}" } + .configureEach { dependsOn(writeUuid) } + } +} + // Generate Kotlin code for the Fenix Glean metrics. ext { // Enable expiration by major version. diff --git a/mobile/android/fenix/app/src/main/AndroidManifest.xml b/mobile/android/fenix/app/src/main/AndroidManifest.xml index 97f31394d7234..4324d47ab4bd5 100644 --- a/mobile/android/fenix/app/src/main/AndroidManifest.xml +++ b/mobile/android/fenix/app/src/main/AndroidManifest.xml @@ -828,6 +828,9 @@ + + def proguardUuid = variant.buildType == "debug" ? "" : + UUID.nameUUIDFromBytes((buildId + variant.name).bytes).toString() + variant.manifestPlaceholders.put("sentryProguardUuid", proguardUuid) + + def variantNameCap = variant.name.capitalize() + def uuidOutputFile = layout.buildDirectory.file("sentry/${variant.name}/proguard-uuid.txt") + def writeUuid = tasks.register("writeSentryProguardUuid${variantNameCap}") { + outputs.file(uuidOutputFile) + doLast { + def destFile = uuidOutputFile.get().asFile + destFile.parentFile.mkdirs() + destFile.text = proguardUuid + } + } + tasks.matching { it.name == "assemble${variantNameCap}" || it.name == "bundle${variantNameCap}" } + .configureEach { dependsOn(writeUuid) } + } } // ------------------------------------------------------------------------------------------------- diff --git a/mobile/android/focus-android/app/src/main/AndroidManifest.xml b/mobile/android/focus-android/app/src/main/AndroidManifest.xml index 82ea5fa42a9df..b2271b85d65db 100644 --- a/mobile/android/focus-android/app/src/main/AndroidManifest.xml +++ b/mobile/android/focus-android/app/src/main/AndroidManifest.xml @@ -231,6 +231,10 @@ + + diff --git a/taskcluster/android_taskgraph/transforms/build_android_app.py b/taskcluster/android_taskgraph/transforms/build_android_app.py index 0229ec0e13535..427d48a762c75 100644 --- a/taskcluster/android_taskgraph/transforms/build_android_app.py +++ b/taskcluster/android_taskgraph/transforms/build_android_app.py @@ -301,4 +301,28 @@ def add_artifacts(config, tasks): }) task["attributes"]["aab"] = artifact_template["name"] + # Only bundles are minified and consumed by the sentry-upload kind. Debug builds have no + # mapping file at all, so declaring these artifacts there would leave them permanently + # missing. + if config.kind == "build-bundle" and gradle_build_type != "debug": + artifacts.append({ + "type": "file", + "name": "public/build/mapping.txt", + "path": ( + "/builds/worker/workspace/obj-build/gradle/build/mobile/android" + f"/{source_project_name}/app/outputs/mapping" + f"/{gradle_build_name}/mapping.txt" + ), + }) + + artifacts.append({ + "type": "file", + "name": "public/build/sentry-proguard-uuid.txt", + "path": ( + "/builds/worker/workspace/obj-build/gradle/build/mobile/android" + f"/{source_project_name}/app/sentry" + f"/{gradle_build_name}/proguard-uuid.txt" + ), + }) + yield task From f6b37909c0bec3c86b1e43d45fd659bf32c9acf2 Mon Sep 17 00:00:00 2001 From: Julien Cristau Date: Fri, 15 May 2026 15:23:45 +0100 Subject: [PATCH 2/2] Bug 1923139 - add sentry-upload task for Android ProGuard mapping. r?#taskgraph-reviewers Add a sentry-upload Taskcluster task that uploads ProGuard mapping files to Sentry after each nightly, beta, and release Android build. The task fetches mapping.txt and sentry-proguard-uuid.txt artifacts from the build, then calls sentry-cli with --uuid to associate the mapping with the UUID embedded in the app. Differential Revision: https://phabricator.services.mozilla.com/D294703 --- taskcluster/docker/sentry/Dockerfile | 1 + .../docker/sentry/upload_proguard_mapping.sh | 49 ++++++++++++++++++ taskcluster/docs/kinds.md | 4 ++ .../transforms/sentry_upload.py | 49 ++++++++++++++++++ taskcluster/kinds/sentry-upload/kind.yml | 50 +++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100755 taskcluster/docker/sentry/upload_proguard_mapping.sh create mode 100644 taskcluster/gecko_taskgraph/transforms/sentry_upload.py create mode 100644 taskcluster/kinds/sentry-upload/kind.yml diff --git a/taskcluster/docker/sentry/Dockerfile b/taskcluster/docker/sentry/Dockerfile index c07fdd3961280..0964980a5af59 100644 --- a/taskcluster/docker/sentry/Dockerfile +++ b/taskcluster/docker/sentry/Dockerfile @@ -5,6 +5,7 @@ VOLUME /builds/worker/checkouts ADD prepare.sh /setup/prepare-docker.sh ADD submit_sentry_release.sh /usr/bin/submit_sentry_release.sh +ADD upload_proguard_mapping.sh /usr/bin/upload_proguard_mapping.sh RUN /bin/bash /setup/prepare-docker.sh && rm -R /setup # Set a default command useful for debugging diff --git a/taskcluster/docker/sentry/upload_proguard_mapping.sh b/taskcluster/docker/sentry/upload_proguard_mapping.sh new file mode 100755 index 0000000000000..723d8e1e7db1a --- /dev/null +++ b/taskcluster/docker/sentry/upload_proguard_mapping.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +set -o nounset +set -o pipefail + +MAPPING_FILE="$1" +UUID_FILE="$2" + +run() { + local proguard_uuid + proguard_uuid=$(cat "$UUID_FILE") || return 1 + local secret + secret=$(curl --silent --fail "http://taskcluster/secrets/v1/secret/$SENTRY_SECRET") || return 1 + local sentry_auth_token sentry_org sentry_project sentry_url + sentry_auth_token=$(echo "$secret" | jq -r ".secret.sentryToken") + sentry_org=$(echo "$secret" | jq -r ".secret.sentryOrg") + sentry_project=$(echo "$secret" | jq -r ".secret.sentryProject") + sentry_url=$(echo "$secret" | jq -r ".secret.sentryUrl") + + SENTRY_AUTH_TOKEN="$sentry_auth_token" sentry-cli \ + --url "$sentry_url" \ + --org "$sentry_org" \ + dif upload \ + --type proguard \ + --uuid "$proguard_uuid" \ + --project "$sentry_project" \ + "$MAPPING_FILE" || return 1 +} + +with_backoff() { + local failures=0 + while ! "$@"; do + failures=$(( failures + 1 )) + if (( failures >= 5 )); then + echo "[with_backoff] Unable to succeed after 5 tries, failing the job." + return 1 + else + local seconds=$(( 2 ** (failures - 1) )) + echo "[with_backoff] Retrying in $seconds second(s)" + sleep "$seconds" + fi + done +} + +with_backoff run diff --git a/taskcluster/docs/kinds.md b/taskcluster/docs/kinds.md index 7e892d32952e0..cfcc8e4d2ad9c 100644 --- a/taskcluster/docs/kinds.md +++ b/taskcluster/docs/kinds.md @@ -804,6 +804,10 @@ Interact with Sentry, such as by publishing new project releases. Generate missing macOS and windows system symbols from crash reports. +## sentry-upload + +Upload Android ProGuard mapping files to Sentry to enable deobfuscation of crash stack traces. + ## system-symbols-upload Upload macOS and windows system symbols to tecken. diff --git a/taskcluster/gecko_taskgraph/transforms/sentry_upload.py b/taskcluster/gecko_taskgraph/transforms/sentry_upload.py new file mode 100644 index 0000000000000..f2fe3616d8cae --- /dev/null +++ b/taskcluster/gecko_taskgraph/transforms/sentry_upload.py @@ -0,0 +1,49 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from taskgraph.transforms.base import TransformSequence + +transforms = TransformSequence() + +_SENTRY_SECRET_TMPL = "project/releng/gecko/build/level-{level}/sentry-upload-{app}" + + +# Attributes that `copy-attributes` brings over from the upstream build-bundle task but that the +# task transform then overwrites with its own defaults, paired with the task key each has to be +# promoted back into. `shipping_phase` and `shipping_product` are deliberately absent: the task +# transform sets those with setdefault, so the copied values already survive. +_SCHEDULING_ATTRIBUTES = ( + ("run_on_projects", "run-on-projects"), + ("run_on_repo_type", "run-on-repo-type"), +) + + +def _get_app(build_type): + if build_type.startswith("fenix-"): + return "fenix" + return "focus-android" + + +@transforms.add +def sentry_upload(config, tasks): + level = config.params["level"] + for task in tasks: + attributes = task["attributes"] + build_type = attributes.get("build-type", "") + secret_path = _SENTRY_SECRET_TMPL.format( + app=_get_app(build_type), + level=level, + ) + + task["worker"].setdefault("env", {})["SENTRY_SECRET"] = secret_path + task.setdefault("scopes", []).append(f"secrets:get:{secret_path}") + + # Mirror the upstream build's scheduling rather than restating it per build type, so an + # upload never pulls a shippable build into a graph the build itself would not run in. + for attribute, key in _SCHEDULING_ATTRIBUTES: + value = attributes.get(attribute) + if value is not None: + task.setdefault(key, value) + + yield task diff --git a/taskcluster/kinds/sentry-upload/kind.yml b/taskcluster/kinds/sentry-upload/kind.yml new file mode 100644 index 0000000000000..998434b1b3429 --- /dev/null +++ b/taskcluster/kinds/sentry-upload/kind.yml @@ -0,0 +1,50 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +loader: taskgraph.loader.transform:loader + +transforms: + - taskgraph.transforms.from_deps + - gecko_taskgraph.transforms.sentry_upload + - android_taskgraph.transforms.treeherder + - gecko_taskgraph.transforms.job + - gecko_taskgraph.transforms.task + +kind-dependencies: + - build-bundle + +tasks: + sentry-upload: + description: Upload Android ProGuard mapping file to Sentry + from-deps: + with-attributes: + build-type: + - fenix-nightly + - fenix-release + - fenix-beta + - focus-nightly + - focus-release + - focus-beta + - klar-release + copy-attributes: true + fetches: + build-bundle: + - artifact: mapping.txt + extract: false + - artifact: sentry-proguard-uuid.txt + extract: false + worker-type: b-linux + worker: + docker-image: {in-tree: sentry} + taskcluster-proxy: true + max-run-time: 1800 + run: + using: run-task + checkout: false + command: /usr/bin/upload_proguard_mapping.sh /builds/worker/fetches/mapping.txt /builds/worker/fetches/sentry-proguard-uuid.txt + treeherder: + symbol: + by-build-type: + klar-.*: klar-map + default: map