はじめに
React NativeアプリのCIに使えるサービスとして、有力なものの一つがBitriseです。
BitriseでCIを行う際にキャッシュを利用すると、2回目以降のビルド時間を短縮できます。
では実際、キャッシュの設定をどうすればよいかを考えていきます。
Bitriseでキャッシュを利用するには
CIによるビルドの一般論として、以下の様にキャッシュを利用することで、2回目以降のビルド時間が短縮できます。
ビルドの流れ
- Gitリポジトリをクローンする。
- (あれば)前回のビルドの際に保存しておいたキャッシュを取得する。
- 各種の依存性をインストールする。もしもキャッシュを取得できていたら、ここの所要時間を短縮できる。
- ビルドする。
- ビルド成果物を所定の場所に格納する。
- 特定のファイルやディレクトリをキャッシュとして保存しておく。
Bitriseにも、キャッシュの取得(Pull)や保存(Push)を行うためのステップが用意されています。
Pullは「もしキャッシュがあれば、それを取ってくる」を行うだけですので、さほど細かい設定はありません。
一方、Pushは「何をキャッシュするか」や「どういう時にはキャッシュを更新するか」などを設定する必要があり、この設定が時間短縮効果に影響してきます。
Pushの設定項目Cache paths
にnode_modulesを指定する
Pushには色々な設定項目があるのですが、何をキャッシュするかを指定するCache paths
がもちろん肝心です。初期値として設定されている$BITRISE_CACHE_DIR
に加えて、キャッシュしたいパスを書き足していきます。
React Nativeアプリのプロジェクトの場合、キャッシュ対象としてまず思いつくのはnode_modulesディレクトリでしょう。
ここでの留意点として、Cache pathsには矢印つきの記法があることに注目してください。Cache paths
のあたりを押すと表示される詳細説明にて触れられています。「このファイルが変わった時には、キャッシュのこの部分を必ず更新してね」というファイルを指定することもできるのです。
node_modulesに対応するファイルはというと、package-lock.jsonになるでしょう(yarnを使っているならyarn.lock)。そこでCache pathsには以下の様に書き足します。
node_modules -> package-lock.json
node_modulesをGitリポジトリのルート直下に置いていない場合は、パスも指定すればOKです。
path/to/node_modules -> path/to/package-lock.json
node_modules以外のキャッシュはどうするか
無事にnode_modulesをキャッシュする設定ができました。これで、2回目以降のnpm install(またはyarn install)にかかる時間がかなり短縮されます。正しく設定できているかどうか、試しにビルドして確認しておきましょう。
node_modulesの他にも、iOSやAndroidに特有の依存性などもキャッシュしたいところです。「でも、そういうネイティブな部分はあんまり詳しくないし、どんなパスを設定したらよいのやら・・・」という人も多いのではないでしょうか。
自分で設定しなくても、良きにキャッシュしてくれる仕掛けが実はあった
実は、Pushの設定項目にはCache paths collected runtime
というものも存在します。設定値は変更不可です。ここの指定と、先ほど自分で指定したCache pathsを合わせた結果が、実際のキャッシュ対象となります。
ではこちらで指定されている$BITRISE_CACHE_INCLUDE_PATHS
という環境変数の値は何なのかというと、Pushの手前にあるステップに応じて自動的にセットされているのです。
例えば、Android Build
というステップがあります。
これをデフォルト設定で使っているだけで、以下の様な値が$BITRISE_CACHE_INCLUDE_PATHSの末尾に追加されます。
/Users/vagrant/.gradle -> /Users/vagrant/git/modules/SampleApp/android/gradle.deps
/Users/vagrant/.kotlin -> /Users/vagrant/git/modules/SampleApp/android/gradle.deps
/Users/vagrant/.m2 -> /Users/vagrant/git/modules/SampleApp/android/gradle.deps
Android Buildの他にも、いくつかのステップには同様の処理が仕込まれています。BitriseのステップはGitHubに公開されていますので、ソースコードを辿っていけば、$BITRISE_CACHE_INCLUDE_PATHSに値を指定する処理を確認することもできます。
ともあれ、(任意のスクリプトを実行する汎用のステップではなく)Bitriseに用意されたステップを用いてビルドしていれば、良きにはからってキャッシュしてくれるということです。Bitriseのドキュメント(下記のURL)を見ても、「Cocoapods installやAndroid Buildといったステップと、Cache:PullやCache:Pushを使ってね」という程度の説明しかありません。
https://devcenter.bitrise.io/caching/caching-cocoapods/
https://devcenter.bitrise.io/caching/caching-gradle/
Bitriseはモバイルアプリに特化したCIサービスですから、iOS/Androidアプリをビルドする際にほぼ誰もがキャッシュしたいパスは、細かい設定なしにキャッシュするように設計されているのでしょう。
ここまでのまとめ
React Nativeアプリの場合、node_modulesをキャッシュするように指定しておけばとりあえずOKです。
その際、package-lock.json(またはyarn.lock)が更新されたらキャッシュも更新されるように、矢印つきの記法にしておきましょう。
もっと最適化したかったら
Pushの設定項目の一つであるDebug mode
をtrueにしておくと、以下の様な詳細がビルドのログに出力されます。キャッシュ設定を突き詰める際に役立ちます。
- キャッシュ対象のパス
- キャッシュ対象のパスに含まれていても、例外的に無視したいパス
- 前回のビルド後にキャッシュしたものに、今回のビルド後に変更が生じたかどうか(つまりキャッシュの更新が必要かどうか)
- Pushの各工程の所要時間
以下の図は、私がビルドした際のログの抜粋です。
Androidアプリのビルドの度に生じるログファイル(ファイル名が毎回違う)のために、File changes found
と判定されてしまっています。例外的に無視したいパスの設定に改善の余地があるということでしょう(*.log
を無視する設定は入っていたものの、その設定が*.out.log
には効かなかったらしいです)。
File changes found
になったために、新しいキャッシュファイルを生成したり、それをアップロードして保存する処理が走ってしまい、15秒くらい損をしています。
こうした現象を潰しながらキャッシュ設定を最適化していけば、ビルド時間を更に短縮できるかもしれません。下記などを天秤にかけて、最適化するかどうかを判断するとよいでしょう。
- ビルドの頻度
- ビルド全体の時間
- 短縮できそうな時間
- キャッシュ設定を試行錯誤するのに必要な時間(≒ ビルド全体の時間 x N回)
頑張って最適化したとしても、今後、自分のプロジェクトが使っているライブラリの変更や、Bitriseのステップの変更などの拍子に状況が変わるかもしれません。「別にこれくらいはいいや」と、ほどほどに打ち切るのも十分ありです。