[CD-Deploy] refs/heads/main #20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |