Xcode 虛擬目錄問題|混乱解消とXcodeGen・Tuist統合の開源ツール解決策
Xcodeの虚拟目录が引き起こす構造混乱を抱えるApple開発者向けに、XcodeGenやTuistとの統合を可能にする独自オープンソースツールで開発効率を劇的改善。
本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。
記事一覧
XCode 仮想フォルダの長年の問題と私のオープンソースツールによる解決策
Apple 開発者の職業病:Xcodeの初期に仮想フォルダを使用したため、ディレクトリ構造が混乱し、XcodeGenやTuistなどの最新ツールとの統合が難しくなった。
Photo by Saad Salim
この投稿の英語版:
Xcodeの仮想ディレクトリの長年の問題と私のオープンソースツールによる解決策を探る
背景
チームの規模やプロジェクトの成長に伴い、Xcodeプロジェクトファイル(.xcodeproj)のサイズは徐々に大きくなります。プロジェクトの複雑さによっては、十万行、さらには百万行に達することもあります。複数人が複数のブランチで並行開発を行うと、どうしてもコンフリクトが発生します。XcodeProjファイルでコンフリクトが起きると、Storyboardや.xibのコンフリクトと同様に解決が非常に難しいです。これらも純粋な記述ファイルであるため、コンフリクト解消時に他人が追加したファイルを誤って削除したり、逆に他人が削除したファイルの参照が戻ってきてしまうことがよくあります。
もう一つの問題は、モジュール化が進むにつれて、Xcodeプロジェクトファイル(.xcodeproj)でのモジュールの作成や管理のプロセスが非常に使いにくいことです。モジュールに変更があっても、Xcodeプロジェクトファイルの差分(Diff)でしか確認できず、チームのモジュール化推進に不利です。
もしコンフリクトを防ぐだけなら、pre-commitでFile Sortingを簡単に行えます。既存のスクリプトはGitHubに多数あり、そのまま参考に設定可能です。
XCFolder
要点を簡潔に Xcodeの初期の仮想フォルダを、Xcode内のフォルダ構造に合わせて実体フォルダに変換するツールを作成しました。
スクロールして続きを見る…
モダンなXcodeプロジェクトファイル管理
具体的な考え方は、複数人開発でStoryboardや.xibの使用を推奨しないのと同様に、「Xcodeプロジェクトファイル」をメンテナンスしやすく、反復可能で、コードレビュー可能なインターフェースで管理する必要があります。現在、市場で主に使われている無料ツールは以下の2つです:
XCodeGen :老舗のツールで、YAMLでXcodeプロジェクトの内容を定義し、それを元にXcodeプロジェクトファイル(.xcodeproj)を生成します。
YAMLで直接構造を定義するため導入や学習コストが低いですが、モジュール化や依存管理機能は弱く、YAMLの動的設定も制限されています。Tuist :近年登場した新しいツールで、Swift DSLを使ってXcodeプロジェクトの内容を定義し、それをXcodeプロジェクトファイル(.xcodeproj)に変換します。
より安定かつ多機能で、モジュール化や依存管理機能が内蔵されていますが、学習や導入のハードルは高めです。
どの方法でも私たちのコアワークフローは以下のようになります:
Xcode プロジェクト内容設定ファイルの作成(XcodeGen の
project.yamlまたは Tuist のProject.swift)XcodeGen または Tuist を開発者および CI/CD サーバー環境に導入する
XcodeGen または Tuist を使って設定ファイルから
.xcodeprojXcode プロジェクトファイルを生成する/*.xcodeprojディレクトリファイルを.gitignoreに追加する開発者のワークフローを調整し、ブランチを切り替える際に設定ファイルから
.xcodeprojXcode プロジェクトファイルを生成するために XcodeGen または Tuist を実行する必要があります。CI/CD のプロセスを調整するために、設定ファイルを使って
.xcodeprojXcode プロジェクトファイルを生成するために、XcodeGen または Tuist を実行する必要があります。完了
.xcodeproj の Xcode プロジェクトファイルは、XcodeGen または Tuist によって YAML または Swift DSL の設定ファイルから生成されます。同じ設定ファイルと同じツールのバージョンであれば、同じ結果が得られます。そのため、.xcodeproj ファイルを Git にコミットする必要はありません。これにより、将来的に .xcodeproj ファイルの競合が発生しなくなります。プロジェクトの構成変更やモジュールの追加・変更は、設定ファイルに戻って調整します。Yaml や Swift で書かれているため、簡単に反復やコードレビューが可能です。
Tuist Swift サンプル: Project.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import ProjectDescription
let project = Project(
name: "MyApp",
targets: [
Target(
name: "MyApp",
platform: .iOS,
product: .app,
bundleId: "com.example.myapp",
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone, .ipad]),
infoPlist: .default,
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: []
),
Target(
name: "MyAppTests",
platform: .iOS,
product: .unitTests,
bundleId: "com.example.myapp.tests",
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone, .ipad]),
infoPlist: .default,
sources: ["Tests/**"],
dependencies: [.target(name: "MyApp")]
)
]
)
XCodeGen YAML の例: project.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: MyApp
options:
bundleIdPrefix: com.example
deploymentTarget:
iOS: '15.0'
targets:
MyApp:
type: application
platform: iOS
sources: [Sources]
resources: [Resources]
info:
path: Info.plist
properties:
UILaunchScreen: {}
dependencies:
- framework: Vendor/SomeFramework.framework
- sdk: UIKit.framework
- package: Alamofire
MyAppTests:
type: bundle.unit-test
platform: iOS
sources: [Tests]
dependencies:
- target: MyApp
ファイルディレクトリ構造
XCodeGen や Tuist は、ファイルの実際のディレクトリや位置に基づいて Xcode プロジェクトファイル(.xcodeproj)のディレクトリ構造を生成します。実際のディレクトリが Xcode プロジェクトのディレクトリとなります。
したがって、ファイルの実際のディレクトリ位置が非常に重要であり、私たちはそれを直接Xcodeプロジェクトのファイルディレクトリとして使用します。
これは現代のXcode/Xcodeプロジェクトにおいて、これら二つのディレクトリの位置が同じであることはごく普通のことですが、この問題こそが本記事で探究したいテーマです。
初期のXcodeにおける仮想ディレクトリの使用
早期のXcodeでは、ファイルディレクトリ上で右クリックして「New Group」を選んでも実際のディレクトリは作成されず、ファイルはプロジェクトのルートディレクトリに置かれ、.xcodeprojのXcodeプロジェクトファイル内でファイル参照されます。そのため、ディレクトリはXcodeプロジェクトファイル内でのみ見え、実際には存在しません。
時代の変化とともに、AppleはXcodeでこの奇妙な設計を徐々に廃止しました。後のXcodeでは「New Group with Folder」が追加され、デフォルトで実体フォルダを作成するようになり、作成しない場合は「New Group without Folder」を選択する必要がありました。そして現在(Xcode 16)では「New Group」のみが残り、実体フォルダに基づいて自動的にXcodeプロジェクトのファイルディレクトリが生成されます。
仮想フォルダの問題
XCodeGenやTuistが使えない理由は、どちらも実際のディレクトリ構造からXcodeプロジェクトファイル(.xcodeproj)を生成する必要があるためです。
Code Reviewの難しさ:GitのWeb GUIでディレクトリ構造が見えず、ファイルがすべて平坦に表示されます。
DevOpsやサードパーティツールの統合が困難:例えばSentryやGithubでは、ディレクトリごとに警告や自動割り当てを設定できますが、ディレクトリがなくファイルだけの場合は設定できません。
プロジェクトのディレクトリ構造が非常に複雑で、多くのファイルがルートディレクトリに平置きされています。
古くから存在し、初期に仮想フォルダの問題に気づかなかったプロジェクトでは、仮想フォルダ内のファイルが3,000個以上あります。手動で対応して移動するなら、終わったら辞職して売り子になるしかないでしょう。本当に「Apple開発者の職業病 😔」と呼べます。
Xcode プロジェクトの仮想フォルダから実体フォルダへの変換
上述の理由から、Xcodeプロジェクトの仮想フォルダを実際のフォルダに変換することが急務です。そうしなければ、今後のプロジェクトのモダナイズや効率的な開発プロセスの推進ができません。
❌ Xcode 16 の「Convert to folder」オプション
XCode 16
昨年Xcode 16がリリースされた時、このメニューの新しいオプションに注目しました。元々の期待は、仮想ディレクトリのファイルを自動的に実体ディレクトリに変換してくれることでした。
しかし実際には、先にファイルを作成し、対応する実際の場所に配置する必要があります。「Convert to folder」をクリックすると、新しいXcodeプロジェクトのディレクトリ設定方式「PBXFileSystemSynchronizedRootGroup」に変更されます。正直に言うと、変換自体には全く役に立たず、変換後に新しいディレクトリ設定方式にアップグレードできるだけです。
目次が作成されておらず、ファイルが正しく配置されていない場合、「Convert to folder」をクリックすると以下のエラーが表示されます:
1
2
3
4
関連付けられていないフォルダ
各グループには関連付けられたフォルダが必要です。次のグループはフォルダに関連付けられていません:
• xxxx
ファイルインスペクタを使って各グループにフォルダを関連付けるか、内容を別のグループに移動した後にグループを削除してください。
🫥 オープンソースプロジェクト venmo / synx
Githubで長時間検索した結果、Rubyで書かれたこの仮想ディレクトリを実ディレクトリに変換するオープンソースツールだけが見つかりました。実際に動かしてみると効果はありましたが、長期間メンテナンスされておらず(約10年間更新なし)、多くのファイルは手動で対応・移動する必要があり、完全な変換はできなかったため断念しました。
しかし、このオープンソースプロジェクトに触発されて、自分で変換ツールを開発しようと思いました。
✅ 私のオープンソースプロジェクト ZhgChgLi / XCFolder
純粋なSwiftで開発されたコマンドラインツールで、XcodeProj を使用して .xcodeproj Xcodeプロジェクトファイルを解析し、すべてのファイルの所属ディレクトリを取得、ディレクトリを解析して仮想ディレクトリを実体ディレクトリに変換し、ファイルを正しい場所に移動、最後に .xcodeproj Xcodeプロジェクトファイルのディレクトリ設定を調整することで変換を完了します。
使用方法
1
2
3
git clone https://github.com/ZhgChgLi/XCFolder.git
cd ./XCFolder
swift run XCFolder YOUR_XCODEPROJ_FILE.xcodeproj ./Configuration.yaml
例えば:
1
swift run XCFolder ./TestProject/DCDeviceTest.xcodeproj ./Configuration.yaml
CI/CD モード( Non Interactive Mode ):
1
swift run XCFolder YOUR_XCODEPROJ_FILE.xcodeproj ./Configuration.yaml --is-non-interactive-mode
Configuration.yaml には実行したいパラメータを設定できます:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 無視するディレクトリ、解析や変換は行わない
ignorePaths:
- "Pods"
- "Frameworks"
- "Products"
# 無視するファイルタイプ、変換や移動はされない
ignoreFileTypes:
- "wrapper.framework" # フレームワーク
- "wrapper.pb-project" # Xcodeプロジェクトファイル
#- "wrapper.application" # アプリケーション
#- "wrapper.cfbundle" # バンドル
#- "wrapper.plug-in" # プラグイン
#- "wrapper.xpc-service" # XPCサービス
#- "wrapper.xctest" # XCTestバンドル
#- "wrapper.app-extension" # アプリ拡張機能
# ディレクトリ作成とファイル移動のみ行い、.xcodeprojのディレクトリ設定は変更しない
moveFileOnly: false
# git mvコマンドを優先してファイルを移動する
gitMove: true
⚠️ 実行前の注意事項:
Gitに未コミットの変更がないことを必ず確認してください。スクリプトの誤動作でプロジェクトディレクトリが汚染される恐れがあります。
(スクリプト実行時にチェックが入り、未コミットの変更があると❌ Error: There are uncommitted changes in the repositoryエラーが発生します)デフォルトでは、
git mvコマンドを優先してファイルを移動し、gitのファイル履歴を完全に保持します。移動に失敗した場合やGitプロジェクトでない場合のみ、FileSystem Moveでファイルを移動します。
完了を待つだけでOK:
⚠️ 実行後の注意:
プロジェクトディレクトリに漏れている(赤色の)ファイルがないか確認してください。数が少ない場合は手動で修正してください。数が多い場合は、Configuration.yaml の ignorePaths、ignoreFileTypes の設定が正しいか確認するか、Issueを作成 してお知らせください。
Build Setting 内の関連パス(例:
LIBRARY_SEARCH_PATHS)を手動で変更する必要があるか確認してください。クリーン&ビルドを試してみてください。
もし現在の
.xcodeprojXcode プロジェクトファイルを管理するのが面倒なら、XcodeGen や Tuist を使って直接ディレクトリやファイルを再生成することもできます
スクリプトの修正:
直接 ./Package.swift をクリックするだけで、プロジェクトの調整スクリプトを開くことができます。
その他の開発メモ
得意なのは XcodeProj により、Swiftオブジェクトとして
.xcodeprojXcodeプロジェクトファイルの内容 に簡単にアクセスできることです。同様に Clean Architecture アーキテクチャを使って開発しています。
PBXGroup の設定で path がなく name のみの場合は仮想ディレクトリ、path がある場合は実体ディレクトリとなります。
Xcode 16 の新しいディレクトリ設定
PBXFileSystemSynchronizedRootGroupは、ルートディレクトリを宣言するだけで実際のディレクトリから自動的に解析され、各ディレクトリやファイルを.xcodeproj内で個別に宣言する必要がなくなりました。直接SPM(Package.swift)でコマンドラインツールを開発するのは本当に便利です!
ご質問やご意見がありましたら、ぜひ お問い合わせ ください。
Post Mediumから変換されたもの by ZMediumToMarkdown.
本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。






