大部份由ChatGPT產生及從中學習,另一部份參考自 之前的文章 shell script 包版,及同事的yml sample

dev.yml 如下:


name: "dev.yml"

on:
  # 手動觸發包版
  workflow_dispatch:
  
  # trigger push to develop
  push:
    branches:
      # "develop" changed 包送審
      - 'develop/*' # (PR(s) Merge到 develop後 包版)

  # trigger pull request for all branches
  pull_request:
    types: [opened, synchronize, reopened]
    branches:

      # PR `feature -> develop` 包給QA
      - 'develop' # Branch開頭是 "develop/" 發PR trigger 包版

concurrency:
  group: ${{ github.workflow_ref }}-${{ github.ref }}
  cancel-in-progress: true

env:  # 🌍 全域環境變數
  APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
  APP_STORE_CONNECT_API_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
  APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}

  IOS_APP_NAME: ${{ vars.IOS_APP_NAME }}
  IOS_WORKSPACE_NAME: ${{ vars.IOS_WORKSPACE_NAME }}
  IOS_SCHEME_NAME: ${{ vars.IOS_SCHEME_NAME_DEV }}
  infoPlist: ${{ vars.IOS_INFO_PLIST_NAME_DEV }}

  # For step: `Build archive`, run ci_scripts/ci_pre_xcodebuild.sh from project (set SDK key, SDK secret)
  sdk_key_secret: ${{ secrets.SDK_PRODUCTION_SECRET }}
  
