GitHub ActionsでFlutter macOSアプリをTestFlightにのせる
はじめに
Fluterで『Tokeru』という名前のmacOSのプロダクトを開発しています。
GitHub ActionsからTestFlightにアップロードするところまでを取り入れてみたので記事に残しておこうかなと思います。
どハマりして1日悩んだ箇所もあるので、参考になると嬉しいです。
環境変数を設定すればあとはコピペで行けるんじゃないかなと思います。
このActionsはTokeruのリポジトリで使っています。
Tokeruは絶賛Beta版です!
誰でも触ることができるので、ぜひTestFlightからインストールしてみてください。
環境
- Flutter 3.16.9
- Flutterのバージョン管理にはFVMを使用
ローカルでの準備
pip3 install codemagic-cli-tools
今回はCodemagicのCLIツールを使うため、ローカルでもpipでインストールしておきます。
pip3 install codemagic-cli-tools
秘密鍵の作成
以下をコマンドで実行するとcert_key
というファイルができるので、後にSecretに入れるため残しておきます。
<certificate_name>.p12
はDistribution用のp12ファイルをキーチェーンからexportたものに置き換えてください。
openssl pkcs12 -in <certificate_name>.p12 -nodes -nocerts | openssl rsa -out cert_key
Mac インストーラー配布証明書の作成
作成したcert_keを使ってMac インストーラー配布証明書を作成します。
ローカルの~/Library/MobileDevice/Certificates
にも証明書が作成されます。
GitHub Actions上で作成することもできますが、実行のたびに増えていってしまうので、1度ローカルで作ってしまってActions上では取得するだけにします。
app-store-connect certificates create \
--type MAC_INSTALLER_DISTRIBUTION \
--certificate-key=@file:./cert_key \
--save
GitHubのSecretを設定する
Actions内で必要なSecretをGitHubに設定してきます。
「Settings > secrets and variables > Actions」で設定できます。
CERT_KEY
先ほど作成したcert_key
の中身をそのまま作成します。
APP_BUNDLE_ID
BUNDLE ID: アプリのbundle idを設定します。
APPLE_APP_ID
APPLE ID: App Store ConnectからApple idを取得し、設定します。
App Store Connect APIまわり
App Store ConnectからAPI Keyを作成し設定します。
権限はApp Manger
以上で作成します
-
APP_STORE_CONNECT_ISSUER_ID
: Issuer ID -
APP_STORE_CONNECT_KEY_IDENTIFIER
: Key ID -
APP_STORE_CONNECT_PRIVATE_KEY
: p8ファイルの中身
Secretは以上です。
次は実際のymlファイルを見ていきます。
GitHub Actionsの中身
忙しい人のために最終的なymlファイルを先に載せておきます。
この後にそれぞれの説明を書いていこうと思います。
name: Build and Release macOS App
on:
workflow_dispatch:
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
APP_STORE_CONNECT_KEY_IDENTIFIER: ${{ secrets.APP_STORE_CONNECT_KEY_IDENTIFIER }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
APP_BUNDLE_ID: ${{ secrets.APP_BUNDLE_ID }}
APPLE_APP_ID: ${{ secrets.APPLE_APP_ID }}
jobs:
deploy_macos_app:
runs-on: macos-latest
steps:
- name: "Checkout"
uses: actions/checkout@v4
- name: "setup java"
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "12.x"
- name: "Read flutter version from fvm config"
id: flutter_info
run: |
FLUTTER_VERSION=$(jq -r '.flutterSdkVersion' ./.fvm/fvm_config.json)
echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV
shell: bash
- name: "Setup Flutter"
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: "Install codemagic-cli-tools"
run: |
pip3 install codemagic-cli-tools
- name: "Install the Apple certificate"
run: |
echo -n "$CERT_KEY" >> $RUNNER_TEMP/cert_key
- name: "Install the Apple provisioning profile(MAC_APP_STORE)"
run: |
app-store-connect fetch-signing-files "$APP_BUNDLE_ID" \
--platform MAC_OS \
--type MAC_APP_STORE \
--certificate-key=@file:$RUNNER_TEMP/cert_key \
--create
- name: "Install the Certificates(MAC_INSTALLER_DISTRIBUTION)"
run: |
app-store-connect certificates list \
--type MAC_INSTALLER_DISTRIBUTION \
--certificate-key=@file:$RUNNER_TEMP/cert_key \
--save
- name: "keychain initialize"
run: |
keychain delete
keychain initialize
- name: "keychain add-certificates"
run: |
keychain add-certificates
- name: "xcode-project use-profiles"
run: |
/usr/bin/plutil -replace CFBundleIdentifier -string $APP_BUNDLE_ID ios/Runner/Info.plist
find **/*.xcodeproj -type f | xargs sed -i "" -E 's/PRODUCT_BUNDLE_IDENTIFIER = ".+";//g'
xcode-project use-profiles
- name: Build macOS app
run: |
build_number=$(app-store-connect get-latest-build-number $APPLE_APP_ID)
new_build_number=$((build_number + 1))
flutter build macos --dart-define-from-file=dart_defines/prod.json --build-number=10039
- name: "Package macOS app"
run: |
APP_NAME=$(find $(pwd) -name "*.app")
PACKAGE_NAME=$(basename "$APP_NAME" .app).pkg
echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV
echo "PACKAGE_NAME=$PACKAGE_NAME" >> $GITHUB_ENV
xcrun productbuild --component "$APP_NAME" /Applications/ unsigned.pkg
INSTALLER_CERT_NAME=$(keychain list-certificates \
| jq '[.[]
| select(.common_name
| contains("Mac Developer Installer"))
| .common_name][0]' \
| xargs)
xcrun productsign --sign "$INSTALLER_CERT_NAME" unsigned.pkg "$PACKAGE_NAME"
rm -f unsigned.pkg
- name: "Upload TestFlight"
run: |
app-store-connect publish --path "$PACKAGE_NAME" --testflight --beta-group=group01
Actionsの説明
それぞれを説明していきたいと思います。
トリガー
トリガーをworkflow_dispatch
にしています。
正直ここはプロジェクトによるので深掘りしません。お好きなトリガーを設定してあげてください。
on:
workflow_dispatch:
runs-on
当たり前なのですが、macOSアプリをビルドするのでubuntuではなくmacosを使います。
runs-on: macos-latest
Setup Flutterまで
TokeruはFVMでバージョン管理しているので、fvm_config.json
からバージョンを取得し、flutterをインストールしています。
ここはご自身の環境によって変えてください。(flutter analyzeやflutter testを実行してたらすでにあるとは思いますが)
- name: "Read flutter version from fvm config"
id: flutter_info
run: |
FLUTTER_VERSION=$(jq -r '.flutterSdkVersion' ./.fvm/fvm_config.json)
echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV
shell: bash
- name: "Setup Flutter"
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
Install codemagic-cli-tools
GitHub Actions内でもCodemagicのCLIツールを使うため、pipでインストールします。
- name: "Install codemagic-cli-tools"
run: pip3 install codemagic-cli-tools
Install the Apple certificate
secret変数に設定したcert_key
をファイルとして書き出します。
- name: "Install the Apple certificate"
run: |
echo -n "$CERT_KEY" >> $RUNNER_TEMP/cert_key
Install the Apple provisioning profile(MAC_APP_STORE)
MAC_APP_STORE
のプロファイルを取得します。--create
オプションは、もしなければ作成してくれます。
- name: "Install the Apple provisioning profile"
run: |
app-store-connect fetch-signing-files "$APP_BUNDLE_ID" \
--platform MAC_OS \
--type MAC_APP_STORE \
--certificate-key=@file:$RUNNER_TEMP/cert_key \
--create
Install the Certificates(MAC_INSTALLER_DISTRIBUTION)
ローカルで作成した証明書を取得します。
こちらは無くても作成してくれないので、ローカルで先ほど作成しておきました。
- name: "Install the Certificates(MAC_INSTALLER_DISTRIBUTION)"
app-store-connect certificates list \
--type MAC_INSTALLER_DISTRIBUTION \
--certificate-key=@file:$RUNNER_TEMP/cert_key \
--save
xcode-project use-profiles
xcode-project use-profiles
は取得したプロファイル、証明書を参考にproject.pbxproj
ファイルを更新してくれるコマンドです。
ここで注意なのが、xcodeの設定でbundle idをcom.example.${DEFINE_BUILD_ENV}
のような環境ごとに変えるようにしているとxcode-project use-profiles
はbundle idをうまく認識しせず、Did not find matching provisioning profiles for code signing!
となってしまいます。
困ったことにこのケースはfailedにならず、Actionsも最後まで通るのでかなりハマりました。
なので、Info.plist
のAPP_BUNDLE_ID
を直接書き換えています。
- name: "xcode-project use-profiles"
run: |
/usr/bin/plutil -replace CFBundleIdentifier -string $APP_BUNDLE_ID ios/Runner/Info.plist
find **/*.xcodeproj -type f | xargs sed -i "" -E 's/PRODUCT_BUNDLE_IDENTIFIER = ".+";//g'
xcode-project use-profiles
Build macOS app
flutter build macos
を行うのですが、普通にビルドするだけだと後のアップロードでbuild numberのかぶりで失敗します。
なのでapp-store-connect get-latest-build-number
コマンドで最新のbuild numberを取得して、+1した値をbuild-number
オプションに渡してあげています。
- name: Build macOS app
run: |
build_number=$(app-store-connect get-latest-build-number $APPLE_APP_ID)
new_build_number=$((build_number + 1))
flutter build macos --dart-define-from-file=dart_defines/prod.json --build-number=$new_build_number
Package macOS app
.app
ファイルをApp Store Connectにアップロードできる形の.pkg
に変換します。
その後、xcrun productsign --sign
で署名をします。
- name: "Package macOS app"
run: |
APP_NAME=$(find $(pwd) -name "*.app")
PACKAGE_NAME=$(basename "$APP_NAME" .app).pkg
xcrun productbuild --component "$APP_NAME" /Applications/ unsigned.pkg
INSTALLER_CERT_NAME=$(keychain list-certificates \
| jq '[.[]
| select(.common_name
| contains("Mac Developer Installer"))
| .common_name][0]' \
| xargs)
xcrun productsign --sign "$INSTALLER_CERT_NAME" unsigned.pkg "$PACKAGE_NAME"
rm -f unsigned.pkg
Upload TestFlight
最後にアップロードして終わりです。
--testflight
オプションをつけることで外部テスターへの審査を自動で行ってくれます。
また、--beta-group
オプションには追加したい外部テスターグループの名前をつけます。
これで完了です。
- name: "Upload TestFlight"
run: |
app-store-connect publish --path "$PACKAGE_NAME" --testflight --beta-group=group01
さいごに
長くなりましたが最後まで読んでいただきありがとうございます。
XでもFlutter、個人開発周りのことを発信しているのでよかったらフォローしてください。
参考
Discussion