🌱

CIの速度を改善する(bitrise × mint)

2020/11/19に公開

現在開発中のプロジェクトでは、CI環境としてbitriseを採用しています。
また、それに付随してfastlaneや、swift純正のCLI管理ツールであるMintも利用しています。

今回はiOS14対応でxcodeのバージョンアップをしたことに伴い、CI速度が大幅に落ちてしまった問題にどのように立ち向かい、何を学んだのかを共有したいと思います。

TL;DR;

① bitrise上でコードで環境変数を指定する際は以下のように行う
bitrise上で環境変数を利用する際はGUIで指定する方法とコードで指定する方法があります。
githubで管理したいためコードで指定していますが、その際は以下のように envman add を利用する必要があります。

🙆‍♂️ envman add --key MINT_PATH --value "./mint/lib"
🙅‍♂️ MINT_PATH=./mint/lib

② bitrise上でのcacheはbranchやstackに依存している
bitriseでのcacheはブランチやstack(xcodeバージョン)に依存しているため、それを変更した際は新しくcacheを生成する必要があります。

③ PullRequestベースでCIを走らせている時はcacheの更新はされない。enable_cacheのようなworkflowを用意しbitrise console上からworkflowを動かしてcacheを更新する必要がある。
PRベースでCIを走らせてもcacheを更新することはできません。(既存のcacheを利用することしかできない)
そのため、新しくcacheを生成するにはbitrise側から更新する必要があります。
そのために最適な方法は、cacheを更新する専用のworkflowを用意し、console上から呼び出すことです。

本記事では、具体的な調査の流れを記しつつ、上記の結論に至った理由を書いていきたいとおもいます。

前置き

環境

  • fastlane
  • bitrise
  • mint
  • xcode12.0.1

現状

  • iOS14対応をした直後である(Xcodeを11.xから12.xに更新した直後)
  • CIが既定時間(90分)内に終わらずtimeoutしてしまう。

原因箇所の特定

  • まずはbitriseのログより原因箇所を特定し、CI改善の指針を立てます。
  1. script@1.1.6 の箇所が20分以上もかかっています。
  2. fastlane@2.4.0 の途中でtimeoutしてしまっているので、ここも70分近くかかっていると推測できます。

さらに、各workflowで時間がかかってしまっている理由を確認したところ、以下が原因であることが判明しました。

  1. script@1.1.6 では mint bootstrap をしているが、それが毎回行われている(cacheされていない)

  2. fastlane@2.4.0 でbuildが2回走っている。(ipaを書き出すため && testを走らせるため)

※PRをdevelopブランチへマージする度にipaを作成し、QRコードベースでアプリをインストール出来るようにしています。

この2つを解決することで、CI速度を改善することが出来るとわかりました。
今回は以下の理由のため、②の解決は見送ることにしました。

  • ipa書き出し用のbuildと、test用のbuildはそれぞれbuild configurationが異なる。(cf. release build, debug build)
  • 両方のbuildをrelease buildで統一することも可能だが、release buildはdebug buildに比べbuild時間が長いので、あまり大きな改善には繋がらなそう。(それに対し、実装コストが高そう。)

ここまでで方針は整いました。
本記事でもこれ以降は、bitriseでどのようにmintのbuildをcacheしていくかということに焦点当てお話ししていきたいと思います。

bitriseでMintをcacheしていく

Cache flowを把握する

cacheされていない原因を探る前に、どのような流れでcacheされているのかを知る必要があります。
公式ドキュメント: キャッシュについて にあるように、以下の流れでcacheが行われます。

  1. Bitrise.io Cache:Pull
  2. キャッシュの利用(mint bootstrap, carthage bootstrap, pod install etc...)
  3. Bitrise.io Cache: Push

これのどのステップで問題が発生しているのかを特定していきます。

Bitrise.io Cache:Pull

+------------------------------------------------------------------------------+
| (2) cache-pull@2.1.6                                                         |
+------------------------------------------------------------------------------+
| id: cache-pull                                                               |
| version: 2.1.6                                                               |
| collection: https://github.com/bitrise-io/bitrise-steplib.git                |
| toolkit: go                                                                  |
| time: 2020-10-14T09:58:00Z                                                   |
+------------------------------------------------------------------------------+
|                                                                              |
Config:
- CacheAPIURL: [REDACTED]
- DebugMode: false
- StackID: osx-xcode-12.2.x
- BuildSlug: 0b075395ef897673
Downloading remote cache archive
Checking archive and current stacks
current stack id: osx-xcode-12.2.x
archive stack id: 
Cache was created on stack: , current stack: osx-xcode-12.2.x
Skipping cache pull, because of the stack has changed
|                                                                              |
+---+---------------------------------------------------------------+----------+
| ✓ | cache-pull@2.1.6                                              | 6.68 sec |
+---+---------------------------------------------------------------+----------+

