Published on

Xcode 15에서만 Flutter iOS 빌드 실패 체크리스트

Authors

서로 같은 Flutter 코드인데도 Xcode 15에서만 iOS 빌드가 깨지고, Xcode 14.x 또는 CI 환경에서는 정상인 상황이 종종 발생합니다. 이런 케이스는 대부분 "코드 문제"라기보다 Xcode 15 툴체인 변화(Clang, Swift, ld, 기본 빌드 설정), iOS SDK 업데이트, CocoaPods 통합 방식, 코드사인 체인 변화가 겹치며 터집니다.

이 글은 원인 후보를 넓게 나열하기보다, 실제로 시간을 줄여주는 "진단 순서" 중심 체크리스트로 정리합니다. 각 단계는 재현 로그 확보, 환경 고정, Pods 재생성, 빌드 설정 점검, 서명 점검 순으로 진행합니다.

CocoaPods 충돌이 의심된다면 아래 글도 함께 보세요. Flutter iOS 빌드 실패? CocoaPods 충돌 해결법

1) 먼저 로그를 "Xcode 15 기준"으로 고정 수집하기

Xcode UI에서 보이는 에러 한 줄만 가지고는 원인 분류가 어렵습니다. 동일한 명령으로 로컬과 CI를 비교할 수 있게 로그를 고정해 두면, 이후 단계에서 변경 전후 비교가 쉬워집니다.

1-1. Flutter 빌드 로그

flutter --version
flutter doctor -v
flutter clean
flutter build ios --debug -v

-v 로그에서 다음 키워드를 찾습니다.

  • SwiftEmitModule, CompileSwiftSources (Swift 컴파일)
  • Ld 또는 linker command failed (링커)
  • PhaseScriptExecution (Run Script 단계)
  • CodeSign (서명)

1-2. Xcodebuild로 재현 (UI 의존성 제거)

cd ios
xcodebuild \
  -workspace Runner.xcworkspace \
  -scheme Runner \
  -configuration Debug \
  -sdk iphonesimulator \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  clean build | tee xcode15-build.log

이 로그는 "Xcode 15에서만" 깨지는 포인트를 정확히 잡는 데 가장 유용합니다.

2) 환경 차이부터 제거: Flutter, Ruby, CocoaPods, Xcode 선택

Xcode 15만 실패한다면, 우선 "Xcode가 바뀌면서 간접적으로 바뀐 것"을 고정해야 합니다.

2-1. Xcode 선택 확인

xcode-select -p
xcodebuild -version

Xcode를 여러 버전 설치해두면 xcode-select 경로가 의도와 다를 수 있습니다.

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

2-2. CocoaPods와 Ruby 버전 확인

Xcode 15 업데이트 이후 Ruby 환경이 섞여서 Pods 설치 결과가 달라지는 경우가 있습니다.

ruby -v
which ruby
pod --version
which pod

가능하면 프로젝트에서 Bundler를 사용해 CocoaPods 버전을 고정합니다.

cd ios
bundle init
bundle add cocoapods
bundle exec pod install

3) Pods 재생성: Xcode 15에서만 깨질 때 가장 먼저 해볼 것

Xcode 15는 iOS SDK와 빌드 설정 기본값이 바뀌며, 기존 Pods 캐시나 DerivedData가 남아 있으면 애매한 증상이 납니다.

3-1. 캐시/Pods/DerivedData 싹 정리

flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
cd ios
pod repo update
pod install

3-2. Runner.xcworkspace로 열었는지 확인

Pods를 쓰는 Flutter iOS는 반드시 Runner.xcworkspace로 열어야 합니다. Runner.xcodeproj로 열면 Xcode 15에서 링크 단계가 더 쉽게 터집니다.

4) Xcode 15 변경점으로 자주 터지는 빌드 설정 점검

여기부터는 에러 메시지 유형별로 확인합니다.

4-1. linker command failed 또는 중복 심볼

증상 예:

  • ld: duplicate symbol
  • Undefined symbols for architecture arm64

체크 포인트:

  1. 시뮬레이터/디바이스 아키텍처가 섞였는지
  2. 정적 라이브러리와 동적 라이브러리 링크가 중복됐는지
  3. 특정 Pod가 Xcode 15의 링커와 충돌하는지

시뮬레이터에서 arm64 관련 이슈

Apple Silicon 환경에서 시뮬레이터는 arm64로 돌고, 일부 Pod가 x86_64 전용 바이너리를 포함하면 깨집니다.

  • Xcode Build Settings에서 Excluded Architectures를 점검합니다.
  • 임시 우회로 시뮬레이터에서 arm64를 제외할 수 있지만, 장기적으로는 Pod 업데이트가 정답입니다.

Pod 업데이트:

cd ios
pod update

4-2. Swift 컴파일 에러: SwiftEmitModule 실패

Xcode 15는 Swift 컴파일러가 바뀌면서, 이전에는 경고였던 것이 에러가 되거나 모듈 인터페이스 호환성 문제가 드러나는 경우가 있습니다.

체크 포인트:

  • 플러그인(특히 iOS 네이티브 코드 포함)이 Xcode 15를 지원하는 버전인지
  • IPHONEOS_DEPLOYMENT_TARGET이 너무 낮게 잡혀 있는지

