Flutter × BitriseでCI/CD構築

FlutterプロジェクトのCI/CD構築にあたって、Bitriseを使用しました。
BitriseはGUI操作だけでなく、YAMLファイルを編集してWorkflowのStepを管理することもできるため、柔軟なCI環境を構築できます。
基本的な設定はドキュメントを見ながらの構築になりますが、Flutterプロジェクトの場合、ある程度工夫しないと求めている挙動を実現できなかったため、工夫した部分をまとめていきます。
## 実現したワークフロー
**test_and_deploy_debug**
	• コードの静的解析とテスト実行
    • debug環境をビルド
	• AndroidアプリをビルドしてDeployGateへデプロイ
	• iOSはアーカイブしてApp Store Connectにアップロード
**test_and_deploy_release**
	• コードの静的解析とテスト実行
    • release環境をビルド
	• AndroidアプリをビルドしてDeployGateへデプロイ
    • さらにGooglePlayConsoleに.aabファイルをアップロード
	• iOSはアーカイブしてApp Store Connectにアップロード
<p align="center">
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428088/a736cc0d-590c-42c3-92ea-6957ff176d15.png" width="250" />
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428088/559d62b1-1d73-4b59-90df-1be6eb0e5b25.png" width="250" />
</p>

## Flutter Test
https://devcenter.bitrise.io/en/testing.html

そのままこのステップを実行すると、ずっとワークフローが終わらない状態になります。
ログの中に
> ERR : The current activation of `junitreport` cannot resolve to the same set of dependencies.

とあるのを発見しました。
❗️原因: junitreport = Flutterのテスト結果をJUnit形式のXMLに変換するツールです。
Bitriseでテストカバレッジを出力する際に必要となります。
原因を調査したところ、ビルドマシン上にインストールされている junitreport と、実際に使用しようとしている junitreport の依存関係が一致していないことが分かりました。
そのため、ビルドマシン上で改めて junitreport を正しくインストール・有効化する必要があります。

✅対策: 
Flutter Testステップの前にScriptステップを追加する
``` ruby:Script content
flutter pub global activate junitreport
```
Flutter環境に junitreport というツールをグローバルインストールして使えるようにします。
## Change Android versionCode and versionName
https://devcenter.bitrise.io/en/builds/build-numbering-and-app-versioning.html

Bitrise側でversionName, versionCodeを変更したい場合に使用するステップです。ドキュメント通りに設定してもversionCodeが設定した値に反映されません。

❗️原因: Flutterのデフォルトの設定では、versionNameとversionCodeの設定は
``` groovy:app/build.gradle
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
```
このように設定されています。
ビルドマシンではこの値を参照することが出来ないため、更新されません。
✅対策:
ビルドマシン上に**flutter.versionCode**と**flutter.versionName**を作成し、その値を参照するように設計する
Change Android versionCode and versionNameステップの前にScriptステップを追加する。
``` ruby:Script content
echo "flutter.versionCode=$BITRISE_BUILD_NUMBER" > "$BITRISE_SOURCE_DIR/android/local.properties"
echo "flutter.versionName=1.0.0" >> "$BITRISE_SOURCE_DIR/android/local.properties"
```
上記は仮に1.0.0のように固定値を入れています、Env varsで設定した値を入れてもOKです。
## Google Play Deploy
https://devcenter.bitrise.io/en/deploying/android-deployment/deploying-android-apps-to-bitrise-and-google-play.html

見落としやすいですが、ステップのConfiguring the Stepにこのように記載されていました

> Please note that in order to successfully use this Step, you must upload your first APK or AAB file manually,

なので、まずは手動でGooglePlayConsoleに.apkファイルまたは.aabファイルをアップロードする必要ありです。

👉json　key fileインストールエラーについて

> Authenticating
Failed to create HTTP client: failed to create auth config from json key file [REDACTED], error: %!s(<nil>)

💡やった事
ProjectSettings→FilesにserviceAccount.jsonkeyを保存。そこで生成されるDownloadURLをWorkflow画面のSecretsの変数に設定
Secretes変数を**Service Account JSON key file path**に設定

このように設定しても上記エラーは解消できませんでした...😂
✅対策: workflowのYamlファイルにFilesで設定されたDownloadURLを指定する
GUI上では設定できませんが(Secrets変数しか設定できない)、yamlファイルで指定してビルドを実行したところ、エラー回避できました🎉

👉 keystoreの設定時のエラーについて

https://devcenter.bitrise.io/en/code-signing/android-code-signing/android-code-signing-with-android-studio.html

ドキュメント通りに設定し、ビルドを実行すると、
> A failure occurred while executingcom.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
   > SigningConfig "release" is missing required property "storeFile".

というエラーになります。
❗️原因: 公式通りにAndroidの署名設定を行うと、keystoreとkey.propertiesを連携させて署名を実現している構成になります。
通常、これらのファイルはセキュリティ上gitignoreでリポジトリ上に存在しないため、ビルドマシン上にkey.propertiesとkeysotreを作成し、ローカル上のFlutterプロジェクトと構成を合わせる必要があります。

https://docs.flutter.dev/deployment/android

✅対策: Flutter Buildステップ以前にSciriptステップとFile Downloderステップを使用し、ローカル環境と同じ構成にする。
``` ruby:Script content
echo "storeFile=./upload.jks" > android/key.properties
echo "storePassword=$BITRISEIO_ANDROID_KEYSTORE_PASSWORD" >> android/key.properties
echo "keyPassword=$BITRISEIO_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD" >> android/key.properties
echo "keyAlias=$BITRISEIO_ANDROID_KEYSTORE_ALIAS" >> android/key.properties
```
まずこのスクリプトでビルドマシン上にkey.propertiesを作成します。
そして、FileDownloaderで
**Download source URL**に$BITRISEIO_ANDROID_KEYSTORE_URLを設定し、
**Download destination path**にandroid/app/upload.jksを設定すればOKです

## まとめ
ビルド → エラー → 対策の繰り返しでしたが、最終的にやりたかった挙動を実現できたのは非常に良い経験となりました。

Bitriseでは、このビルドをトリガーによって柔軟に起動させる設定も可能です。
そのため、一度ワークフローをしっかり構築しておけば、アプリ運用が長期化すればするほど、作業者の負担はどんどん減っていくことになります。
これは非常にありがたい仕組みだと感じました🎉

ただ、Bitriseのドキュメントは情報量がとても多く、日本語ページも用意されてはいるのですが、
翻訳の精度が低く、内容の理解が難しいと感じました。
結局、英語のページを参照しながら、自力で翻訳しつつ作業を進める必要があり、大変でした。