少し情報が欠如していますが、stackが変更されたためにcacheの取得がされていません。
(現在使っているstackに対するcacheが生成されていない。)

実際にiOS14対応でbitrise上Xcodeのバージョン(stack)を変更していました。

Cache was created on stack: , current stack: osx-xcode-12.2.x
Skipping cache pull, because of the stack has changed

Bitrise.io Cache:Push

Cache:Pullのログを見る限り、そもそもcacheが存在していなかったので、今度はCache:Pushのログを確認し、正常にcacheの生成が行われているのかを確認します。

+------------------------------------------------------------------------------+
| (0) cache-push@2.3.2                                                         |
+------------------------------------------------------------------------------+
| id: cache-push                                                               |
| version: 2.3.2                                                               |
| collection: https://github.com/bitrise-io/bitrise-steplib.git                |
| toolkit: go                                                                  |
| time: 2020-10-14T10:04:52Z                                                   |
+------------------------------------------------------------------------------+
|                                                                              |
WARN[10:04:52] The step's (cache-push@2.3.2) Run-If expression evaluated to false - skipping 
INFO[10:04:52] The Run-If expression was: .IsCI | and (not .IsPR) 
|                                                                              |
+---+---------------------------------------------------------------+----------+
| - | cache-push@2.3.2                                              | 3.16 sec |
+---+---------------------------------------------------------------+----------+

bitriseでは仕様上、PullRequestでCIを走らせた場合、cacheが作られないようになっています。
cacheを生成する場合は、bitriseコンソールからCIを走らせる必要があります。
参考: Cache Push is not working since Run-If expression is false(which should not be false)

そのため enable_cache という、キャッシュを更新するためのworkflowを用意しています。
そのworkflowを以下の Start/Schedule a Build から呼び出すことでキャッシュを更新します。

workflowは以下のようになっています。
beforeではgit clone, cache:pullなど全てのworkflowで事前に行うべき処理を、
afterではcache:pullなど全てのworkflowの最後に行うべき処理をまとめています。
fastlaneの ios bootstrap 処理の中ではcarthage, cocoapodsなどのbootstrapを行います。

enable_cache:
  after_run:
  - after
  before_run:
  - before
  steps:
  - fastlane@2.7.0:
      inputs:
      - lane: ios bootstrap
  - slack@3.1.3:
      inputs:
      - webhook_url: "$SLACK_WEBHOOK_URL"

このようにしてコンソール側からCIを動かすことで、正常にcacheを生成することが出来ました🎉🎉

余談: その他つまづいたポイント

以上の流れでキャッシュを生成し、CI速度を改善出来るはずだったのですが、その他にもいくつかつまづいた点がありました。
私の知識不足による側面が大きかったかもしれませんが、同じ問題で悩んでいる方がいるかもしれませんので参考程度に記したいと思います。

環境変数の指定方法

環境変数の指定方法は以下の2つの方法があります。

  1. GUIを利用して指定する方法 参考
  2. bitrise.ymlでコードで指定する方法

githubで差分管理出来るようするために、後者の方法を採用しています。
そのため以下のように記述していました。

- script@1.1.6:
    inputs:
    - content: |-
        ~省略~
        # set mint cache path
        MINT_PATH=./mint/lib
        MINT_LINK_PATH=./mint/bin
        ~省略~

一見正しく記述できているように見えるのですが、正しくは以下のように envman add を追加する必要がありました。 参考: Ruby Gemsのキャッシュ

# set mint cache path
envman add --key MINT_PATH --value "/usr/local/lib/mint"
envman add --key MINT_LINK_PATH --value "/usr/local/bin"

ちなみに、path自体は ./mint/lib でも /usr/local/lib/mint でも(適当な変数で)大丈夫です。
この機会にMintの公式ドキュメントに則し、 /usr/local/lib/mint と指定するように変更しました。

まとめ

Mintを導入したタイミングでキャッシュがされなくなりCI時間がかなり伸びてしまい、追い討ちをかけるようにXcodeのアップデートでcarthage, cocoapodsのキャッシュも消えてしまいCIが90分以内に終わらないという状況になってしまいましたが、無事改善することができました。

結果として、以下のようにCI時間は以前の半分近くまで削減することが出来ました🙌

Discussion