😱

【iOS】 CI/CDビルドエラー解決記:xconfigとproject.pbxprojの設定優先順位の罠

に公開
2025/11/23

はじめに

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.
https://developer.apple.com/documentation/xcode/build-settings-reference

A list of paths to be searched by the Swift compiler for additional Swift modules.

この設定が空("")になっていました。
これが原因で、Swiftコンパイラがモジュールを見つけられなくなったようです。


次のステップ

ここまでで、以下のことがわかりました:

  • SWIFT_INCLUDE_PATHS = "" という空の設定が追加されている
  • ただし、この設定はどこに保存されているのか?
  • 空の値を設定すると、なぜ問題になるのか?

これらを理解するために、Xcodeのビルド設定がどこに保存されるかを理解する必要があります。


ステップ2: Xcodeのビルド設定ファイルについて

Xcodeのビルド設定は、主に2つの場所に保存されます:

  1. project.pbxproj - Xcodeが自動管理するファイル
  2. 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.
https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project

なぜ使うのか?

  • ビルド設定をコードとして管理できる
  • 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は以下の順序で設定値を割り当てます
(優先度:低→高) :
https://nshipster.com/xcconfig/

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は正直者。設定ミスを確実に検出します。


学んだこと

1. 空の設定は削除すること

空の値("")も「設定」として扱われ、デフォルト値やxcconfigを上書きします。

❌ 悪い例:

SWIFT_INCLUDE_PATHS = "";  // 何も指定したくない

✅ 良い例:

// 設定自体を書かない(削除する)

2. 設定の優先順位を理解する

project.pbxproj > xcconfig > デフォルト値

3. CI/CDログを信じる

ローカルで動いても、CI/CDで失敗するなら、CI/CDが正しいです。
ローカルのキャッシュに惑わされないこと。


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ログを信じることが大切

Discussion