jobs:
  build_and_archive:
    runs-on:
      group: mobile-app-builders
    timeout-minutes: 60  # 設定工作超時限制

    steps:
      - name: Check Xcode version
        run: /usr/bin/xcodebuild -version

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1    # 只取得最新的提交
          fetch-tags: ${{ github.ref_type == 'tag' }}  # 只在處理 tag 時才取得標籤

      # 環境檢查
      - name: Verify development environment
        run: |
          echo "Checking Xcode installation..."
          xcode-select -p
          echo "Checking available devices..."
          xcrun simctl list devices
          echo "Checking Ruby version..."
          ruby --version
          echo "Checking CocoaPods version..."
          pod --version

          projectPath="${PWD}"

          echo "group=${{ github.workflow_ref }}-${{ github.ref }}"
          echo "GITHUB_WORKSPACE=${GITHUB_WORKSPACE}"
          echo "projectPath=${projectPath}"
          echo "RUNNER_TEMP=${RUNNER_TEMP}"
          
          echo "APP_STORE_CONNECT_API_KEY_ID=${APP_STORE_CONNECT_API_KEY_ID}"
          echo "APP_STORE_CONNECT_API_PRIVATE_KEY=${APP_STORE_CONNECT_API_PRIVATE_KEY}"
          echo "APP_STORE_CONNECT_API_ISSUER_ID=${APP_STORE_CONNECT_API_ISSUER_ID}"
          echo "IOS_APP_NAME=${IOS_APP_NAME}"
          echo "IOS_WORKSPACE_NAME=${IOS_WORKSPACE_NAME}"
          echo "IOS_SCHEME_NAME=${IOS_SCHEME_NAME}"
          echo "infoPlist=${infoPlist}"

          echo "\r\n列出 ${GITHUB_WORKSPACE} 目錄內容以便調試"
          ls -la "${GITHUB_WORKSPACE}"
          
          echo "\r\n列出 ${projectPath}/${IOS_WORKSPACE_NAME} 目錄內容以便調試"
          ls -la "${projectPath}/${IOS_WORKSPACE_NAME}"
          
          if /usr/libexec/PlistBuddy -c "Print :CFBundleName" "${projectPath}/${IOS_WORKSPACE_NAME}/${infoPlist}" &>/dev/null; then
            CFBundleName=$(/usr/libexec/PlistBuddy -c "Print :CFBundleName" "${projectPath}/${IOS_WORKSPACE_NAME}/${infoPlist}")
          else
            echo "❌ CFBundleName not found in ${projectPath}/${IOS_WORKSPACE_NAME}/${infoPlist}"
            exit 1
          fi

          version=$( cat "${projectPath}/${IOS_WORKSPACE_NAME}.xcodeproj/project.pbxproj" | grep -m1 'MARKETING_VERSION' | cut -d'=' -f2 | tr -d ';' | tr -d ' ' )
          defaultIPAName="${CFBundleName}.ipa"
          newADIPAName="${CFBundleName}_ad_hoc_${version}.ipa"
          newAppStoreIPAName="${CFBundleName}_appstore_${version}.ipa"

          echo "CFBundleName=${CFBundleName}" >> $GITHUB_ENV
          echo "version=${version}" >> $GITHUB_ENV
          echo "defaultIPAName=${defaultIPAName}" >> $GITHUB_ENV
          echo "newADIPAName=${newADIPAName}" >> $GITHUB_ENV
          echo "newAppStoreIPAName=${newAppStoreIPAName}" >> $GITHUB_ENV

          echo "RUNNER_TEMP=${RUNNER_TEMP}" >> $GITHUB_ENV

      - name: Run pod install
        run: pod install --repo-update
        working-directory: ./ # 如果你的 Podfile 不在根目錄,可以改這裡的路徑

      - name: Create App Store Connect API Key File
        run: |
          # 要把 p8 檔案放置於 private_keys 資料夾下,才可以讓 xcodebuild altool 的時候方便使用(不需要特別指定參數)
          APP_STORE_CONNECT_API_PRIVATE_KEY_PATH=./private_keys/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8

          # import app store connect api private key from secrets
          mkdir -p private_keys
          echo -n "$APP_STORE_CONNECT_API_PRIVATE_KEY" | base64 --decode -o ${APP_STORE_CONNECT_API_PRIVATE_KEY_PATH}

          # transform relative path to absolute path
          APP_STORE_CONNECT_API_PRIVATE_KEY_PATH=$(realpath ${APP_STORE_CONNECT_API_PRIVATE_KEY_PATH})
          echo "APP_STORE_CONNECT_API_PRIVATE_KEY_PATH=$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH" >> $GITHUB_ENV

      - name: Clean Derived Data (before build)
        run: rm -rf ~/Library/Developer/Xcode/DerivedData/*

      - name: Build archive
        env:
          APP_STORE_CONNECT_API_PRIVATE_KEY_PATH: ${{ env.APP_STORE_CONNECT_API_PRIVATE_KEY_PATH }}
        run: |
          xcodebuild archive -workspace "$IOS_WORKSPACE_NAME.xcworkspace" -scheme "$IOS_SCHEME_NAME" -sdk iphoneos -archivePath "$RUNNER_TEMP/$IOS_APP_NAME.xcarchive" \
           -allowProvisioningUpdates \
           -authenticationKeyIssuerID "$APP_STORE_CONNECT_API_ISSUER_ID" \
           -authenticationKeyID "$APP_STORE_CONNECT_API_KEY_ID" \
           -authenticationKeyPath "$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH"

      - name: Export ipa (AppStore + Ad-Hoc)
        env:
          APP_STORE_CONNECT_API_PRIVATE_KEY_PATH: ${{ env.APP_STORE_CONNECT_API_PRIVATE_KEY_PATH }}
          defaultIPAName: ${{ env.defaultIPAName }}
          newADIPAName: ${{ env.newADIPAName }}
          newAppStoreIPAName: ${{ env.newAppStoreIPAName }}
          projectName: ${{ env.IOS_WORKSPACE_NAME }}
        run: |
          projectPath=${GITHUB_WORKSPACE}
          exportFolder="打包IPA"
          currentDate="$(date +'%y%m%d_%H%M%S')"
          folderName="${projectName}_${currentDate}"
          fileNameWithExtension="${0##*/}" # 含副檔名
          fileName="${fileNameWithExtension%.*}" # 不含副檔名

          # AppStore ExportOptionsPlist
          appStoreExportOptionsPlist="${projectPath}/${exportFolder}/AppStoreExportOptions.plist"
          appStoreExportPath="${RUNNER_TEMP}/${folderName}/${fileName}_appstore"
          echo "appStoreExportPath=${appStoreExportPath}" >> $GITHUB_ENV

          # Ad-hoc ExportOptionsPlist
          adHocExportOptionsPlist="${projectPath}/${exportFolder}/AdHocExportOptions.plist"
          adHocExportPath="${RUNNER_TEMP}/${folderName}/${fileName}_adhoc"
          echo "adHocExportPath=${adHocExportPath}" >> $GITHUB_ENV
          
          echo "\r\nappStoreExportPath = ${appStoreExportPath}\r\n"
          echo "\r\nadHocExportPath = ${adHocExportPath}\r\n"

          echo "trigger by ${GITHUB_EVENT_NAME}"

          if [[ "$GITHUB_EVENT_NAME" == "push" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
          
            echo "export IPA: appstore + ad-hoc"

            # export ipa (但如果你硬要在同一個 job 中「模擬並行」)
            "${projectPath}/${exportFolder}/ipa.command" "$RUNNER_TEMP/$IOS_APP_NAME.xcarchive" "${appStoreExportOptionsPlist}" "${appStoreExportPath}" "$APP_STORE_CONNECT_API_ISSUER_ID" "$APP_STORE_CONNECT_API_KEY_ID" "$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH" &
            "${projectPath}/${exportFolder}/ipa.command" "$RUNNER_TEMP/$IOS_APP_NAME.xcarchive" "${adHocExportOptionsPlist}" "${adHocExportPath}" "$APP_STORE_CONNECT_API_ISSUER_ID" "$APP_STORE_CONNECT_API_KEY_ID" "$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH" &
            wait             # 等兩者都跑完

            echo "\r\n重新命名 ipa"
            mv "${adHocExportPath}/${defaultIPAName}" "${adHocExportPath}/${newADIPAName}"
            mv "${appStoreExportPath}/${defaultIPAName}" "${appStoreExportPath}/${newAppStoreIPAName}"

          elif [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
            echo "export IPA: appstore only"
            "${projectPath}/${exportFolder}/ipa.command" "$RUNNER_TEMP/$IOS_APP_NAME.xcarchive" "${appStoreExportOptionsPlist}" "${appStoreExportPath}" "$APP_STORE_CONNECT_API_ISSUER_ID" "$APP_STORE_CONNECT_API_KEY_ID" "$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH"

            echo "\r\n重新命名 ipa"
            mv "${appStoreExportPath}/${defaultIPAName}" "${appStoreExportPath}/${newAppStoreIPAName}"
            
          else
            echo "Triggered by something else: $GITHUB_EVENT_NAME (未預期的trigger方式🤔)"
          fi
          
          # 列出 build 目錄內容以便調試
          ls -la "${RUNNER_TEMP}/${folderName}"

      - name: Upload to TestFlight
        env:
          appStoreExportPath: "${{ env.appStoreExportPath }}"
          newAppStoreIPAName: "${{ env.newAppStoreIPAName }}"
        run: |
          xcrun altool --upload-app -f "${appStoreExportPath}/${newAppStoreIPAName}" -t ios \
          --apiKey "${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}" \
          --apiIssuer "${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}"

      - name: Upload XCArchive for Github Artifact
        if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/heads/develop/')) || github.event_name == 'workflow_dispatch' }}
        uses: actions/upload-artifact@v4
        with:
          name: "${{ env.IOS_SCHEME_NAME }}.xcarchive"
          path: "${{ env.RUNNER_TEMP }}/${{ env.IOS_APP_NAME }}.xcarchive"

      - name: Upload AdHoc IPA for Github Artifact
        if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/heads/develop/')) || github.event_name == 'workflow_dispatch' }}
        uses: actions/upload-artifact@v4
        with:
          name: "${{ env.newADIPAName }}"
          path: "${{ env.adHocExportPath }}"

dev.yml放置路徑:.github/workflows/dev.yml


打包IPA/ipa.command (用來平行執行打包ad-hoc ipa, appstore ipa 用)

# 讀取傳入參數
archivePath=$1
exportOptionsPlist=$2
exportPath=$3
APP_STORE_CONNECT_API_ISSUER_ID=$4
APP_STORE_CONNECT_API_KEY_ID=$5
APP_STORE_CONNECT_API_PRIVATE_KEY_PATH=$6
           
xcodebuild -exportArchive -archivePath "${archivePath}" -exportOptionsPlist "${exportOptionsPlist}" -exportPath ${exportPath} -allowProvisioningUpdates \
           -authenticationKeyIssuerID "$APP_STORE_CONNECT_API_ISSUER_ID" \
           -authenticationKeyID "$APP_STORE_CONNECT_API_KEY_ID" \
           -authenticationKeyPath "$APP_STORE_CONNECT_API_PRIVATE_KEY_PATH"


 

文章標籤
全站熱搜
創作者介紹
創作者 小賢 的頭像
小賢

小賢的部落格

小賢 發表在 痞客邦 留言(0) 人氣(49)