ci: Split sentry symbols upload into separate script

Jakub Konka created

Sometimes there are spurious storage errors in Sentry
which means we lack debug symbols for some release tags.

Change summary

.github/workflows/reupload_sentry_symbols.yml                | 179 ++++++
script/bundle-linux                                          |  21 
script/bundle-mac                                            |  22 
script/bundle-windows.ps1                                    |  16 
script/lib/sentry-upload.ps1                                 |  28 
script/lib/sentry-upload.sh                                  |  29 
script/reupload-sentry-symbols                               |  19 
script/upload-sentry-symbols                                 | 119 +++
script/upload-sentry-symbols.ps1                             |  39 +
tooling/xtask/src/tasks/workflows.rs                         |   2 
tooling/xtask/src/tasks/workflows/reupload_sentry_symbols.rs |  73 ++
11 files changed, 499 insertions(+), 48 deletions(-)

Detailed changes

.github/workflows/reupload_sentry_symbols.yml 🔗

@@ -0,0 +1,179 @@
+# Generated from xtask::workflows::reupload_sentry_symbols
+# Rebuild with `cargo xtask workflows`.
+name: reupload_sentry_symbols
+env:
+  CARGO_TERM_COLOR: always
+  RUST_BACKTRACE: '1'
+on:
+  workflow_dispatch:
+    inputs:
+      tag:
+        description: Git tag to rebuild and upload symbols for (e.g. v0.200.0-pre)
+        required: true
+        type: string
+jobs:
+  upload_linux_x86_64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: namespace-profile-32x64-ubuntu-2004
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: steps::setup_linux
+      run: ./script/linux
+    - name: steps::install_mold
+      run: ./script/install-mold
+    - name: steps::download_wasi_sdk
+      run: ./script/download-wasi-sdk
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_unix
+      run: ./script/upload-sentry-symbols --verify
+    timeout-minutes: 60
+  upload_linux_aarch64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: namespace-profile-8x32-ubuntu-2004-arm-m4
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: steps::setup_linux
+      run: ./script/linux
+    - name: steps::install_mold
+      run: ./script/install-mold
+    - name: steps::download_wasi_sdk
+      run: ./script/download-wasi-sdk
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_unix
+      run: ./script/upload-sentry-symbols --verify
+    timeout-minutes: 60
+  upload_mac_x86_64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: namespace-profile-mac-large
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
+      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
+      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_unix
+      run: ./script/upload-sentry-symbols --verify
+    timeout-minutes: 60
+  upload_mac_aarch64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: namespace-profile-mac-large
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
+      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
+      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_unix
+      run: ./script/upload-sentry-symbols --verify
+    timeout-minutes: 60
+  upload_windows_x86_64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: self-32vcpu-windows-2022
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
+      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
+      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
+      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
+      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
+      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
+      FILE_DIGEST: SHA256
+      TIMESTAMP_DIGEST: SHA256
+      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_windows
+      run: ./script/upload-sentry-symbols.ps1 -Verify
+      shell: pwsh
+    timeout-minutes: 60
+  upload_windows_aarch64:
+    if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
+    runs-on: self-32vcpu-windows-2022
+    env:
+      CARGO_INCREMENTAL: 0
+      ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
+      ZED_MINIDUMP_ENDPOINT: ${{ secrets.ZED_SENTRY_MINIDUMP_ENDPOINT }}
+      AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
+      AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
+      AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
+      ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
+      CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
+      ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
+      FILE_DIGEST: SHA256
+      TIMESTAMP_DIGEST: SHA256
+      TIMESTAMP_SERVER: http://timestamp.acs.microsoft.com
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+        ref: ${{ inputs.tag }}
+    - name: steps::setup_sentry
+      uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b
+      with:
+        token: ${{ secrets.SENTRY_AUTH_TOKEN }}
+    - name: reupload_sentry_symbols::upload_job::upload_symbols_windows
+      run: ./script/upload-sentry-symbols.ps1 -Verify
+      shell: pwsh
+    timeout-minutes: 60
+defaults:
+  run:
+    shell: bash -euxo pipefail {0}

