Published on

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

Authors

서론

Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install이 실패하거나, Xcode 빌드는 되는데 flutter build ios만 깨지는 식의 “애매한” 문제가 자주 발생합니다. 특히 Podfile 커스텀(예: use_frameworks!, use_modular_headers!, iOS deployment target 조정)과 CocoaPods 버전/캐시/락파일이 엉키면, 에러 메시지는 제각각인데 실제 원인은 몇 가지 패턴으로 수렴합니다.

이 글은 Podfile·CocoaPods 충돌로 인한 Flutter iOS 빌드 실패를 빠르게 분류하고, “어떤 순서로 무엇을 지워야/고쳐야” 재발 없이 해결되는지 실무 기준으로 정리합니다.

> 비슷한 성격의 ‘캐시/환경 꼬임’ 문제를 체계적으로 추적하는 관점은 systemd 서비스가 계속 재시작될 때 진단 체크리스트 같은 글에서 다루는 방식과도 통합니다. iOS 빌드도 결국 환경/의존성/캐시의 조합 문제이기 때문입니다.


증상으로 빠르게 분류하기 (가장 흔한 6가지)

아래 증상 중 하나라도 해당하면 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

2) Xcode에서만 빌드 실패

  • ld: library not found for -l...
  • Undefined symbols for architecture arm64
  • Module 'xxx' not found

3) Flutter 빌드에서만 실패

  • Flutter.framework 관련 링크 에러
  • pod install은 되지만 flutter build ios에서 스크립트 단계 실패

4) Apple Silicon(M1/M2/M3)에서만 발생

  • ffi/네이티브 확장 설치 문제
  • pod install이 Rosetta/ARM 환경 혼용으로 깨짐

5) use_frameworks! 도입 이후 폭발

  • Firebase/GoogleMaps/각종 플러그인에서 모듈/헤더 충돌

6) iOS deployment target 올린 뒤 연쇄 실패

  • 플러그인이 요구하는 최소 iOS 버전과 프로젝트 설정 불일치

원인 지도: Podfile, Lockfile, Pods 캐시의 삼각관계

Flutter iOS 의존성은 대략 다음과 같은 레이어로 관리됩니다.

  • ios/Podfile: 어떤 방식(프레임워크/헤더)으로 Pod를 설치할지, iOS 최소버전, 후처리(hook) 등을 정의
  • ios/Podfile.lock: “이번에 설치된 Pod 버전의 스냅샷”
  • ios/Pods/: 실제 설치된 소스/바이너리
  • ~/Library/Caches/CocoaPods, ~/.cocoapods: 다운로드/스펙 캐시

문제는 Podfile을 바꿨는데 lock/pods/caches가 이전 상태를 강하게 끌고 가는 경우입니다. 그래서 해결도 대부분 “정리 → 재설치 → Xcode 설정 정합성 확인” 순서로 갑니다.


1단계: 가장 먼저 할 ‘정리’ (재현성 있는 기본 루틴)

아래는 충돌 해결의 표준 루틴입니다. 팀 내에서 스크립트로 고정해두면 재발률이 크게 줄어듭니다.

클린 스크립트 (권장)

# 프로젝트 루트
flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ios/.symlinks ios/Flutter/Flutter.framework ios/Flutter/Flutter.podspec

# CocoaPods 캐시까지 강하게 정리하고 싶다면(상황에 따라)
pod cache clean --all
rm -rf ~/Library/Caches/CocoaPods
rm -rf ~/.cocoapods

# 의존성 재받기
flutter pub get

# iOS 재설치
cd ios
pod repo update
pod install --verbose
cd ..

왜 이렇게까지 지우나?

  • Podfile.lock이 남아 있으면 Pod 버전이 “고정”되어, Podspec 업데이트/플러그인 업데이트가 반영되지 않을 수 있습니다.
  • Pods/가 남아 있으면 “Podfile과 실제 설치 상태가 불일치”가 발생합니다.
  • .symlinks/Flutter.framework 관련 파일이 꼬이면 Flutter 엔진/플러그인 링크 단계에서 이상한 에러가 납니다.

