Skip to content

[CD-Deploy] refs/heads/main #20

[CD-Deploy] refs/heads/main

[CD-Deploy] refs/heads/main #20

Workflow file for this run

# Workflow(Action) 名稱
name: CD-Deploy
# Actions Log 的標題名稱
run-name: "[CD-Deploy] ${{ github.ref }}"
# 同個 Concurrency Group 如果有新的 Job 會取消正在跑的
# 例如 重複觸發相同分支的打包任務,會取消前一個任務
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
# 觸發事件
on:
# 手動表單觸發
workflow_dispatch:
# 表單 Inputs 欄位
inputs:
# App 版本號
VERSION_NUMBER:
description: 'Version Number of the app (e.g., 1.0.0). Auto-detect from the Xcode project if left blank.'
required: false
type: string
# App Build Number
BUILD_NUMBER:
description: 'Build number of the app (e.g., 1). Will use a timestamp if left blank.'
required: false
type: string
# App Release Note
RELEASE_NOTE:
description: 'Release notes of the deployment.'
required: false
type: string
# 其他 Workflow 呼叫此 Workflow 觸發
# Nightly Build 會呼叫使用
workflow_call:
inputs:
# App 版本號
VERSION_NUMBER:
description: 'Version Number of the app (e.g., 1.0.0). Auto-detect from the Xcode project if left blank.'
required: false
type: string
# App Build Number
BUILD_NUMBER:
description: 'Build number of the app (e.g., 1). Will use a timestamp if left blank.'
required: false
type: string
# App Release Note
RELEASE_NOTE:
description: 'Release notes of the deployment.'
required: false
type: string
BRANCH:
description: 'Branch'
type: string
# 定義全域靜態變數
env:
APP_STORE_CONNECT_API_KEY_FILE_NAME: "app_store_connect_api_key.json"
# Job 工作項目
# Job 會並發執行
jobs:
# Job ID
deploy:
# Job 名稱 (可省略,有設定在 Log 顯示比較好讀)
name: Deploy - Firebase App Distribution
# Runner Label - 使用 GitHub Hosted Runner macos-15 來執行工作
# 請注意:因為此專案是 Public Repo 可以無限免費使用
# 請注意:因為此專案是 Public Repo 可以無限免費使用
# 請注意:因為此專案是 Public Repo 可以無限免費使用
# 如果是 Private Repo 需要按計量收費,macOS 機器是最貴的(10倍),可能跑 10 次就達到 2,000 分鐘免費上限
# 建議使用 self-hosted Runner
runs-on: macos-15
# 設定最長 Timeout 時間,防止異常情況發生時無止盡的等待
timeout-minutes: 30
# use zsh
# 可省略,只是我習慣用 zsh,預設是 bash
defaults:
run:
shell: zsh {0}
# 工作步驟
# 工作步驟會照順序執行
steps:
# git clone 當前專案 & checkout 到執行的分支
- name: Checkout repository
uses: actions/checkout@v3
with:
# Git Large File Storage,我們的測試環境用不到
# default: false
lfs: false
# 如果有指定則 Checkout 指定分支,沒有則使用預設(當前分支)
# 因 on: schedule 事件只能在 main 主分支執行,因此想做 Nightly Build 之類的工作就需要指定分支
# e.g. on: schedule -> main 分支,Nightly Build master 分支
ref: ${{ github.event.inputs.BRANCH || '' }}
# ========== Certificates Steps ==========
# 建議是使用 Fastlnae - Match 管理開發憑證並在 Lane 中直接執行 match 安裝設定好
# Match 會用另一個 Private Repo 管理憑證,但要設定好 SSH Agent 才有權限 git clone private repo
# ref: https://stackoverflow.com/questions/57612428/cloning-private-github-repository-within-organisation-in-actions
#
#
# --- 以下是沒有使用 Fastlane - Match 的情況下直接下載 & Import 憑證給 Runner 的做法 ---
# ref: https://docs.github.com/en/actions/how-tos/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development
#
# GitHub Actions Secret 無法儲存檔案,因此所有憑證檔案都要先轉成 Base64 Encoded 文字格式存在 Secret
# 在 GitHub Actions Step 中再動態讀出來寫入 TEMP 檔案並移動到正確位置給系統讀取使用
# 其他設定細節請參考文章
#
- name: Install the Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.BUILD_CERTIFICATE_P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
# GitHub Hosted Runner 為自定義字串
# Self-hosted Runner 為機器登入密碼
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
# App Store Connect API Fastlane JSON Key
# 另一個在打包環境幾乎是必須的 App Store Connect API Fastlane JSON Key (.json)
# format: .json 內容格式:https://docs.fastlane.tools/app-store-connect-api/
# 裡面包含 App Store Connect API .p8 Key
# 會在後續帶給 Fastlane,用於上傳到 Testflight、App Store API 使用
#
# GitHub Actions Secret 無法儲存檔案,因此所有憑證檔案都要先轉成 Base64 Encoded 文字格式存在 Secret
# 在 GitHub Actions Step 中再動態讀出來寫入 TEMP 檔案供其他步驟引用使用
# 其他設定細節請參考文章
- name: Read and Write Apple Store Connect API Key to Temp
env:
APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}
APP_STORE_CONNECT_API_KEY_PATH: "${{ runner.temp }}/${{ env.APP_STORE_CONNECT_API_KEY_FILE_NAME }}"
run: |
# import certificate and provisioning profile from secrets
echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode -o $APP_STORE_CONNECT_API_KEY_PATH
# ========== Env Setup Steps ==========
# 讀取專案指定的 XCode 版本
# 在後續之中,我們自己手動指定使用的 XCode_x.x.x.app
# 而不使用 xcversion,因為 xcversion 已經 sunset 不穩定。
- name: Read .xcode-version
id: read_xcode_version
run: |
XCODE_VERSION=$(cat .xcode-version)
echo "XCODE_VERSION: ${XCODE_VERSION}"
echo "xcode_version=${XCODE_VERSION}" >> $GITHUB_OUTPUT
# 也可以直接在這指定全域 XCode 版本,這樣就不用在後續步驟指定 DEVELOPER_DIR
# 但此指令需要 sudoer 權限,如果是 self-hosted runner 就要確定 runner 執行環境有 sudo 權限
# sudo xcode-select -s "/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer"
# 讀取專案指定的 Ruby 版本
- name: Read .ruby-version
id: read_ruby_version
run: |
RUBY_VERSION=$(cat .ruby-version)
echo "RUBY_VERSION: ${RUBY_VERSION}"
echo "ruby_version=${RUBY_VERSION}" >> $GITHUB_OUTPUT
# 安裝或設定 Runner Ruby 版本成專案指定版本
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "${{ steps.read_ruby_version.outputs.ruby_version }}"
# 可設可不設,原因是之前在 self-hosted 起多個 runner 跑 CI/CD 因為 cocoapods repos 是共用目錄
# 解決的問題是:有很小的機率會出現在同時 pod install 時拉 cocoapods repos 出現衝突(因為預設都是用) $HOME/.cocoapods/
# GitHub Hosted Runner 則不需此設定
# - name: Change Cocoapods Repos Folder
# if: contains(runner.labels, 'self-hosted')
# run: |
# # 每個 Runner 用自己的 .cocoapods 資料夾,防止資源衝突
# mkdir -p "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/"
# export CP_HOME_DIR="$HOME/.cocoapods-${{ env.RUNNER_NAME }}"
# rm -f "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/repos/cocoapods/.git/index.lock"
# ========== Cache Setting Steps ==========
# 請注意,就算是 self-hosted,Cache 目前也是 Cloud Cache 會計算用量
# 規則:7 天未 hit 自動刪除、單個 Cache 上限 10 GB、Action 成功才會 Cache
# Public Repo: 免費無限制
# Private Repo: 5 GB 起
# Self-hosted 可以自己用 shell script 撰寫 Cache & Restore 策略或使用其他工具協助
# Bundle Cache (Gemfile)
# 對應 Makefile 中我們指定了 Bundle 安裝路徑 ./vendor 下
- name: Cache Bundle
uses: actions/cache@v3
with:
path: |
./vendor
key: ${{ runner.os }}-bundle-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-bundle-
# CocoaPods Cache (Podfile)
# 默認就是 專案/Pods 下
- name: Cache CocoaPods
uses: actions/cache@v3
with:
path: |
./Product/Pods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('Product/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-
# Mint cache
# 對應 Makefile 中我們指定的 Mint 安裝路徑 ./mint 下
- name: Cache Mint
uses: actions/cache@v3
with:
path: ./mint
key: ${{ runner.os }}-mint-${{ hashFiles('Mintfile') }}
restore-keys: |
${{ runner.os }}-mint-
# ====================
# 專案 Setup & 依賴安裝
- name: Setup & Install Dependency
run: |
# 執行 Makefile 中封裝的 Setup 指令,對應成指令大概是:
# brew install mint
# bundle config set path 'vendor/bundle'
# bundle install
# mint bootstrap
# ...
# 等等 setup 指令
make setup
# 執行 Makefile 中封裝的 Install 指令,對應成指令大概是:
# mint run yonaskolb/XcodeGen --quiet
# bundle exec pod install
# ...
# 等等 install 指令
make install
- name: Deploy Beta
id: deploy
# 指定工作目錄,這樣後續指令就不用在特別 cd ./Product/
working-directory: ./Product/
env:
# 打包 Input 參數
VERSION_NUMBER: ${{ inputs.VERSION_NUMBER || '' }}
BUILD_NUMBER: ${{ inputs.BUILD_NUMBER || '' }}
RELEASE_NOTE: ${{ inputs.RELEASE_NOTE || '' }}
AUTHOR: ${{ github.actor }}
# Repo -> Settings -> Actions secrets and variables -> secrets
# Firebase CLI Token 密鑰 (取得方式請參考文章)
FIREBASE_CLI_TOKEN: ${{ secrets.FIREBASE_CLI_TOKEN }}
# Apple Developer Program Team ID
TEAM_ID: ${{ secrets.TEAM_ID }}
# 指定這個 Job 要使用 XCode_x.x.x 指定的版本執行
DEVELOPER_DIR: "/Applications/Xcode_${{ steps.read_xcode_version.outputs.xcode_version }}.app/Contents/Developer"
run: |
# 取得當前 Timestamp
BUILD_TIMESTAMP=$(date +'%Y%m%d%H%M%S')
# 如果 BUILD_NUMBER 沒有值,用 Timestamp 當 App Build Number
BUILD_NUMBER="${BUILD_NUMBER:-$BUILD_TIMESTAMP}"
ID="${{ github.run_id }}"
COMMIT_SHA="${{ github.sha }}"
BRANCH_NAME="${{ github.ref_name }}"
AUTHOR="${{ env.AUTHOR }}"
# 組合 Release Note
RELEASE_NOTE="${{ env.RELEASE_NOTE }}
ID: ${ID}
Commit SHA: ${COMMIT_SHA}
Branch: ${BRANCH_NAME}
Author: ${AUTHOR}
"
# 執行 Fastlane 打包&部署 Lane
bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"
# GitHub Actions 建議的 self-hosted 安全性設定:
# ref: https://docs.github.com/en/actions/how-tos/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development#required-clean-up-on-self-hosted-runners
# 對應 Step: Install the Apple certificate and provisioning profile
# 用途是刪除機器上下載下來的金鑰憑證
# 如果你是用 Match 則需要改寫成 Match 的 Clean
- name: Clean up keychain and provisioning profile
if: ${{ always() && contains(runner.labels, 'self-hosted') }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision