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
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