Rust 和 GitHub Actions 的持續集成
大家好,我是螃蟹哥。
CI/CD 是近些年熱門的話題,GitHub 上之前常用第三方工具實現,後來官方出了 GitHub Actions,所以有必要使用下。本文介紹一個 Rust 項目如何使用 GitHub Actions。
大約一個月前,我開始檢查我的計劃清單項目之一 — 學習 Rust 編程語言 [1]。
與其他語言一樣,學習該語言的前期工作也是要掌握如何設置合適的開發環境。我用什麼 lint 工具、格式化等?更重要的是,我如何使用這些工具來創建合適的 CI/CD,以確保我 checkout 和部署的代碼是正常的?就我而言,我的第一個 Rust 項目是更新一個內部工具,這個工具是我在 homelab 中用於通過 SSH 連接到機器:vaultssh[2]。完成原型後,我試圖回答以下問題:
-
哪些組件需要組成 CI 管道以確保我的代碼正常?
-
部署呢?
-
我需要編寫自定義工具還是有可用的社區資源?
這是三部分系列中的第一篇文章,我們將在該系列文章中使用 Rust 和 GitHub Actions 來研究 CI/CD。在本文中,我們將研究創建 CI 管道。
GitHub Actions
針對此特定管道,我決定用 GitHub Actions[3]。它正在成爲 CI/CD 世界中的流行選擇,主要是因爲許多項目已經在 GitHub 上,並且選擇它很自然。我一直是項目 Azure DevOps[4] 的長期用戶,並且很少對它感到失望,但是,我想如果我正在處理一種新語言,我也可以處理一個新的管道代理。
創建新存儲庫時,默認情況下會啓用 GitHub Actions。你可以通過單擊代碼倉庫頂部的 “Actions” 選項卡來訪問它的主界面:
如果你之前從未將它用於你的倉庫,系統會提示你創建第一個工作流。可以通過將配置文件添加到.GitHub/workflows
倉庫內的路徑來添加工作流。GitHub Actions 將自動在此目錄中搜索*.yml
文件併爲每個文件添加一個新的工作流程。如擴展名所示,這些配置文件是使用非常流行的 YAML[5] 語法創建的。
完全理解如何配置工作流超出了本系列的範圍。相反,我會向你指出 GitHub 網站上提供的優秀文檔 [6]。在本系列的其餘部分,我將假設對 GitHub Actions 有基本的瞭解,但會在可能的情況下提供一些細節。
持續集成
Rust 一個很優秀的地方是它的包 (crate) 生態系統,特別是其明星雲集的包管理器,即 Cargo[7]。這個工具不僅巧妙地包裝了對編譯器的調用,而且還處理了許多其他任務,包括一些非常適合持續集成的任務:格式化和測試代碼。即使 Cargo 不檢查特定的 box,它也有豐富的插件生態系統來實現出色的開發體驗(我們將使用的)。
Rust 中的基本 CI 工作流依賴以下子命令:
$> cargo fmt # Lint
$> cargo test # Test
請注意,這不是詳盡的檢查,可以而且應該考慮許多其他工具(即代碼覆蓋率)。但是,在本文我們將使用幾個簡單。包括這個:
$> cargo clippy
這個clippy
工具添加了一組額外的 lints,在cargo check
這些 lints 之上可以捕獲許多常見的 Rust 陷阱。它不是 Cargo 開箱即用的,但是,它是我之前談到的豐富插件生態系統的一部分,可以使用rustup
以下命令進行安裝:
$> rustup component add clippy
上面的內容很重要,因爲我們需要在 CI 工作流程中考慮到這一點。
記住這四個命令後,我們已準備好在我們的工作流程中創建第一個作業。但是,此時,我們需要停下來問問自己列表中的第三個問題,我是否需要在這裏編寫自定義工具?在這種情況下,它不會寫太多,即只需確保 Rust 在 runner 上正確設置,然後調用並捕獲 Cargo 的輸出。然而,當我學習 GitHub Actions 時,我不斷地想起這個神奇的 GitHub Marketplace[8],它應該充滿了有用的工具。它確實沒有讓人失望,因爲不久我就遇到了出色的 actions-rs[9] 工具集。它巧妙地包裝了我們執行上述檢查所需的所有功能,甚至處理確保在運行器上正確設置 Rust 環境。現在我們可以創建我們的第一個作業:
lints:
name: Run cargo fmt and cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
這項作業負責運行cargo fmt
和cargo clippy
。我們在 Ubuntu 系統上運行,並利用 actions-rs 提供的兩個操作。該toolchain
action 負責用於rustup
,確保在運行器上正確設置 Rust 環境。此時,我們使用四個參數:
-
profile
:確定rustup
運行時安裝的內容。我們將其設置爲 minimum 以安裝編譯大多數 Rust 應用程序所需的最低限度。這意味着每次我們的 CI 運行時,我們都需要設置這個環境——所以我們能做的任何事情來減少它所花費的時間都是成功的。有關更多信息,請參閱文檔 [10]。 -
toolchain
: 大致可以翻譯成 Rust 編譯器的版本安裝。這可以是特定版本或發佈渠道(channel)。在我們的例子中,我們使用的是穩定的 Rust,因爲我的特定項目就是用它來構建的。值得注意的是,一些 Rust 項目針對多個版本的 Rust 編譯器進行測試,在這種情況下,使用矩陣策略 [11] 是一個很好的方式。 -
override
:確保toolchain
上面指定的內容用於我們剩餘的工作。雖然在這種情況下不是絕對必要的,因爲我們只安裝了一個工具鏈,但最好讓它明確說明我們正在使用什麼。 -
components
: 如前所述,最小配置文件不包括cargo fmt
或cargo clippy
,我們有必要在rustup
安裝過程中指示將它們添加爲組件。
第三個和第四個 action 調用 Cargo 並允許我們傳遞任意參數和選項。像我一樣,你可能會問爲什麼我們不直接使用 cargo run
命令令進行調用。雖然跨操作保持一致(使用相同的提供者)有一定的價值,但在這種情況下,更大的好處是actions-rs
操作完成了捕獲 Cargo 輸出並將其合併到 GitHub Actions UI 中可理解的視圖的工作。此外,正如我們稍後將看到的,它提供了一個漂亮的開關,供cross
在進行交叉編譯時使用。
剩下的工作將模仿我們上面所做的大部分工作,只將參數更改爲 Cargo
。
test:
name: Run cargo test
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo test -no-run
uses: actions-rs/cargo@v1
with:
command: test --no-run
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
感謝 /u/matklad[12] 的建議,我將測試分爲兩部分。第一個調用cargo test
與no-run
代碼編譯的一切,但沒有實際運行測試。第二個實際運行了測試。這很有用,因爲它使我們能夠很好地瞭解編譯時間,因爲 GitHub Actions 會自動報告每個步驟所花費的時間。
Caching
在結束之前,還有最後一件事值得解決。那些使用過 Rust 的人對這樣一個事實並不陌生,即編譯器對於大中型項目通常很慢(具有正確的依賴關係,甚至可以擴展到小型項目)。這通常通過使用本地緩存來加速,這會顯着增加第一個之後的後續構建。但是,如前所述,運行器在每次作業運行後都會被擦除,因此我們不能依賴於我們的工作流運行時的情況。
幸運的是 GitHub Actions 提供了一個操作來幫助我們:
- name: Load cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
此操作需要一個以換行符結尾的路徑列表,這些路徑將被備份到緩存中以在將來的運行中重新使用。緩存由我們使用運行器操作系統構建的唯一鍵和 Cargo 鎖定文件的哈希值標識,以確保我們僅在適當的情況下使用它。運行器操作系統在這裏似乎沒有必要,但稍後當我們查看部署時它變得很重要,因此我將其包括在內以保持一致性。
值得注意的是,這可以通過在將target
目錄上傳到緩存之前清除目錄來進一步改進。rust-analyzer[13] 項目有一個很好的例子,你也可以使用另一個 GitHub 上的操作:
- name: Cache
uses: Swatinem/rust-cache@v1
到目前爲止,應將上述內容附加到我們所有的作業中,以最大限度地利用它。在我自己的項目中,我發現在引入緩存後,我的 CI 作業運行時間減少了 30% 以上。
結論
最終的工作流程如下所示:
on:
push:
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
name: CI
env:
RUST_TOOLCHAIN: stable
TOOLCHAIN_PROFILE: minimal
jobs:
lints:
name: Run cargo fmt and cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: ${{ env.TOOLCHAIN_PROFILE }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
components: rustfmt, clippy
- name: Cache
uses: Swatinem/rust-cache@v1
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
test:
name: Run cargo test
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: ${{ env.TOOLCHAIN_PROFILE }}
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
- name: Cache
uses: Swatinem/rust-cache@v1
- name: Run cargo test --no-run
uses: actions-rs/cargo@v1
with:
command: test --no-run
- name: Run cargo test
uses: actions-rs/cargo@v1
env:
RUST_TEST_THREADS: 1
with:
command: test
我已將rustup
配置文件和工具鏈參數重構爲環境變量,以使其更加明確我們正在使用的內容。工作流配置爲在每個推送和拉取請求上運行,不包括任何僅修改 Markdown 文件的提交。此外,你可以在你的自述文件中添加一個 badge,它會報告你最新的工作流程運行的狀態:
<a href="https://GitHub.com/jmgilman/vaultssh/actions/workflows/ci.yml">
<img src="https://GitHub.com/jmgilman/vaultssh/actions/workflows/ci.yml/badge.svg"/>
</a>
替換倉庫名稱並確保你的工作流被調用ci.yml
。
下篇文章我們將研究向工作流程添加持續部署策略。
作者:HomeOps,原文鏈接:https://www.homeops.dev/continuous-integration-with-github-actions-and-rust/,螃蟹哥編譯。
參考資料
[1] Rust 編程語言: https://www.rust-lang.org/
[2] vaultssh: https://GitHub.com/jmgilman/vaultssh
[3] GitHub Actions: https://GitHub.com/features/actions
[4] Azure DevOps: https://azure.microsoft.com/en-us/services/devops/
[5] YAML: https://yaml.org/
[6] 優秀文檔: https://docs.GitHub.com/en/actions
[7] Cargo: https://GitHub.com/rust-lang/cargo
[8] GitHub Marketplace: https://GitHub.com/marketplace?type=actions
[9] actions-rs: https://GitHub.com/actions-rs
[10] 有關更多信息,請參閱文檔: https://rust-lang.GitHub.io/rustup/concepts/profiles.html
[11] 矩陣策略: https://docs.GitHub.com/en/actions/reference/workflow-syntax-for-GitHub-actions#jobsjob_idstrategymatrix
[12] /u/matklad: https://www.reddit.com/user/matklad/
[13] rust-analyzer: https://GitHub.com/rust-analyzer/rust-analyzer/blob/94d9fc2a28ea5d97e3a9293b9dac05bdb00304cc/xtask/src/pre_cache.rs#L30-L53
關注「Rust 編程指北」
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/iTH6TfuurRKm_Ksb5tfQXA