ホワイトラベルアプリのCodemagicを利用したマルチストアビルド対応

# ホワイトラベル方式とは
たくさんの機能セットをもっている「白紙の状態」のアプリに、クライアントごとの設定をビルド時などに練り込んで、クライアント独自のブランドのアプリを構築していくことをいいます。
簡単に言えば、1つのソースコードから、設定に応じて、さまざまなデザイン・機能のアプリをビルド・構築していくスタイルをホワイトラベルといいます。

現在携わっているプロジェクトでは、GoogleCloud/Fireabseを利用してまして、1クライアントに対して、アプリが4つ、WEBサイトが2つを構築しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81962/4e6d7a39-0769-60d4-2dec-1baa02903ba5.png)


# ビルド自動化
現在携わっている事業の特性上、たくさんのアプリをビルドする必要（現在40アプリほど）があります。リリース前の微調整の際などは、頻繁にビルド・テストを繰り返しますし、バージョンアップの際も同様です。
これを当初はエンジニアのローカル環境で行っていたのですが、環境間の設定の違いやバージョンの違いなどを間違えがないように気をつけながらビルドを行なっていくことが大変になり、ビルド自動化ツールの開発を始めました。

# マルチストア対応
ホワイトラベル方式でアプリをビルドしていますが、リリース先は自社のストアアカウントだけではなく、お客様が用意したストアアカウントの場合もあります。
なので、ストアに関する情報もビルド時に動的に解決する必要がありました。

# Codemagic
技術選定の結果、Codemagicをビルドツールとして採択しました。
モバイルに特化したビルドツールということもあり、一番めんどくさい証明書管理周りが優れていたのと、Macマシーンでのビルドが比較的安かったというのが選定理由になります。

# ビルドフロー
独自ビルドツールより、Gitのブランチ（=リリースバージョン）を指定して、設定値を入力して、CodemagicのWebAPIを起動して、ビルドを開始しています。
RestAPIはこちらを参照ください。

https://docs.codemagic.io/rest-api/builds/

# Variables Group
他のビルドツール同様に、ビルド実行時の変数をCodemagicで設定しておけるのですが、これをグルーピングしておくことができるので、この機能を利用しています。
ビルドを実行するときに利用する環境変数群を設定することができるので、これを利用して実現します。dev, eva, stg, prodでの使い分けはもちろん、マルチストアでも必須の機能です。

# iOSビルド設定
iOSの自動署名に必要な環境変数。以下の5つがセットになります。ストアが増えた時は、このセットを増やします。
環境変数グループにストアの名前(fullspeed_store)をつけておき、実行するときにその名前を渡します。

