XcodeGenを用いてxcodeprojから卒業する

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2017 の投稿記事です。

こんにちは。スタディサプリENGLISHのiOSエンジニアの井原 (@nonchalant0303) です。

今回は『XcodeGen』というツールを導入して、iOSにおけるプロジェクトファイルのxcodeproj1)xcodeprojの実態はディレクトリで、プロジェクト構成を管理しているのはprojct.pbxprojですが、この記事ではまとめてxcodeprojと呼んでいますの管理を改善したという話について述べていきます。

https://github.com/yonaskolb/XcodeGen

導入の経緯

xcodeprojはiOSプロジェクトにおける様々な設定を管理しています。コンパイル対象のファイルなども管理しているので、チーム開発をしているとファイル追加を含んだPR同士でコンフリクトが発生してしまうことがあります。また、xcodeprojは可読性が低く、コンフリクトの解消にはそれなりの時間がかかります。この問題を解決するために、xcodeprojをソースコード管理下から取り除きシンプルな定義でプロジェクト設定の管理を可能にするツールXcodeGenを導入してxcodeprojの管理方法について見直そうと考えました。

xcodeproj

xcodeprojは主に以下のような設定を管理しています。

  • Configuration
  • Build Settings
  • Build Phases
  • Target

それぞれについて簡単に説明します。

Configuration

主に、DebugやReleaseのbuild時の最適化オプションなどを含んだ設定を管理しています。主に、開発時はDebug Configuration, リリース時はRelease Configurationでビルドします。また、社内デプロイなどではInHouse Configurationなどの専用の設定を用意することもあります。

Build Settings

アプリアイコン、info.plistやOther Linker Flagsなどのアプリケーションに関する様々な設定を管理しています。

Build Phases

依存フレームワーク、コンパイル対象ファイルやビルド時に走るスクリプトなどのコンパイルに関する様々な設定を管理しています。

Target

ターゲットは開発しているアプリケーションと1:1の関係になります。主にアプリケーションにおけるBuild Settings, Build Phasesなどの設定を管理しています。

既存のツール

mergepbx

xcodeprojをマージするときに自動でコンフリクトを解消するPython製のツールです。.gitconfigにmergepbxを追加してマージ時にフックしてコンフリクトの解消をします。しかし、自分の環境だとよくfileRefが解消されず、自分が追加したファイルの参照がすべて消えたxcodeprojが吐き出されます。また、各メンバーがそれぞれ設定する必要があり、ローカルで正しく運用されているかはGit上では検知できません。

https://github.com/simonwagner/mergepbx

xcconfig

Build Settingsをxcodeprojで管理せずに別ファイルで管理できます。xcodeprojではxcconfigへのパスを指定するだけなので、コンフリクトしても範囲を限定でき解消も比較的容易になります。しかし、コンフリクトするのは主にファイル追加のタイミングなのであまり有効だとは言えません。ファイル管理はBuild Phasesで管理されているので、Build Settingsを切り出しても問題の解決にならないためです

XcodeGen

Configuration, Build Settings, Build Phases, Targetなどのxcodeprojが管理している設定を定義したymlからxcodeprojを生成するツールです。プロジェクト設定はxcodeprojからymlの管理下になるので、ソースコード管理下からxcodeprojを取り除くことが可能になります。また、xcodeprojにおけるBuild Phasesと違いコンパイル対象をファイル単位ではなくディレクトリ単位で定義するのでファイル追加によるコンフリクト発生の大部分を解決することができます。どのようにymlで定義するのかを以下のプロジェクトを例に紹介します。

Target Dependencies
StaticDI Presentataion, Domain, Infrastructure, Utility
Presentataion Domain, Utility
Domain Infrastructure
Infrastructure Utility
Utility

各ターゲット・フレームワークの依存関係は上記のようになっており、それぞれテストターゲットを持ちます。このような構成のプロジェクトは以下のようなproject.ymlで定義されます。

主に設定するのは5行目以降のtargetsの中身になります。StaticDIターゲットを例に解説していきます。


dependencies (l.2)

依存しているフレームワークを定義します。StaticDIターゲットはPresentation, Domain, Infrastructure, Utilityに依存しています。

type (l.7)

targetのタイプがapplicationかframeworkかを定義します。

platform (l.8)

iOSかmacOSかを定義します。

sources (l.9)

コンパイル対象のディレクトリを定義します。

settings (l.10)

Build Settingsの項目を定義します。各キー名はpbxprojの名前と一致しています。

scheme (l.18)

Schemeに関する情報を定義します。テストターゲットの指定やDebug時に渡す環境変数などを定義できます。

postbuildScripts (l.20)

Build後に走るスクリプトに関して定義します。ここではSwiftLintに関するスクリプトを定義しています。


このように各ターゲットを設定して、それぞれのdependeciesで依存関係を定義します。細かい設定方法は公式のドキュメントが参考になります。


project.ymlを用意して上記のコマンドを実行すればxcodeprojが生成できます。実際にXcodeGenを用いてプロジェクト管理しているレポジトリはNonchalant/StaticDIで公開しています。

XcodeGenを導入して変わったこと

可読性が上がった

xcodeprojの独自定義されたxmlより、project.ymlのほうが可読性が高い印象を受けました。また、project.yml上で定義されていない設定はすべてデフォルト設定が適用されるので、すべて列挙されているxcodeprojよりファイルの行数も大幅に削減されました。

コンフリクトの発生頻度が下がった

これまでxcodeprojがコンフリクトするときはほとんどがファイル追加によるものでした。XcodeGenを用いるとファイル毎にソースを定義するのではなくディレクトリで定義するので、ファイル追加によるコンフリクトはほぼほぼ発生しなくなりました。

余計な差分に入り込まない

xcodeprojはbuild生成物の情報も管理しており、buildの状況により差分が発生してしまうケースがあります。そのため、xcodeprojの差分がPRによく紛れるので、xcodeprojのコードレビューが困難になります。しかし、xcodeprojはソースコード管理下から外しているので、build生成物などの情報はPRに紛れ込まなくなりました。また、xcodeproj上でのデバッグ設定なども個々人が定義できデバッグが楽になるという副産物もありました。

まとめ

XcodeGenにもまだまだ足りない機能がありますが、非常に活発に活動しているレポジトリですのでIssueやPRを一度見てみるのをオススメします。みなさんもxcodeprojを管理するのを卒業してXcodeGenを活用することを考えてみてはいかかでしょうか。

脚注

脚注
1 xcodeprojの実態はディレクトリで、プロジェクト構成を管理しているのはprojct.pbxprojですが、この記事ではまとめてxcodeprojと呼んでいます