ReactNative 使うなら Bitrise はいかが?


この記事はBitrise Advent Calendar 2019、21日目の記事です。
前回は @AtsushiIzu さんでBitrise Shipを使ってみた(iOS編) の紹介でした。
Bitrise Add-ons はあまり使ったことがないので、そんなことできるんだなーって参考になりました。

自己紹介

初めまして、バックエンドエンジニアとして7年くらい働いています。
メインな言語は、Java・C#です。

少人数現場にいることが多く、本来の役割であるバックエンド側の領域を超えて
インフラ、アプリ、開発効率最適化のための施策を検討することもあります。

今回は、ある現場に入った時に、レガシーな ReactNative実装のアプリをメンテする機会があり、
アプリ側のメンテナンスがあまりに辛かったので、Bitrise を使って改善した時の取り組みを踏まえて、
Bitrise を勧める理由を皆さんにシェアしようと思います。

ReactNative の個人的に面倒だったところ

大きく2つの実装ツールがある

実装ツールが大きく2種類あり(ReactNative-CLI, Expo)、それぞれビルド方法がちょっと違う。
ビルド時に問題が発生して文献を調べる時は、どっちの話なのか精査する必要があります。

自分が関わった案件はレガシーアプリなので、昔からある ReactNative-CLI が使われていました。
(Expoでの実装もこの後別で経験しますが、快適だったです...)

iOS/Androidの知識が必要

あるあるだと思うのですが、ReactNative 使うからといって、JavaScript系(or TypeScript)のみの知識では開発は無理です。例えば、iOSアプリを作るには証明書の話は避けて通れないし、Androidだったら最近では64bit対応をしましたが、中身を知らないとかなり厳しい。

ですが、現実的には Javascript はできるが、iOS/Androidはよくわからない人ばっかりです。
では、そういった人達に1からiOS/Android周りの知識を教えて、環境を構築すべきでしょうか?

自分は、誰かは見ないといけないと思うけど、全員が知る必要はないと思っています。
実際、iOS/Android側の作業って、Javascript側に比べれば圧倒的に少ないですから。

オペミスが発生しやすい

コマンド複数叩かないとアプリがビルドできないです。
自分の案件の場合、細かいコマンドを示すなら下記になります。

iOSビルドの仕方
# JS側で必要な処理
# nodeパッケージのインストール, build(tsc叩いている)
yarn
yarn run build

# iOS側で必要な処理
# cocoapodインストール、デバッグ起動
(cd ios && pod install)
react-native run-ios

そして、厄介なのが、どれかやらなくてもコンパイルできるケースが結構あること。
つまりは、実行時エラーで分かるのですが、オペミスなのか自身の実装ミスなのかを判断することが結構あります。
(本当職人芸みたいなもので、経験積まないと結構な時間が取られます)

あるあるなのが、他人のブランチがマージされた後にパッケージインストールせずに実装作業して、
それが原因でエラー出て、解決に数時間奪われるなんて、ざらにあります。

つまり:ビルド手順を自動化しないと心が荒みます

Bitrise を選択した理由

  • モバイル向けCI/CDツール
  • React Native向けのデフォルトプロジェクト設定を持っていた
  • iOSアプリを考慮すると安めの値段
  • 無料プランの範囲で使ってみた感じ、使い勝手が良かった

Bitrise使って本当に良かったなって思うこと

デフォルトで色々揃っている

これは、Bitriseの勉強会にいけば、必ず一回は聞きますが、UIはすごく使い易いです。

また、各ステップに関しても、Bitrise・サードパーティが提供しているため、
もしかしたら少ない作業量で実装できてしまうかもしれません。

なお、今日「react」で検索すると以下のステップが見つかりました。(自分は使ってませんが...)

また、ビルドする環境では、最初から node, npm, yarn, brew などのツールがデフォルトでインストールされているため、
これらをインストールする手順を含めなくてもいいのはありがたいです。

処理共通化が簡単

1連の処理の流れをワークフローと言いますが、Bitriseではワークフローの中に別のワークフローを含めることができます。
もちろん、削除したり順番を変えたりも簡単です。

[追加画面]

このため、自分はワークフローを機能単位で切っていました。(上記の別のワークフロー見るとよく分かると思います)
こうすることにより、ReactNative特有の面倒な問題を解決できます。

一連の流れとして、ReactNative なので Android,iOSのSTG環境アプリを同時に作りたい。
ただ、場合によってはiOSアプリだけ作るようにして欲しいとか。
また、上記のケースに加えて、サーバの向き先を本番に向けたいとか。

もうそんな物を全て作ったら、同じようなコピー処理がわんさかできてしまいます。
(Bitrise は既存ワークフローを複製して新しいワークフローを作ることもできるので)

ですが、稀にビルド手順を少しだけ変えたいということがあります。
そうすると、そういった複製しまくった処理を一つ一つ直さないといけません。

なお。機能単位となるワークフロー名は、アンダースコア(_)から初めると、ビルドするときにユーティリティワークフローという扱いになり、UIから起動できないようにできるのでオススメです。

ワークフローごとにパラメータを変える

また、Bitrise は各ワークフローに対してパラメータを設定することができるので(EnvVars)、
パラメータが異なる物に対しても、共通化ができます。