- APP_STORE_TEAM_ID
Apple Developer ストアのTeam IDです。[こちら](https://developer.apple.com/account)から確認できます。

- APP_STORE_CONNECT_ISSUER_ID
[こちら](https://appstoreconnect.apple.com/access/integrations/api)より取得できます。

- APP_STORE_CONNECT_KEY_IDENTIFIER
StoreConnect APIキーです。Create new App Store Connect API Key.
[こちら](https://appstoreconnect.apple.com/access/integrations/api)より発行できます。

- APP_STORE_CONNECT_PRIVATE_KEY (secret)
StoreConnect APIキーのシークレットです。ダウンロードしたp8ファイルの中身をセットしてください。

- CERTIFICATE_PRIVATE_KEY(secret)
あらかじめ証明書を発行しておき、その中のプライベートキーを取得します。シークレットとして環境変数には保存しましょう。
すでにp12ファイルをもっているのでしたら、以下のコマンドで抽出できました。

```
openssl pkcs12 -in xxxxxxx.p12 -nodes -nocerts -legacy | openssl rsa -out ios_distribution_private_key
```
ios_distribution_private_keyの中身がプライベートキーですので、それをセットしてください。

最終的な設定イメージは以下の通りです。

![Screenshot 2024-11-27 at 10.55.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81962/641297ba-fa6f-6c39-bb80-dfb301017bf0.png)


その後は、以下のスクリプトをcodeamgic.yamlに記述して自動署名を行います。これにより、Identifier, Profileが自動で生成されます。

```
keychain initialize
app-store-connect fetch-signing-files "$BUILD_APP_ID" --type IOS_APP_STORE --create
keychain add-certificates
xcode-project use-profiles --custom-export-options='{"destination": "export"}'
```

この辺りは、[こちら](https://docs.codemagic.io/yaml-code-signing/alternative-code-signing-methods/)の中の、「Define environment variables by yourself」という項を参考にしています。

以上のやり方でしたら、コードの変更やcodemagic.yamlの変更は発生しません。
Codemagicのコンソールから変数値の設定と、ビルドの起動パラメーターにストア名のEnvGroupを渡せばいいのです。

# Android
Androidは残念ながらcodemagic.yamlをストア追加時に編集する必要が出ました。
Codemagic > Code signing identitiesの設定より、Android keystoresを設定します。このときの名前が大事です。ストア名＋環境変数名です。

```
  multi_android_signing: &multi_android_signing
    - keystore: xxx_store
      keystore_environment_variable: xxx_store_CM_KEYSTORE_PATH
      keystore_password_environment_variable: xxx_store_CM_KEYSTORE_PASSWORD
      key_alias_environment_variable: xxx_store_CM_KEY_ALIAS
      key_password_environment_variable: xxx_store_CM_KEY_PASSWORD
    - keystore: yyy_store
      keystore_environment_variable: yyy_store_CM_KEYSTORE_PATH
      keystore_password_environment_variable: yyy_store_CM_KEYSTORE_PASSWORD
      key_alias_environment_variable: yyy_store_CM_KEY_ALIAS
      key_password_environment_variable: yyy_store_CM_KEY_PASSWORD
```
android/app/build.gradleは以下のようにします。
```
...
  android {
      ...
      defaultConfig { ... }
      signingConfigs {
          release {
              if (System.getenv()["CI"]) { // CI=true is exported by Codemagic
                  storeFile file(System.getenv()["CM_KEYSTORE_PATH"])
                  storePassword System.getenv()["CM_KEYSTORE_PASSWORD"]
                  keyAlias System.getenv()["CM_KEY_ALIAS"]
                  keyPassword System.getenv()["CM_KEY_PASSWORD"]
              } else {
                  keyAlias keystoreProperties['keyAlias']
                  keyPassword keystoreProperties['keyPassword']
                  storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
                  storePassword keystoreProperties['storePassword']
              }
          }
      }
      buildTypes {
          release {
              ...
              signingConfig signingConfigs.release
          }
      }
  }
  ...
```

そして、Androidアプリのビルド前にKEYSTORE用の環境変数をSTORE名から解決してセットします。
```
        for var in CM_KEYSTORE_PATH CM_KEYSTORE_PASSWORD CM_KEY_ALIAS CM_KEY_PASSWORD; do
            temp_var="${RELEASE_STORE}_$var"
            export "$var"="${!temp_var}"
        done
        flutter build apk
```
ちょっとAndroidはゴリっとしていますが、命名規約でなんとか修正を最小限に抑え込めたと思っています。ドキュメントはここらへんを参考にしています。

https://docs.codemagic.io/yaml-code-signing/signing-android/

Codemagicでのマルチストア対応は、あまり記事がなく、公式ドキュメントもどれを参考にしていいか、わからなかったのですが、今回試行錯誤した結果をまとめました。

余談ですが、Androidのビルドは当初Linuxマシーンで行っていたのですが、時間がかかったので、MacOS M2にしたところ、半分の時間で終わりました。LinuxとMacOSの料金はちょうど倍違うのですが、実行時間が半分になったので、今は両方ともMacOS M2でビルドしています。

以上