GitHub Actions で Automatically manage signing を使って Flutter の ipa ビルドする

2022/12/31に公開

GitHub Actions で Flutter アプリの TestFlight 用バイナリ(ipa)をビルドする方法を調べると「Automatically manage signing」 を無効にしてビルドする記事が出てきます。
オプションを有効にしたままビルドする方法が見つからなかったので調べました。検証は Flutter で行いましたが、Flutter を使わない iOS ネイティブのプロジェクトでも同様の手順でビルドできると思います。

Automatically manage signing と Cloud signing

Automatically manage signing について改めて調べると、Xcode13 以降では Automatically manage signing を有効にすると Cloud signing という機能が使われるようです。

Cloud signing については WWDC21 のビデオと Xcode13 のリリースノートに記述があります。

https://developer.apple.com/videos/play/wwdc2021/10204/

Xcode 13 Release Notes | Apple Developer Documentation

抜粋

xcodebuild now supports the use of App Store Connect API keys for authentication with the Apple Developer website. This enables the use of automatic signing via xcodebuild in headless environments, such as build machines and continuous integration setups. To use API keys with xcodebuild, create an API key on App Store Connect and pass the key along with its identifier and your team’s issuer identifier to xcodebuild using the new parameters authenticationKeyPath, authenticationKeyID, and authenticationKeyIssuerID, respectively. When creating a key, you can assign it a role to control its permissions for performing automatic signing tasks. To learn more about creating and managing keys, see Creating API Keys for App Store Connect API. (51444716)


xcodebuild は、Apple Developer ウェブサイトでの認証に App Store Connect API キーの使用をサポートするようになりました。これにより、ビルドマシンや継続的インテグレーション設定などのヘッドレス環境において、xcodebuild を介した自動署名の利用が可能になります。xcodebuild で API キーを使用するには、App Store Connect で API キーを作成し、新しいパラメータ authenticationKeyPath, authenticationKeyID, および authenticationKeyIssuerID をそれぞれ使用して、その識別子とあなたのチームの発行者識別子と一緒にキーを xcodebuild に渡します。鍵を作成するとき、自動署名タスクを実行するためのその権限を制御するために、その役割を割り当てることができます。キーの作成と管理について詳しくは、「App Store Connect API の API キーを作成する」を参照してください。(51444716)

Automatic signing in the Xcode distribution assistant now supports cloud signing. With cloud signing, Xcode distribution signs your app using signing certificates created and managed on Apple servers, requiring no setup on your local Mac other than signing in to Xcode with your Apple ID. Cloud signing is available when signing for App Store Connect, Ad Hoc, Enterprise, or Developer ID distribution. Cloud signing certificates are stored securely on Apple servers; you can’t transmit or store the private key on your Mac. Similar to standard distribution signing certificates, cloud signing certificates are accessible only to members of your development team with the Admin role (or Account Holder for Developer ID). Use App Store Connect (Users and Access) to set permissions for users with other roles. You don’t need to save or share cloud signing certificates with other developers on your team, as any team member with the necessary permissions can sign them for distribution with cloud signing. If you already have a valid distribution signing certificate and matching provisioning profiles installed on your Mac, Xcode uses those and signs locally rather than using cloud signing. Additionally, cloud signing isn’t available when using manual distribution signing. (70706409)


Xcode 配布アシスタントでの自動署名は、クラウド署名をサポートするようになりました。クラウド署名では、Xcode 配布は、Apple サーバ上で作成および管理された署名証明書を使用してアプリケーションに署名し、Apple ID で Xcode にサインインする以外に、ローカル Mac でセットアップが必要ではありません。クラウド署名は、App Store Connect、アドホック、エンタープライズ、または開発者 ID の配布のために署名するときに利用可能です。クラウド署名証明書は、Apple のサーバーに安全に保存されます。あなたの Mac 上で秘密鍵を送信または保存することはできません。標準の配布用署名証明書と同様に、クラウド署名証明書は、管理者ロールを持つ開発チームのメンバー(または Developer ID の場合はアカウント所有者)だけがアクセスできます。他のロールを持つユーザーのアクセス許可を設定するには、App Store Connect(ユーザーとアクセス)を使用します。必要な権限を持つチームメンバーであれば、クラウド署名で配布用の署名ができるため、チームの他の開発者とクラウド署名証明書を保存または共有する必要はありません。あなたが既に有効な配布署名証明書と一致するプロビジョニングプロファイルがあなたの Mac にインストールされている場合、Xcode はそれらを使用し、クラウドサインを使用するのではなく、ローカルに署名します。さらに、手動配布署名を使用する場合、クラウド署名は利用できません。(70706409)

簡単に説明すると、配布用バイナリを作成するために従来は配布証明書(Apple Distribution)とプロビジョニングプロファイルを用意する必要がありましたが、Cloud signing を利用すると Apple のサーバー上で署名が行われるのでこれらの作成や管理が不要になります、という内容です。Xcode をローカルで利用する際に GUI から AppleID でログインすると思いますが、ローカルではこの認証情報を利用してサーバー上で署名が行われるようです。そのため、普段の開発では Cloud signing の存在を意識することがありません。
また、Xcode 13 以降では xcodebuild から Apple Developer ウェブサイトとの認証に App Store Connect API を使用することができるようになり、これによってヘッドレスな CI 環境でも Cloud signing の利用がサポートされました。

Automatically manage signing と Cloud signing を利用するモチベーション

Cloud signing を使うと App Store Connect API で払い出した API 情報(IssueID / API Key / .p8 ファイル)だけを用意すれば TestFlight やリリース向けの ipa に署名ができます。そのうえ、App Store Connect API を利用するための API 情報は払い出したあと無期限で利用できるので更新が不要です

