The transition to Xcode 16 (and iOS 18 SDK) has introduced a stricter build environment that exposes long-standing fragility in the CocoaPods dependency graph used by Flutter.
If you are encountering "Include of non-modular header inside framework module", errors related to BoringSSL/gRPC, or Sandboxing violation errors during the build phase, you are dealing with a conflict between Clang's module system and legacy header import styles found in many Objective-C libraries (like Firebase).
This post provides the root cause analysis and a persistent configuration fix to resolve these build failures.
The Root Cause: LLVM Modules & Build Sandboxing
1. The Module Map Conflict
Apple's Clang compiler uses Modules to replace the traditional #include / #import preprocessor mechanism. Modules provide better build performance and encapsulation by using "Module Maps" (.modulemap) to define explicitly which headers belong to a framework.
The error "Include of non-modular header inside framework module" occurs when a Framework (Module A) tries to import a header file that exists on the file system but is not declared in the Module Map of the library it belongs to (Module B).
In the context of Flutter and Firebase:
- CocoaPods generates Module Maps for dependencies.
- Complex C++ dependencies (like
gRPC-C++orBoringSSL-GRPCused by Firestore) often have headers that are accessible via search paths but not formally exposed in the generated module map. - Xcode 16 defaults to a stricter Clang configuration that treats this violation as a hard error rather than a warning.
2. User Script Sandboxing
Xcode 15 introduced (and Xcode 16 enforces) User Script Sandboxing. This prevents build scripts (like the shell scripts CocoaPods embeds to copy resources) from accessing files outside of their declared input and output lists. Many older Flutter plugins and CocoaPods scripts do not declare these lists correctly, causing the build to fail with permission errors (often involving rsync).
The Fix: Podfile post_install Hooks
Do not manually change settings in the Xcode Build Settings GUI. Those changes will be wiped out the next time you run flutter pub get or pod install.
The correct solution is to inject build settings into the CocoaPods generation process via the ios/Podfile.
Step 1: Clean Build Artifacts
Before applying the fix, ensure you aren't fighting cached artifacts.
flutter clean
rm -rf ios/Pods
rm -rf ios/Podfile.lock
Step 2: Update the Podfile
Open ios/Podfile. Locate the post_install block. If it does not exist, create it at the bottom of the file.
Add the logic to force CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES to YES and disable strict sandboxing for Pod targets.
platform :ios, '14.0' # Ensure minimum target is reasonable for modern plugins
# ... standard Flutter Podfile setup ...
target 'Runner' do
use_frameworks! :linkage => :static # Recommended for Firebase/Firestore
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# ----------------------------------------------------------
# FIX 1: Allow Non-Modular Includes
# Solves: "Include of non-modular header inside framework module"
# Context: frequent issue with BoringSSL-GRPC and Firebase headers
# ----------------------------------------------------------
config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
# ----------------------------------------------------------
# FIX 2: Disable User Script Sandboxing
# Solves: "rsync: rename... Operation not permitted"
# Context: Allows Pod scripts to modify files outside strict inputs
# ----------------------------------------------------------
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
# ----------------------------------------------------------
# FIX 3: Ensure Deployment Target Consistency
# Solves: Warnings/Errors about conflicting deployment targets
# ----------------------------------------------------------
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
# ----------------------------------------------------------
# FIX 4: GCC_PREPROCESSOR_DEFINITIONS for C++ content
# Solves: " 'pb_release' has no member named 'release' " in some gRPC versions
# ----------------------------------------------------------
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1'
end
end
# Standard Flutter hook must remain
flutter_additional_ios_build_settings(installer)
end
Step 3: Re-install Pods
Run the installation to generate the patched Xcode workspace.
cd ios
pod install --repo-update
cd ..
flutter build ios
The Explanation
Why CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES?
This flag instructs the Clang compiler to relax the module validation. When it encounters an import for a header that isn't in the module map, instead of throwing a fatal error, it falls back to a textual include (treating the file essentially as text pasted into the source). While not "pure" in the LLVM Module sense, it is the standard workaround for consuming large, legacy C++ libraries wrapped in Objective-C frameworks (like Firebase) within the Flutter ecosystem.
Why Disable Sandboxing?
The ENABLE_USER_SCRIPT_SANDBOXING = NO setting is a temporary measure. Ideally, plugin maintainers should update their .podspec files to explicitly list all input_files and output_files required by their build scripts. However, blocking your release while waiting for 3rd party vendors to update build scripts is impractical. Disabling sandboxing restores the behavior of Xcode 14, allowing the scripts to execute rsync or shell commands freely within the project directory.
Conclusion
Xcode 16's strict compliance checks are technically correct but practically disruptive for the current state of the Flutter/CocoaPods ecosystem. By injecting these overrides via the Podfile, you ensure that your CI/CD pipeline and team members receive the fix automatically without requiring manual Xcode project tweaking.