Skip to content

feat(ios): add SPM dependency resolution support alongside CocoaPods #4590

feat(ios): add SPM dependency resolution support alongside CocoaPods

feat(ios): add SPM dependency resolution support alongside CocoaPods #4590

Workflow file for this run

name: Testing E2E iOS
on:
workflow_call:
inputs:
record_screens:
description: 'Record host screencapture and simulator video artifacts'
type: boolean
default: false
workflow_dispatch:
inputs:
iterations:
description: 'Number of iterations to run. Default 1. Max 122.'
required: true
default: 1
type: number
record_screens:
description: 'Record host screencapture and simulator video artifacts'
type: boolean
default: false
pull_request:
branches:
- '**'
paths-ignore:
- 'docs/**'
- 'website/**'
- '.spellcheck.dict.txt'
- '**/*.md'
push:
branches:
- main
- release-v*
paths-ignore:
- 'docs/**'
- 'website/**'
- '.spellcheck.dict.txt'
- '**/*.md'
permissions:
contents: read
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# We want to generate our matrix dynamically
# Initial job generates the matrix as a JSON, and following job will use deserialize and use the result
matrix_prep:
# Do not run the scheduled jobs on forks
if: (github.event_name == 'schedule' && github.repository == 'invertase/react-native-firebase') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.build-matrix.outputs.result }}
steps:
- id: build-matrix
# https://github.com/actions/github-script/releases
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0
with:
script: |
// by default, we will run one iteration
let iterationArray = [0]
// workflow dispatch will be a drop-down of different options
if (context.eventName === "workflow_dispatch") {
const inputs = ${{ toJSON(inputs) }}
console.log('inputs is: ' + JSON.stringify(inputs))
const iterationInput = inputs.iterations
console.log('iterations input is: ' + iterationInput)
// this will expand for example with input 5 => [0, 1, 2, 3, 4]
iterationArray = []
for (let i = 0; i < iterationInput; i++) {
iterationArray.push(i);
}
console.log('iterationArray is: ' + iterationArray)
}
// If we are running on a schedule it's our periodic passive scan for flakes
// Goal is to run enough iterations on all systems that we have confidence there are no flakes
if (context.eventName === "schedule") {
const iterationCount = 15
for (let i = 0; i < iterationCount; i++) {
iterationArray.push(i);
}
}
// we want to test debug and release - they generate different code
let buildmode = ['debug', 'release'];
// Test both SPM and CocoaPods dependency resolution modes
let depResolution = ['spm', 'cocoapods'];
return {
"iteration": iterationArray,
"buildmode": buildmode,
"dep-resolution": depResolution
}
- name: Debug Output
run: echo "${{ steps.build-matrix.outputs.result }}"
# This uses the matrix generated from the matrix-prep stage
# it will run unit tests on whatever OS combinations are desired
ios:
name: iOS (${{ matrix.buildmode }}, ${{ matrix.dep-resolution }}, ${{ matrix.iteration }})
runs-on: macos-26
needs: matrix_prep
# TODO matrix across APIs, at least 11 and 15 (lowest to highest)
timeout-minutes: 107
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
RNFB_RECORD_SCREENS: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.record_screens && '1' || '0' }}
CCACHE_SLOPPINESS: clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
CCACHE_FILECLONE: true
CCACHE_DEPEND: true
CCACHE_INODECACHE: true
CCACHE_LIMIT_MULTIPLE: 0.95
# macos-26 + latest-stable matches local Xcode 26.5; firebase-ios-sdk requires Xcode 26.2+
XCODE_VERSION: latest-stable
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- name: Setup Environment for Screen Recording
# https://github.com/guidepup/setup-action/releases
uses: guidepup/setup-action@5ab89fbb6641406f6f6c91057225f3fb397c2eea # v0.20.0
continue-on-error: true
if: env.RNFB_RECORD_SCREENS == '1'
with:
record: true
- name: Upload Screen Recording Environment Setup
# https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always() && env.RNFB_RECORD_SCREENS == '1'
with:
name: screenrecording-setup-${{ matrix.buildmode }}-${{ matrix.iteration }}.mov
path: ./recordings/
# Set up tool versions
# https://github.com/actions/setup-node/releases
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
- name: Configure JDK
# https://github.com/actions/setup-java/releases
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with:
distribution: 'temurin'
java-version: '21'
# https://github.com/maxim-lobanov/setup-xcode/releases
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
with:
xcode-version: ${{ env.XCODE_VERSION }}
- name: Workaround Simulator Availability issues
run: |
# If using `latest-stable` xcode-version above, this is likely not needed.
# If you use *anything* but latest-stable, you may inadvertently be using a SimRuntime that is no longer installed.
# This will install the needed SimRuntime if it is missing.
if [[ "$XCODE_VERSION" != "latest-stable" ]] && ! xcrun simctl list | grep "^iOS $XCODE_VERSION"; then
# note if you specify the build version to be exactly same as xcode version
# you may see "iOS <version> is not available for download"
#xcodebuild -downloadPlatform iOS -buildVersion "$XCODE_VERSION"
xcodebuild -downloadPlatform iOS
fi
# Apparently just listing the installed simulators fixes availability inconsistency
# Without this, runs fail *intermittently* with build target not available because simulators not available
# See https://github.com/actions/runner-images/issues/13459#issuecomment-3681674842
xcrun simctl list
# https://github.com/actions/checkout/releases
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
with:
fetch-depth: 50
# Set path variables needed for caches
- name: Set workflow variables
id: workflow-variables
run: |
echo "metro-cache=$HOME/.metro" >> $GITHUB_OUTPUT
echo "xcode-version=$(xcodebuild -version|tail -1|cut -f3 -d' ')" >> $GITHUB_OUTPUT
- name: Yarn Cache Restore
# https://github.com/actions/cache/releases
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: yarn-cache
continue-on-error: true
with:
path: .yarn/cache
key: ${{ runner.os }}-ios-yarn-v1-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-ios-yarn-v1
- name: Detox Framework Cache Restore
# https://github.com/actions/cache/releases
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: detox-cache
continue-on-error: true
with:
path: ~/Library/Detox/ios
key: ${{ runner.os }}-detox-framework-cache-${{ steps.workflow-variables.outputs.xcode-version }}
# Detox is compiled during yarn install, using Xcode, set up cache first
# https://github.com/hendrikmuhs/ccache-action/releases
- uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23
name: Xcode Compile Cache
with:
key: ${{ runner.os }}-${{ matrix.buildmode }}-${{ matrix.dep-resolution }}-ios-v3 # makes a unique key w/related restore key internally
save: "${{ github.ref == 'refs/heads/main' }}"
create-symlink: true
max-size: 1500M
- name: Yarn Install
# https://github.com/nick-fields/retry/releases
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 15
retry_wait_seconds: 60
max_attempts: 3
command: yarn
- name: Setup Ruby
# https://github.com/ruby/setup-ruby/releases
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
with:
ruby-version: 3
- name: Update Ruby build tools
# https://github.com/nick-fields/retry/releases
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 2
retry_wait_seconds: 60
max_attempts: 3
command: gem update cocoapods xcodeproj
- name: Pods Cache Restore
# https://github.com/actions/cache/releases
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: pods-cache
continue-on-error: true
with:
path: tests/ios/Pods
key: ${{ runner.os }}-${{ matrix.dep-resolution }}-ios-pods-v3-${{ hashFiles('tests/ios/Podfile.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.dep-resolution }}-ios-pods-v3
- name: Configure Dependency Resolution Mode
run: |
if [[ "${{ matrix.dep-resolution }}" == "cocoapods" ]]; then
echo "Configuring CocoaPods-only mode (disabling SPM)"
cd tests/ios
sed -i '' "s/^linkage = 'dynamic'/linkage = 'static'/" Podfile
printf '%s\n' '$RNFirebaseDisableSPM = true' | cat - Podfile > Podfile.tmp && mv Podfile.tmp Podfile
sed -i '' "/SWIFT_ENABLE_EXPLICIT_MODULES/d" Podfile
echo "Podfile configured for CocoaPods-only mode"
else
echo "Using default SPM mode (dynamic linkage)"
fi
- name: Pod Install
# https://github.com/nick-fields/retry/releases
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 20
retry_wait_seconds: 30
max_attempts: 3
command: yarn tests:ios:pod:install
- name: Firestore Emulator Restore
# https://github.com/actions/cache/releases
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: firestore-emulator-cache
continue-on-error: true
with:
# The firebase emulators are pure javascript and java, OS-independent
enableCrossOsArchive: true
path: ~/.cache/firebase/emulators
key: firebase-emulators-v1-${{ github.run_id }}
restore-keys: firebase-emulators-v1
- name: Start Firestore Emulator
run: yarn tests:emulator:start-ci
# https://bitrise.io/blog/post/xcode-15-performance-regressions
# https://developer.apple.com/forums/thread/805625?answerId=865340022#865340022
- name: Install yeetd and fix iOS perf issues
run: |
wget https://github.com/biscuitehh/yeetd/releases/download/1.0/yeetd-normal.pkg
sudo installer -pkg yeetd-normal.pkg -target /
yeetd &
launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist
# Vendored Homebrew formulae — pin/update: okf-bundle/ci-workflows/ios.md#pinned-homebrew-utilities
- name: Install brew utilities
# https://github.com/nick-fields/retry/releases
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 5
retry_wait_seconds: 60
max_attempts: 3
command: bash .github/workflows/scripts/install-homebrew-rnfb.sh applesimutils xcbeautify
- name: Build iOS App Debug
if: contains(matrix.buildmode, 'debug')
run: |
ccache -s
export SKIP_BUNDLING=1
export RCT_NO_LAUNCH_PACKAGER=1
set -o pipefail
echo $PATH
which clang
yarn tests:ios:build
ccache -s
shell: bash
- name: Build iOS App Release
if: contains(matrix.buildmode, 'release')
run: |
ccache -s
export RCT_NO_LAUNCH_PACKAGER=1
set -o pipefail
echo $PATH
which clang
yarn tests:ios:build:release
ccache -s
shell: bash
- name: Metro Bundler Cache Restore
if: contains(matrix.buildmode, 'debug')
# https://github.com/actions/cache/releases
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: metro-bundler-cache
continue-on-error: true
with:
path: ${{ steps.workflow-variables.outputs.metro-cache }}
key: ${{ runner.os }}-ios-metro-v1-${{ github.run_id }}
restore-keys: ${{ runner.os }}-ios-metro-v1
- name: Pre-fetch Javascript bundle
if: contains(matrix.buildmode, 'debug')
run: |
nohup yarn tests:packager:jet-ci > metro.log 2>&1 &
printf 'Waiting for packager to come online'
until curl --output /dev/null --silent --head --fail http://localhost:8081/status; do
printf '.'
sleep 2
done
echo "Packager is online! Preparing bundle..."
curl --output /dev/null --silent --head --fail "http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false&inlineSourceMap=true"
echo "...javascript bundle ready"
- name: Start host screen recording
if: env.RNFB_RECORD_SCREENS == '1'
continue-on-error: true
run: |
nohup sh -c "sleep 314159265 | screencapture -v -C -k -T0 -g screenrecording.mov > screenrecording.log 2>&1 &"
- name: Pre-Boot Simulator
# Separate Simulator boot from Detox run. boot-simulator.sh polls bootstatus and logs
# migration progress so long first-boot waits are visible in the step log.
# https://github.com/nick-fields/retry/releases
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 12
retry_wait_seconds: 60
max_attempts: 3
command: RNFB_START_SIM_LOGS=0 ./.github/workflows/scripts/boot-simulator.sh
- name: Start Simulator App Log
# One filtered sim log stream (testing + SpringBoard/invertase + Storage); optional sim video when record_screens.
# RNFB_SIM_LOG_STDOUT=1 surfaces sim-app.log lines in the step log for iOS Storage diagnosis (B3.1).
continue-on-error: true
run: |
chmod +x ./.github/workflows/scripts/boot-simulator.sh
RNFB_RECORD_SCREENS="${RNFB_RECORD_SCREENS}" RNFB_SIM_BOOT_MODE=logs RNFB_SIM_LOG_STDOUT=1 ./.github/workflows/scripts/boot-simulator.sh
- name: Wait for host load to settle
continue-on-error: true
run: |
chmod +x ./.github/workflows/scripts/wait-for-load-settle.sh
./.github/workflows/scripts/wait-for-load-settle.sh
- name: Detox Test Debug
if: contains(matrix.buildmode, 'debug')
timeout-minutes: 82
run: |
yarn tests:ios:test-cover 2>&1 | tee detox-step.log
exit ${PIPESTATUS[0]}
- name: Process iOS native coverage
if: always() && contains(matrix.buildmode, 'debug')
continue-on-error: true
run: yarn tests:ios:test:process-coverage
- name: Detox Test Release
if: contains(matrix.buildmode, 'release')
timeout-minutes: 82
run: |
yarn tests:ios:test:release 2>&1 | tee detox-step.log
exit ${PIPESTATUS[0]}
- name: Write flake summary
if: always()
continue-on-error: true
run: |
chmod +x ./.github/workflows/scripts/flake-summary.sh
RNFB_DETOX_LOG=detox-step.log ./.github/workflows/scripts/flake-summary.sh
- name: Stop Screen and App Video and System Logging
if: always()
continue-on-error: true
run: |
killall -int simctl
if [[ "${RNFB_RECORD_SCREENS}" == "1" ]]; then
killall -int screencapture
fi
pkill -f resource-monitor.sh || true
- name: Upload App Video
# https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always() && env.RNFB_RECORD_SCREENS == '1'
with:
name: simulator-${{ matrix.buildmode }}-${{ matrix.iteration }}_video
path: simulator.mp4
- name: Upload Simulator App Log
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always()
with:
name: sim-app-${{ matrix.buildmode }}-${{ matrix.iteration }}_log
path: sim-app.log
- name: Upload resource monitor log
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always()
with:
name: resource-monitor-${{ matrix.buildmode }}-${{ matrix.iteration }}_log
path: resource-monitor.log
- name: Upload flake summary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always()
with:
name: flake-summary-${{ matrix.buildmode }}-${{ matrix.iteration }}
path: flake-summary.txt
- name: Upload Detox step log
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always()
with:
name: detox-step-${{ matrix.buildmode }}-${{ matrix.iteration }}_log
path: detox-step.log
- name: Upload Metro log
# https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always() && contains(matrix.buildmode, 'debug')
with:
name: metro-${{ matrix.buildmode }}-${{ matrix.iteration }}_log
path: metro.log
- name: Upload Screen Recording
# https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always() && env.RNFB_RECORD_SCREENS == '1'
with:
name: screenrecording-${{ matrix.buildmode }}-${{ matrix.iteration }}
path: screenrecording.*
- name: Upload Firebase emulator script logs
# https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
if: always()
with:
name: emulator-scripts-logs-${{ matrix.buildmode }}-${{ matrix.iteration }}
path: .github/workflows/scripts/*.log
# https://github.com/codecov/codecov-action/releases
- name: Upload Codecov e2e TS (iOS)
uses: codecov/codecov-action@e53489f4d376d79066609109e7a95a29eb3740b1 # v7.0.0
if: contains(matrix.buildmode, 'debug')
continue-on-error: true
with:
files: coverage/lcov.info
flags: e2e-ts-ios
verbose: true
- name: Upload Codecov iOS native
uses: codecov/codecov-action@e53489f4d376d79066609109e7a95a29eb3740b1 # v7.0.0
if: contains(matrix.buildmode, 'debug')
continue-on-error: true
with:
files: coverage/ios-native/lcov.info
flags: ios-native
verbose: true
- name: Yarn Cache Save
# https://github.com/actions/cache/releases
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: "${{ github.ref == 'refs/heads/main' }}"
continue-on-error: true
with:
path: .yarn/cache
key: ${{ steps.yarn-cache.outputs.cache-primary-key }}
- name: Detox Framework Cache Save
# https://github.com/actions/cache/releases
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: "${{ github.ref == 'refs/heads/main' }}"
continue-on-error: true
with:
path: ~/Library/Detox/ios
key: ${{ steps.detox-cache.outputs.cache-primary-key }}
- name: Pods Cache Save
# https://github.com/actions/cache/releases
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: "${{ github.ref == 'refs/heads/main' }}"
continue-on-error: true
with:
path: tests/ios/Pods
key: ${{ steps.pods-cache.outputs.cache-primary-key }}
- name: Firestore Emulator Cache Save
# https://github.com/actions/cache/releases
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: "${{ github.ref == 'refs/heads/main' }}"
continue-on-error: true
with:
# The firebase emulators are pure javascript and java, OS-independent
enableCrossOsArchive: true
path: ~/.cache/firebase/emulators
key: ${{ steps.firestore-emulator-cache.outputs.cache-primary-key }}
- name: Metro Bundler Cache Save
# https://github.com/actions/cache/releases
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
if: "${{ contains(matrix.buildmode, 'debug') && github.ref == 'refs/heads/main' }}"
continue-on-error: true
with:
path: ${{ steps.workflow-variables.outputs.metro-cache }}
key: ${{ steps.metro-bundler-cache.outputs.cache-primary-key }}