关注「Rust编程指北」,一起学习 Rust,给未来投资
大家好,我是螃蟹哥。
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[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 会自动报告每个步骤所花费的时间。
在结束之前,还有最后一件事值得解决。那些使用过 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/,螃蟹哥编译。
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编程指北」