第10章 持续集成实践
快速入门
一个基础的 CI(持续集成)会构建并测试你的项目:
GitHub Actions
要在 GitHub Actions 上测试你的包,以下是一个 .github/workflows/ci.yml 文件示例:
name: Cargo Build & Test
on:
push:
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
name: Rust project - latest
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v6
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
这将测试所有三个发布通道(注意,任何一个工具链版本上的失败都会导致整个任务失败)。你也可以在 GitHub 界面中点击 "Actions" > "new workflow" 并选择 Rust,即可将默认配置添加到你的仓库。更多详细信息请参阅 GitHub Actions 文档。
GitLab CI
要在 GitLab CI 上测试你的包,以下是一个 .gitlab-ci.yml 文件示例:
stages:
- build
rust-latest:
stage: build
image: rust:latest
script:
- cargo build --verbose
- cargo test --verbose
rust-nightly:
stage: build
image: rustlang/rust:nightly
script:
- cargo build --verbose
- cargo test --verbose
allow_failure: true
这将在 stable 通道和 nightly 通道上进行测试,但 nightly 通道的任何破坏都不会导致整体构建失败。详情请参阅 GitLab CI 文档。
builds.sr.ht
要在 sr.ht 上测试你的包,以下是一个 .build.yml 文件示例。请务必将 <your repo> 和 <your project> 更改为要克隆的仓库地址及克隆后的目录。
image: archlinux
packages:
- rustup
sources:
- <你的仓库>
tasks:
- setup: |
rustup toolchain install nightly stable
cd <你的项目>/
rustup run stable cargo fetch
- stable: |
rustup default stable
cd <你的项目>/
cargo build --verbose
cargo test --verbose
- nightly: |
rustup default nightly
cd <你的项目>/
cargo build --verbose ||:
cargo test --verbose ||:
- docs: |
cd <你的项目>/
rustup run stable cargo doc --no-deps
rustup run nightly cargo doc --no-deps ||:
这样会在 stable 和 nightly 两个通道上运行测试并构建文档,但 nightly 上的失败不会导致整体构建失败。详情请参阅 builds.sr.ht 文档。
CircleCI
要在 CircleCI 上测试你的包,下面是一个 .circleci/config.yml 示例文件:
version: 2.1
jobs:
build:
docker:
# 最新镜像标签请查看 https://circleci.com/developer/images/image/cimg/rust#image-tags
- image: cimg/rust:1.77.2
steps:
- checkout
- run: cargo test
如果想搭建更复杂的流水线,比如 flaky 测试检测、缓存和构建产物管理,请参阅 CircleCI Configuration Reference。
验证最新依赖
在 Cargo.toml 中指定依赖时,通常匹配的是一个版本区间。穷举测试所有版本组合并不现实,但至少验证一下最新版本,就能覆盖到使用 cargo add 或 cargo install 的用户场景。
测试最新版本时需要考虑以下因素:
- 尽量减少影响本地开发或 CI 的外部因素
- 新依赖的发布频率
- 项目愿意承担的风险程度
- CI 成本,包括间接成本,比如 CI 服务有并行任务数上限,达到上限后新任务只能排队串行执行。
一些可行的方案包括:
- 未将
Cargo.lock提交到版本控制- 取决于 PR 提交频率,许多依赖版本可能未经测试
- 这代价是牺牲了构建的确定性
- 配置一个 CI 任务验证最新依赖,但将其标记为“失败时继续”
- 取决于 CI 服务,失败状态可能不易察觉
- 取决于 PR 提交频率,可能会消耗比必要更多的资源
- 配置定时 CI 任务验证最新依赖
- 托管式 CI 服务可能会禁用那些许久未更新的仓库中的定时任务,从而被动地影响这些包
- 取决于 CI 服务,失败通知可能无法送达能够处理该问题的人员
- 若未与依赖发布频率相匹配,可能导致测试的版本覆盖不足,或产生冗余测试
- 通过 PR 定期更新依赖,例如使用 Dependabot 或 RenovateBot
- 可以将每个依赖更新隔离在独立的 PR 中,或合并为一个 PR
- 仅消耗必要的资源
- 可配置运行频率,以平衡 CI 资源消耗与依赖版本覆盖率
以下是一个使用 GitHub Actions 验证最新依赖的示例 CI 任务:
jobs:
latest_deps:
name: Latest Dependencies
runs-on: ubuntu-latest
continue-on-error: true
env:
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: allow
steps:
- uses: actions/checkout@v6
- run: rustup update stable && rustup default stable
- run: cargo update --verbose
- run: cargo build --verbose
- run: cargo test --verbose
注意事项:
- 设置
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS是为了确保解析器不会因项目的Rust 版本而限制所选的依赖项。
对于出现平台特定或特定 Rust 版本失败风险较高的项目,可能需要测试更多的组合。
验证 rust-version
发布指定了 rust-version 的软件包时,必须校验该字段的正确性。
以下是可辅助完成此任务的第三方工具:
以下是使用 GitHub Actions 执行此检查的一种示例方法:
jobs:
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: taiki-e/install-action@cargo-hack
- run: cargo hack check --rust-version --workspace --all-targets --ignore-private
此示例旨在平衡检查的完整性与执行耗时:
- 仅使用单一平台进行测试,因为大多数项目与平台无关,且依赖平台特定库来验证其行为。
- 使用
cargo check,因为贡献者最常遇到的问题涉及 API 可用性,而非运行时行为。 - 跳过未发布的软件包,因为假定只有通过注册表消费该项目的用户才关心
rust-version。
检查警告
通常,项目希望在官方分支上保持“无警告”状态,而在本地开发时则可以较为宽松。
可以使用 build.warnings = "deny" 在存在警告时使 CI 任务失败。
以下是使用 GitHub Actions 检查警告的 CI 任务示例:
jobs:
warnings:
runs-on: ubuntu-latest
env:
CARGO_BUILD_WARNINGS: deny
steps:
- uses: actions/checkout@v6
- run: rustup update stable && rustup default stable
- run: rustup component add clippy
- run: cargo clippy --all-targets --all-features --keep-going
注意事项:
- 由于围绕警告行为的兼容性保证有限,新的工具链版本可能导致 CI 失败。 建议通过一个自动创建 PR 的任务来固定工具链版本,以便在新版本发布时升级工具链。
- 在选择不需要检查的平台、功能以及软件包/构建目标组合时,需权衡检查的彻底性与执行耗时。
clippy-sarif