また、子となるネストされたワークフローに対しても適用されるため、
同じ処理なんだけど、親のワークフローで違った挙動をさせることができます。

下記は、最後のバージョン問題に関わるものなんですが、
release は iOS/Android 両方リリースするからバージョンを両方あげたい。
・・・が、iOSのみのリリースであれば、Androidのバージョンはあげたくないため、
バージョンを変更しないようパラメータで分岐させています。

テストがしやすい

Bitriseの料金プランを見るとわかりますが、
Developer ライセンスでやっているので(月額$40)、ビルド回数の制限はありません。
45分以内にビルドできるなら、なんでもOKです。このため、手順を容易にテストできます。

バージョンを作るロジックも処理共通化を生かして、テスト用のワークフロー作って次のようにテストができちゃいます。
(テスト用のワークフロー:Versioning、テスト対象:__Set-Versionワークフロー)

(__Set-Versionワークフローに関しては、下部に細かい話があります)

ワークフロー実行時に追加でパラメータ指定

iOS/Androidを同時にリリースする場合などに、バージョンどうするかという話があります。
自分は現場が複数あったので、PCが近くにないのに、別顧客からリリースしたいって話がきたりしました。

クラウドサービス使って、リモートでもリリースできるようにしたのに、PCがないとアプリのソースコードがないからアプリバージョンも変えられない。
すると、リリースができない。そんなの辛い。
(最悪、別顧客のPCにGithubから落としてくればいけるけど..)

そのため、自分はリリース時にバージョンを Bitrise 側で更新して、バージョン更新内容をGitリポジトリにPUSHするようにしていました。
一般的に機能的なアップデートは、一番下の番号をあげるのが普通なのでデフォルトではバージョンの上げ方をそれぞれ、下記のようにしてました。

  • バージョン: x.y.z -> x.y.(z+1)
  • ビルド番号: max(iOSのビルド番号, Androidのビルド番号)+1

しかし、大きな改修などがあると、バージョンを明示的に指定したいことがあります。
そういった場合も、リリース時にバージョンを指定するようにできなければいけません。
この余地を残すため、ワークフローの実行時にパラメータを受け取ったら、それを反映するようなロジックを作っていました。

参考として、ビルド番号取得するロジックの内部実装は、こんな感じになってます。

ビルド番号取得ロジック仕様:

  • BUILD_NUMBER が指定されていたら、BUILD_NUMBER をビルド番号に採用
  • BUILD_NUMBER が指定されていなければ、IOSビルド番号とAndroidビルド番号比較して大きいもの+1を採用
次のビルド番号決定するシェル
#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x

PRODUCT_PLIST=$BITRISE_SOURCE_DIR/ios/[アプリ名]-production-Info.plist
GRADLE_PROP=$BITRISE_SOURCE_DIR/android/gradle.properties

CURRENT_IOS_BUILD_NUMBER=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$PRODUCT_PLIST"`
CURRENT_ANDROID_BUILD_NUMBER=`cat "$GRADLE_PROP" | grep 'ANDROID_VERSION_CODE=[0-9][0-9]*' | sed 's/ANDROID_VERSION_CODE=\([0-9]*\)/\1/g'`
if [ -z "CURRENT_IOS_BUILD_NUMBER" -o -z "$CURRENT_ANDROID_BUILD_NUMBER" ]; then
    echo 'CURRENT_BUILD_NUMBER(iOS: '$CURRENT_IOS_BUILD_NUMBER', Android:'$CURRENT_ANDROID_BUILD_NUMBER') is NOT found!' >&2
    exit 1
fi

NEXT_BUILD_NUMBER=$BUILD_NUMBER
if [ -z "$NEXT_BUILD_NUMBER" ]; then
    if [ $CURRENT_IOS_BUILD_NUMBER -ge $CURRENT_ANDROID_BUILD_NUMBER ]; then
        NEXT_BUILD_NUMBER=$(($CURRENT_IOS_BUILD_NUMBER + 1))
    else
        NEXT_BUILD_NUMBER=$(($CURRENT_ANDROID_BUILD_NUMBER + 1))
    fi
fi
envman add --key "CURRENT_IOS_BUILD_NUMBER" --value "$CURRENT_IOS_BUILD_NUMBER"
envman add --key "CURRENT_ANDROID_BUILD_NUMBER" --value "$CURRENT_ANDROID_BUILD_NUMBER"
envman add --key "NEXT_IOS_BUILD_NUMBER" --value "$NEXT_BUILD_NUMBER"
envman add --key "NEXT_ANDROID_BUILD_NUMBER" --value "$NEXT_BUILD_NUMBER"

こういった複雑な処理を容易に組み込めるのは、前述のテスト容易性によるものですね。

最後に

Flutter、Xamarine、ApacheCordovaも同じような課題を持つことがあるかもなので、
今回の話はReactNative に限らず色々なアプリで適用できるかもしれません。

今回、こういう環境を作ることによって、下記を実現することができました。

  • STGアプリ作成、リリース作業の安定した運用
  • ビルド手順等の共有においては Bitrise を見れば分かる
  • 引き継ぎコストも小さくて済んだ
  • 実装者はtypescriptの実装に専念することができ、本質のコミュニケーションが増えた

大幅な効率化に繋がったんではないかと思ってます。
ReactNative 使うのであれば、Bitriseを使ってみてはいかがでしょうか?