Published on

Flutter iOS 빌드 실패 - bitcode·arm64·Pod 오류 정복

Authors

서버 장애처럼 iOS 빌드 실패도 “증상은 다양하지만 원인은 몇 가지로 수렴”합니다. Flutter iOS 빌드는 Xcode + CocoaPods + Flutter toolchain + 서드파티 네이티브 SDK가 맞물려 동작하므로, 작은 설정 불일치가 Undefined symbols for architecture arm64 같은 치명적인 링크 에러로 증폭됩니다.

이 글은 Flutter iOS 빌드에서 특히 많이 만나는 3대 유형인 bitcode, arm64, Pod 오류를 로그 패턴 중심으로 분류하고, 가장 재현성 높은 해결 루틴을 제공합니다. 문제를 “감”으로 해결하는 대신, 어디를 먼저 확인해야 하는지 체크리스트로 정리합니다.

참고로, 이런 트러블슈팅은 쿠버네티스에서 CrashLoopBackOff 원인을 빠르게 좁히는 방식과 비슷합니다. 원인별로 관찰 포인트가 다르기 때문입니다. 관련 글: K8s CrashLoopBackOff 원인별 빠른 진단·복구

1) 먼저 확인할 공통 전제: 환경/캐시 정리

iOS 빌드는 캐시 오염(derived data, pods, flutter artifacts)로 같은 오류가 계속 반복되는 경우가 많습니다. 아래는 “문제 해결 전에” 최소 1회 실행할 가치가 있는 표준 정리 루틴입니다.