script/bundle-linux 🔗

@@ -2,6 +2,7 @@
 
 set -euxo pipefail
 source script/lib/blob-store.sh
+source script/lib/sentry-upload.sh
 
 # Function for displaying help info
 help_info() {
@@ -90,22 +91,10 @@ if ! command -v sentry-cli >/dev/null 2>&1; then
 else
     if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
         echo "Uploading zed debug symbols to sentry..."
-        # note: this uploads the unstripped binary which is needed because it contains
-        # .eh_frame data for stack unwinding. see https://github.com/getsentry/symbolic/issues/783
-        for attempt in 1 2 3; do
-            echo "Attempting sentry upload (attempt $attempt/3)..."
-            if sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev \
-                "${target_dir}/${target_triple}"/release/zed \
-                "${target_dir}/${remote_server_triple}"/release/remote_server; then
-                echo "Sentry upload successful on attempt $attempt"
-                break
-            else
-                echo "Sentry upload failed on attempt $attempt"
-                if [ $attempt -eq 3 ]; then
-                    echo "All sentry upload attempts failed"
-                fi
-            fi
-        done
+        upload_to_sentry \
+            "${target_dir}/${target_triple}"/release/zed \
+            "${target_dir}/${remote_server_triple}"/release/remote_server \
+            || true
     else
         echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
     fi

script/bundle-mac 🔗

@@ -2,6 +2,7 @@
 
 set -euo pipefail
 source script/lib/blob-store.sh
+source script/lib/sentry-upload.sh
 
 build_flag="--release"
 target_dir="release"
@@ -297,7 +298,6 @@ function upload_debug_symbols() {
         echo "local install; skipping sentry upload."
     elif [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
         echo "Uploading zed debug symbols to sentry..."
-        exe_path="target/${target_triple}/release/Zed"
         if ! dsymutil --flat "target/${target_triple}/${target_dir}/zed" 2> target/dsymutil.log; then
             echo "dsymutil failed"
             cat target/dsymutil.log
@@ -308,23 +308,9 @@ function upload_debug_symbols() {
             cat target/dsymutil.log
             exit 1
         fi
-        # note: this uploads the unstripped binary which is needed because it contains
-        # .eh_frame data for stack unwinding. see https://github.com/getsentry/symbolic/issues/783
-        # Try uploading up to 3 times
-        for attempt in 1 2 3; do
-            echo "Sentry upload attempt $attempt..."
-            if sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev \
-                "target/${target_triple}/${target_dir}/zed.dwarf" \
-                "target/${target_triple}/${target_dir}/remote_server.dwarf"; then
-                break
-            else
-                echo "Sentry upload failed on attempt $attempt"
-                if [ $attempt -eq 3 ]; then
-                    echo "All sentry upload attempts failed"
-                    exit 1
-                fi
-            fi
-        done
+        upload_to_sentry \
+            "target/${target_triple}/${target_dir}/zed.dwarf" \
+            "target/${target_triple}/${target_dir}/remote_server.dwarf"
     else
         echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
     fi

script/bundle-windows.ps1 🔗

@@ -7,6 +7,7 @@ Param(
 )
 
 . "$PSScriptRoot/lib/workspace.ps1"
+. "$PSScriptRoot/lib/sentry-upload.ps1"
 
 # https://stackoverflow.com/questions/57949031/powershell-script-stops-if-program-fails-like-bash-set-o-errexit
 $ErrorActionPreference = 'Stop'
@@ -164,20 +165,7 @@ function UploadToSentry {
         return
     }
     Write-Output "Uploading zed debug symbols to sentry..."
-    for ($i = 1; $i -le 3; $i++) {
-        try {
-            sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev $CargoOutDir
-            break
-        }
-        catch {
-            Write-Output "Sentry upload attempt $i failed: $_"
-            if ($i -eq 3) {
-                Write-Output "All sentry upload attempts failed"
-                throw
-            }
-            Start-Sleep -Seconds 2
-        }
-    }
+    Upload-ToSentry -Paths @($CargoOutDir)
 }
 
 function MakeAppx {

script/lib/sentry-upload.ps1 🔗

@@ -0,0 +1,28 @@
+# Uploads debug files to Sentry with retry logic.
+#
+# Usage: Upload-ToSentry -Paths @("file1", "file2")
+#
+# Requires sentry-cli and SENTRY_AUTH_TOKEN to be available.
+# Throws if all attempts fail.
+function Upload-ToSentry {
+    param(
+        [Parameter(Mandatory=$true)]
+        [string[]]$Paths
+    )
+
+    for ($attempt = 1; $attempt -le 3; $attempt++) {
+        try {
+            Write-Output "Sentry upload attempt $attempt/3..."
+            sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev @Paths
+            Write-Output "Sentry upload successful on attempt $attempt"
+            return
+        }
+        catch {
+            Write-Output "Sentry upload failed on attempt ${attempt}: $_"
+            if ($attempt -eq 3) {
+                throw "All sentry upload attempts failed"
+            }
+            Start-Sleep -Seconds 5
+        }
+    }
+}

script/lib/sentry-upload.sh 🔗

@@ -0,0 +1,29 @@
+# Uploads debug files to Sentry with retry logic.
+#
+# Usage: upload_to_sentry <file1> [file2 ...]
+#
+# Requires sentry-cli and SENTRY_AUTH_TOKEN to be available.
+# Returns non-zero if all attempts fail.
+upload_to_sentry() {
+    if [[ $# -eq 0 ]]; then
+        echo "Error: no files specified for sentry upload" >&2
+        return 1
+    fi
+
+    # note: this uploads the unstripped binary which is needed because it contains
+    # .eh_frame data for stack unwinding. see https://github.com/getsentry/symbolic/issues/783
+    for attempt in 1 2 3; do
+        echo "Sentry upload attempt $attempt/3..."
+        if sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev "$@"; then
+            echo "Sentry upload successful on attempt $attempt"
+            return 0
+        else
+            echo "Sentry upload failed on attempt $attempt"
+            if [ $attempt -eq 3 ]; then
+                echo "All sentry upload attempts failed" >&2
+                return 1
+            fi
+            sleep 5
+        fi
+    done
+}

script/reupload-sentry-symbols 🔗

@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -e
+
+if [[ $# -ne 1 ]]; then
+    echo "Usage: $0 <tag>" >&2
+    echo "Example: $0 v0.200.0-pre" >&2
+    exit 1
+fi
+
+tag="$1"
+
+if ! git rev-parse "$tag" >/dev/null 2>&1; then
+    echo "Error: tag '$tag' not found. Try 'git fetch --tags' first." >&2
+    exit 1
+fi
+
+gh workflow run reupload_sentry_symbols.yml -f tag="$tag"
+echo "Triggered reupload_sentry_symbols workflow for $tag"

script/upload-sentry-symbols 🔗

@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+source script/lib/sentry-upload.sh
+
+# Builds Zed and remote_server binaries and uploads debug symbols to Sentry.
+# Intended for re-uploading symbols for releases where the original upload
+# failed or the files were corrupted.
+#
+# Usage: script/upload-sentry-symbols [--verify]
+#
+# Environment:
+#   SENTRY_AUTH_TOKEN  (required) Sentry authentication token
+#
+# Options:
+#   --verify  After uploading, verify the debug IDs are resolvable on Sentry
+
+verify=false
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --verify)
+            verify=true
+            shift
+            ;;
+        *)
+            echo "Unknown option: $1" >&2
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${SENTRY_AUTH_TOKEN:-}" ]]; then
+    echo "Error: SENTRY_AUTH_TOKEN is required" >&2
+    exit 1
+fi
+
+if ! command -v sentry-cli >/dev/null 2>&1; then
+    echo "Error: sentry-cli is not installed" >&2
+    echo "Install with: curl -sL https://sentry.io/get-cli | bash" >&2
+    exit 1
+fi
+
+target_dir="${CARGO_TARGET_DIR:-target}"
+version_info=$(rustc --version --verbose)
+host_line=$(echo "$version_info" | grep host)
+target_triple=${host_line#*: }
+
+case "$(uname -s)" in
+    Linux)
+        musl_triple=${target_triple%-gnu}-musl
+        remote_server_triple=${REMOTE_SERVER_TARGET:-"${musl_triple}"}
+
+        echo "==> Building zed and cli (release) for ${target_triple}..."
+        cargo build --release --target "${target_triple}" --package zed --package cli
+
+        echo "==> Building remote_server (release) for ${remote_server_triple}..."
+        export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=+crt-static"
+        cargo build --release --target "${remote_server_triple}" --package remote_server
+
+        upload_paths=(
+            "${target_dir}/${target_triple}/release/zed"
+            "${target_dir}/${remote_server_triple}/release/remote_server"
+        )
+        ;;
+    Darwin)
+        echo "==> Building zed and cli (release) for ${target_triple}..."
+        cargo build --release --target "${target_triple}" --package zed --package cli
+
+        echo "==> Building remote_server (release) for ${target_triple}..."
+        cargo build --release --target "${target_triple}" --package remote_server
+
+        echo "==> Extracting DWARF debug info..."
+        dsymutil --flat "${target_dir}/${target_triple}/release/zed"
+        dsymutil --flat "${target_dir}/${target_triple}/release/remote_server"
+
+        upload_paths=(
+            "${target_dir}/${target_triple}/release/zed.dwarf"
+            "${target_dir}/${target_triple}/release/remote_server.dwarf"
+        )
+        ;;
+    *)
+        echo "Error: unsupported platform (use upload-sentry-symbols.ps1 on Windows)" >&2
+        exit 1
+        ;;
+esac
+
+echo "==> Debug files to upload:"
+for path in "${upload_paths[@]}"; do
+    ls -lh "$path"
+    sentry-cli debug-files check "$path" || true
+done
+
+echo "==> Uploading debug symbols to Sentry..."
+upload_to_sentry "${upload_paths[@]}"
+
+if [ "$verify" = true ]; then
+    echo "==> Verifying uploaded debug files..."
+    all_found=true
+    for path in "${upload_paths[@]}"; do
+        debug_id=$(sentry-cli debug-files check "$path" 2>/dev/null | grep -oP 'Debug ID:\s*\K\S+' || true)
+        if [ -n "$debug_id" ]; then
+            echo "Checking debug ID: $debug_id"
+            if sentry-cli --org zed-dev --project zed debug-files find "$debug_id" 2>&1 | grep -q "missing"; then
+                echo "WARNING: Debug ID $debug_id still reported as missing" >&2
+                all_found=false
+            else
+                echo "OK: $debug_id found on Sentry"
+            fi
+        else
+            echo "WARNING: Could not extract debug ID from $path" >&2
+        fi
+    done
+    if [ "$all_found" = false ]; then
+        echo "Some debug files could not be verified. They may need time to process." >&2
+        exit 1
+    fi
+fi
+
+echo "==> Done."

script/upload-sentry-symbols.ps1 🔗

@@ -0,0 +1,39 @@
+# Builds Zed and remote_server binaries and uploads debug symbols to Sentry.
+# Intended for re-uploading symbols for releases where the original upload
+# failed or the files were corrupted.
+#
+# Usage: script/upload-sentry-symbols.ps1 [-Verify]
+#
+# Environment:
+#   SENTRY_AUTH_TOKEN  (required) Sentry authentication token
+
+[CmdletBinding()]
+Param(
+    [Parameter()][switch]$Verify
+)
+
+. "$PSScriptRoot/lib/sentry-upload.ps1"
+
+$ErrorActionPreference = 'Stop'
+$PSNativeCommandUseErrorActionPreference = $true
+
+if (-not (Test-Path "env:SENTRY_AUTH_TOKEN")) {
+    Write-Error "SENTRY_AUTH_TOKEN is required"
+}
+
+if (-not (Get-Command "sentry-cli" -ErrorAction SilentlyContinue)) {
+    Write-Error "sentry-cli is not installed. Install with: winget install -e --id=Sentry.sentry-cli"
+}
+
+$target_dir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { "target" }
+
+Write-Output "==> Building zed and cli (release)..."
+cargo build --release --package zed --package cli
+
+Write-Output "==> Building remote_server (release)..."
+cargo build --release --package remote_server
+
+Write-Output "==> Uploading debug symbols to Sentry..."
+Upload-ToSentry -Paths @($target_dir)
+
+Write-Output "==> Done."

tooling/xtask/src/tasks/workflows.rs 🔗

@@ -20,6 +20,7 @@ mod extensions;
 mod nix_build;
 mod publish_extension_cli;
 mod release_nightly;
+mod reupload_sentry_symbols;
 mod run_bundling;
 
 mod release;
@@ -143,6 +144,7 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
         WorkflowFile::zed(publish_extension_cli::publish_extension_cli),
         WorkflowFile::zed(release::release),
         WorkflowFile::zed(release_nightly::release_nightly),
+        WorkflowFile::zed(reupload_sentry_symbols::reupload_sentry_symbols),
         WorkflowFile::zed(run_agent_evals::run_agent_evals),
         WorkflowFile::zed(run_agent_evals::run_cron_unit_evals),
         WorkflowFile::zed(run_agent_evals::run_unit_evals),

tooling/xtask/src/tasks/workflows/reupload_sentry_symbols.rs 🔗

@@ -0,0 +1,73 @@
+use gh_workflow::*;
+
+use crate::tasks::workflows::{
+    runners::{self, Arch, Platform},
+    steps::{self, CommonJobConditions, NamedJob, named},
+    vars::{WorkflowInput, bundle_envs},
+};
+
+const TAG: &str = "${{ inputs.tag }}";
+
+pub fn reupload_sentry_symbols() -> Workflow {
+    let tag = WorkflowInput::string("tag", None)
+        .description("Git tag to rebuild and upload symbols for (e.g. v0.200.0-pre)");
+
+    let jobs = [
+        upload_job(Platform::Linux, Arch::X86_64),
+        upload_job(Platform::Linux, Arch::AARCH64),
+        upload_job(Platform::Mac, Arch::X86_64),
+        upload_job(Platform::Mac, Arch::AARCH64),
+        upload_job(Platform::Windows, Arch::X86_64),
+        upload_job(Platform::Windows, Arch::AARCH64),
+    ];
+
+    let mut workflow = named::workflow()
+        .on(Event::default()
+            .workflow_dispatch(WorkflowDispatch::default().add_input(tag.name, tag.input())))
+        .add_env(("CARGO_TERM_COLOR", "always"))
+        .add_env(("RUST_BACKTRACE", "1"));
+
+    for job in jobs {
+        workflow = workflow.add_job(job.name, job.job);
+    }
+
+    workflow
+}
+
+fn upload_job(platform: Platform, arch: Arch) -> NamedJob {
+    fn upload_symbols_unix() -> Step<Run> {
+        named::bash("./script/upload-sentry-symbols --verify")
+    }
+
+    fn upload_symbols_windows() -> Step<Run> {
+        named::pwsh("./script/upload-sentry-symbols.ps1 -Verify")
+    }
+
+    let runner = match platform {
+        Platform::Linux => arch.linux_bundler(),
+        Platform::Mac => runners::MAC_DEFAULT,
+        Platform::Windows => runners::WINDOWS_DEFAULT,
+    };
+
+    let mut job = Job::default()
+        .runs_on(runner)
+        .timeout_minutes(60u32)
+        .with_repository_owner_guard()
+        .envs(bundle_envs(platform))
+        .add_step(steps::checkout_repo().with_ref(TAG))
+        .add_step(steps::setup_sentry());
+
+    if platform == Platform::Linux {
+        job = steps::install_linux_dependencies(job);
+    }
+
+    job = match platform {
+        Platform::Windows => job.add_step(upload_symbols_windows()),
+        _ => job.add_step(upload_symbols_unix()),
+    };
+
+    NamedJob {
+        name: format!("upload_{platform}_{arch}"),
+        job,
+    }
+}