- Published on
Flutter iOS 빌드 실패? Podfile·CocoaPods 충돌 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install 단계에서 멈추거나, Xcode 아카이브에서만 실패하거나, 심지어는 로컬에서는 되는데 CI에서만 깨지는 일이 생깁니다. 겉으로는 모두 “CocoaPods 문제”처럼 보이지만, 실제로는 Podfile, Podfile.lock, Pods/, Runner.xcworkspace, Ruby/Pods 버전 조합, 그리고 Flutter가 생성하는 iOS 설정이 서로 어긋나면서 발생합니다.
이 글은 Flutter iOS 빌드 실패를 Podfile·CocoaPods 충돌 관점에서 유형별로 분류하고, “삭제 후 재설치” 같은 감각적인 처방이 아니라 원인을 좁히는 순서와 안전한 고정 방법을 정리합니다.
증상 패턴으로 원인 빠르게 분류하기
아래는 현장에서 가장 자주 만나는 실패 패턴입니다.
1) pod install 자체가 실패
대표 로그 예시는 다음과 같습니다.
CocoaPods could not find compatible versions for podSpecs satisfying the dependency were found, but they required a higher minimum deployment targetThe sandbox is not in sync with the Podfile.lockUnable to find a specification for ...
이 경우는 대체로 Podfile 설정, iOS 최소 버전, Podspec 저장소 상태, Podfile.lock/Pods 캐시 불일치가 원인입니다.
2) pod install은 성공하지만 Xcode 빌드에서 실패
Module not foundUndefined symbols for architecture arm64ld: library not foundSwift compiler error또는Non-modular header inside framework module
이 경우는 정적/동적 링크 설정(use_frameworks 등), Swift 버전/빌드 설정, 아키텍처/시뮬레이터 설정, Xcode 프로젝트가 .xcodeproj로 열림 같은 문제로 갈립니다.
3) 로컬은 되는데 CI에서만 실패
대부분은 CocoaPods 버전 차이, Ruby 버전 차이, Pod repo 캐시 상태, Lockfile 미커밋, Apple Silicon/Intel 환경 차이입니다. Python에서 가상환경/인터프리터가 섞여 ModuleNotFoundError가 나는 것처럼, iOS도 “실행 환경”이 조금만 달라져도 의존성 해석 결과가 달라집니다. 비슷한 진단 접근은 이 글의 체크리스트 감각이 도움이 됩니다: pip install은 성공인데 실행하면 ModuleNotFoundError가 뜰 때 venv poetry conda 혼용으로 꼬인 인터프리터와 site-packages를 10분 만에 진단하고 확실히 고치는 체크리스트
가장 먼저 확인할 3가지: 워크스페이스, iOS 최소 버전, Pods 버전
1) Xcode는 반드시 .xcworkspace로 열기
CocoaPods를 쓰는 iOS 프로젝트는 Runner.xcworkspace가 진짜 진입점입니다.
- 올바름:
ios/Runner.xcworkspace - 잘못:
ios/Runner.xcodeproj
잘못 열면 Pods 타겟이 링크되지 않아 “모듈 못 찾음” 류 오류가 연쇄적으로 납니다.
2) iOS 최소 버전이 Pod 요구사항을 만족하는지
최근 SDK/플러그인은 iOS 12, 13 이상을 요구하는 경우가 많습니다. Podfile에서 최소 버전을 명확히 올려 충돌을 없애는 편이 낫습니다.
# ios/Podfile
platform :ios, '13.0'
그리고 Flutter 쪽도 맞춰야 합니다. ios/Flutter/AppFrameworkInfo.plist의 MinimumOSVersion은 Flutter가 관리하므로, 보통은 Podfile과 Xcode 프로젝트 설정에서 일관되게 맞추는 것이 핵심입니다.
3) CocoaPods 버전이 팀/CI와 동일한지
pod는 Ruby gem이라 개발자마다 버전이 달라지기 쉽습니다.
pod --version
ruby -v
which pod
CI에서만 깨지면 대부분 여기서 시작합니다. 팀 단위로는 Gemfile로 CocoaPods 버전을 고정하는 방법이 가장 안정적입니다.
# ios/Gemfile
source 'https://rubygems.org'
gem 'cocoapods', '1.14.3'
cd ios
bundle install
bundle exec pod install
충돌의 핵심: Podfile 옵션 조합이 플러그인과 맞지 않을 때
Flutter iOS에서 충돌을 만드는 대표 옵션은 다음입니다.
use_frameworks!use_modular_headers!inhibit_all_warnings!post_install에서의 빌드 설정 강제
use_frameworks!를 넣었는데 일부 플러그인이 정적 링크를 기대
일부 플러그인은 정적 라이브러리 전제를 두고 있어 use_frameworks!로 동적 프레임워크화되면 심볼 충돌이나 헤더 이슈가 발생할 수 있습니다.
가능하면 Flutter 기본 템플릿에 가깝게 두고, 정말 필요할 때만 use_frameworks!를 제한적으로 사용하세요.
# ios/Podfile
use_frameworks! :linkage => :static
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
위처럼 :static로 고정하면 “프레임워크는 쓰되 정적으로 링크”하는 절충이 가능해 충돌을 줄일 때가 많습니다.
use_modular_headers!가 필요한 경우
Non-modular header inside framework module 류 오류가 반복되면 모듈러 헤더가 도움이 되기도 합니다. 다만 이것도 전역 적용은 부작용이 있어, 가능하면 문제 Pod에만 적용하는 편이 안전합니다.
가장 재현성 높은 해결 순서: 정리 → 재생성 → 고정
여기서부터는 “한 번에 깨끗하게” 정리하는 절차입니다. 순서가 중요합니다.
1) Flutter 산출물 정리
flutter clean
rm -rf ios/Flutter/Flutter.framework ios/Flutter/Flutter.podspec
rm -rf ios/Flutter/App.framework
프로젝트에 따라 경로가 조금 다를 수 있지만, 핵심은 Flutter iOS 산출물과 캐시를 먼저 비우는 것입니다.
2) Pods 관련 파일 정리
rm -rf ios/Pods
rm -f ios/Podfile.lock
rm -rf ios/.symlinks
rm -f ios/Runner.xcworkspace/contents.xcworkspacedata
Podfile.lock을 지울지 여부는 팀 정책에 따라 다릅니다.- 팀/CI 재현성이 중요하면
Podfile.lock은 보통 커밋 대상이며, 이 경우에는 함부로 지우지 말고 “왜 lock과 sandbox가 어긋났는지”를 먼저 봐야 합니다. - 다만 지금은 “충돌 상태를 탈출”하는 목적이므로, 원인 파악 전 임시로 지우고 재생성해 정상 조합을 확보하는 전략이 유효합니다.
3) CocoaPods 캐시/레포 동기화
cd ios
pod repo update
Unable to find a specification 류는 레포가 오래되어 발생하는 경우가 많습니다.
4) Pod 설치는 가능하면 번들로 실행
Gemfile을 도입했다면 아래처럼 실행합니다.
cd ios
bundle exec pod install --verbose
Gemfile이 없다면 일단은:
cd ios
pod install --verbose
5) Xcode 빌드 설정 충돌을 post_install에서 정리
Flutter 플러그인 중 일부는 빌드 설정이 꼬이면 시뮬레이터/디바이스에서만 깨집니다. 다음은 자주 쓰는 안전한 post_install 패턴입니다.
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
# iOS 최소 버전 강제 (Pod가 더 높은 버전을 요구하는 경우 정합성 확보)
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
# Apple Silicon 환경에서 일부 구형 Pod가 문제를 일으킬 때만 제한적으로 사용
# config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
end
end
end
주의할 점은 EXCLUDED_ARCHS는 임시 처방으로는 좋지만, 장기적으로는 Pod 업데이트나 Xcode 버전 정합으로 제거하는 것이 맞습니다.
자주 만나는 에러별 처방전
The sandbox is not in sync with the Podfile.lock
의미는 단순합니다. Pods/ 디렉터리의 설치 상태와 Podfile.lock이 가리키는 상태가 다릅니다.
해결은 둘 중 하나입니다.
- lock을 신뢰한다:
Pods/만 지우고 lock 기준으로 재설치 - 현재를 신뢰한다: lock을 갱신하고 커밋
대부분 팀 개발에서는 lock을 신뢰하는 쪽이 안전합니다.
rm -rf ios/Pods
cd ios
pod install
Specs satisfying the dependency were found, but they required a higher minimum deployment target
Pod가 iOS 13을 요구하는데 프로젝트가 iOS 12로 되어 있는 상황입니다.
Podfile의platform을 올립니다.- Xcode 프로젝트의 Deployment Target도 같이 올립니다.
platform :ios, '13.0'
Undefined symbols for architecture arm64
대부분 아래 중 하나입니다.
- 정적/동적 링크 옵션 충돌
- 특정 Pod가 시뮬레이터 arm64를 지원하지 않음
OTHER_LDFLAGS나-ObjC설정 문제
가장 먼저는 use_frameworks! 사용 여부를 확인하고, 가능하면 use_frameworks! :linkage => :static로 바꿔보는 것이 효과적인 경우가 많습니다.
CI에서만 깨질 때: 환경 고정이 답이다
CI는 깨끗한 환경이라 오히려 문제를 잘 드러냅니다. 로컬에서 우연히 맞아떨어진 조합이 CI에서는 재현되지 않는 것이죠.
권장 고정 포인트는 아래 3가지입니다.
- CocoaPods 버전:
Gemfile로 고정 후bundle exec pod install - Flutter 버전: FVM 또는 CI 캐시로 고정
Podfile.lock커밋: 의존성 해석 결과를 저장
이렇게 하면 “어제는 됐는데 오늘은 안 됨” 류의 변동이 크게 줄어듭니다.
권장 체크리스트 (문제 재발 방지)
Runner.xcworkspace로 열었는가platform :ios가 플러그인 요구사항보다 낮지 않은가pod --version이 팀/CI와 같은가Podfile.lock을 커밋하고 CI에서 그대로 재현하는가use_frameworks!와use_modular_headers!를 “필요한 만큼만” 쓰고 있는가post_install에서 강제하는 값이 과하지 않은가
마무리: “삭제 후 재설치”를 넘어, 충돌을 구조적으로 제거하기
Flutter iOS 빌드 실패는 대부분 CocoaPods가 나빠서가 아니라, 의존성 해석 결과를 고정하지 못했거나, Podfile 옵션이 플러그인 생태계와 어긋났거나, Xcode 진입점/빌드 설정이 틀어진 것에서 시작합니다.
위 순서대로 정리하면, 단발성 해결이 아니라 팀/CI까지 포함해 재현 가능한 상태로 돌아갈 수 있습니다. 특히 CI가 있다면 Gemfile로 CocoaPods를 고정하고 Podfile.lock을 관리하는 것만으로도 실패율이 눈에 띄게 줄어듭니다.