anz blog

Flutter プロジェクトを GitHub Actions を使って AppStoreConnect へアップロードする

Flutter プロジェクトを手動でビルドして AppStoreConnect へアップロードしているのでいい加減なんとかしたいということで、GitHub Actions を使って自動化することに。

やりたいこと

  • GitHub Actions をつかって Flutter プロジェクトをビルドして AppStoreConnect へアップロードする
  • 自動署名(Automatically manage signing)は有効のまま

これらを前提としてあとは以下の特徴があるプロジェクト

  • Firebase を採用している
  • 広告SDKも採用している

GitHub Actions

基本的には上記記事の通り。
あとは少し改善できるところがあったので改善したのと、こちらのプロジェクト特有のもの(Firebaseなど)で必要になったステップを追加したぐらい。
軽く考え方を雑にまとめると以下の通り。

  • Automatically manage signing を有効のまま flutter build を実行すると、マシンには署名情報がないのでエラーになる
  • なので --no-codesign オプションを付けてビルドをして後で手動で AppStoreConnect API を利用して Cloud Signing で署名する
  • ただ --no-codesign オプションをつけるとプッシュ通知の証明書が含まれない
  • そこで自分で Arcive にプッシュ通知の証明書を追加する必要がある

という流れ。
Flutter, GitHub Actions, AppStoreConnect という感じで雑に検索すると自動署名をOFFにして、プロビジョニングプロファイル何かを利用するというのが上がってくるけれど
さすがに今頃自動署名を切るのはありえないし、純粋に年一で更新が必要になるのも面倒くさいので「やりたいこと」にも書いた通り外せない条件。
そうなってくると以外に参考になる記事は少なかったりするので、本当に上記にあげた記事は助けられた。

そんなこんなで最終的に落ち着いたのが以下

name: Build and Deploy

on:
  push:
    branches: [ "main" ]

jobs:
  deliver:
    name: Build and Deploy to AppStoreConnect
    runs-on: macos-latest
    timeout-minutes: 20

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Switch Xcode version
        run: sudo xcode-select -s /Applications/Xcode_16.2.app

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: 'stable'
          flutter-version: '3.24.4'
          cache: true

      - name: Install dependencies & run build_runner
        run: |
          flutter pub get
          dart run build_runner build -d

      - name: Setup Flutterfire CLI
        run: dart pub global activate flutterfire_cli

      # Cloud signingを使って署名するのでここでは no-codesign をつけてビルドだけ
      - name: Build
        run: flutter build ipa --no-codesign

      # 署名せずに archive した場合プッシュ通知の証明書が含まれないので手動で追加する
      - name: Add APNS Entitlements
        run: |
          codesign \
          --entitlements ios/Runner/Runner.entitlements \
          --force \
          --sign "-" build/ios/archive/Runner.xcarchive/Products/Applications/Runner.app

      # dSYMs を Artifacts にあげておく
      - name: Upload dSYMs
        uses: actions/upload-artifact@v4
        with:
          name: dsyms
          path: build/ios/archive/Runner.xcarchive/dSYMs
          retention-days: 7

      - name: Extract App Store Connect API Private Key
        env:
          APPSTORECONNECT_API_KEY_ID: ${{ secrets.APPSTORECONNECT_API_KEY_ID }}
          APPSTORECONNECT_API_AUTHKEY_P8: ${{ secrets.APPSTORECONNECT_API_AUTHKEY_P8 }}
        run: |
          mkdir -p ./private_keys
          echo -n "$APPSTORECONNECT_API_AUTHKEY_P8" | base64 --decode --output ./private_keys/AuthKey_$APPSTORECONNECT_API_KEY_ID.p8

      - name: Deploy AppStoreConnect with ExportOptions.plist
        env:
          APPSTORECONNECT_API_KEY_ID: ${{ secrets.APPSTORECONNECT_API_KEY_ID }}
          APPSTORECONNECT_API_ISSUER_ID: ${{ secrets.APPSTORECONNECT_API_ISSUER_ID }}
        run: |
          xcodebuild -exportArchive \
            -archivePath build/ios/archive/Runner.xcarchive \
            -exportOptionsPlist ios/ExportOptions.plist \
            -exportPath build/ios/ipa \
            -allowProvisioningUpdates \
            -authenticationKeyID $APPSTORECONNECT_API_KEY_ID \
            -authenticationKeyIssuerID $APPSTORECONNECT_API_ISSUER_ID \
            -authenticationKeyPath `pwd`/private_keys/AuthKey_$APPSTORECONNECT_API_KEY_ID.p8

要所にはコメントもあるのでなんのためのステップかはわかるだろうけれど、自分がハマったところや少し工夫したところを少し補足説明。

Xcode のバージョンを指定する

Flutter で広告も導入しているのだけれど、Xcode のバージョンが少し違うだけでビルドが通らなかったりするので、ローカルの環境と完全に一致させるために指定している。

- name: Switch Xcode version
  run: sudo xcode-select -s /Applications/Xcode_16.2.app

Gtihub さんが用意している macOS にはデフォルトでいくつかのXcodeバージョンが入っているので結構切り替えられる。
ここ に macOS として指定できるイメージリストがあるのでそこを参照するといい。
みてて気づいたのが、Xcodes が導入されているんだっていうこと。今回は使わなかったけどいつかつかうかもしれない(笑)

Firebase用のセットアップ

Firebase を採用していて ios 向けのビルドをすると Build Phase に仕込まれている Script が動いて flutterfire コマンドが必要になるので、それ用のセットアップが必要になる。

- name: Setup Flutterfire CLI
  run: dart pub global activate flutterfire_cli

それがこれ。ちなみに Firebase CLI 自体はいらない。それとは違う話。

Flutter の iOS 向けビルド

上記にのせた記事だと build と archive を別ステップとして実行していたけれど、flutter コマンドでまとめられるのでそこはまとめる

# Cloud signingを使って署名するのでここでは no-codesign をつけてビルドだけ
- name: Build
  run: flutter build ipa --no-codesign

flutter build ios ではなくて flutter build ipa までやると archive までやってくれる。

dSYMs のアップロード

これは必須ではない。何かのあれで Firebase Crashlytcs に dSYMs がアップロードできなかったときのために保険的に取っておいてるぐらい

# dSYMs を Artifacts にあげておく
- name: Upload dSYMs
  uses: actions/upload-artifact@v4
  with:
    name: dsyms
    path: build/ios/archive/Runner.xcarchive/dSYMs
    retention-days: 7

AppStoreConnect API 利用まわり

yaml から離れて AppStoreConnect API を利用するときの注意点。
APIキーを生成するときのロールは admin であること。
必要以上な権限を与えたくないので、最初は developer から始めて次に appManager を試してみたけれど、いずれもアップロードステップでエラーになる。
developer ではそもそも API でアップロードできる権限がないし、 appManager では、Cloude signing を利用できないため。


とま、こんな感じでなんとかビルドからアップロードまで自動化できた。
他にも改善できるところとか、より良い方法があればぜひおしえてもらえると!(笑)