作業メモを移行しているため、前提条件が抜けており、一部わかりづらい記載があります。
実機でのアプリの動作確認をしたいのと、ビジネス側にも動作を見てもらいたいのと開発メンバーみんなで QA をしていく体制を取りたい。そのため、Firebase App Distribution にバイナリをアップロードしてダウンロードできるようにしたい。ある程度作業が必要であるため、ここに作業の流れを書いておく。新しいアプリを作るときにこれを見て、スムーズにダウンロードできるようにしたい。
使っている技術やサービスについては以下の通り。
Firebase (Firebase App Distribution) と Github Actions (Workflow Dispatch)
Apple Developer Program のアカウント。
Google Play Console のアカウント。
Firebase プロジェクトを新しく作成してもらうように、ビジネス側に依頼する。
プロジェクトを作成したら自然と出てきますが、こちらのアプリを登録する画面で iOS / Android 両方を登録する。(application id や bundle id などを入力する。)
実際にアップロードの作業を始める前に、App Distribution のサービスを開始する必要がある。iOS / Android のアイコンをクリックし、初期設定する。
アップロードするときの API の中でグループを指定しないといけないのと、バイナリを公開する人を制限しないといけないことから、テスターのグループを作成する。グループの名前については任意だが、Github Actions の workflow の groups
のパラメーターに同じものを渡さないと、404 Not Found
とエラーが出てしまう。
デプロイで使用するための Adhoc の Provisioning File を指定する必要がある。作り方はここでは割愛する。 何か iOS の Capabilities を増やした場合や新しいメンバーが増えてデバイスを増やしたい場合は、Provisioning File を再ダウンロードをする。そのあと、Provisioning file を更新するために再度 Xcode でアップロードする。
注意することは下記となる。
iPhone Distribution
になっているか。GitHub Actions 内でビルドを行うのに Provisioning File の名前を指定しないといけないので、アプリごとでファイル名を決める。
- name: Import Provisioning Profile
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo '${{ secrets.PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ dev.mobileprovision # ここの .mobileprovision ファイルの名前
echo '${{ secrets.NOTIFICATION_SERVICE_PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ ImageNotification.mobileprovision # ここの .mobileprovision ファイルの名前
ExportOptions.plist
を生成するiOS について、実機で検証できるように ipa ファイルを GitHub Actions で Firebase App Distribution にアップロードする必要がある。そのために GitHub Actions 内で flutter build ipa
コマンドを実行するが、export-method を adhoc に選択することを事前に決める必要がある。その情報が入った ExportOptions.plist
ファイルを ipa コマンドの --export-option
オプションに渡す必要があり、一度ローカルで ipa ファイルを生成する。
まずはこちらのコマンドで xrarchive ファイルを生成する。
$ cd path/to/app repository
$ fvm flutter build ipa --release --dart-define=ENVIRONMENT=STAGING
まず、xrarchive ファイルを finder で見つける。
$ open /path/to/apps/build/ios/archive
そのあと、xrarchive ファイルをダブルクリックすると、下の xrachive 一覧が表示される。「Distribute App」をクリックし、オプションたちを決めていく。その後、method に Adhoc を選択する。そのあと証明書と Provisioning Files も選択する。最後に export をクリックして、ipa を生成する。
ipa が生成されるディレクトリの中に ExportOptions.plist
が入っているので、それをアプリディレクトリの ios
ディレクトリに入れる。
$ cat ~/path/to/ipa ios/ExportOptions.plist
出力された結果がこんな感じになったら問題なし。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<true/>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>ad-hoc</string>
<key>provisioningProfiles</key>
<dict>
<key>bundle.id</key>
<string>app.dev</string>
<key>bundle.id.ImageNotification</key>
<string>app.dev.ImageNotification</string>
</dict>
<key>signingCertificate</key>
<string>hash</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>WCPA535FQM</string>
<key>thinning</key>
<string><none></string>
</dict>
</plist>
それをアプリの ios
ディレクトリに入れる。
$ cp ~/path/to/app/ExportOptions.plist ~/path/to/app/repository/ios/ExportOptions.plist
Firebase プロジェクトから設定ファイルをダウンロードし、各 OS で正しいパスに配置する。
iOS | Android |
---|---|
/path/to/app/repository/ios/Runner/GoogleService-Info.plist | /path/to/app/repository/android/app/google-services.json |
Github Actions の Workflow Dispatch を実行する前に、色々な情報を secrets にいれていく必要があるので、どんなキーにどんな値を入れるかをまとめる。
iOS と Android のアプリを反映するのに使うのがこちら。
キー | 説明 | 値がどこにあるか |
---|---|---|
FIREBASE_TOKEN | Firebase CLIで発行できるトークン | 下の FIREBASE_TOKEN を発行する流れで作成する。 |
SUBMODULE_ACCESSKEY | アプリのリポジトリから module を取得するのに必要なキー | 公開鍵/秘密鍵を作成する |
※ key をコピーする際にうまく設定できない場合がある、$ pbcopy でダメだったら cat してそれをコピーするなど改行を含めるなど試してみてください。
(詳しくは Firebaseのドキュメントを参照)
npm で firebase-tools
のパッケージをインストールし、ログインを済ませる。
$ npm install -g firebase-tools
$ firebase login:ci
その後、 firebase login:ci
を実行すると、トークンが発行される。
$ firebase login:ci
Visit this URL on this device to log in:
https://accounts.google.com/o/oauth2/auth?xxx=yyy
Waiting for authentication...
✔ Success! Use this token to login on a CI server:
access token (これがトークンの値)
キー | 説明 | 値がどこにあるか |
---|---|---|
FIREBASE_APP_ID_IOS | Firebase にアプリを登録したときに発行されるID | Firebase Console |
CERTIFICATES_P12 | 証明書 | Apple Developer Program |
CERTIFICATES_P12_PASSWORD | 証明書を作成したときのパスワード | Apple Developer Programで証明書を作成するときに入力したもの |
PROVISIONING_PROFILE | プロビジョニングファイル | Apple Developer Program |
NOTIFICATION_SERVICE_PROVISIONING_PROFILE | 通知サービス用のプロビジョニングファイル | Apple Developer Program |
セキュアな画面であり量が多くなるため割愛するが、証明書の TYPE
が iOS Distribution
になっているかを確認する。特に新しく作成する必要がないのであれば、一旦 expired が 2022/12/29
がものにする。p12 ファイルに対して、 base64
をかけたものを secrets に入れる。
これは p12 ファイルへ出力されるときにパスワードを決められるので、それを覚えておく。
5 のステップで作った Provisioning File を base64 にかけたものを環境変数に入れる。5 のステップで書いたように、何か Provisioning file に更新がかかったときは再度ダウンロードして、base64 をかけたものを環境変数に入れる。
$ base64 -i path/to/provisioningfile.mobileprovision
5 のステップで作った Provisioning File を base64 にかけたものを環境変数に入れる。
$ base64 -i path/to/ImageNotificationprovisioningfile.mobileprovision
キー | 説明 | 値がどこにあるか |
---|---|---|
FIREBASE_APP_ID_ANDROID | firebase にアプリを登録したときに発行されるID | Firebase Console |
ANDROID_KEY_JKS | jks ファイルを base64 化したもの | -- |
ANDROID_KEY_PROPERTIES | android/key.properties に入れるもの | -- |
Firebase Console に書いてあるので、そこから取得する。
ファイルをダウンロードし、下のコマンドを実行する。
$ base64 -i path/to/jks_file.jks
このようなフォーマットで環境変数にいれる必要がある。
storePassword=<storePassword>
keyPassword=<keyPassword>
keyAlias=<keyAlias>
storeFile=/home/runner/work/<repository name>/<repository name>/android/upload_key.jks
環境変数を入れ終わったら、実際に Github Actions を動かしていく。
manual build & deploy for iOS と manual build & deploy for Android を選択する。Workflow Dispatch の中で決められるいくつかの選択肢を埋めて、実行する。
key | value |
---|---|
branch | ビルドをしたいコードが入っているブランチ |
release notes | リリースノート。App Distribution のページで、アップロードされたバイナリの近くに記載される文言。何か確認したいことだったり、最後リリース前の検証などのメモをかけるものになっている。何も埋めなかったら、そのブランチの最新のコミットメッセージが入る。 |
実行時間としては、 iOS は30 ~ 40分、Android は20分ほどで終わる。他にも iOS の機能だったり使用するライブラリが増えたりすると、さらに時間がかかる。
name: Deploy Staging iOS
on:
workflow_dispatch:
inputs:
branche:
description: "Set the branch to build."
required: true
default: "develop"
releaseNotes:
description: "release notes"
default: ""
jobs:
build:
runs-on: macos-14
timeout-minutes: 60
steps:
- name: Select Xcode version
run: sudo xcode-select -s '/Applications/Xcode_15.3.app/Contents/Developer'
- uses: actions/checkout@v3
with:
fetch-depth: 1
ref: ${{ github.event.inputs.branche }}
- name: make ssh directory
run: |
mkdir -p $HOME/.ssh/
- name: checkout module-core
env:
TOKEN: ${{ secrets.SUBMODULE_ACCESSKEY }}
run: |
echo -e "$TOKEN" > $HOME/.ssh/id_module_core_rsa
chmod 600 $HOME/.ssh/id_module_core_rsa
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/id_module_core_rsa"
git submodule update --init --force --recursive lib_core
- name: Create dot env file
run: |
touch .env
echo "${{ secrets.SECRETS_ENV }}" >> .env
- name: Import Provisioning Profile
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo '${{ secrets.PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/apps\ dev.mobileprovision
echo '${{ secrets.NOTIFICATION_SERVICE_PROVISIONING_PROFILE }}' | base64 -d > ~/Library/MobileDevice/Provisioning\ Profiles/dev\ ImageNotification.mobileprovision
- name: Import Code-Signing Certificates
uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- uses: kuhnroyal/flutter-fvm-config-action@v2
id: fvm-config-action
- uses: subosito/[email protected]
with:
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
- name: flutter dependencies install
run: flutter pub get
- name: build ipa
run: flutter build ipa --export-options-plist=ios/ExportOptions/stg.plist --release --dart-define-from-file=flavor/STAGING.json
- name: Upload artifact
uses: actions/[email protected]
with:
name: ios
path: /Users/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/build/ios/ipa/module_app.ipa
if-no-files-found: error
deploy:
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Download artifact
uses: actions/[email protected]
with:
name: ios
path: ios
- name: Firebase App Distribution
uses: wzieba/[email protected]
with:
appId: ${{secrets.FIREBASE_APP_ID_IOS}}
token: ${{secrets.FIREBASE_TOKEN}}
groups: groups
file: ios/module_app.ipa
releaseNotes: ${{ github.event.inputs.releaseNotes || github.event.head_commit.message }}
- name: Delete artifact
uses: geekyeggo/delete-artifact@v1
with:
name: ios
name: Deploy Staging Android
on:
workflow_dispatch:
inputs:
branche:
description: "Set the branch to build."
required: true
default: "develop"
releaseNotes:
description: "release notes"
default: ""
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
ref: ${{ github.event.inputs.branche }}
- name: make ssh directory
run: |
mkdir -p $HOME/.ssh/
- name: checkout module-core
env:
TOKEN: ${{ secrets.SUBMODULE_ACCESSKEY }}
run: |
echo -e "$TOKEN" > $HOME/.ssh/id_module_core_rsa
chmod 600 $HOME/.ssh/id_module_core_rsa
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/id_module_core_rsa"
git submodule update --init --force --recursive lib_core
- name: Create dot env file
run: |
touch .env
echo "${{ secrets.SECRETS_ENV }}" >> .env
- name: Create key.properties
run: |
echo ${{ secrets.ANDROID_KEY_JKS }} | base64 -d > android/upload_key.jks
touch android/key.properties
echo "${{ secrets.ANDROID_KEY_PROPERTIES }}" >> android/key.properties
- name: install java 17.x
uses: actions/setup-java@v4
with:
distribution: "zulu"
java-version: "17.x"
- name: setup cache
uses: actions/cache@v1
with:
path: /Users/runner/hostedtoolcache/flutter
key: ${{ runner.OS }}-flutter-install-cache
- uses: kuhnroyal/flutter-fvm-config-action@v2
id: fvm-config-action
- uses: subosito/[email protected]
with:
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
- name: flutter dependencies install
run: flutter pub get
- name: build apk
run: flutter build apk --release --dart-define-from-file=flavor/STAGING.json
env:
JAVA_OPTS: "-Xmx7G"
- name: Upload artifact
uses: actions/[email protected]
with:
name: android
path: build/app/outputs/flutter-apk/app-release.apk
deploy:
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Download artifact
uses: actions/[email protected]
with:
name: android
path: android
- name: upload artifact to Firebase App Distribution
uses: wzieba/[email protected]
with:
appId: ${{secrets.FIREBASE_APP_ID_ANDROID}}
token: ${{secrets.FIREBASE_TOKEN}}
groups: groups
file: android/app-release.apk
releaseNotes: ${{ github.event.inputs.releaseNotes || github.event.head_commit.message }}
- name: Delete artifact
uses: geekyeggo/delete-artifact@v1
with:
name: android
iOS と Android の両方とも App Distribution のページでダウンロードする。
招待メールの中のリンクを踏み、基本的にこの iOS Firebase App Distribution を使用して iOS アプリを配布するの記事に書いてあることに従う。ダウンロードしたプロファイルがスマホの中に入れば問題なし。
[iOS] Firebase App Distributionを使用してiOSアプリを配布する | DevelopersIO
https://dev.classmethod.jp
特にやることなく、メールの中にリンクを踏むだけ問題なし。
お疲れ様でした。