- Published on
Flutter iOS 빌드 에러 - Podfile·CocoaPods 충돌 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Flutter로 iOS 빌드를 하다 보면, 어느 날 갑자기 pod install이 깨지거나 Xcode 빌드가 실패하면서 “Podfile이 문제다”, “CocoaPods가 꼬였다” 같은 메시지가 쏟아집니다. 특히 Flutter는 iOS 네이티브 프로젝트를 자동 생성/갱신(예: flutter pub get, flutter build ios)하는 흐름이 섞여 있어, Podfile과 Pods/Lockfile, Ruby/CocoaPods 버전, Xcode 프로젝트 설정이 조금만 어긋나도 충돌이 쉽게 납니다.
이 글은 “에러 메시지에 따라 무엇을 먼저 의심하고, 어떤 순서로 정리하면 가장 안전하게 복구되는지”를 원인-증상-해결 형태로 정리합니다. 운영 환경에서 장애를 디버깅할 때 체크리스트로 원인을 좁혀가는 방식이 효과적인데, 접근 자체는 Python uvloop 도입 후 Event loop is closed 해결 가이드처럼 “환경/캐시/버전/재현 순서”를 분해하는 것과 유사합니다.
Podfile·CocoaPods 충돌의 전형적인 증상
아래는 Flutter iOS에서 자주 만나는 에러 패턴입니다.
1) CocoaPods 버전/레포 상태 문제
CocoaPods could not find compatible versions for pod "..."Specs satisfying the '...' dependency were found, but they required a higher minimum deployment target.Unable to find a specification for ...
2) Podfile 설정과 Xcode 프로젝트 설정 불일치
The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to ... but the range of supported deployment target versions is ...use_frameworks!/use_modular_headers!관련 충돌Swift/Objective-C혼합 프로젝트에서module not found또는Framework not found
3) Pods 캐시/Lockfile 꼬임
Sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.The sandbox is not in sync with the Podfile.lock가 반복
4) Apple Silicon / Ruby 환경 문제
- M1/M2에서
ffi빌드 실패,xcodebuild단계에서 아키텍처 충돌 pod install은 되는데 Xcode Archive에서만 실패
가장 먼저 확인할 5가지(원인 분리용)
문제를 빨리 좁히려면, 무작정 rm -rf Pods부터 하기 전에 아래를 먼저 확인하세요.
- Flutter 채널/버전:
flutter --version - Xcode 버전: Xcode 업데이트 직후인지
- CocoaPods 버전:
pod --version - iOS Deployment Target: Podfile의
platform :ios, '...'와 Xcode 프로젝트의 Deployment Target - Lockfile/Pods 상태:
ios/Podfile.lock가 커밋되어 있는지(팀 작업 시 중요)
이 5개 중 하나라도 팀원/CI와 다르면, “내 컴퓨터에서만” 재현되는 충돌이 발생합니다. JWT 검증에서 kid 캐시가 어긋나면 특정 노드에서만 실패하는 것처럼, 로컬 iOS 빌드도 캐시/버전 불일치가 핵심 원인인 경우가 많습니다(관점 참고: JWT 검증 실패 - JWKS kid 불일치·캐시 7가지).
안전한 복구 순서(클린 → 재생성 → 재설치)
아래 순서는 “Pods/DerivedData/Flutter 캐시”를 단계적으로 정리하면서, 재현성을 최대화하는 루틴입니다.
1) Flutter/패키지 캐시 정리
flutter clean
rm -rf .dart_tool
flutter pub get
2) iOS Pods 관련 파일 정리
프로젝트 루트에서:
cd ios
rm -rf Pods
rm -f Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
pod deintegrate
pod repo update
pod install
cd ..
pod deintegrate: Xcode 프로젝트에 주입된 CocoaPods 설정을 제거Podfile.lock제거: 의존성 버전을 새로 고정(단, 팀/CI 정책에 따라 유지가 맞을 수 있음)DerivedData삭제: Xcode 빌드 캐시로 인한 유령 에러 제거
3) Xcode에서 워크스페이스로 열기
CocoaPods를 쓰는 iOS 프로젝트는 반드시:
Runner.xcworkspace열기Runner.xcodeproj로 열면 Pods가 링크되지 않아 실패할 수 있음
Podfile에서 가장 자주 충돌 나는 지점 4가지와 해결
1) iOS Deployment Target 불일치
증상
- 특정 Pod가 더 높은 iOS 최소 버전을 요구
- Xcode 빌드 단계에서
IPHONEOS_DEPLOYMENT_TARGET경고/에러
해결
Podfile의 플랫폼 버전을 올리고, Xcode 프로젝트 설정도 동일하게 맞춥니다.
# ios/Podfile
platform :ios, '13.0'
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
- Flutter 플러그인 중 일부는 iOS 12 이하를 사실상 지원하지 않는 경우가 늘고 있습니다.
- “Podfile만 올리고 Xcode는 그대로”면, 아카이브 단계에서 다시 터지기도 합니다.
2) use_frameworks! / use_modular_headers! 충돌
증상
module not found/Swift pod cannot be integrated as static library류 메시지- Firebase, gRPC, 일부 Swift 기반 Pod에서 충돌
원칙
- 가능하면 Flutter 기본 템플릿 흐름을 유지
- 특정 플러그인이 요구할 때만 옵션을 추가
예시: 동적 프레임워크가 필요할 때
# ios/Podfile
use_frameworks! :linkage => :static
use_modular_headers!
다만 이 설정은 모든 Pod에 영향을 주므로, 적용 후 새로 생긴 에러가 있다면 원인 Pod를 좁혀야 합니다. (예: 어떤 Pod는 modular headers에서만 깨짐)
3) “Sandbox is not in sync with Podfile.lock” 반복
증상
pod install후에도 동일 에러- CI에서는 성공, 로컬만 실패(또는 반대)
원인
Pods/Manifest.lock와Podfile.lock불일치- Xcode workspace가 오래된 Pods 상태를 참조
해결 루틴
cd ios
pod deintegrate
rm -rf Pods
rm -f Podfile.lock
pod install --repo-update
팀 프로젝트라면 Podfile.lock을 커밋하고 고정하는 쪽이 일반적으로 재현성이 좋습니다. 다만 Flutter 플러그인 추가/업데이트 시에는 lock 갱신이 필요하므로, “갱신 PR”과 “기능 PR”을 분리하면 충돌이 줄어듭니다.
4) Apple Silicon(M1/M2)에서 gem/ffi 문제로 pod install 실패
증상
ffi빌드 실패gem install cocoapods에서 네이티브 확장 컴파일 오류
해결 방향
- 시스템 Ruby 대신
rbenv/asdf로 Ruby 버전 고정 - CocoaPods도 프로젝트/팀 단위로 버전 고정
예시(권장): Bundler로 CocoaPods 고정
cd ios
bundle init
# Gemfile에 cocoapods 버전 명시
ios/Gemfile 예시:
source 'https://rubygems.org'
gem 'cocoapods', '1.15.2'
설치/실행:
cd ios
bundle install
bundle exec pod install
이 방식은 “내 로컬에 깔린 pod 버전”에 의존하지 않게 만들어 CI/팀원 간 차이를 줄입니다.
Flutter iOS에서 자주 쓰는 Podfile 템플릿(충돌 최소화)
아래는 실무에서 무난하게 쓰이는 형태입니다. (프로젝트 상황에 맞게 최소한만 수정)
# ios/Podfile
platform :ios, '13.0'
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig not found. Run 'flutter pub get' first."
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in Generated.xcconfig."
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# 필요 시만 사용
# use_frameworks! :linkage => :static
# use_modular_headers!
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
핵심은 flutter_additional_ios_build_settings(target) 호출을 유지하면서, Deployment Target을 Pods까지 일괄 보정하는 것입니다.
Xcode 빌드 단계에서만 터질 때(Archive/Release 전용 이슈)
Debug에서는 되는데 Archive에서만 실패하는 경우는 대개 아래가 원인입니다.
- Release에서만 활성화되는 빌드 설정(Dead code stripping, bitcode, swift optimization)
- 서명/프로비저닝 이슈(이 글 범위 밖)
- 특정 Pod가 Release에서만 스크립트를 실행하다 실패
점검 팁
- Xcode에서
Report navigator의 가장 첫 실패 지점을 봅니다. Run Script단계가 실패면, 스크립트가 참조하는 경로(예:flutter바이너리,.xcconfig)를 확인합니다.Pods-Runner타겟의 Build Settings에서IPHONEOS_DEPLOYMENT_TARGET이 Podfile과 동일한지 확인합니다.
CI/팀 개발에서 충돌을 줄이는 운영 팁
CocoaPods 충돌은 “개발자 1명은 되는데 다른 사람은 안 됨”으로 확산되기 쉽습니다. 아래를 팀 규칙으로 두면 재발이 크게 줄어듭니다.
- CocoaPods 버전 고정:
Gemfile + bundle exec pod install - Podfile.lock 정책 결정: 커밋/미커밋을 명확히(대부분 커밋 권장)
- Xcode 버전 통일: 최소한 메이저 버전은 맞추기
- 플러그인 업데이트 PR 분리: lockfile 변화가 큰 PR은 기능 PR과 분리
이런 “환경을 고정해 재현성을 확보”하는 방식은 인프라 디버깅에서도 동일하게 통합니다. 예를 들어 노드 상태가 간헐적으로 깨질 때도 버전/구성 드리프트를 먼저 의심하듯(EKS Node NotReady - CNI ENI 할당 실패 해결 가이드), iOS 빌드도 로컬 환경 드리프트가 1순위 원인입니다.
결론: 충돌을 “정리 절차”로 표준화하라
Flutter iOS 빌드에서 Podfile·CocoaPods 충돌은 대부분 아래 셋 중 하나로 귀결됩니다.
- 버전 불일치(CocoaPods/Ruby/Xcode/플러그인)
- Deployment Target 불일치(Podfile ↔ Xcode ↔ Pods)
- 캐시/Lockfile 꼬임(Pods/DerivedData/Manifest.lock)
해결의 핵심은 “무엇이 꼬였는지”를 추측하기보다, **표준 복구 루틴(클린 → deintegrate → 재설치 → workspace 빌드)**을 팀에 고정하는 것입니다. 위 템플릿과 명령어를 그대로 체크리스트로 만들어 두면, 대부분의 iOS 빌드 에러는 10~15분 내에 안정적으로 복구됩니다.