Published on

Flutter iOS 빌드 오류 - Podfile.lock 충돌 해결

Authors

Flutter로 iOS 빌드를 하다 보면 어느 날 갑자기 pod install 단계에서 멈추거나, Xcode 아카이브에서 링크 에러가 터지면서 원인이 Podfile.lock 충돌로 귀결되는 경우가 많습니다. 특히 팀 단위 개발에서 Podfile.lock가 브랜치마다 다른 상태로 합쳐지거나, 로컬의 CocoaPods 버전/Specs 레포 상태가 달라지면 같은 커밋인데도 어떤 사람은 빌드가 되고 어떤 사람은 안 되는 상황이 생깁니다.

이 글에서는 Podfile.lock 충돌이 왜 Flutter iOS 빌드 오류로 이어지는지, 어떤 로그가 대표적인 신호인지, 그리고 “지금 당장 빌드 복구”부터 “재발 방지(팀/CI 표준화)”까지 단계별로 정리합니다.

Podfile.lock이 충돌하면 왜 빌드가 깨질까

CocoaPods에서 Podfile.lock은 단순 캐시가 아니라, 의존성 해상 결과(버전, 체크섬, 서브스펙, 소스) 를 고정하는 락 파일입니다. 팀원이 pod install을 실행할 때 CocoaPods는 Podfile.lock에 기록된 버전을 최대한 유지한 채 설치를 재현합니다.

문제는 다음과 같은 상황에서 Podfile.lock이 서로 다른 해상 결과를 갖게 된다는 점입니다.

  • Flutter 플러그인 버전이 바뀌어 iOS Pod 의존성이 바뀜
  • 같은 플러그인이라도 iOS 최소 버전, 빌드 설정, 서브스펙 선택이 달라짐
  • CocoaPods 버전 차이로 해상 방식이 달라짐
  • Specs repo 상태가 달라 동일 Pod가 다른 버전으로 해상됨
  • 머지 과정에서 PODS: 섹션이 충돌/손상되어 체크섬이 꼬임

결과적으로 Xcode 프로젝트는 설치된 Pods와 Pods/Manifest.lock의 일치 여부를 검사하거나, 빌드 시 링크해야 할 프레임워크/라이브러리를 찾지 못해 실패합니다.

대표 증상(로그)로 빠르게 진단하기

아래 메시지들이 보이면 Podfile.lock 불일치/충돌 가능성이 높습니다.

1) Sandbox is not in sync

  • Xcode 빌드 또는 아카이브 단계에서 자주 등장
error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.

이 경우 Pods/Manifest.lockPodfile.lock이 다르다는 뜻입니다. 보통 누군가 Podfile.lock만 바꾸고 pod install 결과가 반영되지 않았거나, 머지 충돌 후 락 파일이 깨졌습니다.

2) CocoaPods could not find compatible versions

[!] CocoaPods could not find compatible versions for pod "Firebase/CoreOnly":
  In snapshot (Podfile.lock):
    Firebase/CoreOnly (= 10.25.0)

  In Podfile:
    Firebase/CoreOnly (= 10.24.0)

스냅샷(락)과 Podfile(요구사항)이 충돌합니다. Flutter 플러그인 업데이트로 요구 버전이 바뀌었는데 락이 예전 상태일 때 흔합니다.

3) Git 머지 충돌 흔적이 남은 Podfile.lock

Podfile.lock 내부에 <<<<<<<, =======, >>>>>>> 같은 마커가 남아 있으면 거의 100% 빌드가 깨집니다. 이 마커는 MDX 본문에서 그대로 쓰면 문제가 될 수 있으니, 실제 파일에서는 이런 문자열이 남지 않도록 반드시 제거해야 합니다.

즉시 복구: 안전한 표준 해결 절차

여기서는 “일단 iOS 빌드를 다시 되게 하는” 데 초점을 둡니다. 원칙은 간단합니다.

  1. iOS 의존성 설치 결과를 깨끗하게 지운다
  2. Flutter 의존성과 iOS Pods를 다시 재현한다
  3. 생성된 락 파일을 기준으로 팀과 동기화한다