배포 타겟 정렬

Podfile에서 최소 타겟을 프로젝트와 맞춥니다.

platform :ios, '12.0'

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
    end
  end
end

주의: 배포 타겟을 올리면 구형 iOS 지원 범위가 줄어듭니다. 앱 정책과 사용자 분포를 확인한 뒤 결정하세요.

4-3. Sandbox: ... deny(1) file-write-create 또는 스크립트 단계 실패

Xcode 15에서 Run Script 단계가 권한/샌드박스 정책에 더 민감하게 보이는 경우가 있습니다.

체크 포인트:

  • Build PhasesRun Script가 프로젝트 외부 경로에 쓰기를 시도하는지
  • 스크립트가 DerivedData가 아닌 임의 위치에 파일 생성하는지

가능한 해결:

  • 스크립트 출력 경로를 $(DERIVED_FILE_DIR) 또는 $(TARGET_BUILD_DIR) 내부로 제한
  • 필요 시 Run ScriptBased on dependency analysis 옵션을 점검해 불필요한 실행을 줄임

5) 코드사인/프로비저닝: Xcode 15에서만 "서명"이 까다롭게 보일 때

Xcode 15에서만 아카이브 또는 디바이스 설치가 실패한다면 서명 체인을 의심해야 합니다.

5-1. 대표 증상

  • No profiles for ... were found
  • Signing for ... requires a development team
  • errSecInternalComponent

5-2. 체크리스트

  • Xcode Signing & Capabilities에서 Team이 올바르게 선택됐는지
  • 자동 서명(Automatically manage signing)을 켰는지/껐는지 팀 정책에 맞는지
  • 키체인에 개발 인증서가 유효한지(만료/폐기)
  • CI에서만 실패한다면 keychain 잠금/권한 문제

CLI로 인증서 확인:

security find-identity -v -p codesigning

6) Flutter 플러그인과 Xcode 15 호환성 점검

"Xcode 15에서만"의 핵심은 결국 네이티브 의존성입니다. Flutter 자체보다 플러그인 쪽에서 iOS 빌드 설정을 강하게 건드리는 경우가 많습니다.

체크 포인트:

  • 최근 추가한 플러그인이 있는지
  • iOS 네이티브 SDK를 포함하는 플러그인(광고, 결제, 분석, 지도)이 Xcode 15 지원 버전인지

권장 절차:

  1. pubspec.yaml에서 최근 변경된 플러그인을 잠시 제거
  2. flutter pub get
  3. Pods 정리 후 재설치
flutter pub get
flutter clean
rm -rf ios/Pods ios/Podfile.lock
cd ios && pod install

7) "Xcode 15에서만" 재현되는 CI 문제: 캐시와 툴체인 고정

로컬은 되는데 Xcode 15 기반 CI에서만 실패한다면, 캐시된 Pods 또는 DerivedData가 원인일 수 있습니다. 또한 CI 이미지에서 Xcode 버전이 바뀌면 Ruby, CocoaPods, SDK가 같이 바뀝니다.

체크리스트:

  • CI에서 Xcode 버전을 명시적으로 고정했는지
  • Pods 캐시를 무작정 재사용하고 있지 않은지
  • pod install을 항상 수행하고, Podfile.lock을 커밋해 재현성을 확보했는지

문제 원인 추적 방식은 쿠버네티스 장애에서 로그와 상태를 먼저 고정하는 접근과 유사합니다. 재현 가능한 로그가 있으면 원인 분리가 훨씬 빨라집니다.

참고로 운영 환경에서 원인 추적 루틴을 정리한 글이 필요하면 다음도 도움이 됩니다.

8) 마지막 정리: 가장 효율적인 진행 순서

아래 순서대로 하면, 불필요하게 설정을 마구 바꾸지 않고도 원인을 빠르게 좁힐 수 있습니다.

  1. flutter build ios -vxcodebuild ... | tee로 로그 고정
  2. xcode-select, xcodebuild -version으로 Xcode 15 사용 확정
  3. DerivedData, Pods, Podfile.lock 삭제 후 pod install 재생성
  4. 에러 유형 분류
    • 링커: 아키텍처, 중복 링크, Pod 업데이트
    • Swift: 플러그인 호환성, 배포 타겟 정렬
    • 스크립트: 출력 경로/샌드박스
    • 서명: Team, 프로파일, 인증서
  5. 최근 추가한 플러그인부터 의심하고 최소 재현으로 축소

부록) 자주 쓰는 명령 모음

# Flutter 환경
flutter doctor -v
flutter clean
flutter build ios -v

# Xcode 선택/버전
xcode-select -p
xcodebuild -version

# CocoaPods 재설치
rm -rf ios/Pods ios/Podfile.lock
rm -rf ~/Library/Developer/Xcode/DerivedData
cd ios && pod repo update && pod install

# 코드사인 인증서 확인
security find-identity -v -p codesigning

위 체크리스트로도 해결이 안 되면, 실패 로그에서 "첫 번째" 컴파일/링크 에러 블록(가장 위쪽의 원인)을 그대로 분리해 보세요. 같은 linker command failed라도 실제 원인은 그 직전의 Undefined symbols나 특정 Pod의 빌드 스텝 실패인 경우가 대부분입니다.