Published on

Flutter iOS 빌드 실패? CocoaPods Podfile 7가지

Authors

서론

Flutter로 iOS 빌드를 하다 보면 pod install 단계에서 멈추거나, Xcode에서 아카이브가 실패하거나, 시뮬레이터만 되고 실기기에서만 죽는 식의 “재현성 낮은” 문제가 종종 발생합니다. 이때 로그를 끝까지 읽어보면 대부분 CocoaPods/Podfile 쪽 설정 충돌로 귀결됩니다.

이 글에서는 Flutter iOS 빌드 실패를 유발하는 Podfile 관련 7가지 대표 원인을 “증상 → 원인 → 해결” 형태로 정리합니다. 단순히 pod deintegrate 같은 처방만 나열하지 않고, 왜 문제가 생기는지어떤 설정이 안전한지를 기준으로 설명합니다.

장애를 디버깅할 때는 체크리스트가 큰 도움이 됩니다. 비슷한 방식으로 원인을 좁혀가는 접근이 필요하다면, 인프라 장애 체크리스트 스타일의 글도 참고해보세요: K8s ImagePullBackOff·ErrImagePull 원인 12가지


0) 먼저 확인할 기본 전제(환경/정리 커맨드)

아래는 Podfile을 건드리기 전에 “캐시/락/파생 파일” 때문에 생기는 가짜 오류를 제거하는 최소 루틴입니다.

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

# CocoaPods 캐시가 꼬인 경우(필요할 때만)
pod cache clean --all

cd ios
pod repo update
pod install --verbose

그리고 Xcode에서 빌드한다면, 반드시 Runner.xcworkspace를 열어야 합니다.


1) iOS Deployment Target 불일치(가장 흔함)

증상

  • Specs satisfying the ... were found, but they required a higher minimum deployment target.
  • 특정 플러그인(예: Firebase, GoogleMaps, Sentry 등) 추가 후 갑자기 pod install 실패

원인

Pod가 요구하는 최소 iOS 버전과, Podfile/프로젝트 설정의 platform :ios, '...' 값이 다릅니다. Flutter 프로젝트는 오래된 템플릿에서 생성된 경우 11.0 같은 값이 남아 있는 경우가 많습니다.

해결

Podfile에서 platform을 올리고, Xcode 프로젝트의 Deployment Target도 일치시킵니다.

# ios/Podfile
platform :ios, '13.0'

# Flutter 기본 include
require File.expand_path(File.join('..', 'Flutter', 'podhelper'), __FILE__)

추가로, Runner 타겟과 Pods-Runner 타겟이 서로 다른 iOS 버전을 가리키면 또 꼬일 수 있으니 Xcode의 Build Settings > iOS Deployment Target도 확인하세요.


2) use_frameworks! / use_modular_headers! 충돌

증상

  • Swift pods cannot yet be integrated as static libraries 류 오류
  • Include of non-modular header inside framework module 오류
  • 특정 Pod만 추가하면 컴파일 에러가 폭발

원인

CocoaPods는 크게 “정적 라이브러리 기반 통합”과 “framework 기반 통합”에서 헤더 처리 방식이 달라집니다.

  • use_frameworks!를 켜면 Pod들이 framework로 통합되며, Swift/ObjC 혼합 환경에서 헤더 모듈화 문제가 발생할 수 있습니다.
  • use_modular_headers!는 모듈 헤더로 강제하여 일부 비모듈 헤더 문제를 줄이지만, 반대로 특정 Pod에서는 문제가 될 수 있습니다.

Flutter 플러그인 조합에 따라 정답이 달라져서, “무조건 켜라/끄라”가 아니라 필요한 경우에만 최소 옵션으로 가는 것이 안정적입니다.

해결 가이드

  1. 특별한 이유가 없다면 use_frameworks!를 먼저 제거하고 시도합니다.
  2. Firebase 등에서 요구하거나 Swift Pod 통합 이슈가 있으면 use_frameworks! :linkage => :static을 검토합니다.
target 'Runner' do
  # 가능하면 기본(미사용) 유지
  # use_frameworks!

  # Swift/특정 SDK가 필요할 때만
  # use_frameworks! :linkage => :static

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