# 프로젝트 루트
flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData/*

# CocoaPods repo 캐시가 꼬였을 때
pod repo update

# iOS 폴더에서 재설치
cd ios
pod install --repo-update
cd ..

flutter pub get

빌드 실행은 Xcode 한 번, CLI 한 번

  • Xcode로 Runner.xcworkspace를 열어 Product - Clean Build Folder를 수행
  • 그 다음 CLI로 flutter build ios 또는 flutter run 실행

Xcode 단독/CLI 단독으로만 돌리면, 실제 CI에서 실패하는 케이스를 놓칠 수 있습니다.

2) Bitcode 오류 완전 정리

2-1. 대표 로그 패턴

  • bitcode bundle could not be generated
  • does not contain bitcode
  • ld: bitcode bundle could not be generated because ...

2-2. 배경: 요즘은 대부분 bitcode를 끄는 게 정답

최근 iOS 생태계에서는 bitcode가 사실상 필수가 아닌 방향으로 정리되었습니다. 문제는 일부 구형 바이너리 SDK가 bitcode 설정과 충돌하거나, 반대로 bitcode가 필요한 것처럼 설정되어 있을 때입니다.

2-3. 해결 1순위: Xcode에서 bitcode 비활성화

Runner 타겟의 Build Settings에서 Enable BitcodeNo로 맞춥니다.

중요 포인트는 Runner만이 아니라, Pods 타겟에도 설정이 전파되어야 한다는 점입니다. 이를 위해 Podfile에 post install 훅을 넣는 방식을 많이 씁니다.

# ios/Podfile
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ENABLE_BITCODE'] = 'NO'
    end
  end
end

이후 pod install을 다시 실행합니다.

2-4. 해결 2순위: 특정 Pod만 bitcode 관련 설정을 강제

전체 Pods에 일괄 적용이 부담이라면, 특정 타겟만 필터링할 수 있습니다.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    next unless ['SomeLegacySDK', 'AnotherBinarySDK'].include?(target.name)

    target.build_configurations.each do |config|
      config.build_settings['ENABLE_BITCODE'] = 'NO'
    end
  end
end

2-5. 그래도 실패한다면: 바이너리 프레임워크 자체가 문제

특정 xcframework 또는 framework가 bitcode 요구/미포함으로 충돌하는 경우, 설정으로 해결이 안 됩니다. 이때는 다음 중 하나가 필요합니다.

  • SDK 최신 버전으로 교체
  • 소스 기반 Pod로 대체
  • 해당 기능을 다른 패키지로 교체

3) arm64 관련 오류: 시뮬레이터 vs 디바이스를 분리해서 생각

arm64 오류는 크게 두 가지로 나뉩니다.

  1. 디바이스 빌드에서 arm64 링크 실패
  2. 시뮬레이터 빌드에서 아키텍처 불일치

3-1. 대표 로그 패턴

  • Undefined symbols for architecture arm64
  • building for iOS Simulator, but linking in object file built for iOS
  • ld: symbol(s) not found for architecture arm64

3-2. Apple Silicon 맥에서 특히 흔한 케이스: 시뮬레이터 아키텍처 충돌

Apple Silicon 환경에서 시뮬레이터는 arm64로 동작하는데, 일부 구형 Pod가 x86_64 시뮬레이터 바이너리만 제공하면 충돌합니다.

해결 A: 시뮬레이터에서만 arm64 제외

Podfile에서 시뮬레이터용 EXCLUDED_ARCHS를 설정합니다.

주의: 본문에 부등호가 들어가면 MDX가 깨질 수 있으니, 모든 비교 기호는 코드 블록 안에서만 사용합니다.

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # 시뮬레이터에서만 arm64 제외
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
  end
end

이 방식은 “시뮬레이터만” 우회하는 것이므로, 실제 디바이스 릴리즈에는 영향이 적습니다.

해결 B: Xcode에서 Rosetta로 실행

Xcode 자체를 Rosetta로 실행해서 시뮬레이터를 x86_64로 돌리는 우회도 있습니다. 다만 장기적으로는 Pod 업데이트로 해결하는 편이 낫습니다.

3-3. 디바이스에서 Undefined symbols for architecture arm64

이 경우는 대개 아래 중 하나입니다.

  • 네이티브 라이브러리가 링크되지 않음
  • Other Linker Flags 또는 -ObjC 필요
  • 패키지 버전 불일치로 심볼 이름이 바뀜

점검 1: Runner.xcworkspace를 열었는지

CocoaPods를 쓰는 Flutter iOS는 원칙적으로 Runner.xcworkspace로 열어야 Pods가 링크됩니다. Runner.xcodeproj로 열면 arm64 심볼 에러가 폭발하는 경우가 많습니다.

점검 2: pod deintegrate 후 재설치

cd ios
pod deintegrate
rm -rf Pods Podfile.lock
pod install --repo-update
cd ..

점검 3: 특정 SDK가 static 또는 dynamic 프레임워크 요구

일부 SDK는 use_frameworks! 또는 use_modular_headers!와 충돌합니다. Flutter 플러그인들이 섞여 있을 때 특히 민감합니다.

예시로, 프레임워크 사용이 필요한 경우:

# ios/Podfile
use_frameworks! :linkage => :static
use_modular_headers!

단, 이 설정은 모든 Pod에 영향을 주므로, 적용 후에는 빌드/런타임 테스트를 반드시 해야 합니다.

4) CocoaPods 오류: 설치 실패, 모듈 미발견, 헤더 충돌

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

해결 루틴

  1. iOS 최소 타겟 버전 상향
  2. Pod repo 업데이트
  3. Flutter 플러그인 버전 정리

Podfile에서 최소 타겟은 아래처럼 맞춥니다.

platform :ios, '13.0'

플러그인이 요구하는 최소 버전이 12.0인데 프로젝트가 11.0이면, 빌드는 당연히 깨집니다.

4-2. Module not found 또는 Could not build module

대표 로그

  • module 'xxx' not found
  • Could not build module 'yyy'

원인

  • use_frameworks! 적용으로 모듈 맵이 바뀜
  • 헤더 검색 경로 충돌
  • Swift/ObjC 혼합 Pod에서 modular headers 필요

해결

  • use_modular_headers! 적용 또는 특정 Pod에만 :modular_headers => true 적용
target 'Runner' do
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  pod 'SomePod', :modular_headers => true
end

4-3. Sandbox: rsync.samba 또는 파일 권한/동기화 문제

CI나 회사 보안 정책이 들어간 맥에서 Pods 복사 단계가 실패하는 경우가 있습니다. 이때는 다음을 점검합니다.

  • 프로젝트 경로에 한글/특수문자 포함 여부
  • 디스크 권한, Full Disk Access
  • DerivedDataPods 제거 후 재생성

빌드 캐시/파일 시스템 문제는 도커에서 캐시가 안 먹히는 문제와 유사하게 “원인을 추적하기 어렵지만 정리 루틴이 효과적”인 편입니다. 관련 글: Docker BuildKit 캐시 안먹힘 원인·해결 7가지

5) 자주 쓰는 실전 처방 세트 (상황별)

5-1. Xcode 업그레이드 후 갑자기 깨짐

  1. flutter upgradeflutter doctor -v
  2. pod repo update
  3. Podfile.lock 삭제 후 재설치
  4. 최소 iOS 타겟 상향
flutter doctor -v
cd ios && pod install --repo-update && cd ..
flutter build ios --release

5-2. 특정 플러그인 추가 후 arm64 에러

  1. 해당 플러그인이 네이티브 바이너리를 포함하는지 확인
  2. 플러그인 이슈 트래커에서 Apple Silicon, arm64 simulator 키워드로 검색
  3. 시뮬레이터만 깨지면 EXCLUDED_ARCHS로 우회
  4. 디바이스도 깨지면 SDK 버전 교체 또는 use_frameworks! 조정

5-3. CI에서만 실패

로컬과 CI의 차이는 대부분 다음 3가지입니다.

  • Xcode 버전
  • CocoaPods 버전
  • Ruby 환경

가능하면 CI에서 CocoaPods 버전을 고정하세요.

sudo gem install cocoapods -v 1.14.3
pod --version

또는 Bundler로 고정합니다.

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

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

6) 최종 체크리스트: 로그를 보고 바로 분기하기

아래는 실제로 시간을 가장 많이 아껴주는 분기표입니다.

6-1. bitcode가 보이면

  • ENABLE_BITCODE = NORunnerPods에 적용
  • 바이너리 SDK가 구형이면 업데이트 또는 교체

6-2. arm64가 보이면

  • 시뮬레이터 전용 오류인지 디바이스도 깨지는지 먼저 구분
  • 시뮬레이터만이면 EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
  • 디바이스면 Runner.xcworkspace 확인, 링크 플래그/프레임워크 설정 점검

6-3. pod install이 실패하면

  • iOS 최소 타겟 상향
  • Podfile.lock 삭제 후 재설치
  • CocoaPods 버전 고정

7) 결론: “설정”이 아니라 “조합”이 문제다

Flutter iOS 빌드 실패는 단일 설정 하나로 끝나는 경우도 있지만, 대부분은 Xcode 버전Pod 생태계, 플러그인 네이티브 SDK의 조합 문제입니다. 그래서 해결도 “한 방”보다 “정리 루틴 + 로그 기반 분기”가 가장 빠릅니다.

이 글의 루틴대로 캐시 정리 후, bitcodearm64를 시뮬레이터/디바이스로 분리해 접근하고, 마지막으로 Podfile post_install로 설정을 강제하면 대다수 케이스는 안정적으로 수습됩니다.