From 7be1cc1c589ed1ea9363d6eb08319ad3725ca562 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Wed, 9 Sep 2026 14:16:22 +0100 Subject: [PATCH] fix(crashlytics, ios): resolve target-folder GoogleService-Info.plist for SPM dSYM upload ios_config.sh hardcoded ${PROJECT_DIR}/GoogleService-Info.plist for the SPM upload-symbols branches. Both Expo's config plugin and RNFB's documented manual/CLI setup place the plist at ios//GoogleService-Info.plist instead, so any SPM Crashlytics setup following those docs failed the dSYM upload build phase. Resolve the plist path once (target-folder first, falling back to the PROJECT_DIR root), reuse it in both upload-symbols invocations, and warn and skip the upload instead of hard-failing when neither location has it. Fixes #9304 --- .../crashlytics/__tests__/ios_config.test.ts | 130 ++++++++++++++++++ packages/crashlytics/ios_config.sh | 21 ++- 2 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 packages/crashlytics/__tests__/ios_config.test.ts diff --git a/packages/crashlytics/__tests__/ios_config.test.ts b/packages/crashlytics/__tests__/ios_config.test.ts new file mode 100644 index 0000000000..78b87ace05 --- /dev/null +++ b/packages/crashlytics/__tests__/ios_config.test.ts @@ -0,0 +1,130 @@ +import { spawnSync } from 'child_process'; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; + +// Stub `upload-symbols` binary. Mirrors the real SPM `upload-symbols` tool closely enough +// for this test: it records the `-gsp` path it was invoked with, and fails (like the real +// binary does) when that path does not point at an existing file. +const UPLOAD_SYMBOLS_STUB = `#!/usr/bin/env bash +set -e +GSP="" +while [[ $# -gt 0 ]]; do + case "$1" in + -gsp) + GSP="$2" + shift 2 + ;; + *) + shift + ;; + esac +done +echo "\${GSP}" >> "\${UPLOAD_SYMBOLS_LOG}" +if [[ ! -f "\${GSP}" ]]; then + echo "error: stub upload-symbols could not find gsp file: \${GSP}" >&2 + exit 1 +fi +exit 0 +`; + +const SCRIPT_PATH = join(__dirname, '..', 'ios_config.sh'); + +describe('Crashlytics ios_config.sh SPM dSYM upload', function () { + let projectDir: string; + let targetName: string; + let buildDir: string; + let dsymFolder: string; + let uploadSymbolsLog: string; + + beforeEach(function () { + projectDir = mkdtempSync(join(tmpdir(), 'rnfb-ios-config-')); + targetName = 'TestApp'; + + const derivedDataRoot = join(projectDir, 'DerivedData', 'TestApp-hash'); + buildDir = join(derivedDataRoot, 'Build', 'Products'); + const spmCrashlyticsDir = join( + derivedDataRoot, + 'SourcePackages', + 'checkouts', + 'firebase-ios-sdk', + 'Crashlytics', + ); + mkdirSync(buildDir, { recursive: true }); + mkdirSync(spmCrashlyticsDir, { recursive: true }); + + const uploadSymbolsPath = join(spmCrashlyticsDir, 'upload-symbols'); + writeFileSync(uploadSymbolsPath, UPLOAD_SYMBOLS_STUB); + chmodSync(uploadSymbolsPath, 0o755); + + uploadSymbolsLog = join(projectDir, 'upload-symbols.log'); + + dsymFolder = join(projectDir, 'dsyms'); + mkdirSync(dsymFolder, { recursive: true }); + }); + + afterEach(function () { + rmSync(projectDir, { recursive: true, force: true }); + }); + + function runIosConfig() { + return spawnSync('bash', [SCRIPT_PATH], { + cwd: projectDir, + encoding: 'utf8', + env: { + PATH: process.env.PATH, + PROJECT_DIR: projectDir, + TARGET_NAME: targetName, + BUILD_DIR: buildDir, + DWARF_DSYM_FOLDER_PATH: dsymFolder, + DWARF_DSYM_FILE_NAME: 'TestApp.app.dSYM', + UPLOAD_SYMBOLS_LOG: uploadSymbolsLog, + }, + }); + } + + it('uploads dSYMs using the target-folder plist when it only exists there', function () { + // This is the reported failure: both Expo's config plugin and RNFB's documented + // manual/CLI setup place the plist at ios//GoogleService-Info.plist, + // not at the PROJECT_DIR root. + const targetFolderPlist = join(projectDir, targetName, 'GoogleService-Info.plist'); + mkdirSync(join(projectDir, targetName), { recursive: true }); + writeFileSync(targetFolderPlist, 'plist-contents'); + + const result = runIosConfig(); + + expect(result.status).toBe(0); + expect(existsSync(uploadSymbolsLog)).toBe(true); + expect(readFileSync(uploadSymbolsLog, 'utf8').trim()).toBe(targetFolderPlist); + }); + + it('falls back to the PROJECT_DIR root plist when the target-folder location has none', function () { + const rootPlist = join(projectDir, 'GoogleService-Info.plist'); + writeFileSync(rootPlist, 'plist-contents'); + + const result = runIosConfig(); + + expect(result.status).toBe(0); + expect(existsSync(uploadSymbolsLog)).toBe(true); + expect(readFileSync(uploadSymbolsLog, 'utf8').trim()).toBe(rootPlist); + }); + + it('warns and skips the upload when the plist is at neither location', function () { + const result = runIosConfig(); + + expect(result.status).toBe(0); + expect(existsSync(uploadSymbolsLog)).toBe(false); + expect(result.stdout).toContain('warning:'); + expect(result.stdout).toContain('GoogleService-Info.plist'); + }); +}); diff --git a/packages/crashlytics/ios_config.sh b/packages/crashlytics/ios_config.sh index 5c88b45e53..7d514ef69e 100755 --- a/packages/crashlytics/ios_config.sh +++ b/packages/crashlytics/ios_config.sh @@ -28,13 +28,28 @@ else # to get the DerivedData project root where SourcePackages lives. SPM_CRASHLYTICS_DIR="${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics" SPM_UPLOAD_SYMBOLS="${SPM_CRASHLYTICS_DIR}/upload-symbols" - if [[ -x "${SPM_UPLOAD_SYMBOLS}" ]]; then + + # GoogleService-Info.plist normally lives in the target folder + # (ios//GoogleService-Info.plist) — that's where both Expo's config + # plugin and RNFB's documented manual/CLI setup place it. Fall back to the + # PROJECT_DIR root for setups that put it there instead. + if [[ -n "${TARGET_NAME}" && -f "${PROJECT_DIR}/${TARGET_NAME}/GoogleService-Info.plist" ]]; then + GSP_PATH="${PROJECT_DIR}/${TARGET_NAME}/GoogleService-Info.plist" + elif [[ -f "${PROJECT_DIR}/GoogleService-Info.plist" ]]; then + GSP_PATH="${PROJECT_DIR}/GoogleService-Info.plist" + else + GSP_PATH="" + fi + + if [[ -z "${GSP_PATH}" ]]; then + echo "warning: GoogleService-Info.plist not found at ${PROJECT_DIR}/${TARGET_NAME}/GoogleService-Info.plist or ${PROJECT_DIR}/GoogleService-Info.plist. Skipping dSYM upload." + elif [[ -x "${SPM_UPLOAD_SYMBOLS}" ]]; then echo "info: Exec FirebaseCrashlytics upload-symbols from SPM" - "${SPM_UPLOAD_SYMBOLS}" -gsp "${PROJECT_DIR}/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}" + "${SPM_UPLOAD_SYMBOLS}" -gsp "${GSP_PATH}" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}" elif [[ -f "${SPM_UPLOAD_SYMBOLS}" ]]; then echo "info: Exec FirebaseCrashlytics upload-symbols from SPM (chmod +x)" chmod +x "${SPM_UPLOAD_SYMBOLS}" - "${SPM_UPLOAD_SYMBOLS}" -gsp "${PROJECT_DIR}/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}" + "${SPM_UPLOAD_SYMBOLS}" -gsp "${GSP_PATH}" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}" else echo "warning: FirebaseCrashlytics run script not found at CocoaPods, framework, or SPM paths. Skipping dSYM upload." echo "warning: Checked: \${PODS_ROOT}/FirebaseCrashlytics/run, \${PROJECT_DIR}/FirebaseCrashlytics.framework/run, ${SPM_UPLOAD_SYMBOLS}"