大部份由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"
文章標籤
全站熱搜
