- Published on
Flutter iOS 빌드 실패? CocoaPods·Xcode 15 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install이 실패하거나, Xcode 15로 올린 뒤 아카이브가 깨지거나, 시뮬레이터는 되는데 실기기에서만 링크 에러가 터지는 식으로 개발 흐름이 끊기는 경우가 많습니다. 특히 Xcode 15는 빌드 시스템과 툴체인이 업데이트되면서, 기존에 “되던 설정”이 더 이상 묵인되지 않는 케이스가 늘었습니다.
이 글은 “일단 다 지우고 다시 깐다”를 넘어, CocoaPods와 Xcode 15 조합에서 Flutter iOS 빌드 실패를 가장 빠르게 수습하는 체크리스트를 제공합니다. 로그에서 원인을 분류하고, 어떤 순서로 무엇을 고쳐야 재발이 줄어드는지에 초점을 맞춥니다.
문제 해결 글은 결국 “재현 가능한 절차”가 핵심입니다. 운영에서 장애를 줄이기 위해 체크리스트를 만드는 것과 결이 비슷합니다. 비슷한 방식의 진단 접근이 궁금하다면 K8s CrashLoopBackOff 원인별 진단·해결 체크리스트도 함께 참고하면 도움이 됩니다.
먼저 확인: 증상별 분류(가장 흔한 6가지)
아래는 Xcode 15 + Flutter에서 자주 나오는 실패 유형입니다.
pod install단계에서 실패
- Ruby/CocoaPods 버전 충돌
ffi네이티브 확장 컴파일 실패CDN: trunk네트워크/캐시 문제
- Xcode 빌드에서
Sandbox: rsync또는 파일 복사 권한 문제
- Xcode 15의 빌드 샌드박스/스크립트 실행 정책 변화
ld: library not found for -l...또는Undefined symbols for architecture arm64
- Pod가 arm64/시뮬레이터 아키텍처를 제대로 제공하지 않음
OTHER_LDFLAGS꼬임, 정적/동적 프레임워크 충돌
Module not found/Non-modular header inside framework module
use_frameworks!/use_modular_headers!조합 문제- 일부 Pod의 모듈맵 충돌
- 아카이브(Archive)에서만 실패
- Debug는 되지만 Release에서 Swift 최적화/링커 차이로 깨짐
Bitcode(과거)나Strip설정, 서명 설정 문제
Flutter.framework/App.framework관련 스크립트 실패
Run Script단계가 잘못된 경로를 참조- Flutter 캐시/DerivedData 꼬임
이제부터는 “가장 확률 높은 것부터” 처리하는 순서로 안내합니다.
0단계: 환경 버전 고정(재발 방지의 시작)
Xcode 15에서 iOS 빌드가 흔들릴 때, 가장 먼저 해야 할 일은 팀/CI에서 버전을 고정하는 것입니다. 로컬에서만 고쳐 놓으면 다음 날 CI가 깨지거나, 다른 개발자 머신에서 동일 이슈가 재발합니다.
- Xcode: 15.x 고정(가능하면 패치 버전까지)
- Flutter: 프로젝트에서 사용하는 채널/버전 고정(
fvm권장) - CocoaPods: 1.12 이상 권장(환경에 따라 1.14+도 가능)
- Ruby: macOS 기본 Ruby 대신
rbenv또는asdf로 고정 권장
버전 확인 명령은 아래처럼 한 번에 기록해 두면 좋습니다.
xcodebuild -version
flutter --version
pod --version
ruby -v
uname -m
Apple Silicon인지(arm64) Intel인지에 따라, Ruby gem 네이티브 확장(예: ffi)에서 증상이 달라질 수 있습니다.
1단계: “클린 재설치”를 올바른 순서로
Flutter iOS 빌드가 꼬였을 때 가장 흔한 실수는, 지우는 범위가 애매해 캐시가 남는 것입니다. 아래 순서로 진행하면 대부분의 “원인 불명” 상태를 초기화할 수 있습니다.
1-1. Flutter 및 iOS 의존성 캐시 정리
flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
1-2. Pod 재설치
cd ios
pod deintegrate
pod repo update
pod install
cd ..
여기까지로 해결되면, 원인은 대부분 Pod 캐시/락파일 불일치였습니다.
2단계: CocoaPods 자체가 실패할 때(ffi, Ruby, CDN)
2-1. ffi 빌드 실패(특히 Apple Silicon)
에러 로그에 ffi 또는 native extension이 보이면 Ruby 환경 문제일 확률이 높습니다. 해결 방향은 “macOS 기본 Ruby를 피하고, 프로젝트/팀에서 Ruby를 고정”입니다.
대표적인 해결 흐름:
rbenv로 Ruby 설치- CocoaPods 재설치
brew install rbenv
rbenv install 3.2.2
rbenv global 3.2.2
gem install cocoapods
pod --version
이미 설치돼 있어도 gem 경로가 꼬여 있을 수 있으니, which ruby, which pod로 실제 실행 파일이 어디인지 확인하세요.
2-2. CDN: trunk/네트워크 문제
회사 네트워크/프록시 환경에서 CocoaPods CDN이 불안정하면 pod install이 간헐적으로 실패합니다. 이 경우는 캐시를 날리고 재시도하는 것만으로는 재발합니다.
pod repo update를 명시적으로 수행- 사내 네트워크 정책이 있다면
CocoaPods Specs접근 허용 확인
CI에서만 실패한다면, 네트워크 문제를 “빌드 실패”로 오인하지 않도록 재시도 정책을 넣는 것도 방법입니다. 레이트리밋/네트워크 장애를 구조적으로 다루는 관점은 OpenAI Responses API 429 레이트리밋 토큰버킷으로 끝내기의 접근과 유사합니다.
3단계: Xcode 15에서 자주 깨지는 Podfile 포인트
Flutter 프로젝트의 ios/Podfile은 보통 템플릿 기반이지만, 플러그인 조합이 늘어나면 예외가 생깁니다.
3-1. iOS 최소 타겟 버전 명시
Xcode 15 + 최신 플러그인 조합에서는 iOS 최소 버전이 낮으면(예: 11) Pod가 요구하는 최소 버전과 충돌할 수 있습니다.
Podfile 상단에 명시적으로 올려 주세요.
platform :ios, '12.0'
프로젝트 상황에 따라 13.0 이상이 필요할 수도 있습니다(특히 최신 Firebase/Google 계열).
3-2. use_frameworks!를 무조건 켜지 않기
use_frameworks!는 Swift Pod 호환을 위해 켜는 경우가 있지만, Flutter 플러그인 조합에 따라 모듈 충돌을 만들거나, 정적/동적 링크 설정을 꼬이게 할 수 있습니다.
- 특별한 이유가 없다면 기본(비활성) 유지
- 꼭 필요하다면
:linkage => :static을 고려
예시:
use_frameworks! :linkage => :static
use_modular_headers!
단, 모든 프로젝트에 정답은 아닙니다. 이 설정을 바꾼 뒤에는 반드시 Pods 재설치와 Xcode 클린 빌드를 함께 하세요.
3-3. post_install에서 빌드 설정 강제(필요할 때만)
특정 Pod가 EXCLUDED_ARCHS나 IPHONEOS_DEPLOYMENT_TARGET로 꼬이는 경우가 있습니다. 이때만 제한적으로 post_install을 사용합니다.
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end
end
end
과도한 강제는 다른 문제를 숨길 수 있으니, “문제 Pod가 무엇인지”를 먼저 로그에서 특정하는 것이 좋습니다.
4단계: Undefined symbols for architecture arm64 해결 루트
이 에러는 원인이 매우 다양하지만, Xcode 15/Flutter에서 흔한 패턴은 아래입니다.
4-1. 시뮬레이터와 실기기 아키텍처 혼동
- 시뮬레이터:
arm64(Apple Silicon) 또는x86_64(Intel) - 실기기:
arm64
Pod가 시뮬레이터용 바이너리만 있거나, 반대로 실기기용만 있는 경우 링크가 깨집니다.
확인 방법(프레임워크 바이너리 아키텍처 확인):
lipo -info ios/Pods/SomePod/Frameworks/Some.framework/Some
4-2. 정적/동적 프레임워크 충돌
use_frameworks!를 켠 뒤부터 링크 에러가 생겼다면, 일단 끄고 재시도해 보세요. 꼭 써야 한다면 :linkage => :static으로 전환했을 때 해결되는 경우가 많습니다.
4-3. Other Linker Flags 꼬임
Xcode에서 OTHER_LDFLAGS에 중복/불필요 플래그가 들어가면 링크가 깨질 수 있습니다. 수동으로 만진 적이 있다면 원복을 권장합니다.
5단계: Sandbox: rsync 및 스크립트 단계 실패
Xcode 15에서 빌드 스크립트/파일 복사 단계가 실패하는 경우가 있습니다. Flutter iOS 빌드는 내부적으로 xcode_backend.sh를 실행하고, 프레임워크를 복사하는 단계가 있어 이 영향이 큽니다.
5-1. DerivedData와 스크립트 권한 정리
rm -rf ~/Library/Developer/Xcode/DerivedData
chmod +x ios/Flutter/flutter_export_environment.sh 2>/dev/null || true
5-2. Xcode에서 스크립트 입력/출력 설정 점검
Build Phases의 Run Script에서 “Based on dependency analysis” 옵션이 켜져 있으면, 입력/출력 파일이 부정확한 프로젝트에서 스크립트가 스킵되거나 반대로 불필요하게 반복 실행될 수 있습니다.
- 스크립트가 실행되지 않아
Flutter.framework가 누락되는 케이스: 입력/출력 경로를 올바르게 지정 - 스크립트가 매번 돌아 빌드가 느려지는 케이스: 입력/출력 지정 후 분석 기반 실행 활성화
프로젝트마다 다르므로, 실패 로그에서 “어떤 단계에서 무엇을 못 찾는지”를 먼저 확인하세요.
6단계: 아카이브(Release)에서만 실패할 때
Debug 빌드는 되는데 Archive에서만 깨지는 케이스는 아래를 우선 점검합니다.
6-1. 서명(Signing)과 프로비저닝
Xcode 15로 올리면서 팀 설정이 바뀌거나, 자동 서명이 꼬이면 Archive에서만 실패할 수 있습니다.
Signing & Capabilities에서 Team/Bundle ID 확인- CI라면
ExportOptions.plist와 프로파일 매칭 확인
6-2. Release 최적화로 인한 심볼/링크 차이
특정 Pod/플러그인이 Release에서만 링크 에러를 내는 경우가 있습니다. 이때는 실패한 심볼이 어느 라이브러리에 있어야 하는지 추적해야 합니다.
- 실패 로그의
Undefined symbols목록 확보 - 어떤 Pod 타겟이 해당 심볼을 제공해야 하는지 확인
필요하면 Xcode의 Report navigator에서 링크 단계 커맨드를 복사해, 터미널에서 재현하는 방식으로 좁혀갈 수 있습니다.
7단계: “한 번에” 복구하는 실전 스크립트
아래는 로컬에서 iOS 빌드 실패를 빠르게 정리할 때 자주 쓰는 묶음입니다.
set -e
flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
cd ios
pod deintegrate
pod repo update
pod install
cd ..
flutter build ios --debug
여기서도 실패한다면, 이제는 “캐시 문제”가 아니라 “설정/호환성 문제”일 확률이 높습니다. 그때부터는 로그를 보고 3단계부터(특히 Podfile, iOS 타겟, use_frameworks!)를 집중 점검하세요.
8단계: 로그를 읽는 요령(원인 Pod 특정)
CocoaPods/Xcode 로그는 길지만, 핵심은 “처음 실패한 지점”입니다.
pod install실패: 가장 위쪽이 아니라, 실제로ERROR:로 시작하는 블록을 찾기- Xcode 빌드 실패: 마지막 에러 한 줄만 보지 말고, 그 직전의
CompileC,Ld,PhaseScriptExecution중 어디서 깨졌는지 확인
특히 링크 에러는 결과가 마지막에 몰려 나오기 때문에, “어떤 타겟을 링크하는 단계인지”를 위로 올라가며 찾는 습관이 중요합니다.
결론: Xcode 15 시대의 Flutter iOS 빌드 전략
정리하면, Flutter iOS 빌드 실패는 크게 두 부류입니다.
- 캐시/락파일/DerivedData 불일치로 생기는 일시적 실패: 올바른 순서의 클린 재설치로 해결
- Xcode 15 및 Pod/플러그인 호환성 문제: iOS 타겟 상향,
use_frameworks!전략,post_install최소 적용, 아키텍처/링크 설정 점검으로 해결
가장 중요한 것은 “팀에서 버전을 고정하고, 실패 유형별 체크리스트를 문서화”하는 것입니다. 그래야 같은 문제가 반복될 때 10분 안에 복구할 수 있고, CI에서도 동일한 절차로 해결됩니다.
추가로, iOS 빌드 실패가 특정 머신에서만 반복된다면 결국 환경 차이(특히 Ruby/CocoaPods 경로, Xcode 선택, 캐시 상태)가 원인인 경우가 많습니다. 그때는 which pod, xcode-select -p, pod env 출력까지 함께 수집해 비교하면 해결 속도가 크게 빨라집니다.