use_frameworks!를 켠 뒤 갑자기 빌드가 깨졌다면, 가장 먼저 되돌려서 원인 범위를 줄이세요.


3) post_install 훅에서 Flutter 기본 설정을 덮어써서 실패

증상

  • Undefined symbols for architecture arm64 (특정 플러그인 심볼 미해결)
  • ld: framework not found 또는 Module not found
  • Debug는 되는데 Archive만 실패

원인

Podfile의 post_install에서 EXCLUDED_ARCHS, OTHER_LDFLAGS, SWIFT_VERSION 등을 일괄 변경하면서 Flutter가 생성한 설정과 충돌합니다. 특히 인터넷에서 복붙한 스니펫이 Runner/Pods 타겟 전체에 과격하게 적용되는 경우가 많습니다.

해결

Flutter 권장 훅 호출을 유지하고, 필요한 설정만 “조건부”로 최소 적용합니다.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    # 예: Apple Silicon 시뮬레이터에서만 임시 회피가 필요할 때
    target.build_configurations.each do |config|
      if config.name == 'Debug'
        config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
      end
    end
  end
end

핵심은 flutter_additional_ios_build_settings(target)를 제거/누락하지 않는 것입니다. 이 호출이 빠지면 Flutter 엔진/플러그인 쪽 링크 설정이 깨지는 경우가 많습니다.


4) Podfile.lock/Pods 캐시 불일치로 “내 컴퓨터만” 깨짐

증상

  • 팀원은 되는데 내 로컬만 pod install이 다르게 동작
  • CI에서는 되는데 로컬에서만 Checksum/Version 관련 오류
  • The sandbox is not in sync with the Podfile.lock 경고 후 빌드 실패

원인

CocoaPods는 Podfile.lockPods/ 디렉터리 상태를 강하게 결합합니다. flutter pub get으로 플러그인 버전이 바뀌었는데 lock/Pods가 예전 상태면 샌드박스가 불일치합니다.

해결

일관된 재설치를 수행합니다.

cd ios
rm -rf Pods Podfile.lock
pod install

팀 프로젝트라면 Podfile.lock을 커밋하는 정책이 일반적입니다(재현성). 다만 플러그인 업데이트 시점에 lock을 함께 갱신해야 합니다.


5) CocoaPods 소스(repo) 설정/네트워크 이슈로 설치 실패

증상

  • Unable to find a specification for ...
  • CDN: trunk Repo update failed / SSL / timed out

원인

사내 프록시/방화벽, 오래된 CocoaPods, 혹은 source가 잘못 지정된 Podfile로 인해 trunk CDN 접근이 불안정해집니다. 또한 pod repo update가 안 된 상태에서 신규 버전 Pod를 찾지 못하기도 합니다.

해결

  1. CocoaPods 업데이트
  2. repo 업데이트
  3. Podfile에 불필요한 source를 남발하지 않기
sudo gem install cocoapods
pod repo update

Podfile에 특정 사설 spec repo를 써야 한다면, trunk와 함께 명시하되 순서/접근성을 점검하세요.


6) Xcode/Swift 버전과 Pod의 Swift 설정 불일치

증상

  • Swift compiler error가 Pods 타겟에서 발생
  • Building for iOS Simulator, but linking in object file built for iOS 같은 혼종 오류

원인

Xcode를 올렸는데(예: Xcode 15/16), 일부 Pod가 요구하는 Swift 버전/빌드 설정과 충돌할 수 있습니다. 특히 오래된 Pod가 SWIFT_VERSION을 명시하지 않거나, 프로젝트가 강제로 낮은 Swift 버전을 사용하도록 설정되어 있으면 문제가 커집니다.

해결

Podfile post_install에서 필요한 경우에만 Swift 버전을 통일합니다(무조건 덮어쓰기는 위험).

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    target.build_configurations.each do |config|
      # 특정 Pod만 예외 처리하는 방식이 더 안전
      if target.name.start_with?('SomeLegacyPod')
        config.build_settings['SWIFT_VERSION'] = '5.0'
      end
    end
  end
end