0) 현재 상태 백업(선택)

문제가 복잡한 레포라면 현재 락 파일을 잠깐 백업해두면 비교가 쉽습니다.

cp ios/Podfile.lock ios/Podfile.lock.bak

1) Flutter 클린 및 패키지 재동기화

flutter clean
flutter pub get

플러그인 목록이 바뀌었는데 iOS 쪽만 재설치하면 계속 꼬일 수 있어, Flutter 레벨부터 정리하는 편이 안전합니다.

2) iOS Pods 폴더/락/워크스페이스 정리

cd ios
rm -rf Pods
rm -f Podfile.lock
rm -f Runner.xcworkspace
  • Pods는 설치 결과물
  • Podfile.lock은 해상 결과
  • Runner.xcworkspace는 CocoaPods가 생성하는 워크스페이스

이 3개를 함께 정리하면 “낡은 결과물만 남아서 생기는 불일치”를 강하게 줄일 수 있습니다.

3) CocoaPods repo 업데이트 후 재설치

pod repo update
pod install
  • pod repo update는 Specs 레포를 최신화합니다.
  • CI에서는 매번 업데이트가 느릴 수 있으니 전략적으로 적용하되, 로컬에서 충돌이 잦으면 한 번은 업데이트해 기준을 맞추는 게 좋습니다.

4) 상위 폴더로 돌아가 빌드 확인

cd ..
flutter build ios --debug

Xcode로 아카이브가 목적이면:

flutter build ipa

머지 충돌이 원인일 때: 올바른 해결 방식

Podfile.lock은 사람이 손으로 “대충 합치기”가 매우 어렵습니다. 충돌이 났다면 보통 두 가지 중 하나로 정리하는 게 안전합니다.

옵션 A: 한쪽 락 파일을 기준으로 채택

  • 릴리즈 브랜치/메인 브랜치에서 정상 빌드되는 락 파일이 있다면, 그 파일을 기준으로 삼는 것이 가장 단순합니다.
  • 이후 모든 팀원이 pod install을 실행해 Pods/Manifest.lock을 맞춥니다.

옵션 B: 락 파일을 재생성해서 기준 확정

충돌이 난 브랜치에서 락 파일을 삭제하고 재생성한 뒤, 그 결과를 기준으로 커밋합니다.

cd ios
rm -f Podfile.lock
rm -rf Pods
pod install

그 다음 생성된 ios/Podfile.lock을 커밋하고 PR에 포함시키세요.

자주 동반되는 추가 이슈 5가지와 처방

1) CocoaPods 버전 차이로 인한 재현 실패

팀원마다 CocoaPods 버전이 다르면 같은 Podfile.lock이어도 설치 과정에서 경고/오류가 갈릴 수 있습니다. 가장 현실적인 방법은 팀 표준 버전을 문서화하고, CI도 같은 버전을 쓰는 것입니다.

확인:

pod --version
ruby --version

권장: Gemfile로 CocoaPods 버전 고정(레포 루트 또는 ios 폴더)

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

gem "cocoapods", "1.15.2"

설치/실행:

bundle install
cd ios
bundle exec pod install

2) Apple Silicon(M1/M2) 환경에서 아키텍처 꼬임

일부 구형 Pod 또는 바이너리 의존성에서 시뮬레이터 아키텍처 문제가 섞이면, 락 충돌처럼 보이지만 실제론 빌드 설정 문제일 수 있습니다. 이 경우 Podfilepost_install에서 시뮬레이터 제외 아키텍처를 조정하는 식으로 해결합니다.

예시(필요한 경우에만 적용):

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
  end
end

3) Firebase/Google 계열 Pod의 상호 의존성 충돌

Firebase는 플러그인 버전 조합에 따라 특정 Pod 버전 범위를 요구합니다. Flutter 플러그인만 올리고 Podfile.lock을 그대로 두면 “스냅샷 vs 요구사항” 충돌이 쉽게 납니다.

해결은 보통 다음 순서가 깔끔합니다.

  • flutter pub upgrade로 플러그인 버전 확정
  • 위에서 설명한 방식으로 Pods와 락 파일 삭제
  • pod install로 재해상

