【iOS】 CI/CDビルドエラー解決記:xconfigとproject.pbxprojの設定優先順位の罠
はじめに
iOSアプリのCI/CD環境で、SDKのビルドが突然失敗するようになりました。
エラーメッセージは no such module 'ExternalSDK'
しかし、ローカル環境では正常にビルドできている...
この問題の解決に5日間を費やしました。
その間、アプリのアップデートは完全にストップ。
チームメンバーも待機状態となり、リリーススケジュールが大幅に遅れることに...
同じような問題で困っている方の時間を節約できればと思い、この記事を書きます。
対象読者
- CI/CDでビルドエラーに悩んでいる方
問題の発生
エラー内容
CI/CDで以下のエラーが発生しました:
/MySDK/MySDK/SomeFile.swift:1:8: error: no such module 'ExternalSDK'
import ExternalSDK
現状としては
- ✅ ローカル環境: 正常にビルド成功
- ❌ CI/CD環境: ビルド失敗
- ✅ 以前のブランチ: CI/CDでも成功していた
調査の流れ
ステップ1: 成功していたブランチとの比較
まず、「以前は動いていたのに、なぜ今は動かないのか?」を調べるため、
成功していたブランチ(develop)と現在のブランチを比較することにしました。
なぜproject.pbxprojを比較するのか?
Xcodeプロジェクトでビルド設定を変更すると、その情報はMySDK.xcodeproj/project.pbxproj というファイルに保存されます。
このファイルがXcodeプロジェクトの「設定の本体」なので、
ビルドの挙動が変わったということは、このファイルに何か変更があったはずです。
# 差分を確認
git diff develop -- MySDK/MySDK.xcodeproj/project.pbxproj
発見:
+ FRAMEWORK_SEARCH_PATHS = "$(inherited)";
+ SWIFT_INCLUDE_PATHS = ""; ← これが怪しい!
空の SWIFT_INCLUDE_PATHS = "" が追加されていました。
- SWIFT_INCLUDE_PATHS とは?
Swiftコンパイラがモジュールをimportするためのパスのリスト です
参考:
Module Import Paths
Setting name: SWIFT_INCLUDE_PATHS
A list of paths to be searched by the Swift compiler for additional Swift modules.
A list of paths to be searched by the Swift compiler for additional Swift modules.
この設定が空("")になっていました。
これが原因で、Swiftコンパイラがモジュールを見つけられなくなったようです。
次のステップ
ここまでで、以下のことがわかりました:
-
SWIFT_INCLUDE_PATHS = ""という空の設定が追加されている - ただし、この設定はどこに保存されているのか?
- 空の値を設定すると、なぜ問題になるのか?
これらを理解するために、Xcodeのビルド設定がどこに保存されるかを理解する必要があります。
ステップ2: Xcodeのビルド設定ファイルについて
Xcodeのビルド設定は、主に2つの場所に保存されます:
- project.pbxproj - Xcodeが自動管理するファイル
- xcconfig - 開発者が手動で管理できるテキストファイル
それぞれの特徴を見てみましょう。
📝 project.pbxprojとは?
project.pbxprojは、Xcodeプロジェクトの設定が保存されているファイルです。
XcodeのGUI(Build Settings画面)で設定を変更すると、このファイルに保存されます。
場所:
MyProject.xcodeproj/project.pbxproj
特徴:
- Xcodeが自動生成・管理
- 直接編集することは少ない
- 複雑なJSON/XML形式
📝 xcconfigファイルとは?
xcconfig(Xcode Configuration)ファイルは、ビルド設定をプレーンテキストで管理し、デバッグビルドとリリースビルドで異なる設定を提供できる仕組みです。
Apple公式ドキュメントより
Specify your project's build settings in plain-text files, and supply different settings for debug and release builds.
なぜ使うのか?
- ビルド設定をコードとして管理できる
- Git差分が見やすい
- 環境ごと(Debug/Release)に設定を分けやすい
- チームで共有しやすい
ファイル名の例:
Debug.xcconfig
Release.xcconfig
中身の例:
// Debug.xcconfig
SWIFT_INCLUDE_PATHS = ExternalSDKModule
FRAMEWORK_SEARCH_PATHS = $(PROJECT_DIR)/Frameworks
2つのファイルの関係
ここで重要な疑問が生まれます:
「同じ設定が、project.pbxprojとxcconfigの両方にあったら、どっちが優先されるのか?」
この答えが、今回の問題の核心です。
ステップ3: Xcodeの設定優先順位の理解
Xcodeのビルド設定には優先順位があります。
NSHipsterのサイトによると、Xcodeは以下の順序で設定値を割り当てます
(優先度:低→高) :
Xcode assigns inherited values in the following order
(from lowest to highest precedence):
優先度:低
1. Platform Defaults(Xcodeの標準値)
2. Xcode Project xcconfig File(プロジェクト用xcconfig)
3. Xcode Project File Build Settings(project.pbxproj)← ここがポイント!
4. Target xcconfig File(ターゲット用xcconfig)
5. Target Build Settings(project.pbxproj)← 最優先
優先度:高
図で理解する
❌ 間違った状態:
┌─────────────────────────────────────┐
│ 優先度3: project.pbxproj │
│ SWIFT_INCLUDE_PATHS = "" │ ← より優先される(空)
└─────────────────────────────────────┘
↓ 上書き
┌─────────────────────────────────────┐
│ 優先度2: xcconfig │
│ SWIFT_INCLUDE_PATHS = ExternalSDK │ ← 無効化される
└─────────────────────────────────────┘
───────────────────────────────────────────────────────────────────────────────────
✅ 正しい状態:
┌─────────────────────────────┐
│ project.pbxproj │
│ (設定なし) │ ← 何も書かない
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ xcconfig │
│ SWIFT_INCLUDE_PATHS = ... │ ← これが有効になる
└─────────────────────────────┘
解決方法
解決策: project.pbxprojから削除
空の設定を削除し、xcconfigの設定を有効にします。
# 正常なブランチの状態に戻す
git checkout develop -- MySDK/MySDK.xcodeproj/project.pbxproj
# 確認
git diff --staged
結果:
- FRAMEWORK_SEARCH_PATHS = "$(inherited)";
- SWIFT_INCLUDE_PATHS = "";
これでxcconfigの SWIFT_INCLUDE_PATHS = ExternalSDKModule が有効になります。
なぜCI/CDだけ失敗したのか?
ローカル環境
- Xcodeが過去のビルドキャッシュを使用
- 一度正常にビルドできていれば、キャッシュで動作し続ける
CI/CD環境
- 毎回クリーンビルド
- キャッシュがないため、設定ミスが即座に顕在化
→ CI/CDは正直者。設定ミスを確実に検出します。
5日間の教訓: ローカルで動いているからといって、設定が正しいとは限らない。
学んだこと
1. 空の設定は削除すること
空の値("")も「設定」として扱われ、デフォルト値やxcconfigを上書きします。
❌ 悪い例:
SWIFT_INCLUDE_PATHS = ""; // 何も指定したくない
✅ 良い例:
// 設定自体を書かない(削除する)
2. 設定の優先順位を理解する
project.pbxproj > xcconfig > デフォルト値
3. CI/CDログを信じる
ローカルで動いても、CI/CDで失敗するなら、CI/CDが正しいです。
ローカルのキャッシュに惑わされないこと。
5日間の反省: 「ローカルで動くから設定は正しいはず」という思い込みが調査を遅らせました。
4. git diffは友達
正常に動いていたブランチとの差分を見ることで、原因を特定できます。
# ファイル全体の差分
git diff develop -- path/to/file
# 特定の設定を検索
git show develop:path/to/file | grep "SWIFT_INCLUDE_PATHS"
まとめ
-
問題:
SWIFT_INCLUDE_PATHS = ""がxcconfigを上書きしていた - 原因: project.pbxproj > xcconfigの優先順位を理解できていなかった
- 解決: project.pbxprojから空の設定を削除した
- 教訓: xcconfigで管理する設定は、project.pbxprojに書かないこと
- 反省: ローカル環境のキャッシュに惑わされず、CI/CDを信じる
5日間という時間とチームへの影響を考えると、この経験は高い授業料でした。
しかし、Xcodeのビルド設定の仕組みを深く理解できたことは、今後の開発に必ず活きるはずです。
CI/CDは正直者。ローカルで動いても油断せず、CI/CDログを信じることが大切
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
Discussion