工作流範本


本文說明如何使用 GitHub Actions(GHA),為 Exercism 語言軌道設置持續整合(CI)工作流。文中提供最佳實踐與範例,讓你能打造出自己快速、可靠又穩健的 CI 工作流。這個資料夾裡的 GHA 工作流可以改造成搭配任何 CI 運作,因為基本結構維持不變。

本文會:

  • 勾勒理想的 CI 工作流
  • 討論各種考量與建議
  • 提供一些可直接使用的範本
  • 最後附上從 Travis 遷移的指南

這些工作流檔案的實作範例可以在 exercism/javascript 裡找到。

求救:這看起來要花_好多_工夫 😓

本文件其餘部分旨在說明這些工作流的運作方式。如果你趕時間,只想從 Travis 或 Circle 換到 GHA,而不打算最佳化 PR 指令碼,可以看看我們這篇 從 Travis 遷移 的 約 10 分鐘指南。

軌道的 CI 動作

以下是建議用來檢查你儲存庫內容是否完整無誤的動作:

  1. 用 configlet linting 檢查 config.json
  2. 檢查是否有 stub
  3. 檢查文件(v3 需要新增檔案;這項之後可能會移到 configlet)
  4. 使用「maintainers」設定對練習進行 lint
  5. 使用 example/exemplar 檔案測試練習(可包含建置步驟)

也可能有各軌道專屬的動作。例如:

  1. 檢查練習設定的完整性
  2. 檢查練習檔案的格式

也許你還想要更多提升便利性的檢查,例如:

  1. 確認 CONTRIBUTING 存在
  2. 確認相依套件有合理的 lockfile
  3. 確認 markdown 檔案內的連結有效
  4. ...

建議

執行檢查的頻率

每項動作都要想想它應該多久執行一次。

  • configlet linting 非常重要(因為 config.json 一旦壞掉,整個軌道都可能跟著壞),所以它大概應該每次都執行,不過每個 commit 只需要跑一次。
  • 檔案是否存在或完整,每個 commit 也只需要檢查一次。
  • 如果一個軌道要在多個 runtime 版本或編譯器版本下執行,那麼建置/測試練習時就應該針對每個支援的版本分別跑一次。
  • PR _大概_只需要對_新增_或_修改_的檔案執行動作,但由於一個檔案可能影響整個練習,所以只要練習中有任何檔案變動,保險起見還是對整個_練習_執行動作比較好。

讓該執行的動作也能在本機執行,會非常有幫助。這表示真正做事的指令碼也能手動執行。要做到這點,不要把動作內嵌在工作流檔案裡,而是建立一個獨立的指令碼。舉例來說,檢查 stub 這件事可以完全用 bash 寫在工作流檔案裡搞定,但這裡建議改為建立一個新的可執行指令碼 scripts/ci-check。

「可是這個指令很短啊,例如 eslint . --ext ts --ext tsx。」

當這個指令需要更新時,就得同步更新文件、工作流檔案,還有_維護者腦袋裡_的那一份。把它抽成一個指令碼就能解決這一切。讀工作流檔案本身也可能非常嚇人。

練習有變動的 PR 要跑的檢查

scripts/pr 和 scripts/pr-check 這兩個指令碼(請見範本)會以多個引數執行,每一個引數對應這個 PR 中變更或新增的檔案。舉例來說,如果 two-fer 更新了,呼叫的樣子可能像這樣:

scripts/pr exercises/two-fer/README.md exercises/two-fer/.meta/example.ext

建議讓所有動作針對有變動的_練習_執行,而不是針對有變動的_檔案_。因為改了某個檔案,很可能會連帶影響整個練習(想想看:設定、套件)。

還沒準備好嗎? / 覺得太複雜?

在實作這個最佳化之前,你完全可以先忽略它!遷移指南也暗示可以之後再補上。如果忽略這些輸入引數,所有檢查就會對所有練習執行。這完全沒問題,只是會花比較久的時間。

完整性檢查

