Published on

Flutter iOS 빌드 실패? Podfile·CocoaPods 충돌 해결

Authors

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 pod
  • Specs satisfying the dependency were found, but they required a higher minimum deployment target
  • The sandbox is not in sync with the Podfile.lock
  • Unable to find a specification for ...

이 경우는 대체로 Podfile 설정, iOS 최소 버전, Podspec 저장소 상태, Podfile.lock/Pods 캐시 불일치가 원인입니다.

2) pod install은 성공하지만 Xcode 빌드에서 실패

  • Module not found
  • Undefined symbols for architecture arm64
  • ld: library not found
  • Swift 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.plistMinimumOSVersion은 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로 되어 있는 상황입니다.

  • Podfileplatform을 올립니다.
  • 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가지입니다.

  1. CocoaPods 버전: Gemfile로 고정 후 bundle exec pod install
  2. Flutter 버전: FVM 또는 CI 캐시로 고정
  3. 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을 관리하는 것만으로도 실패율이 눈에 띄게 줄어듭니다.