※本記事は弊社が技術書典 14 で無料配布する同人誌「ゆめみ大技林 '23」の寄稿です。追筆や訂正等がある場合はこの記事で告知します。
皆さんは iOS 開発においてどんな CI を利用しているでしょうか。Bitrise?Circle CI?いやもしかすると Jenkins のお世話をしている方もいらっしゃるのではないでしょうか。いずれにせよ、CI/CD は現代の開発において必要不可欠な環境と言っても過言ではないでしょう、なぜなら CI/CD こそ我々に提出されたコードをマージする自信をもたらせてくれているのです。
そんな中、アップルがついに公式の CI サービスを 1 年の Beta を経て昨年正式リリースしました。その名も Xcode Cloud です。名前のとおり、Cloud で動く Xcode とイメージして差し支えないでしょう。
筆者が考えるこの Xcode Cloud の最大の特徴は、アップルが自ら提供したサービスである故、これまで大きな鬼門にも言える署名関係の手続きを大幅に簡略したところです。いや、厳密にはもはや署名なんて全く考える必要すらなくなります。そもそも証明書やらプロビジョニングプロフィールの発行を管理しているアップル自身のサーバで動きますから。そして弊社のような「パートナー会社」は、通常の事業会社と比べて頻繁に新規案件が発生しますし、またクライアントさんの環境も関与してくるので、これまで署名関係の作業は非常に複雑でした。Fastlane Match も導入しましたが、やはりそれを理解しながら運用できる人は限られています。だから筆者は Xcode Cloud に大きな期待をしていました。
Xcode Cloud の概要
Xcode Cloud に評価を下す前に、まず簡単に Xcode Cloud はどんな CI か簡単に説明したいと思います。Xcode Cloud は Bitrise などのクラウド CI サービスと同じく、GitHub などの git ホストと連携して仕事する CI です。例えば GitHub に PR を作ったり、特定のブランチにコミットがプッシュされたりしたら、指定の Workflow が実行され、テストを実行したり、アーカイブしてデプロイしたりできます。
ここまで聞いたら、「なんだ普通の CI サービスと同じじゃないか」と思うかもしれませんが、実は Xcode Cloud の動き方は非常に独特です。通常の CI サービスなら、Workflow はそれぞれ YAML ファイルで定義され、YAML ファイルの中にこの Workflow がどんな順番でどの作業を実行するかリストアップされる作りになります。例えばまず GitHub からこのブランチを落としてきて、特定の手順で環境構築して、指定のビルドツールでアプリをビルドして更にテストを実行して、終わったら GitHub にチェック結果のレスポンスを送って、最後に Slack とかにその結果を送って終了すると言った流れ作業でしょう。全て自分で定義できますし、その代わり iOS アプリの署名のような一般的でない作業は一苦労しがちです。
ところが Xcode Cloud は名前の通り Cloud で動く Xcode です。この時点でビルドツールが確定されるし、ビルド時に必要な証明書関係も自動で解決してくれます。なので通常の CI と違って、おおまかな Workflow は最初から決まっています。ユーザがカスタマイズできるのは、この Workflow の中の、いくつかのカスタマイズポイントだけです。アップルらしいと言えばアップルらしいですね、CI サービスまでカスタマイズ性を制限してきます。ちなみに具体的にどんなところがカスタマイズできるかというと、Start Condition(通常の CI で言う Trigger)と、Action(Test や Archive、もちろんこれらの組み合わせも可)と、Post-Action(TestFlight へのアップロードや、メールもしくは Slack への送信)の 3 つだけです1。そして環境構築など、どうしてもスクリプトを実行するプロジェクトもあると思いますが、これらのプロジェクトのために、アップルも親切に ci_scripts
というスクリプト実行ポイントを用意してくれています。これはリポジトリの中に含める必要がありますが、リポジトリクローン後(ci_post_clone.sh)、プロジェクトビルド前(ci_pre_xcodebuild.sh)とプロジェクトビルド後(ci_post_xcodebuild.sh)の 3 つのタイミングがあります。なので要約すると、Xcode Cloud のカスタマイズは、リポジトリ非依存の Workflow には 3 つのカスタマイズポイントと、リポジトリ依存の ci_scripts
には 3 つのスクリプト実行ポイント、それだけです。
Xcode Cloud はクセが強い
もうこの一言に尽きると思います。クセの強いツールは、その性格に合ってる使い方をしていれば(仮にできるなら)、もうこれ以外あり得ないとなることも多いですが、逆に少しでもその性格からズレた使い方をしたら(強いられたら)、そのクセは頭を抱えさせるでしょう。
Xcode Cloud がもたらした夢
まずは Xcode Cloud のメリットから話しましょう。アップルが一体何を目指して Xcode Cloud を開発したのか。
CI 経験がなくてもすぐに使える
筆者が一時期 Bitrise を激推ししていました、その理由がまさに使いやすさにあります。それまでの CI サービスは YAML ファイルを自力で構成を書く必要がありましたが、Bitrise は YAML ファイルを使いながらも、YAML ファイル自身の読みやすさを犠牲にして、Web からの GUI 画面で構成を作れるようにしました。この功績は非常に大きいです、なぜなら GUI のおかげでこれまで CI サービスに壁を感じた初心者まで、「これなら私でも使えるかもしれない」と思わせてくれました。
ところが Bitrise でもやはり多少の壁はあります。iOS アプリの署名関係ももちろんですが、それ以外にもキャッシュの設定やビルドに使うステップの選定など、初めて触った時微妙によくわからない部分も少なからずありました。そしてそこまで全部取っ払ってくれたのが Xcode Cloud です。
概要でも説明したとおり、Xcode Cloud は名前が示した通り Cloud 上で動く Xcode というイメージですので、ローカルで Xcode を使う時とほぼ同じような感覚で Xcode Cloud を使えます。GUI から Actions の段階でどの動きを実行して欲しい、例えばテストを実行したい、アーカイブして App Store Connect にあげたい、などをマウスクリックで指定してあげればすぐできちゃいます。キャッシュの設定などもチェックボックス一つで完結するし、何より署名関係の面倒くさい設定は何も気にしなくていいです。
そしてさらに Xcode のプロジェクト画面から直接 Xcode Cloud のこれまでの実行結果がすぐ見れますので、Xcode Cloud で実行したときに出たワーニングやエラーを Xcode の見慣れた画面で確認できるのも、クラウド上だけ何か不具合が出た時の確認や修正もしやすいです。
署名関係を触らなくていい
すでにこの記事で何回も触れてきましたが、やはり署名関係を触らなくていいのは非常に大きいので、単独で一つのメリットとして数えていいと思います。何しろこれまで iOS 開発において、証明書の管理は CI/CD の鬼門だったとも言えます。いちいちキーチェンから証明書と鍵を取り出して、プロビジョニングプロファイルと一緒に CI に上げて、そしてそれらを使うようにいちいち CI でセットしなくてはいけなかったのは、アプリ開発とは全く別分野の知識を必要としますし、fastlane みたいなツールを使っても署名周りの最低限の知識が必要ですし、何よりそれ使うとせっかくの署名周りを楽にしてくれる Auto Signing が使えなくなります。
それらの問題は全て Xcode Cloud はあっさり解決してくれます。なにしろアップル自身のサーバだから、Auto Signing のままアップルが全て解決してくれます。特に受託開発2の仕事をこなす我々からしてみると、今後コードを書くためにお客さんに証明書や鍵の書き出し方法を教えなくて済む3のは非常にウキウキします。
Workflow の依存と Repository の依存が明確に分かれている
概要の部分でも簡単に触れましたが、通常の CI/CD サービスは一連の処理は全て YAML にまとめられてるに対し、Xcode Cloud は Workflow 自体は流れが決まっており、いくつかのカスタマイズポイントだけ、例えばどんな Action を実行するか、どこの Slack Channel に通知を投げるかとかを設定できるだけで、この流れは Workflow 依存でリポジトリの内容によって変えられません。そして実際のビルド時に必要な処理は全て ci_scripts
のなかで定義しないといけなくて、これは逆にリポジトリの中に書かないといけなくて、Workflow によって変更できないです4。
これは最初かなりクセが強い部分だと思っていました。しかし冷静に考えてみたら、意外とこれはこれで非常に理にかなってると思いました。なぜなら Workflow がどう変えようが、Xcode が無事ビルドできるようにするには必ず環境構築は必要で、これはテストの Workflow だろうかリリースの Workflow だろうか関係ないし、手順も基本 Workflow によって変わることはないはずなので共通化したほうが便利です。むしろその手順は Workflow によって変わりませんが、リポジトリの状態、すなわち開発のプログレスによって変わることは普通に考えられるし実際よくあることだと思いますので、リポジトリ依存にした方が確実であって、例えば一時的に古いコミットをビルドしたい時でも Workflow が変わったからと言ってビルドできないようなハプニングも発生しないはずです。
もちろん通常の CI/CD サービスでも、そのような環境構築のためのスクリプトをリポジトリに入れて、Workflow YAML から直接それを呼び出せば同じ動きを再現できますが、Xcode Cloud の良さはこの仕様を強制化したことだと思います。
他にもたくさん! Xcode Cloud のメリット
ボリュームの都合で詳細を割愛したいと思いますが、例えば App Store Connect との連携が取りやすかったり、Xcode アプリ内から直接ビルド結果が見れたり、何も弄らなくても簡単に複数の画面サイズの端末でしかも並行してテスト実行できたりするのも、Xcode Cloud ならではのメリットだと思います。特に並行して複数端末でテスト実行できるのは、地味に PR レビューのリードタイム短縮に大きく貢献できるではないかと思います。
夢から覚ましてくれるキック5
せっかく Xcode Cloud が夢のような世界を見せてくれましたが、残念ながらいざ使ってみると、現実は夢のようにいかないことも改めて痛感してしまいます。
カスタマイズ性が乏しい
何度でも言いますが、Xcode Cloud はあくまで文字通り Cloud 上で動く Xcode なので、そもそも Xcode の動作が必ず求められます。つまり Action として、Build、Test、Analyze、そして Archive のどれかを必ず実行しなくてはなりません。たとえば定期的に Renovate だけ回して、依存ライブラリーの更新が必要かどうかを確認したいだけ、みたいな Workflow は Xcode Cloud ではできないです。
ただまあこれ意外とそこまで困りません、と言うのもこのような Workflow なら Mac 環境自体も特に必要としないので、GitHub Actions の Linux 環境でも十分です。Linux 環境なので、懸念な 10 倍料金もないし、処理時間も基本短いので、比較的に負担なく回せます。
他にも各 Step の順番が決められてて、YAML 依存の CI/CD サービスと同じ感覚で自由に組み合わせられないのも大きな欠点の一つと言えますが、弊社の場合は今の所そこまで不便を感じていない(と言うのも Xcode Cloud が決めた順番以外のことをやろうとしていない)ので、割愛します。
ビルド実行に罠あり
概要でもご紹介した通り、Workflow の一連の処理で、カスタムスクリプトを実行できるカスタマイズポイントが 3 つあります:クローン後の post_clone.sh
、依存解決後ビルド前の pre_xcodebuild.sh
、そしてビルド後の post_xcodebuild.sh
。しかし、この紹介だと、一つの Workflow のなかに、この 3 つのスクリプトがそれぞれ 1 回ずつ、もしくは xxx_xcodebuild.sh
系のスクリプトは Action の数分だけあると思うでしょう。
残念ながら実は違います。Archive は確かに 1 回だけですが、Test は Test Destination 分 +1 あります。例えば Recommended iPhones に指定した場合、Destination は執筆現在では画面サイズが違う 4 種類の iPhone が含まれるので、xcodebuild
コマンドは 4+1 で合計 5 回実行されます:最初の 1 回目は build-for-testing
、そして端末の数分の test-without-build
の Xcode Build Action が実行されます。
このようにビルド実行を分けるメリットも大きいです:時間がかかるビルド作業を 1 回だけ実行すれば、その後様々な端末でテストアクションだけ専念できるので、複数端末での時間コストが削減できます。しかもテストアクションは並行作業できるので、現実世界でかかった時間は最も長かったテスト端末での実行時間のみです。例えばビルドに 4 分、テストにそれぞれ 2 分、2分、2.5 分と 2.5 分かかった場合、現実世界でかかった時間は合計 4+2.5=6.5 分6となります。
ただしこのような分け方は大きな罠もあります:完全なビルド結果を取得できないです。例えば弊社の場合、danger-swift-kantoku という Danger-Swift のプラグインを開発しており、ビルド後の .xcresult
からビルド結果を取得して、ビルドワーニングやコードカバレッジを PR ページに書き込むようにしています。もちろんこのプラグインを使わなくても、似たような要望は多くの開発現場にあると思います。しかし、Xcode Cloud がビルドアクションを細かく分けてしまっているため、一度に全ての結果を取得できません:ビルドワーニングは build-for-testing
後の post_xcodebuild.sh
からしか取れないし、テストのコードカバレッジは test-without-build
後の post_xcodebuild.sh
にしか取れません。Xcode Cloud の書き込みは上書きされるので、例えば先のビルドワーニングを書き込んでも、その後コードカバレッジを報告するとビルドワーニングが上書きされるので、せっかく取得した意味がないです。
幸いなことに Xcode Cloud 自身は何も設定しなくても、勝手にビルドワーニングを Check Notice として GitHub に報告するので、Checks タブの Annotations セクションや Files Changed タブで表示してくれます。しかし残念ながらこれは PR の一番よく見られる最初の Conversation タブからは見れないし、レベルも Notice でしかないし、それにコメントを追加したり Resolved として閉じたりすることもできないので非常に不便を感じます。さらにコードカバレッジはそもそも報告してくれないから、やはりかなり不便を感じます。
Swift Package Plugins の実行に書き込み権限がない
せっかく CI/CD をモダン(?)な Xcode Cloud に移行するんだから、開発環境自体もモダンな SwiftPM ベースな開発に移行したいと考えたわけです。そこで R.swift も Package.swift
から導入し、さらにその実行まで Package.swift
で .plugin
から実行させると、ローカルでは問題ありませんが、Xcode Cloud で実行しようとすると Error: You don’t have permission to save the file “R.generated.swift” in the folder “Target”
エラーが発生します。その結果 R.swift がソースファイルを生成できないので、それに依存したソースコードが参照できずビルド失敗します。
もちろんワークアラウンドとして、そもそも R.swift を Package.swift
からではなく、これまで通り Xcode Project から導入して Build Phase から実行させてあげれば問題ないですが、せっかく SwiftPM ベースの開発への移行が無駄になるからモヤモヤするので、筆者にとってこれもかなり致命的です。
Workflow の構築や管理が共通化しにくい
これは特に弊社のような新規案件が頻発する受託開発2会社では大きい問題です。通常の Bitrise などの YAML で Workflow を管理する CI/CD サービスなら、共通な YAML、もしくはそれをベースにした YAML のテンプレートを用意すれば、コマンド一つで Workflow 構築ができて、非常に早いし属人もしにくいです。さらにテンプレートを更新したら、マイグレーションスクリプトさえ用意すれば、実際の案件に使ってるカスタマイズされた Workflow でも更新を適用しやすいです。
ところが Xcode Cloud は逆に誰でも手軽に触れるよう GUI で Workflow を構築するスタイルなので、これまでのようにコマンド一つでの Workflow 構築が非常に難しくなってきます。一応アップルが親切に AppStoreConnect API で Xcode Cloud Workflow を作成や編集できるように、JSON オブジェクトで管理する API を用意してくれていますが、残念ながらそのドキュメントの整備は最近まで質が非常に低く、リクエストやレスポンスにどんなパラメーターがあるかとか、そのパラメーターにどんな値が利用できるもしくは返却されるかの説明が一切なく、あるのは単純なサンプル一つだけでしたので、実質使えませんでした。最近ドキュメントが多少整備されてきて、パラメーターの値についての説明とかがだいぶ増えてきましたが、やはり YAML の置き換え一発で全ての Workflow を管理するのと比べると、API の呼び出しと JSON の編集で一つずつ Workflow を管理するのは大変だし、保守性も低いです。
では私は Xcode Cloud を選ぶべきか
これもまた非常に難しい問題です。気持ちとしては Xcode Cloud をお勧めしたいです。神不在の Web 開発7や、神が複数ある Android アプリ開発8と比べると、唯一神がある iOS アプリ開発はこう言った環境選定はなるべく精神を使いたくないので、ひとまずアップル公式のものを選んでおけばいいと言いたいです。ところが前のセクションで述べたとおり、Xcode Cloud は CI/CD サービスとしてのクセが非常に強く、これまでのノウハウが Xcode Cloud で通用しないし逆も然りなので、現実的にはやはりお勧めしにくいです。そのため、個人的には下記のチェックリストを全て当てはまった場合のみ、Xcode Cloud を選んでも大丈夫かと思います:
- 私はアップル信者です
- 私が開発したアプリは複雑な依存関係がなく、Swift Package Plugins によるファイル生成等の特殊なことをやっていません
- 私は Danger(Danger-Swift 含む)や fastlane を使いません
- 私は頻繁に新規のプロダクトを作ったりしません
ちなみに弊社の場合、引き続き Xcode Cloud の検証を進めながら、当分の間は Bitrise 続投です。WWDC で Xcode Cloud について何か改良があるといいですね…
-
環境変数や Xcode のバージョンなどももちろん設定できますが、これは Workflow の手順ではないのでここにカウントしていません。 ↩
-
厳密には弊社ゆめみは「受託開発」ではなく、お客様と一緒にサービスを作る BnB2C モデルを採用していると自負しております。具体的な違いは本記事のテーマではないので割愛しますが、気になる方はぜひ「ゆめみオープンハンドブック」を検索して、「YUMEMI Corporate BASICs」のセクションをご覧ください。 ↩ ↩2
-
自社サービスや個人開発なら基本関係ない話ですが、弊社のようにサービス会社(お客様会社)と開発会社(弊社)が異なる場合、アプリリリースする際は基本サービス会社の ADP アカウントからリリースすることがほとんどですので、CI/CD 設定時、本番リリースの設定にはサービス会社の開発証明書などを利用することが必要です。 ↩
-
厳密には
ci_scripts
の中でCI_WORKFLOW
環境変数が使えるので、やろうとすれば具体的な処理の切り替えはできます;ただ例えばこの Workflow ならこのci_scripts
、その Workflow ならそのci_scripts
、と言うようなファイルの直接な切り替えは直接にはできません。 ↩ -
映画「インセプション」において、夢から目覚めるための方法の一つです。詳細はぜひ映画を観て欲しいですが、簡単に映画の中の設定を説明すると、人間が現実世界で落下したりすると、三半規管の働きによって脳が覚醒し、夢から覚めるのです。この記事ではデメリットの表現として使います。 ↩
-
ただし課金時間は 4+2+2+2.5+2.5=13 分になりますが。 ↩
-
フロントなら Vue.js や React、バックならさらにフレームワーク以前に PHP や Ruby など、様々な環境があって統一されていないので、何を使えばいいかの総合判断が難しい場合ありますよね。 ↩
-
多くの場合は Google を神と思って差し支えないですが、Kotlin 言語自体は Google のものではないので… ↩