一方で、Automatically manage signing を無効にして、従来通りの方法で署名を行う場合は以下の 2 つが必要になります。

  • 配布用証明書(Apple Distribution)
  • 配布用プロビジョニングプロファイル

どちらも 1 年に一度更新が必要です(プロビジョニングプロファイルは配布用証明書に紐づいている)。

署名するためのワークフロー

実際に GitHub Actions で Cloud signing を使って署名をするワークフローです、ワークフローの全体は記事の末尾に掲載しています。

用意するもの

App Store Connect API で払い出した API 情報(IssueID / API Key / .p8)が必要なので以下のような感じで secrets に登録します、APPLE_API_で始まるものが App Store Connect API の API 情報です。ファイルは base64 に変換して文字列として登録します。

画像ではAPPLE_DEVELOPMENT_CERTIFICATE_...で始まる値が登録されていますがこちらは不要です。

App Store Connect API の private key の配置

App Store Connect API の private key (.p8)をローカルに配置します

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

xcrun altool -h の内容を読むと .p8 ファイルの配置場所はいくつかのパターンがあるようです、上記のスクリプトでは ./private_keys 配下に置きました。

--apiKey <api_key>        apiKey. Required for JWT authentication (in lieu of username/password).
                          This option will search the following directories in sequence for a private key file
                          with the name of 'AuthKey_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys',
                          and '~/.appstoreconnect/private_keys'. Additionally, you can set environment variable $API_PRIVATE_KEYS_DIR
                          or a user default API_PRIVATE_KEYS_DIR to specify the directory where your AuthKey file is located.
--apiIssuer <issuer_id>   Issuer ID. Required if --apiKey is specified.

ExportOptions.plist の作成

配布用の ipa をビルドする時はxcodebuild -exportArchiveを使いますが、その際に-exportOptionsPlistで指定するExportOptions.plistを作成します。
ポイントは Cloud signing を利用する際は<key>teamID</key>での teamID が必要です。

<?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>
    <false/>
    <key>manageAppVersionAndBuildNumber</key>
    <false/>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>${APPLE_TEAM_ID}</string>
  </dict>
</plist>

${APPLE_TEAM_ID}はアプリの TeamID に置き換えてください。

Archive and Export by xcodebuild

.xcarchive と .ipa を作成する step です

- name: Run flutter build
    id: build
    run: flutter build ios --release --no-codesign

- name: Archive by xcodebuild
    env:
        APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
        APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
    run: xcodebuild archive CODE_SIGNING_ALLOWED=NO -workspace ./ios/Runner.xcworkspace -scheme Runner -configuration Release -archivePath ./build/ios/Runner.xcarchive

- name: Export by xcodebuild
    env:
        APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
        APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
    run: xcodebuild -exportArchive -archivePath ./build/ios/Runner.xcarchive -exportPath ./build/ios/ipa -exportOptionsPlist ./ios/ExportOptions.plist -allowProvisioningUpdates -authenticationKeyIssuerID $APPLE_API_ISSUER_ID -authenticationKeyID $APPLE_API_KEY_ID -authenticationKeyPath `pwd`/private_keys/AuthKey_$APPLE_API_KEY_ID.p8

最初に flutter build ios で Flutter をビルドしますがこの段階で署名をするとエラーになるので --no-codesign オプションをつけて無効にしています。

その次の xcodebuild archive ... でアーカイブを作成しますが、コマンドの後半の

CODE_SIGNING_ALLOWED=NO

を指定することで署名を無効化しています、このタイミングでは署名が不要なためです。

最後の xcodebuild -exportArchive で .ipa を作成して、タイミングで配布用の署名が行われます。

Upload

こちらは署名とは関係がありませんが altool と App Store Connect API を使って .ipa をアップロードする処理です。

- name: Upload to App Store Connect
    env:
        APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
        APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
    run: xcrun altool --upload-app --type ios -f $IPA_PATH --apiKey $APPLE_API_KEY_ID --apiIssuer $APPLE_API_ISSUER_ID

ワークフロー(全体)

ワークフロー全体はこちら

name: Automatically manage signing with Cloud signing

on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  ios:
    runs-on: macos-12
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v3

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

      - name: Install Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: "stable"
          cache: true

      - name: Run flutter pub get
        run: flutter pub get

      - name: Run flutter build
        id: build
        run: flutter build ios --release --no-codesign

      - name: Archive by xcodebuild
          env:
              APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
              APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
          run: xcodebuild archive CODE_SIGNING_ALLOWED=NO -workspace ./ios/Runner.xcworkspace -scheme Runner -configuration Release -archivePath ./build/ios/Runner.xcarchive

      - name: Export by xcodebuild
        env:
          APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
        run: xcodebuild -exportArchive -archivePath ./build/ios/Runner.xcarchive -exportPath ./build/ios/ipa -exportOptionsPlist ./ios/ExportOptions.plist -allowProvisioningUpdates -authenticationKeyIssuerID $APPLE_API_ISSUER_ID -authenticationKeyID $APPLE_API_KEY_ID -authenticationKeyPath `pwd`/private_keys/AuthKey_$APPLE_API_KEY_ID.p8

      - name: Detect path for ipa file
        run: |
          echo "IPA_PATH=$(find build/ios/ipa -type f -name '*.ipa')" >> $GITHUB_ENV

      - name: Upload to App Store Connect
        env:
          APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
        run: xcrun altool --upload-app --type ios -f $IPA_PATH --apiKey $APPLE_API_KEY_ID --apiIssuer $APPLE_API_ISSUER_ID

記事が参考になったら Like や Twitter をフォローしてもらえると嬉しいです、

Discussion