Published on

Flutter iOS 빌드 No such module 해결 7단계

Authors

서론

Flutter로 iOS 빌드를 하다 보면 No such module 'XXX'는 가장 흔하면서도 시간을 잡아먹는 오류입니다. 특히 CI 환경이나 팀 개발에서 Pod 캐시/락파일/워크스페이스가 조금만 어긋나도 동일한 메시지가 반복됩니다. 이 글은 “무조건 클린” 같은 감에 의존하지 않고, 어떤 레이어에서 모듈이 사라졌는지를 7단계로 좁혀가며 해결하도록 구성했습니다.

오류가 발생하는 지점은 크게 3가지입니다.

  • Swift 컴파일 단계: import Firebase 같은 구문에서 모듈을 못 찾음
  • Objective-C/헤더 브릿징 단계: Could not build module 'xxx'로 연쇄 발생
  • 링킹/아키텍처 단계: 시뮬레이터/디바이스 아키텍처 불일치가 “모듈 없음”으로 위장

아래 단계는 “가벼운 확인 → 재생성 → 설정 교정” 순으로 진행합니다.

> 참고: iOS 쪽 Podfile.lock 충돌이 원인인 케이스는 별도 글인 Flutter iOS 빌드 Podfile.lock 충돌 해결 가이드에서 더 깊게 다룹니다.

0) 먼저 에러를 정확히 분류하기

로그에서 어떤 모듈이 없다고 하는지어느 타겟에서 터졌는지를 확인해야 합니다.

  • 예: No such module 'FirebaseCore' → CocoaPods로 들어오는 iOS 프레임워크 모듈
  • 예: No such module 'Runner' → Xcode 타겟/워크스페이스/빌드 설정 문제
  • 예: No such module 'SomePlugin' → Flutter 플러그인(iOS Pod) 설치/통합 문제

Xcode에서 빌드한다면, 에러가 난 파일을 클릭해 Target MembershipBuild Phases도 함께 확인하세요.

1단계) Xcode에서 .xcworkspace로 열었는지 확인

CocoaPods를 쓰는 iOS 프로젝트는 반드시 Runner.xcworkspace로 열어야 합니다.

  • 잘못된 경우: Runner.xcodeproj를 열어 빌드
  • 올바른 경우: Runner.xcworkspace를 열어 빌드

확인 방법:

  • Xcode 상단 타이틀에 Runner.xcworkspace가 보이는지
  • 좌측 Project Navigator에 Pods 프로젝트가 포함되어 있는지

CLI에서 열기:

open ios/Runner.xcworkspace

이 단계에서 해결되는 경우가 의외로 많습니다. 특히 팀원이 “xcodeproj로 열고 빌드 후 커밋” 같은 실수를 했을 때 반복됩니다.

2단계) iOS 디렉터리에서 CocoaPods 설치 상태를 재생성

No such module은 결국 “해당 모듈이 빌드 시스템에 등록되지 않았다”는 뜻이므로, Pods 설치 상태를 재생성하는 것이 핵심입니다.

권장 시퀀스(가장 안전한 쪽):

flutter clean
rm -rf ios/Pods ios/Podfile.lock
rm -rf ios/.symlinks
rm -rf ios/Flutter/Flutter.framework ios/Flutter/Flutter.podspec

flutter pub get
cd ios
pod repo update
pod install
cd ..
  • ios/.symlinks는 Flutter 플러그인 Pod 연결이 꼬였을 때 효과적입니다.
  • Flutter.framework/Flutter.podspec는 오래된 캐시가 남아 “모듈을 찾는데 실제 바이너리/스펙이 불일치”하는 케이스에서 도움이 됩니다.

> CI에서 재현된다면, 캐시 정책 때문에 Pods가 부분적으로만 복원되어 생길 수 있습니다. 캐시 키를 Podfile.lockpubspec.lock에 연동하는 것이 안정적입니다.

3단계) Podfile의 use_frameworks! / use_modular_headers! 조합 점검

Swift에서 import로 모듈을 읽으려면, Pod가 어떤 형태로 통합되는지(프레임워크/정적 라이브러리/모듈 헤더)가 매우 중요합니다.

대표적인 실패 패턴:

  • use_frameworks!를 켰는데 일부 Pod가 정적 링크/모듈맵과 충돌
  • use_modular_headers!가 필요한데 누락되어 모듈맵 생성이 안 됨

예시 Podfile(Flutter 기본 구조 내에서 조정):

platform :ios, '13.0'

# 필요 시만 사용(프로젝트/Pod 호환성에 따라)
use_frameworks! :linkage => :static
use_modular_headers!

target 'Runner' do
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
  • Firebase 계열이나 일부 Swift Pod는 use_frameworks!가 필요할 수 있습니다.
  • 반대로 특정 Pod는 use_frameworks!에서 깨지기도 하므로, 문제가 생기면 :linkage => :static 또는 설정 제거로 A/B 테스트합니다.

변경 후에는 반드시 Pods를 재설치하세요.

cd ios
pod deintegrate
pod install

4단계) Xcode Build Settings에서 Framework Search Paths / Header Search Paths 확인