> 정리 후에도 같은 에러가 반복된다면, 그때부터는 “설정 자체의 충돌”을 의심해야 합니다.


2단계: Podfile에서 충돌을 만드는 대표 옵션 3개

ios/Podfile은 작은 변화로도 결과가 크게 바뀝니다. 특히 아래 3개가 자주 문제를 만듭니다.

(A) platform :ios, 'xx.x' 불일치

Pod가 요구하는 최소 iOS 버전이 올라갔는데 프로젝트가 낮게 유지되면 설치가 실패합니다.

권장 예시

platform :ios, '13.0'

# Flutter 기본 템플릿 구조를 유지
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

flutter_root = File.expand_path('..', __FILE__)
load File.join(flutter_root, 'Flutter', 'podhelper.rb')

target 'Runner' do
  use_frameworks! :linkage => :static
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
  end
end
  • 최근 플러그인 조합에서는 iOS 12 이하가 점점 더 자주 깨집니다.
  • 배포 타겟을 올린 뒤에는 Xcode 프로젝트의 Runner 타겟 설정도 동일하게 맞춰야 합니다.

(B) use_frameworks!로 인한 모듈/링킹 충돌

예전에는 use_frameworks!가 필요했던 케이스가 많았지만, 요즘은 플러그인 조합에 따라 오히려 충돌을 유발합니다.

  • Swift 기반 Pod 때문에 필요해서 넣었는데, 다른 Pod가 정적 링크를 가정하면 충돌
  • 해결책으로 use_frameworks! :linkage => :static를 쓰는 경우가 많습니다(위 예시처럼)

(C) use_modular_headers!의 득과 실

  • 헤더 탐색 문제를 줄이지만, 일부 Pod는 모듈 헤더 구성과 충돌할 수 있습니다.
  • 충돌이 난다면 이 옵션을 제거하고 다시 pod install로 비교해보세요.

3단계: “Sandbox is not in sync”와 Lockfile 충돌 처리

자주 보이는 에러입니다.

원인

  • Pods/Manifest.lockPodfile.lock이 불일치
  • 팀원이 lockfile만 커밋하거나, 반대로 Pods 상태만 바뀐 경우

해결

대부분은 아래로 끝납니다.

cd ios
rm -rf Pods Podfile.lock
pod install

CI 환경이라면 pod install --deployment를 쓰는 팀도 있는데, 이 옵션은 lockfile과 완전히 동일한 상태만 허용하므로 로컬에서 lockfile 갱신이 필요할 때는 오히려 방해가 될 수 있습니다.


4단계: Apple Silicon에서 CocoaPods/Ruby 아키텍처 꼬임

M1/M2에서 “어제까지 되던 pod install이 오늘은 안 됨”의 상당수는 아키텍처 혼용입니다.

체크 포인트

  • which ruby / ruby -v
  • which pod / pod --version
  • 터미널이 Rosetta로 열려 있는지

권장: rbenv(또는 asdf)로 Ruby 고정

시스템 Ruby에 의존하면 macOS 업데이트 때마다 흔들립니다.

# 예: rbenv 사용
brew install rbenv
rbenv install 3.2.2
rbenv global 3.2.2

gem install cocoapods -v 1.15.2
pod --version

그리고 Xcode/터미널을 같은 아키텍처로 맞추세요(ARM로 통일 권장). Rosetta로 pod install을 돌리고, ARM으로 Xcode 빌드하면 링크 단계에서 이상현상이 나기도 합니다.


5단계: 빌드 설정 충돌(특히 Firebase/Google 계열) 진단법

Pod 자체는 설치되는데 빌드가 깨질 때는 Xcode Build Settings가 원인인 경우가 많습니다.

자주 보는 에러

  • Undefined symbols for architecture arm64
  • ld: symbol(s) not found
  • Module not found

