🐮

FlutterアプリのCDの備忘録

2024/08/30に公開

Firebaseでテストアプリを配布するためのCDをGitHub Actionsで構築したので備忘録としての記事です。Automatically manage signingに対応しています。

workflow

iOS用
name: "[DEV] Build and Publish iOS"

on:
  push:
    # developブランチへのpushがトリガー
    branches: [develop]

jobs:
  build:
    runs-on: macos-latest
    # buildが止まらなくなると困るので30分を指定
    timeout-minutes: 30
    # GitHubのシークレットの環境名
    environment: 
      name: development
    steps:
      # repositoryをセットアップ
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Extract App Store Connect API Private Key in ./private_keys
        env:
          IOS_API_KEY_ID: ${{ secrets.IOS_API_KEY_ID }}
          IOS_API_AUTHKEY_P8_BASE64: ${{ secrets.IOS_API_AUTHKEY_P8_BASE64 }}
        run: |
          mkdir ./private_keys
          echo -n "$IOS_API_AUTHKEY_P8_BASE64" | base64 --decode --output ./private_keys/AuthKey_$IOS_API_KEY_ID.p8
      # flutterをセットアップ
      - name: Flutter get
        uses: subosito/flutter-action@v2
        with:
          channel: stable
          flutter-version: latest
          cache: true

      - name: Check version
        run: flutter --version

      # Packageをセットアップ
      - name: Setup packages
        run: |
          flutter pub get
          dart run build_runner build -d
      # Firebaseをセットアップ
      - name: Setup Firebase Project
        env:
          FIREBASE_PROJ_NAME: ${{ secrets.FIREBASE_PROJ_NAME }}
          FIREBASE_AUTH_TOKEN: ${{ secrets.FIREBASE_AUTH_TOKEN }}
        run: |
          curl -sL https://firebase.tools | bash
          dart pub global activate flutterfire_cli
          # i=iOS a=Android m=macOS w=Web x=Windows (--platformsにプラットフォームを指定していても、-mや-xの指定もないとエラーになるため不要でも各プラットフォームごとのパッケージ名を入れる。
          flutterfire configure -p $FIREBASE_PROJ_NAME -y --platforms "ios" -i "com.example.example-app" -a "com.example.example-app" -m "com.example.example-app"  -w "1:XXX:web:YYY" -x "1:XXX:web:YYY" -t $FIREBASE_AUTH_TOKEN -f > null
      - name: Run flutter build
        id: build
        run: flutter build ipa --release --no-codesign --dart-define-from-file=lib/flavors/json/${{ secrets.FLAVOR_ENV_NAME }}.json --build-number=$GITHUB_RUN_NUMBER

      # Automatically manage signing でアーカイブ
      - name: Archive by xcodebuild
        env:
            IOS_API_ISSURE_ID: ${{ secrets.IOS_API_ISSURE_ID }}
            IOS_API_KEY_ID: ${{ secrets.IOS_API_KEY_ID }}
        run: xcodebuild archive CODE_SIGNING_ALLOWED=NO -workspace ./ios/Runner.xcworkspace -scheme Runner -configuration Release -archivePath ./build/ios/Runner.xcarchive

      # IPAをbuild
      - name: Export by xcodebuild
        env:
          IOS_API_ISSURE_ID: ${{ secrets.IOS_API_ISSURE_ID }}
          IOS_API_KEY_ID: ${{ secrets.IOS_API_KEY_ID }}
          # ローカルでarchiveしたときに一緒に生成されるExportOptions.plistの中身をbuildに使用する。
          # 手動サインインをしたときのExportOptionsにしないとCD中にエラーが出るため注意。
          EXPORT_OPTIONS: ${{ secrets.EXPORT_OPTIONS }}
        run: |
          echo $EXPORT_OPTIONS > ios/Runner/ExportOptions.plist
          xcodebuild -exportArchive -archivePath ./build/ios/Runner.xcarchive -exportPath ./build/ios/ipa -exportOptionsPlist ./ios/Runner/ExportOptions.plist -allowProvisioningUpdates -authenticationKeyIssuerID $IOS_API_ISSURE_ID -authenticationKeyID $IOS_API_KEY_ID -authenticationKeyPath `pwd`/private_keys/AuthKey_$IOS_API_KEY_ID.p8
      # macos上ではFirebase配布のActionを実行できないためipaファイルをartifacts storageに一時保存する
      - name: Collect ipa artifacts
        uses: actions/upload-artifact@v2
        with:
          name: release-ipa
          path: build/ios/ipa/*.ipa

  release:
    name: Release ipa to Firebase
    needs: [build]
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment: 
      name: development
    steps:
      - uses: actions/checkout@v4

      # artifacts storageからipaファイルをダウンロード
      - name: Get release-ipa from artifacts
        uses: actions/download-artifact@v2
        with:
          name: release-ipa

      # Firebase Distributionにアップロード
      - name: Upload artifact to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
        # firebaseプロジェクトのID
          appId: ${{ secrets.FIREBASE_IOS_ID }}
          # firebaseのサービスユーザーのcredential json
          serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
          # firebaseテスターのグループ
          groups: developer
          file: ${{ secrets.FLAVOR_ENV_NAME }}.ipa
          # マージしたブランチ名をリリースノートに設定
          releaseNotes: ${{ github.ref_name }}
          # 詳細なログ出力
          debug: true
Android
name: "[DEV] Build and Publish Android"

on:
  push:
    branches: [develop]

jobs:
  build:
    runs-on: ubuntu-latest
    # buildが止まらなくなると困るので30分を指定
    timeout-minutes: 30
    # GitHubのシークレットの環境
    environment: 
      name: development
    steps:
      # repositoryをセットアップ(actionsを使う場合は必須)
      - name: Setup repository
        uses: actions/checkout@v4

      # 実装時点のjavaの最新ver
      - uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: "17.x"

      # flutterをセットアップ
      - name: Setup flutter
        uses: subosito/flutter-action@v2
        with:
          channel: stable
          flutter-version: latest
          cache: true

      - name: Check version
        run: flutter --version

      # Packageをセットアップ
      - name: Setup packages
        run: |
          flutter pub get
          dart run build_runner build -d
      # Firebase Projectをセットアップ
      - name: Setup Firebase Project
        env:
          FIREBASE_PROJ_NAME: ${{ secrets.FIREBASE_PROJ_NAME }}
          FIREBASE_AUTH_TOKEN: ${{ secrets.FIREBASE_AUTH_TOKEN }}
        run: |
          curl -sL https://firebase.tools | bash
          dart pub global activate flutterfire_cli
          # i=iOS a=Android m=macOS w=Web x=Windows (--platformsにプラットフォームを指定していても、-mや-xの指定もないとエラーになるため不要でも各プラットフォームごとのパッケージ名を入れる。
          flutterfire configure -p $FIREBASE_PROJ_NAME -y --platforms "ios, android" -i "com.example.example-app" -a "com.example.example-app" -m "com.example.example-app"  -w "1:XXX:web:YYY" -x "1:XXX:web:YYY" -t $FIREBASE_AUTH_TOKEN > null
      # key.propertiesを出力
      - name: Create key.properties
        run: |
          mkdir -p android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}
          echo ${{ secrets.ANDROID_KEY_JKS }} | base64 -d > android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/release.jks
          echo 'storeFile=keystore/${{ secrets.FLAVOR_ENV_NAME }}/release.jks' >> android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/key.properties
          echo 'storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}' >> android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/key.properties
          echo 'keyPassword=${{ secrets.ANDROID_ALIAS_PASSWORD }}' >> android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/key.properties
          echo 'keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}' >> android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/key.properties
          cat android/app/keystore/${{ secrets.FLAVOR_ENV_NAME }}/key.properties
      # apkファイルを生成
      - name: Build APK
      # ローカルではflavorでbuildしているため
        run: flutter build apk --release --build-number=$GITHUB_RUN_NUMBER --dart-define-from-file=lib/flavors/json/${{ secrets.FLAVOR_ENV_NAME }}.json

      # Firebase Distributionにアップロード
      - name: Upload apk to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          # firebaseプロジェクトのID
          appId: ${{ secrets.FIREBASE_ANDROID_ID }}
          # firebaseのサービスユーザーのcredential json
          serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
          # firebaseテスターのグループ
          groups: developer
          file: ./build/app/outputs/flutter-apk/app-release.apk
          releaseNotes: ${{ github.ref_name }}
          # 詳細なログ出力
          debug: true
Version up
name: bump

on:
  workflow_dispatch:
    inputs:
      bump:
        type: choice
        description: Please Choice Bump Target
        options:
          - build
          - patch
          - minor
          - major

env:
  GIT_USER_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com'
  GIT_USER_NAME: 'github-actions[bot]'

permissions:
  contents: write

jobs:
  bump:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      # repositoryをセットアップ
      - name: Checkout repository
        uses: actions/checkout@v4

      # flutterをセットアップ
      - name: Flutter get
        uses: subosito/flutter-action@v2
        with:
          channel: stable
          flutter-version: latest
          cache: true

      - name: Run flutter version
        run: flutter --version

      # Packageをセットアップ
      - name: Setup packages
        run: |
          flutter pub get
          dart run build_runner build -d
      - name: Init git config
        run: |
          git config --local user.name $GIT_USER_NAME
          git config --local user.email $GIT_USER_EMAIL
      - name: Bump up version
        run: |
          echo choice: ${{ github.event.inputs.bump }}
          flutter pub run cider bump ${{ github.event.inputs.bump }} --bump-build
          echo "BUMP_VERSION=$(flutter pub run cider version)" >> $GITHUB_ENV
      - name: Commit and push pubspec.yaml
        run: |
          git add -u pubspec.yaml
          echo "Bumped version number to $BUMP_VERSION" | git commit --file=-
          git push

各種環境変数一覧

環境変数名 対象OS 説明
ANDROID_ALIAS_PASSWORD Android Android エイリアスパスワード
ANDROID_KEY_ALIAS Android Android keyエイリアス
ANDROID_KEY_JKS Android base64エンコード済みAndroid key jks android/app/key.jks
ANDROID_STORE_PASSWORD Android Android ストアパスワード
CREDENTIAL_FILE_CONTENT Android/iOS base64エンコード済みfirebaseサービスユーザーcredential json
DANGER_GITHUB_API_TOKEN Android/iOS Danger用のGit Hub APIトークン
EXPORT_OPTIONS iOS ローカルでarchiveしたときに一緒に生成されるExportOptions.plistをbase64にエンコードし設置
FIREBASE_ANDROID_ID Android firebase プロジェクト設定のアプリ ID(1:から始まる値)
FIREBASE_AUTH_TOKEN Android/iOS Firebaseのトークン情報
FIREBASE_IOS_ID iOS firebase プロジェクト設定のアプリ ID(1:から始まる値)
FIREBASE_PROJ_NAME Android/iOS firebaseのプロジェクト名
FLAVOR_ENV_NAME Android/iOS buildしたい環境名(Flavorと環境名を統一しているため)
IOS_API_AUTHKEY_P8_BASE64 iOS AppStoreConnectAPIで払い出したAPI情報(base64エンコード済みp8ファイル)
IOS_API_ISSURE_ID iOS AppStoreConnectAPIで払い出したAPI情報(IssueID)
IOS_API_KEY_ID iOS AppStoreConnectAPIで払い出したAPI情報(API key)

参考記事

https://zenn.dev/yorifuji/articles/build-automatically-manage-singin-on-ci
https://zenn.dev/shima999ba/articles/ae1fc477744e2a
https://zenn.dev/yass97/articles/e8d1e460ae6a59
https://zenn.dev/yorifuji/articles/flutter-github-actions-template

Discussion