또한 Xcode에서 Build Settings > Build Libraries for Distribution 같은 옵션이 켜져 있으면 일부 Pod에서 ABI/모듈 이슈가 날 수 있으니, 문제 발생 시 해당 옵션도 점검하세요.


7) 아키텍처/시뮬레이터(Apple Silicon) 관련 설정이 Podfile에 하드코딩됨

증상

  • M1/M2/M3에서 시뮬레이터 빌드만 실패
  • ld: building for iOS Simulator, but linking in dylib built for iOS 또는 arm64 관련 오류

원인

과거에는 Apple Silicon 전환 초기에 EXCLUDED_ARCHS를 Podfile에 하드코딩해 회피하는 경우가 많았습니다. 시간이 지나면서 많은 Pod가 정상 지원을 시작했는데도, 예전 회피 코드가 남아 오히려 현재 환경에서 문제를 일으킵니다.

해결

  • 가능한 한 최신 Pod/플러그인 버전으로 올리고
  • EXCLUDED_ARCHS는 “필요한 경우 + Debug 시뮬레이터 한정”으로 최소화합니다.
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    target.build_configurations.each do |config|
      # 최후의 수단: Debug + Simulator에만 제한
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' if config.name == 'Debug'
    end
  end
end

만약 Release/Archive까지 이 설정이 들어가면, App Store 제출용 바이너리에서 예상치 못한 링크 문제가 생길 수 있으니 적용 범위를 좁히는 것이 중요합니다.


실전: “Podfile 안전 템플릿” 예시

아래는 Flutter iOS에서 비교적 안전하게 시작할 수 있는 Podfile 형태입니다. 플러그인 요구사항이 생길 때마다 옵션을 추가하되, 전역 강제는 피하는 방향입니다.

platform :ios, '13.0'

# CocoaPods 통계 비활성화(빌드 약간 빨라짐)
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

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

require File.expand_path(File.join('..', 'Flutter', 'podhelper'), __FILE__)

flutter_ios_podfile_setup

target 'Runner' do
  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)

    # 필요한 경우에만 최소한으로 추가
    target.build_configurations.each do |config|
      if config.name == 'Debug'
        # Apple Silicon 시뮬레이터 이슈가 있을 때만 사용
        # config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
      end
    end
  end
end

로그를 “원인별로” 읽는 팁

Podfile 문제는 로그가 길어서 감으로 때려 맞히기 쉽습니다. 다음처럼 분류하면 훨씬 빨리 좁혀집니다.

  1. pod install 단계에서 실패: iOS 최소 버전, spec repo, 네트워크, Pod 간 의존성 충돌 가능성이 큼
  2. Xcode 컴파일 단계에서 실패: 헤더 모듈화(use_frameworks!/use_modular_headers!), Swift 버전, 빌드 세팅 덮어쓰기(post_install) 가능성이 큼
  3. Archive에서만 실패: Release 설정/Bitcode/서명/링커 플래그/EXCLUDED_ARCHS 범위 문제 가능성이 큼

이런 식의 “체크리스트 기반 디버깅”은 다른 분야에서도 동일하게 통합니다. 문제를 빠르게 재현/격리하는 관점은 다음 글의 접근과도 유사합니다: Assistants API v2 run이 queued나 in_progress에 멈출 때 실전 디버깅 체크리스트


결론

Flutter iOS 빌드 실패는 겉으로는 Xcode 오류처럼 보이지만, 실제로는 **Podfile의 작은 선택(플랫폼 버전, framework 통합 방식, post_install 훅, 아키텍처 예외 처리)**이 누적되어 터지는 경우가 많습니다.

정리하면 다음 순서가 가장 효율적입니다.

  1. platform :ios와 Xcode Deployment Target 정합성 확보
  2. use_frameworks!/use_modular_headers!는 “필요할 때만”
  3. post_install은 Flutter 기본 설정을 유지한 채 최소 변경
  4. lock/Pods 불일치면 과감히 재설치
  5. Apple Silicon 예외처리는 Debug+Simulator로 제한

위 7가지를 체크해도 해결이 안 된다면, 그때는 특정 Pod/플러그인 이름과 에러 로그의 “첫 번째 실패 지점”을 기준으로 더 좁혀야 합니다(대부분 첫 에러가 진짜 원인입니다).