체크리스트

  1. Runner 타겟과 Pods-Runner 타겟의 iOS Deployment Target 일치
  2. Build Settings > Other Linker Flags에 중복/불필요한 플래그가 있는지
  3. Framework Search Paths / Header Search Paths를 수동으로 만졌는지
  4. EXCLUDED_ARCHS가 남아 있는지(특히 시뮬레이터)

시뮬레이터 arm64 제외가 남아있는 경우

과거 Intel 시대 workaround가 남아 있으면 Apple Silicon에서 반대로 문제가 됩니다.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # 필요할 때만 사용. 무조건 제외는 지양.
      # config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
    flutter_additional_ios_build_settings(target)
  end
end

6단계: “최후의 한 방” — Pod 재설치가 아니라 Xcode 프로젝트 재생성

정리를 다 했는데도 Runner.xcodeproj/Runner.xcworkspace 내부 설정이 꼬였을 수 있습니다. 특히 여러 번의 수동 설정 변경(서명, 빌드 설정, capabilities) 이후에 발생합니다.

안전한 재생성 절차(주의해서 진행)

  1. ios/ 폴더를 백업
  2. 서명/번들ID 등 필요한 설정을 메모
  3. Flutter 템플릿 기반으로 iOS를 재생성
# 루트에서 ios 폴더를 다른 이름으로 백업
mv ios ios_backup
flutter create .

# 다시 필요한 파일만 이관 (GoogleService-Info.plist, entitlements 등)
# 이후 pod install
cd ios && pod install && cd ..

이 방법은 “원인을 끝까지 파고들 시간은 없고, 오늘 안에 빌드가 되어야 하는” 상황에서 특히 효과적입니다.


CI에서 재발 방지: 버전 고정과 캐시 전략

로컬에서만 고치고 끝내면 CI에서 다시 터지는 경우가 많습니다.

권장 사항

  • CocoaPods 버전 고정(팀 표준)
  • Xcode 버전 고정(가능하면)
  • Podfile.lock 커밋 여부를 팀 규칙으로 명확히

Bundler로 CocoaPods 고정(추천)

# Gemfile
source 'https://rubygems.org'

gem 'cocoapods', '1.15.2'
bundle install
cd ios
bundle exec pod install

이렇게 하면 “누구는 1.14, 누구는 1.15” 같은 미묘한 차이로 발생하는 충돌을 줄일 수 있습니다.


문제 해결을 더 빠르게 만드는 로그 수집 팁

원인을 좁히려면 로그가 중요합니다.

cd ios
pod install --verbose 2>&1 | tee pod-install.log

또한 Xcode 빌드 로그에서 실제 실패한 첫 지점을 찾는 것이 핵심입니다. 마지막 에러는 연쇄 실패의 결과일 때가 많습니다. 이런 “첫 원인 찾기” 방식은 장애 분석 전반에 통하는데, 예를 들어 systemd 서비스가 반복 재시작될 때 원인 추적법에서 소개하는 접근과 유사합니다.


결론: 해결 순서를 고정하면 대부분 끝난다

Flutter iOS 빌드 실패가 Podfile·CocoaPods 충돌로 보일 때는, 감으로 Podfile을 계속 만지기보다 아래 순서를 고정하는 것이 가장 빠릅니다.

  1. 클린 루틴으로 Pods/lock/캐시 정리 후 재설치
  2. Podfile의 핵심 옵션(platform, use_frameworks!, use_modular_headers!)을 최소화하고 검증
  3. Apple Silicon/Ruby/CocoaPods 아키텍처 혼용 제거
  4. Xcode Build Settings 정합성(Deployment Target, EXCLUDED_ARCHS 등) 점검
  5. 그래도 안 되면 iOS 프로젝트 재생성으로 상태 리셋

이 과정을 따르면 “왜인지 모르겠지만 빌드가 안 되는” 상태에서 벗어나, 재현 가능한 방식으로 문제를 종결할 수 있습니다.