4) CI에서만 실패하는 Podfile.lock 불일치

로컬은 되는데 CI에서만 실패하면 대개 다음 중 하나입니다.

  • CI 캐시가 Pods만 캐시하고 Podfile.lock 변경을 반영하지 않음
  • CI가 pod install 대신 pod update를 수행
  • Ruby/CocoaPods 버전이 로컬과 다름

권장 패턴:

  • 캐시 키에 ios/Podfile.lock 해시를 포함
  • pod install만 수행하고, 업데이트는 명시적으로만

5) use_frameworks! 또는 use_modular_headers! 변경으로 인한 대규모 재해상

Flutter 플러그인 조합에 따라 use_frameworks! 옵션이 들어가면, 같은 Pod라도 빌드 방식이 바뀌어 락 파일이 크게 달라질 수 있습니다. 이 변경은 팀 전체에 영향을 주므로, PR에서 iOS 빌드 로그와 함께 변경 이유를 명확히 남기고 락 파일을 함께 갱신하세요.

재발 방지: 팀에서 지켜야 할 운영 규칙

1) Podfile.lock은 커밋한다(대부분의 앱 프로젝트)

Flutter iOS 앱(최종 산출물이 앱)에서는 Podfile.lock을 커밋해 팀/CI가 동일한 의존성을 재현하도록 하는 것이 일반적으로 유리합니다.

  • 장점: 재현성, 디버깅 용이
  • 단점: 플러그인 업데이트 때 락 파일 변경이 자주 발생

2) 플러그인 업데이트 PR에는 락 파일 변경을 “필수 포함”

pubspec.yaml만 바뀌고 ios/Podfile.lock이 그대로면, 누군가는 로컬에서만 해결된 상태일 수 있습니다. PR 체크리스트에 다음을 넣어두면 좋습니다.

  • flutter pub get 이후 iOS에서 pod install 수행
  • ios/Podfile.lock 변경 포함
  • Xcode 빌드 또는 flutter build ios 통과 로그 첨부

3) 문제 재현/추적은 “상태 비교”로 접근

락 파일 충돌은 감으로 고치기보다, 어떤 상태가 달라졌는지 비교하는 게 빠릅니다. 이 접근은 분산 시스템에서 상태 불일치를 추적하는 방식과도 닮았습니다. 디버깅 관점은 아래 글의 방법론도 참고할 만합니다.

4) CI 파이프라인에서 의존성 설치 단계를 명시적으로 고정

예시(GitHub Actions 개념):

flutter pub get
cd ios
bundle exec pod install
cd ..
flutter build ios --release

모노레포에서 캐시/파이프라인 최적화가 필요하다면, 의존성 단계의 표준화와 캐시 전략이 중요합니다.

실전 체크리스트: 10분 안에 끝내는 해결 루틴

아래 순서대로 하면 대부분의 Podfile.lock 충돌 이슈는 정리됩니다.

  1. flutter clean 실행
  2. flutter pub get 실행
  3. ios 폴더에서 Pods, Podfile.lock, Runner.xcworkspace 삭제
  4. bundle exec pod install (또는 pod install) 실행
  5. flutter build ios로 확인
  6. 변경된 ios/Podfile.lock을 커밋하고 팀에 공유

빌드/배포 파이프라인에서 권한/환경 차이로 문제가 반복된다면, “로컬은 되는데 자동화는 안 되는” 유형의 진단 패턴을 함께 참고하면 도움이 됩니다.

마무리

Flutter iOS 빌드에서 Podfile.lock 충돌은 흔하지만, 해결법은 의외로 표준화할 수 있습니다. 핵심은 “락 파일과 설치 결과물을 한 세트로 보고”, 팀/CI가 동일한 도구 버전과 절차로 pod install을 재현하도록 만드는 것입니다.

한 번이라도 Podfile.lock 충돌로 시간을 크게 썼다면, 다음 PR부터는 플러그인 업데이트 시 Podfile.lock 갱신을 필수로 포함하고, CocoaPods 실행을 bundle exec로 고정하는 것만으로도 재발률이 눈에 띄게 줄어듭니다.