如果軌道有一份「頂層」相依檔案,以及/或其他設定檔,就加入一個完整性步驟(它會與 scripts/sync 或 bin/sync 並存,後者會把所有設定檔複製到所有練習),確保頂層/基礎檔案和複製到各練習目錄的那一份完全相同。這樣一來,就能更新相依套件、把它們同步到整個儲存庫,也能確保所有練習的設定都一致。

常見的做法是使用總和檢查碼。Ubuntu(以及各種其他 Linux 發行版)內建了一個叫做 sha1sum 的工具,不過用_任何_方法把設定檔雜湊或化約成一個總和檢查碼值(md5、sha1、crc32)都可以:

$ sha1sum README.md
cd58091c5043bf21f00d39ff1740d8b2976deeff *README.md

安全性檢查

如果軌道使用了需要存取 GitHub token 或其他機密的額外工作流,最佳做法是把工作流中用到的所有動作都固定在某個特定的 commit。詳情請見 GitHub 的安全性強化指南。

例如:

- uses: julia-actions/setup-julia@v1
+ uses: julia-actions/setup-julia@d26d1111976eae5f00db04f0515ab744ec9cd79e # 1.3.1

如果工具鏈有相依管理的 lockfile,可以考慮把它提交進儲存庫,並在工作流檔案裡使用「凍結的 lockfile」。例如:npm ci、yarn install --frozen-lockfile 和 bundle install --frozen。這樣能確保變更相依套件時 lockfile 是最新的,也能防止惡意套件混進來。

範本

這個目錄裡至少有下列範本:

  • configlet.yml:這個工作流會抓取最新的 configlet 二進位檔,並對這個儲存庫進行 lint。每個 commit 都會執行。對於 PR,會對實際的 commit 以及「合併後」的樹狀結構執行。
  • ci.yml:這個工作流只在 main 分支上執行,每個 commit 執行一次。
    1. 對所有練習執行「pre-check」指令(檢查 stub、lint、文件等)
    2. 對所有練習、針對多個版本執行「ci」指令(建置與測試)
  • pr.ci.yml:這個工作流只在 PR 上執行,每個 commit 執行一次。
    1. 對變更的檔案執行「pre-check」指令(檢查 stub、lint、文件等)
    2. 對變更的練習、針對多個版本執行「ci」指令(建置與測試)

非 PR 的工作流也可以透過 workflow_dispatch 觸發。

每個檔案的最上方都列出了應該提供哪些「指令碼」。如果你希望它們是二進位檔,就把 scripts/xxx 換成 bin/xxx。有些工具鏈會_要求_二進位檔必須放在 bin 資料夾裡。

  • scripts/ci:應該用範例解答對照測試,建置並測試所有練習的指令碼
  • scripts/ci-check:應該對所有練習進行 lint,並可選擇檢查 stub、設定完整性等的指令碼
  • scripts/pr:和 scripts/ci 相同,但只對從輸入路徑解析出來的練習執行
  • scripts/pr-check:和 scripts/ci-check 相同,但只對從輸入路徑解析出來的檔案或練習執行

疑難排解

如果你遇到任何問題,或希望有人幫你檢視工作流,請 @ 一下 @exercism/github-actions 團隊。

改了某個頂層檔案,而它應該觸發所有練習的 CI 執行

在撰寫當下,pr.ci.yml 只允許「extension」測試。理想上,應該改成只要某些檔案有變更就一律觸發(例如用來跑測試的二進位檔)。不過,這類變更通常不常發生,而且是由維護者處理,所以 ci.yml 在 main 上對所有東西一律執行,大概已經夠安全了。

在 Windows 上建立了 scripts/xxx 檔案,結果在 {other OS} 上不能用

預設情況下,在 Windows 上建立的檔案,其 git-index 中不會內嵌關於是否可執行的中介資料,因為 Windows 的權限模型不一樣。Git 預設會用 git-index 的中介資料來判斷檔案在 POSIX 系統上是否應該可執行,因此會讓 scripts/xxx 無法執行。

git update-index --chmod=+x scripts/xxx
git commit -m "Make scripts/xxx executable"