Rust 和 GitHub Actions 的持續集成

大家好,我是螃蟹哥。

CI/CD 是近些年熱門的話題,GitHub 上之前常用第三方工具實現,後來官方出了 GitHub Actions,所以有必要使用下。本文介紹一個 Rust 項目如何使用 GitHub Actions。


大約一個月前,我開始檢查我的計劃清單項目之一 — 學習 Rust 編程語言 [1]。

與其他語言一樣,學習該語言的前期工作也是要掌握如何設置合適的開發環境。我用什麼 lint 工具、格式化等?更重要的是,我如何使用這些工具來創建合適的 CI/CD,以確保我 checkout 和部署的代碼是正常的?就我而言,我的第一個 Rust 項目是更新一個內部工具,這個工具是我在 homelab 中用於通過 SSH 連接到機器:vaultssh[2]。完成原型後,我試圖回答以下問題:

這是三部分系列中的第一篇文章,我們將在該系列文章中使用 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 fmtcargo clippy。我們在 Ubuntu 系統上運行,並利用 actions-rs 提供的兩個操作。該toolchain action 負責用於rustup,確保在運行器上正確設置 Rust 環境。此時,我們使用四個參數:

第三個和第四個 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 testno-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