KINTO Tech Blog
iOS

[iOS] すぐできる!CIクレジット超ドケチ節約術

Cover Image for [iOS] すぐできる!CIクレジット超ドケチ節約術

KINTOテクノロジーズで my route(iOS) を開発しているRyommです。
CIクレジットの節約のため、 いくつか取り組んできたことを紹介します。

はじめに

弊プロジェクトにおいては、CIツールとしてBitriseを利用しています。
昨年は通常のユニットテストに加えて スナップショットテストを導入 したり、 SPMに移行 したりしました。
気付くとBitrise CIの1回あたりの実行時間が多いときは約25分ほどに膨れ上がり、多くの実装が行われた月は予算を超過してしまうこともしばしば発生するようになってしまいました。
Bitriseは契約分を超過すると高額になってしまうので、執筆時点の為替レートでは超過分のCI1回の実行に約400円ほど掛かっている計算です。たっっか!
そういうわけで、クレジットが超過しそうになるとCIを動かさないためにPRのマージも必要最低限に抑える動きが生まれてしまっていました。

この状況を打破すべく取り組んできた、弊プロジェクトにおけるクレジット節約術を紹介します。

CLIツールのセットアップを見直す

BitriseのBuild結果を見ると、どのステップにどのくらい時間が掛かったのか見ることができます。

BitriseのBuild結果(Before)
BitriseのBuild結果

これを見ると、「Script Runner」にて12分も掛かっていることがわかります。
これは、swiftLintやLicensePlistのセットアップを行っているステップです。以前執筆した記事にて紹介しましたが、Build Phaseで実行するためにworkspaceとは独立して作成したパッケージにて、ライブラリを落として使えるようにしています。

ま〜たしかにこれは時間かかるよね〜というところなので、短縮していきます。
幸いここで使いたいライブラリはBuild Tool Pluginに対応しているため、そちらに移し替えていくことでこのステップを省くことができます。

元々 license_plist.yml や .swiftlint.yml などの設定は済んでいるため、ただプロジェクトの Package Dependencies にパッケージを追加し、ターゲットの Build Phase の Run Build Tool Plug-ins にプラグインを追加してあげればOKです。

Build Phaseの設定
Build Phaseの設定

LicensePlistはプラグインだと outputPath の場所の指定が効かないため、READMEにあるように Settings.bundle の下にライセンスのファイルを移動するようBuildPhaseに含める必要があります。
また、パッケージはFrameworksでリンクしているパッケージではなく、アプリ本体に含める必要があります。

これで「Script Runner」のステップが丸々不要になったので、12分の短縮...そして消費クレジットも半分になりました!🎉
BitriseのBuild結果(After)
BitriseのBuild結果

さらにプロジェクト構成が単純化し、セットアップやバージョン更新の際に別途シェルを実行するような必要もなくなりました。

今回のケースでは全てBuild Tool Pluginに対応していたため構成ごと変更しましたが、別のアプローチとして nest も試しました。こちらは既存のCLIツールを別パッケージに分けて管理する構成のまま、CI時間を短縮させることができます。

tools ディレクトリ配下にあったCLIツールをインストールするためのパッケージをnestに置き換えます。

Project/
├── Hoge.xcworkspace
├── Hoge.xcodeproj
├── Test/
│   └── ...
├── ...
└── tools
    └── nextfile.yaml // ここを置き換える

nest bootstrap nestfile.yaml を実行すると tools/.nest/bin 内にバイナリがインストールされていることが確認できるので、これを Build Phase で実行されるように設定します。

nestを使ったswiftlintの実行
Build Phaseでswiftlintを設定

Build Tool Plugin に対応していなかったりする場合には有用かもしれません。

テストを見直す

弊プロジェクトでは、1つのテストターゲット内に全てのテストが詰め込まれていたため、常に全てのテストが実行されていました。

またその中でもスナップショットテストは全て実行すると1時間ほど掛かる非常に重いテストのため、リファレンス画像と比較するメソッドはCI上ではコメントアウトして実行されないようにしていました。しかし、比較前の非同期の描画処理の待機などは実行されてしまうため、失敗時はタイムアウトが積もり積もって長い時間待つこととなり、これもクレジットを食い潰す要因の一つとなっていました。

そこで、CI上では動かしていないスナップショットテストを別のテストターゲットへ切り離し、TestPlanを使って実行するテストをコントロールするようにしてみました。

まずは、時間のかかるスナップショット用のテストターゲットを作成します。
テストターゲットの作成
テストターゲットを作成

既存のテストターゲットを参考にターゲットの設定を行ったら、 Build Phases の Compile Sources もしくは各テストファイルの File inspector より Target Membership から、スナップショットテストを新規作成したターゲットへ移していきます。
このとき、移動したテストファイルが元のテストターゲット内のテストファイルと依存関係があるとビルドできなくなるため、都度依存を切り離していきます。

ターゲットを移し替える
ターゲットを変更していく

次に、TestPlanを作成します。
TestPlanとは、実行するテストとテストの設定をまとめたものです。その際、実行するテストを指定できる範囲はテストターゲット単位です。
このためにテストターゲットを分離させました。

TestPlanはスキーマに紐づけることができ、弊アプリではスキーマとTestPlanが1対1になるようにしています。
そしてCI上で使用するためのスキーマ用のTestPlanにおいては、スナップショットテストを実行しないようにします。

TestPlanの設定
TestPlanの設定

実際に動かしてみると、CI上では失敗しない限り大きくは実行時間は変わりません。しかし、ローカルにおけるテスト体験は大きく改善しました。ロジックのみの変更でもスナップショットテストが実行されてしまっていたのが、チェックを外すだけで実行しないようにできるため、大幅な時間短縮が実現しました。

元々CI上では動かしていなかったテストをそもそも実行しないようにした形ですが、クレジット利用状況とのバランスを調整した上でスナップショットテストも実行できるようにしていきたいと考えています。

おわりに

ここに紹介したもの以外にも、型推論に時間が掛かっているコードを修正したり、使用していないアセットを削除することなどもビルド時間の短縮につながります。
これらの取り組みによってCI1回あたりの時間は平均約22分程度から12分程度まで短縮され、45%ほどのクレジット節約が実現しました。

今回はすぐにできる範囲としてビルド前後の時間短縮が主でしたが、次はビルド自体の時間ももっと短縮したいです。

Facebook

関連記事 | Related Posts

イベント情報

製造業でも生成AI活用したい!名古屋LLM MeetUp#6
Mobility Night #3 - マップビジュアライゼーション -