はじめに
Rettyのアプリ開発チームでiOSアプリ/Androidアプリ/サーバーサイドエンジニアをしている山田です。
最近Rettyのアプリ開発チームでは「より高速な施策開発を行なっていきたい」「CIにかかるコストを削減したい」といったモチベーションから、特に時間がかかるiOSアプリのCIがより早く完了する状態にしたい、という声が上がるようになってきました。
これを実現するために日々いろいろな手法を模索しているのですが、今回はその文脈でPodBuilderと言うツールを試してみたので、その事例を共有したいと思います。
PodBuilderとは
iOSアプリ開発ではお馴染みの依存関係マネージャーであるCocoaPodsですが、そのまま利用するとCIが実行される度にCocoaPods経由で導入した依存をビルドする必要があり、場合によってはこれが結構な時間になってしまいます。
PodBuilderはこのような課題を解決するツールです。CocoaPodsで導入した依存を事前にFrameworkへビルドする事ができ、プロジェクトからは事前に生成されたFrameworkへ依存することで、アプリケーションをビルドするたびに発生する依存のビルド時間を省き、ビルド時間短縮を実現することができます。
PodBuilderの導入
導入手順
以下のような手順でRettyアプリへの導入を行いました。
- Gemfileに
gem 'pod-builder'
を追加 bundle install
を実行bundle exec pod_builder init
を実行bundle exec pod_builder build_all
を実行- 成功すれば導入完了。失敗した場合は設定ファイルを調整し、4に戻る
概ねReadMeに記載の手順に従って導入しています。 Rettyアプリの場合設定ファイルにいくつか調整が必要で、以下のような設定を追加で行っています。
spec_overrides の設定
ReadMeにも以下のように記載の通り、依存ライブラリのpodspecにswift_version
の記述が存在しない場合、エラーが発生します。
このような場合は適切なswift_version
を調べ、spec_overrides
で適切な値を書き込むように設定する必要があります。
build_settings_overrides の設定
追加でビルド時の設定が必要になる場合は、こちらで設定を行う必要があります。 例としてRettyアプリでは以下のようなケースに遭遇し、これを利用した設定を行っています。
- 一部ライブラリが特定のバージョンのSwiftを利用でないとビルドができず、
SWIFT_VERSION
を変更する必要があった - テストで利用するライブラリが
ENABLE_TESTABILITY
をtrueに設定する必要があった
RettyでのPodBuilder.jsonの設定例
一部を抜粋した形でRettyでの設定例を掲載します。 なお、この例は導入した場合にどのくらいのビルド時間削減効果を得られるのかを計測する目的でお試しをしている段階でのもので、実際に運用しているものではありません。 そのため、この設定例は本運用には適さない可能性があります。本運用を前提としてこれを参考にされる場合は、より設定を詰める必要がありそうです。
{ "project_name": "Retty", "spec_overrides": { "Google-Mobile-Ads-SDK": { "module_name": "GoogleMobileAds" }, "${specにバージョンの記載がないライブラリ}": { "swift_version": "5.0.0" }, }, "skip_licenses": [ ], "skip_pods": [ "Firebase", "GoogleMaps" ], "force_prebuild_pods": [ "Firebase", "GoogleTagManager" ], "build_settings": { "ENABLE_BITCODE": "NO", "GCC_OPTIMIZATION_LEVEL": "s", "SWIFT_OPTIMIZATION_LEVEL": "-Osize", "SWIFT_COMPILATION_MODE": "wholemodule", "CODE_SIGN_IDENTITY": "", "CODE_SIGNING_REQUIRED": "NO", "CODE_SIGN_ENTITLEMENTS": "", "CODE_SIGNING_ALLOWED": "NO", "EXCLUDED_ARCHS[sdk=iphonesimulator*]": "arm64" }, "build_settings_overrides": { "${ビルドするSwiftのバージョンを4.0に固定する必要のあるライブラリ}": { "SWIFT_VERSION": "4.0" }, "${ENABLE_TESTABILITYの設定が必要なライブラリ}": { "ENABLE_TESTABILITY": true } }, "build_system": "Latest", "license_filename": "Pods-acknowledgements", "subspecs_to_split": [ ], "lfs_update_gitattributes": false, "lfs_include_pods_folder": false, "use_bundler": true }
Rettyアプリでの効果
実際にRettyアプリに導入した際に、どのくらいの効果があったのかを紹介します。
Rettyアプリの構成紹介
まずは導入対象のRettyアプリの構成を紹介します。
Podでの依存の数
RettyアプリはCocoaPods経由で38個の依存を導入しています。代表的なものとしては以下のような依存が存在します
- Firebase
- AnalyticsやCrashlytics、RemoteConfigなどを利用しています
- Firestoreは利用していません
- RealmSwift
- Alamofire
- Kingfisher
- FBSDKLoginKit
- LineSDKSwift
Rettyアプリのコード規模
882個のファイルからなる10万行ほどのSwiftのコードと、143個のxib, 72個のstoryboardが存在しています。
CIでのビルド時間
以下の通り、Rettyで利用しているCIサービスであるBitriseのInsights機能によると、直近三ヶ月でのビルド時間はTypical run time(ステップの実行に要した時間の中央値*1 )で7m57.2sとなっています。このステップはfastlane scan
を実行するもので、厳密にはビルドだけではなくテストのための時間も含まれてしまうものではありますが、今回はこの値でどのくらい改善されるのかを比較していきます。
検証結果
PodBuilder導入後にCIを5回実行し、計測したビルド時間が以下の通りです。
回数 | ビルド時間 |
---|---|
1回目 | 4m36s |
2回目 | 4m30s |
3回目 | 4m42s |
4回目 | 4m42s |
5回目 | 4m42s |
平均: 4m38.4s
中央: 4m42s
最頻: 4m42s
という結果になりました。前述の通り、RettyアプリのCIでのビルド時間の中央値は7m57.2sなので、おおよそ3mと少し、割合にして40%のビルド時間を削減することに成功しています。繰り返しになりますがテストの時間も若干ながら含まれてしまっている値ではあるため、それを省けば50%近い削減効果がありそうです。ReadMeには
for a large project we designed this tool for we saw a 50% faster compile times, but YMMV
という記述があるのですが、まさにそれを実現できている感じです。非常に早くビルドが完了するようになり、かなり良い結果を得られました。
PodBuilderの問題点
ここまででPodBuilderを導入することでビルド時間を大きく削減できることがわかりました。一方で、以下のような問題も存在します。
PodBuilder導入により依存管理の複雑さが増す
PodBuilderを導入していなければ、CocoaPodsによる依存を追加する場合には
- PodBuilder/Podfileに追記
bundle exec pod install
を実行- Podfile, Podfile.lockをcommit & push
するだけの単純な手順で済みます。一方でPodBuilderを導入すると
- Podfileに追記
bundle exec pod_builder build_all
を実行- エラーが発生したら設定ファイルを調整し、再度2をやり直し
- Frameworkが出来たらこれを所定のストレージにアップロード
- PodBuilder/Podfile, PodBuilder/PodBuilder.json, Podfile, Podfile.lock, PodBuilder/Podfile.restoreをcommit & push
という手順となってしまい、複雑さの増加が否めません。ここはビルド時間削減とのトレードオフと考え、どちらを取るべきかチームでよく話し合うべきポイントかと思われます。
ビルド以外の部分での消費時間が増加すること
単にビルド時間だけを見れば時間の削減には消費していますが、当然ビルド済みのFrameworkを取得する時間が追加で発生することは忘れてはいけません。Rettyアプリの場合、
- とりあえず試してみるのが目的だった
- 100MBを超えるバイナリが無かった
- Rettyが利用しているGitHubでは100MBを超えるファイルをgitに含んでアップロードすることはできません
の二つの理由でビルド済みFrameworkはgitに直接含んで試しましたが、この場合では普段は4s程度で完了するcloneが10倍の40sもかかるようになってしまいました。Git LFSを使うなどの改善点もありますが、ひとまずそれなりのサイズのバイナリをダウンロードするにはそれなりの時間がかかるという部分は必ず考慮しなければならないです。
おわりに
今回の記事ではiOSアプリのCIがより早く完了する状態にしたいというモチベーションから、PodBuilderの導入をお試ししてみた結果を紹介しました。結果は予想以上でしたが、導入に付随する他の問題点があったり、Carthageへ移行する、XCFrameworkを使うようにするなどの他の意見もあり、今後どうするのかというのは活発に議論がなされている最中です。
Rettyのアプリ開発チームでは本記事のように日頃の開発体験を良くするためにどのような工夫ができるのか、というのを常にチームで話し合い、自ら改善を実施しながら開発を進めています。本記事でRettyのアプリ開発チームについて興味が湧きましたら、ぜひMeetyで気軽にお話ししましょう!