- Published on
Flutter iOS 빌드 실패 - CocoaPods 1.15+ 충돌 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Flutter로 iOS 앱을 빌드하다가 어느 날 갑자기 pod install 또는 Xcode 빌드가 실패하는 경험은 흔합니다. 특히 CocoaPods를 1.15+로 올린 뒤부터는 Ruby 환경(시스템 Ruby vs rbenv), Pod 프로젝트 생성 로직, 플러그인 Podspec, iOS 최소 타겟/아키텍처 설정이 미묘하게 충돌하면서 “어제까지 되던” 빌드가 깨지는 케이스가 자주 보고됩니다.
이 글은 Flutter iOS 빌드 실패를 유발하는 CocoaPods 1.15+ 관련 충돌을 증상별로 분류하고, 가장 재현성이 높은 정리→고정→재설치 순서의 해결 루틴을 제공합니다. CI에서 캐시가 꼬인 경우까지 포함해, 한 번에 정상 상태로 되돌리는 것을 목표로 합니다.
> 참고: CI 캐시/파생 산출물이 원인인 경우는 iOS에 한정되지 않습니다. 캐시를 완전 초기화하는 접근은 GitLab CI 캐시 꼬임 - 빌드 완전 초기화 가이드도 함께 보면 도움이 됩니다.
1. CocoaPods 1.15+에서 충돌이 늘어난 이유
CocoaPods는 단순히 의존성을 내려받는 도구가 아니라, .xcworkspace 및 Pods 프로젝트를 생성하고, 빌드 설정을 주입하며, 스크립트 페이즈까지 구성합니다. 1.15+로 올라오면서 다음 축에서 충돌이 더 자주 드러납니다.
- Ruby/Gem 환경 차이: macOS 업데이트 후 시스템 Ruby가 바뀌거나, rbenv/rvm과 섞여
pod실행 바이너리가 서로 다른 gemset을 바라봄 - Xcode 15+ / iOS 타겟 상향: 일부 플러그인이 낮은 iOS 타겟을 요구하거나, 반대로 최신 SDK에서 deprecated 설정을 끌어안고 있음
- Pods 프로젝트 생성/통합 방식 변화:
use_frameworks!,use_modular_headers!조합, Swift/ObjC 혼합, 정적/동적 링크 전략에서 경계 케이스 발생 - 아키텍처/시뮬레이터 이슈: Apple Silicon에서
arm64시뮬레이터,EXCLUDED_ARCHS설정이 꼬이면 특정 Pod만 빌드 실패
2. 대표 증상(에러 메시지)별 원인 빠른 분류
아래는 CocoaPods 1.15+ 업그레이드 이후 Flutter iOS에서 자주 보는 실패 유형입니다.
2.1 The sandbox is not in sync with the Podfile.lock
- 원인: Pods 디렉터리/Manifest.lock과 Podfile.lock이 불일치. 캐시/부분 설치로 흔함.
- 해결 방향: Pods/Lock/DerivedData 정리 후 재설치.
2.2 CocoaPods could not find compatible versions for pod ...
- 원인: 특정 플러그인 Podspec이 요구하는 버전과 현재 Pod repo 인덱스, iOS 타겟, 다른 Pod의 제약이 충돌.
- 해결 방향:
pod repo update, iOS 최소 타겟 상향, 플러그인 버전 조정.
2.3 Unable to find a specification for ...
- 원인: Specs repo가 오래됐거나, CDN 접근 문제, 사내 프록시/네트워크 이슈.
- 해결 방향:
pod repo update또는pod install --repo-update, 네트워크 확인.
2.4 Error: CocoaPods's specs repository is too out-of-date
- 원인: CocoaPods 업데이트 후 Specs 인덱스가 구버전.
- 해결 방향: repo 업데이트.
2.5 ld: library not found for -l... / Undefined symbols for architecture arm64
- 원인: 정적/동적 링크 충돌,
use_frameworks!적용 방식 문제, 아키텍처 제외 설정 문제. - 해결 방향: Podfile의 링크 전략 정리,
post_install에서 빌드 설정 통일.
3. 가장 확실한 1차 해결: iOS 산출물 완전 정리 후 재설치
CocoaPods 충돌은 “상태” 문제인 경우가 많습니다. 아래 순서로 정리하면 대다수는 복구됩니다.
3.1 Flutter/Pods/Xcode 캐시 정리
# 프로젝트 루트
flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ios/.symlinks
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# (선택) CocoaPods 캐시까지 강하게 정리
pod cache clean --all
3.2 iOS 의존성 재설치
cd ios
pod repo update
pod install --repo-update
cd ..
flutter pub get
flutter build ios --no-codesign
pod install --repo-update는 네트워크가 허용되는 환경이라면 가장 안전합니다.- CI에서는 매번 repo update가 느릴 수 있으므로, 캐시 전략을 별도로 설계하되 “문제 발생 시 완전 초기화” 루틴을 준비해두는 것이 좋습니다.
4. CocoaPods 1.15+ 충돌을 줄이는 Podfile 표준 템플릿
Flutter 프로젝트의 ios/Podfile은 플러그인 조합에 따라 점점 복잡해집니다. CocoaPods 1.15+에서 문제를 줄이려면 iOS 타겟, 프레임워크/헤더 전략, post_install에서의 빌드 설정 통일이 핵심입니다.
아래는 실전에서 자주 쓰는 안정화 템플릿입니다(프로젝트 상황에 맞게 값 조정).
platform :ios, '13.0'
# CocoaPods 통합 방식
# - Swift 기반 Pod가 많거나, 특정 SDK가 framework를 요구하면 use_frameworks! 필요
# - 반대로 Firebase/Google 계열은 정적 링크가 안정적인 경우가 많음
# 필요 시만 활성화
# use_frameworks! :linkage => :static
# 충돌이 잦을 때는 modular headers가 도움이 되기도 함
# use_modular_headers!
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_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed 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_xcode_build_settings_path}"
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__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
# Apple Silicon 시뮬레이터에서 특정 Pod가 arm64를 못 버티는 경우에만 사용
# config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
# iOS 최소 타겟 통일(플러그인이 낮게 잡는 경우 방지)
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
# 경고를 에러로 승격하는 Pod가 있을 때 완화
# config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO'
end
end
end
포인트
platform :ios, '13.0'처럼 최소 타겟을 명시적으로 상향하면, 최신 Xcode/CocoaPods 조합에서 충돌이 줄어듭니다.EXCLUDED_ARCHS는 “만병통치약”이 아닙니다. 특정 Pod가 시뮬레이터arm64에서만 깨질 때에만 제한적으로 적용하세요.use_frameworks!는 플러그인/SDK에 따라 필요하지만, 불필요하게 켜면 링크 충돌이 늘 수 있습니다. 문제가 재현되는 조합에서만 결정적으로 적용하는 편이 좋습니다.
5. CocoaPods 버전 고정(권장)과 Ruby 환경 정리
로컬과 CI가 서로 다른 CocoaPods를 쓰면, 같은 Podfile이라도 결과물이 달라져 “내 컴퓨터에선 되는데” 문제가 생깁니다. 가장 안전한 방법은 Bundler로 CocoaPods 버전을 고정하는 것입니다.
5.1 Gemfile로 CocoaPods 고정
ios/Gemfile을 만들거나(혹은 루트에 두고 cd ios에서 실행), 다음처럼 고정합니다.
source 'https://rubygems.org'
gem 'cocoapods', '~> 1.15.2'
설치 및 실행:
cd ios
bundle install
bundle exec pod install --repo-update
이제부터는 pod install 대신 반드시 bundle exec pod install을 사용하면, 팀/CI 모두 동일한 CocoaPods 버전으로 동작합니다.
5.2 pod가 어디서 실행되는지 확인
which pod
pod --version
ruby -v
gem env home
which pod가/usr/local/bin/pod인지, rbenv shim인지에 따라 gem이 달라집니다.- macOS 업데이트 후 시스템 Ruby가 바뀌면, 기존에 설치한 CocoaPods가 “있는데 없는” 상태처럼 보일 수 있습니다.
6. Flutter 플러그인/Podspec 충돌: iOS 타겟과 버전 제약 정리
CocoaPods 1.15+ 자체의 문제라기보다, 업그레이드로 인해 기존에 묻혀 있던 제약 충돌이 표면화되는 경우가 많습니다.
6.1 iOS 최소 타겟 상향이 필요한 신호
- 특정 Pod가
requires iOS 13.0같은 메시지를 내며 설치 실패 - Xcode 15+에서 오래된 SDK/플러그인이 경고를 에러로 만들며 빌드 실패
해결:
platform :ios, '13.0'(또는 프로젝트 요구에 맞게 12/14 등)로 올리고- Xcode 프로젝트의 Deployment Target도 Runner/Pods 모두 동일하게 맞춥니다(위
post_install참고).
6.2 플러그인 버전 동기화
Flutter 플러그인은 내부적으로 iOS Pod 의존성을 갖습니다. pubspec.yaml에서 플러그인 버전을 올렸는데 iOS 쪽 Pod가 따라오지 않으면 충돌이 납니다.
flutter pub outdated
flutter pub upgrade --major-versions
cd ios && bundle exec pod install --repo-update
- “CocoaPods 1.15+ 충돌”처럼 보여도 실제 원인은 플러그인 버전 핀ning일 때가 많습니다.
7. CI에서만 실패하는 케이스: 캐시/DerivedData/Pods 재사용
CI는 속도를 위해 Pods/, CocoaPods 캐시, DerivedData까지 재사용하는 경우가 있는데, CocoaPods 업그레이드 직후에는 이 전략이 독이 되기도 합니다.
권장 패턴:
- 기본은 캐시를 쓰되, 실패 시 강제 초기화 잡을 제공
- CocoaPods 버전은 Bundler로 고정
Podfile.lock은 커밋하여 일관성 확보
예시 스크립트(초기화 포함):
set -e
flutter --version
flutter pub get
cd ios
bundle install
# 문제가 잦으면 조건부로 초기화
rm -rf Pods Podfile.lock
pod cache clean --all || true
bundle exec pod install --repo-update
cd ..
flutter build ios --no-codesign
CI 캐시가 원인일 때의 사고방식은 iOS에서도 동일합니다. 필요하면 GitLab CI 캐시 꼬임 - 빌드 완전 초기화 가이드를 함께 참고해 “깨끗한 상태로 되돌리는 버튼”을 마련하세요.
8. 체크리스트: 재발 방지 운영 팁
bundle exec pod install로 CocoaPods 버전 고정platform :ios, 'xx.x'를 Podfile에 명시하고,post_install로 Pods까지 타겟 통일use_frameworks!/use_modular_headers!는 필요할 때만(무작정 켜지 않기)- Apple Silicon 시뮬레이터 이슈는
EXCLUDED_ARCHS를 “최후의 수단”으로 제한 적용 - CI는 캐시를 쓰되, 실패 시 완전 초기화 루틴을 준비
결론
CocoaPods 1.15+ 이후 Flutter iOS 빌드 실패는 단일 원인이라기보다, Ruby/CocoaPods 버전 불일치 + Pods 산출물 상태 꼬임 + iOS 타겟/링크 전략 충돌이 겹쳐 나타나는 경우가 대부분입니다. 가장 효과적인 접근은 (1) 산출물 완전 정리로 상태를 리셋하고, (2) Bundler로 CocoaPods 버전을 고정하며, (3) Podfile의 iOS 타겟과 빌드 설정을 일관되게 만드는 것입니다.
위 루틴을 팀 표준으로 만들면 “업데이트 한 번에 iOS 빌드가 전원 사망”하는 상황을 크게 줄일 수 있습니다.