iOS Frontend Environment Debugging Retrospective (M1/arm64, Xcode 16)
TL;DR
I repeatedly failed building a React Native 0.81 + RNFirebase Messaging iOS app on an Apple Silicon (M1/arm64) machine due to a combination of:
- C/C++ header include-order pitfalls (RCT-Folly, Yoga, glog) amplified by custom header tweaks
- CocoaPods quirks (bundled version vs system pod, encoding)
- Firebase module map exposure (missing modular headers)
The final green build came from reverting to standard RN 0.81 Pod settings, keeping architecture defaults, and enabling modular headers for GoogleUtilities, FirebaseCoreInternal, and FirebaseCore — plus using the system pod to avoid a known Bundler/CocoaPods issue.
Environment
- Machine: Apple Silicon (M1, arm64)
- macOS: 15.6.x (Darwin 24.6.0)
- Xcode: 16.1 (Build 17A400), iPhoneSimulator SDK 26.0
- React Native: 0.81.x (Hermes enabled)
- CocoaPods:
- Bundled pod had an intermittent “pathname contains null byte” bug
- System
podworked reliably
- iOS Deployment Target: 15.1
- Firebase: RNFirebase (App + Messaging)
Symptoms Observed
- Yoga headers not found, e.g.
'yoga/Yoga.h' file not found. - RCT-Folly errors referencing
<cstdio>/<cstring>/<cmath>with C headers taking precedence over libc++ wrappers (leading toFILE/pthreadissues). - glog mutex-related build breaks (
mutex.h/pthread macros). - CocoaPods “pathname contains null byte” during
pod installwith the bundled Ruby/CocoaPods. - Firebase compile errors:
Module 'FirebaseCore' not foundinFirebaseInstallationssources. - Intermittent Xcode/Ruby locale hiccups (resolved by ensuring UTF-8 env and re-running).
Root Causes
- Over-customized header search paths for Pods (global
HEADER_SEARCH_PATHS,SYSTEM_HEADER_SEARCH_PATHS,OTHER_CPLUSPLUSFLAGS,-isystem) changed include order, making C headers shadow libc++ and breaking RCT-Folly. - Attempting to “fix” headers globally (Pods + App target) unintentionally polluted multiple targets (glog, Yoga, Folly, React-*), causing cascading failures.
- Using frameworks / modular header toggles globally introduced module map mismatches for Swift pods and RNFirebase transitive deps.
- Bundled CocoaPods path/encoding edge-case (“pathname contains null byte”) blocked deterministic installs.
- FirebaseCore module was not exposed as a modular header in our setup, causing
Module 'FirebaseCore' not found.
What Actually Fixed It
- Reset Podfile to the standard RN 0.81 shape; don’t globally alter header search paths or C++ flags.
- Keep Legacy Architecture (disable New Architecture explicitly) and target iOS 15.1 to satisfy React Native codegen.
- Avoid forcing
use_frameworks!; stick to default static libraries. - Make only the necessary Firebase pods modular:
GoogleUtilities,FirebaseCoreInternal, and criticallyFirebaseCore.
- Use system
pod install(not Bundler) to avoid the “pathname contains null byte” error. - Build on an arm64 simulator (M1 host), letting headermap/modulemaps drive includes.
- Do not add Yoga paths manually; rely on Yoga’s own modulemap and RN defaults.
- Only if needed: for glog, ensure pthread defines via target-specific settings (but avoid global hacks).
Result: Build succeeded. App runs, and FCM token logs as expected.
Minimal Podfile (Working Shape)
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, '15.1'
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
prepare_react_native_project!
target 'EchoReadApp' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
# Ensure Swift pods expose module maps for Firebase
pod 'GoogleUtilities', :modular_headers => true
pod 'FirebaseCoreInternal', :modular_headers => true
pod 'FirebaseCore', :modular_headers => true
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
# No global header/cxx overrides here. Keep RN defaults.
end
end
Key point: No global use_frameworks!, no global HEADER_SEARCH_PATHS/SYSTEM_HEADER_SEARCH_PATHS hacks, no project-wide -isystem injections.
Verified Working Build Path
- Clean CocoaPods integration
cd ios pod deintegrate rm -rf build Pods Podfile.lock - Install with system CocoaPods
pod install - Xcode clean + build (arm64 simulator)
xcodebuild \ -workspace EchoReadApp.xcworkspace \ -scheme EchoReadApp \ -sdk iphonesimulator \ -configuration Debug \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ ONLY_ACTIVE_ARCH=YES \ build - Run the app (will print FCM token on first launch after permission)
# ensure Metro is running npm start # or: npx react-native start # then run on simulator npx react-native run-ios --simulator "iPhone 17 Pro"
Guidance for Future Changes
- Prefer target-scoped tweaks over global Podfile-wide settings.
- Let RN’s
react_native_post_installdo the heavy lifting; avoid overriding header maps. - For Firebase on RN iOS, keep
GoogleUtilities,FirebaseCoreInternal, andFirebaseCoreas modular headers when using static libs. - On Apple Silicon, prefer arm64 simulators and avoid x86_64-specific workarounds.
- If
pod installmisbehaves under Bundler, try systempodas a fallback. - Treat any “fix” that alters global include order with suspicion; verify by reverting to defaults first.