Pods가 설치되어 있어도, 빌드 설정이 꼬이면 모듈을 못 찾습니다. 특히 다음 상황에서 자주 발생합니다.

  • 수동으로 FRAMEWORK_SEARCH_PATHS를 건드렸거나
  • .xcconfig 병합이 깨졌거나
  • 타겟이 여러 개(Dev/Prod)인데 한쪽만 설정이 누락

확인 포인트:

  • Build Settings > Framework Search Paths$(inherited)가 있는지
  • Build Settings > Header Search Paths$(inherited)가 있는지
  • Other Swift Flags에 이상한 플래그가 들어갔는지

대부분은 $(inherited) 누락이 치명적입니다. Pods가 생성한 설정을 상속받지 못하면, Xcode는 모듈맵을 못 읽습니다.

또한 Runner 타겟뿐 아니라 RunnerTests, Notification Service Extension 같은 부가 타겟이 있을 때 그쪽에서만 실패하는 경우도 많습니다.

5단계) 시뮬레이터/디바이스 아키텍처 및 Excluded Architectures 점검

Apple Silicon(M1/M2/M3) 환경에서 No such module이 뜨지만 실제 원인은 “아키텍처 불일치로 해당 바이너리가 로드 불가”인 경우가 있습니다.

점검 체크리스트:

  • 시뮬레이터 빌드인데 Pod가 arm64 시뮬레이터를 지원하지 않음
  • Excluded Architectures가 잘못 설정되어 필요한 아키텍처가 제외됨

일시적 회피(문제 Pod가 arm64 simulator 미지원일 때):

  • Xcode > Runner Target > Build Settings > Excluded Architectures
    • Any iOS Simulator SDK = arm64

또는 Podfile post_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

주의: 이 설정은 근본 해결이 아니라 호환성 확보 전까지의 임시 조치입니다. 가능하면 Pod/플러그인 버전을 올려 arm64 simulator를 지원하게 만드는 것이 정석입니다.

6단계) DerivedData 및 모듈 캐시 정리로 “유령 모듈” 제거

Xcode는 모듈 캐시를 강하게 사용합니다. Pods를 바꿨는데도 계속 같은 No such module이 뜨면, DerivedData에 남은 이전 모듈이 원인일 수 있습니다.

정리 명령:

rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Developer/Xcode/ModuleCache.noindex

그리고 Xcode를 완전히 종료했다가 다시 열어 빌드합니다.

추가로 Flutter 쪽 캐시도 의심되면:

flutter doctor -v
flutter precache --ios

7단계) 플러그인/Pod 버전 충돌과 락파일 상태를 “재현 가능”하게 고정

팀/CI에서만 터지는 No such module은 보통 “내 로컬은 되는 조합”이 우연히 맞아떨어진 것입니다. 다음을 고정해야 재현성과 안정성이 생깁니다.

  • pubspec.lock 커밋(앱 프로젝트라면 커밋 권장)
  • Podfile.lock 커밋(iOS 앱은 커밋 권장)
  • CocoaPods 버전 통일(팀/CI 동일 버전)

CocoaPods 버전 확인:

pod --version

Bundler로 고정(권장):

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

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

특히 Podfile.lock 충돌/불일치가 원인이라면 위에서 언급한 내부 글(Flutter iOS 빌드 Podfile.lock 충돌 해결 가이드)의 패턴(락파일 우선순위, repo update 타이밍, CI 캐시 키)을 함께 적용하면 재발률이 크게 줄어듭니다.

자주 나오는 케이스별 빠른 처방

케이스 A: No such module 'FirebaseCore'

  1. .xcworkspace 확인
  2. pod install 재실행
  3. Podfile에서 use_frameworks! :linkage => :static + use_modular_headers! 조합 A/B
  4. DerivedData 삭제

케이스 B: 특정 플러그인만 No such module (예: path_provider_foundation)

  • ios/.symlinks 삭제 후 flutter pub getpod install
  • 플러그인 버전 업데이트(Flutter/Dart SDK와 호환되는지 확인)

케이스 C: CI에서만 실패

  • bundle exec pod install로 CocoaPods 버전 고정
  • 캐시 키를 pubspec.lock + Podfile.lock에 묶기
  • pod repo update를 CI에서 매번 할지(느리지만 안정) 전략 결정

CI 문제를 체계적으로 다루는 방식은 배포/권한 이슈지만 접근법(재현 가능성, 환경 고정, 로그 분해)이 유사합니다. 참고로 GitHub Actions OIDC로 AWS 배포 권한 오류 해결 글의 “환경 차이를 줄이는 체크리스트”도 같은 원리로 적용할 수 있습니다.

결론: 7단계는 “모듈이 등록되는 경로”를 복구하는 과정

No such module은 대개 다음 중 하나로 귀결됩니다.

  • 워크스페이스/Pods가 빌드 그래프에 포함되지 않음
  • Pod 통합 방식(framework/modular header)이 Swift import 조건과 불일치
  • 캐시/DerivedData가 이전 상태를 계속 참조
  • 아키텍처/타겟 설정이 실제 빌드 산출물을 로드하지 못함

이 글의 7단계를 순서대로 적용하면, “무작정 클린”이 아니라 어떤 레이어에서 끊겼는지를 확인하며 안정적으로 복구할 수 있습니다. 다음에 같은 오류가 나도, 로그에서 모듈/타겟을 분류한 뒤 1→7 순으로 좁혀가면 대부분 10~20분 내로 해결됩니다.