From 8ece3e9e8e3ac021b5aa376a2ad8e90f809284c9 Mon Sep 17 00:00:00 2001 From: phuongta-pi Date: Sun, 24 May 2026 11:43:58 +0700 Subject: [PATCH] fix(crashlytics): resolve SPM run script for Flutter 3.44+ and custom -derivedDataPath Fixes #443. `flutterfire configure` injects an Xcode Run Script Build Phase that spawns `firebase-ios-sdk/Crashlytics/run` to upload dSYMs. With Swift Package Manager, the binary lives at `/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run`, but the previous implementation hardcoded a regex that only matches Xcode's standalone DerivedData layout: DERIVED_DATA_PATH=$(echo "$BUILD_ROOT" | sed -E 's|(.*DerivedData/[^/]+).*|\1|') This breaks two real-world layouts: 1. **Flutter 3.44+ project-local builds** (`flutter build ios`) place SourcePackages under `/build/ios/SourcePackages/...`, which contains no `DerivedData/` segment - sed leaves `BUILD_ROOT` untouched and the spawned path doesn't exist (`ProcessException: No such file or directory`). 2. **`xcodebuild -derivedDataPath `** runs (e.g. fastlane gym with its per-run isolation directory) place SourcePackages under `/SourcePackages/...`, again no `DerivedData/` segment. Replace the regex with a `resolve_spm_run_script` helper that strips `/Build/...` from `BUILD_DIR` (then `BUILT_PRODUCTS_DIR` / `BUILD_ROOT` as fallbacks) to recover the derived-data root. Xcode build settings guarantee `BUILD_DIR` has the shape `/Build/...` across all three layouts, so the strip-based anchor is reliable everywhere the previous regex was, plus the two cases it missed. Also fall back to `exit 0` with a warning if no candidate `run` file exists, so a misconfigured project surfaces a clear message instead of killing the archive with `ProcessException`. Verified by hand against: - Xcode standalone DerivedData (`~/Library/.../DerivedData/Runner-X`) - Flutter `build/ios` derived-data (Flutter 3.44.0, SPM enabled) - fastlane gym custom `-derivedDataPath` --- .../src/firebase/firebase_apple_writes.dart | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/flutterfire_cli/lib/src/firebase/firebase_apple_writes.dart b/packages/flutterfire_cli/lib/src/firebase/firebase_apple_writes.dart index 8cdb57a1..cf29ed6c 100644 --- a/packages/flutterfire_cli/lib/src/firebase/firebase_apple_writes.dart +++ b/packages/flutterfire_cli/lib/src/firebase/firebase_apple_writes.dart @@ -399,12 +399,47 @@ bashScript = %q( #!/bin/bash PATH="\${PATH}:\$FLUTTER_ROOT/bin:\${PUB_CACHE}/bin:\$HOME/.pub-cache/bin" -if [ -z "\$PODS_ROOT" ] || [ ! -d "\$PODS_ROOT/FirebaseCrashlytics" ]; then - # Cannot use "BUILD_DIR%/Build/*" as per Firebase documentation, it points to "flutter-project/build/ios/*" path which doesn't have run script - DERIVED_DATA_PATH=\$(echo "\$BUILD_ROOT" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|') - PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT="\${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run" -else +# Locate the firebase-ios-sdk Crashlytics 'run' script. +# +# CocoaPods: \$PODS_ROOT/FirebaseCrashlytics/run +# SPM: /SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run +# +# The derived-data root depends on how Xcode was invoked: +# * flutter build ios -> /build/ios +# * Xcode standalone build -> ~/Library/Developer/Xcode/DerivedData/Runner-XXXXX +# * xcodebuild -derivedDataPath (e.g. fastlane gym) -> +# +# In every case BUILD_DIR has the shape /Build/..., so +# stripping "/Build/..." from BUILD_DIR (or BUILT_PRODUCTS_DIR / BUILD_ROOT +# as fallbacks) reliably yields the derived-data root. +resolve_spm_run_script() { + local source="\$1" + [ -z "\$source" ] && return 1 + local root + root=\$(printf '%s' "\$source" | sed -E 's|/Build/.*||') + local candidate="\${root}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run" + if [ -f "\$candidate" ]; then + echo "\$candidate" + return 0 + fi + return 1 +} + +if [ -n "\$PODS_ROOT" ] && [ -d "\$PODS_ROOT/FirebaseCrashlytics" ]; then PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT="\$PODS_ROOT/FirebaseCrashlytics/run" +else + PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\$(resolve_spm_run_script "\$BUILD_DIR") + if [ -z "\$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" ]; then + PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\$(resolve_spm_run_script "\$BUILT_PRODUCTS_DIR") + fi + if [ -z "\$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" ]; then + PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\$(resolve_spm_run_script "\$BUILD_ROOT") + fi +fi + +if [ -z "\$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" ] || [ ! -f "\$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" ]; then + echo "warning: Crashlytics 'run' script not found; skipping symbol upload" + exit 0 fi # Command to upload symbols script used to upload symbols to Firebase server