前の記事でローカルから fastlane ios beta を実行して TestFlight にアップできるようにしました。
同じ Fastfile を使って、今度は GitHub Actions から自動でデプロイできる環境を整えた話です。
CI ワークフロー(ios-ci.yml)
PR や main プッシュで Dart/Flutter のチェックと iOS の unsigned ビルドを行います。
name: iOS CI
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ios-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
dart-checks:
name: Format, Analyze, Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: dart format --output=none --set-exit-if-changed lib test
- run: flutter analyze lib test
- run: flutter test
ios-build:
name: iOS Build (unsigned)
runs-on: macos-latest
needs: dart-checks
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Build iOS (no codesign)
run: |
flutter pub get
flutter build ios --release --no-codesignポイントは2つ。
dart-checksは ubuntu で実行しています。
Linux でも Flutter の解析・テストは動くので、macOS ランナーより高速かつ安価。ios-buildはneeds: dart-checksで依存を張っているので、フォーマットやテストが落ちたら iOS ビルドは走りませんconcurrencyのcancel-in-progress: trueで、同じブランチへの連続プッシュ時に古い実行をキャンセルします
デプロイワークフロー(ios-testflight.yml)
deploy ブランチへのプッシュか v* タグで TestFlight までデプロイします。
name: iOS TestFlight
on:
workflow_dispatch:
push:
branches:
- deploy
tags:
- "v*"
permissions:
contents: read
concurrency:
group: ios-testflight
cancel-in-progress: false
jobs:
testflight:
name: Build and upload to TestFlight
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: 🦋 Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
- name: 📥 Get dependencies
run: flutter pub get
- name: ✅ Run tests
run: flutter test --coverage
- name: 🔨 Pre-build iOS
run: flutter build ios --release --no-codesign
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
bundler-cache: true
working-directory: ios
- name: Deploy to TestFlight
working-directory: ios
env:
CI: "true"
APP_STORE_CONNECT_APPLE_ID: ${{ secrets.APP_STORE_CONNECT_APPLE_ID }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
run: bundle exec fastlane ios beta
- name: Upload gym logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gym-logs
path: ~/Library/Logs/gym/
- name: Upload IPA artifact
uses: actions/upload-artifact@v4
with:
name: app-ipa
path: ios/build/*.ipaいくつか気にしたところ。
まずはローカルで、envファイルなどを作ってからghaでテストを推奨
まあ、基本的なことなのですがghaで環境変数などを設定して、でいきなりやろうとすると混乱します。
なのでローカルで確実に.envファイルなどを使って動くことを確認してからが最適だと思います。
ローカルでテストする記事はこちら
ログとIPAをアーティファクトに保存
失敗時に gym のログを、成功時に IPA をアーティファクトとして保存しています。失敗したときのデバッグに便利です。
Secrets の設定
リポジトリの Settings → Secrets and variables → Actions に以下を登録します。
| Secret 名 | 値 |
|---|---|
APP_STORE_CONNECT_APPLE_ID | Apple ID のメールアドレス |
APP_STORE_CONNECT_TEAM_ID | Apple Developer のチームID |
APP_STORE_CONNECT_API_KEY_ID | API キーのID(XXXXXXXXXX の部分) |
APP_STORE_CONNECT_ISSUER_ID | Issuer ID(UUID形式) |
APP_STORE_CONNECT_API_KEY_CONTENT | .p8 ファイルを Base64 エンコードした文字列 |
MATCH_GIT_URL | 証明書リポジトリの URL |
MATCH_PASSWORD | match のパスフレーズ |
MATCH_GIT_BASIC_AUTHORIZATION | user:PAT を Base64 エンコードした文字列 |
APP_STORE_CONNECT_API_KEY_CONTENT は .p8 ファイルを Base64 に変換したものです。
base64 -i AuthKey_XXXXXXXXXX.p8 | tr -d '\n'MATCH_GIT_BASIC_AUTHORIZATION は match が証明書リポジトリにアクセスするための認証情報です。
echo -n "yourname:ghp_xxxxxxxxxxxx" | base64デプロイのトリガー方法
deploy ブランチへのプッシュ
main の内容を deploy ブランチにマージ(またはプッシュ)するとデプロイが走ります。
git checkout deploy
git merge main
git push origin deployバージョンタグを打つ
v プレフィックスのタグでもトリガーされます。
git tag v1.0.1
git push origin v1.0.1手動トリガー
workflow_dispatch を設定しているので、GitHub の Actions タブから「Run workflow」ボタンでいつでも手動実行できます。
まとめ
ローカルで一回やって終えばGHA上では特に困ることはありませんでした。
ランナーの時間制限に無料プランだと速攻でヒットしそうです。。。
次の記事では Xcode Cloud を使った構成を紹介します。
GHA と違い、macOS ランナー代がかからない点が魅力です。
ただ、xcode cloudはなんかよくわからんエラーが連発したので正直安定しているのかが不明です💦
→ Flutter × Xcode Cloud でビルドする