リンクをコピーしました

前の記事でローカルから 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-checksubuntu で実行しています。
    Linux でも Flutter の解析・テストは動くので、macOS ランナーより高速かつ安価。
  • ios-buildneeds: dart-checks で依存を張っているので、フォーマットやテストが落ちたら iOS ビルドは走りません
  • concurrencycancel-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_IDApple ID のメールアドレス
APP_STORE_CONNECT_TEAM_IDApple Developer のチームID
APP_STORE_CONNECT_API_KEY_IDAPI キーのID(XXXXXXXXXX の部分)
APP_STORE_CONNECT_ISSUER_IDIssuer ID(UUID形式)
APP_STORE_CONNECT_API_KEY_CONTENT.p8 ファイルを Base64 エンコードした文字列
MATCH_GIT_URL証明書リポジトリの URL
MATCH_PASSWORDmatch のパスフレーズ
MATCH_GIT_BASIC_AUTHORIZATIONuser: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 でビルドする

関連リンク