Rust 2026 经验谈 - 从零到发布 CI/CD 全链路

2173 字
11 分钟
Rust 2026 经验谈 - 从零到发布 CI/CD 全链路

一个 Rust 库从本地开发到用户 cargo add 可用,中间要经过测试、lint、版本管理、发布、文档部署等多个环节。把这些环节串成可靠的自动化流水线,是库维护者的基本功。本文将分享我在 20+ 个 Rust crate 的发布实践中积累的 CI/CD 经验。

GitHub Actions + cargo 典型 Workflow#

基础 Workflow#

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo check --workspace
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-nextest --locked
- run: cargo nextest run --workspace
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace -- -D warnings
fmt:
name: Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo doc --workspace --no-deps
env:
RUSTDOCFLAGS: -D warnings

缓存配置:为什么用 Swatinem/rust-cache#

Swatinem/rust-cache@v2 是 Rust CI 缓存的事实标准。它缓存:

  • ~/.cargo/registry:下载的 crate 源码
  • ~/.cargo/git:git 依赖
  • target/:编译产物(按 key 哈希分区)

关键配置

- uses: Swatinem/rust-cache@v2
with:
key: ubuntu-stable # 区分不同 job 的缓存
cache-on-failure: true # 即使测试失败也缓存编译产物

踩坑:如果你的 workflow 有多个 job 都编译同一个项目,它们会各自维护 target/ 缓存,可能导致重复编译。解决方案是在根 job 中编译,后续 job 依赖其缓存。或者接受这个开销——在 GitHub Actions 的并行执行模型下,重复编译往往比串行更快。

Matrix 测试:多平台 × 多版本#

test-matrix:
name: Test (${{ matrix.os }}, ${{ matrix.rust }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # 一个失败不影响其他
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, beta]
exclude:
- os: windows-latest
rust: beta # 排除 Windows beta,减少 CI 时间
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.os }}-${{ matrix.rust }}
- run: cargo test --workspace

MSRV 测试(Minimum Supported Rust Version):

msrv:
name: MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.85.0" # Rust 2024 Edition 的最低稳定版本;声明在 Cargo.toml 的 rust-version
- run: cargo check

Cargo.toml 中声明 MSRV:

[package]
rust-version = "1.85.0"

cargo 会在用户工具链低于 rust-version 时给出清晰的错误提示。

语义化版本自动化#

cargo-release:一站式发布工具#

Terminal window
cargo install cargo-release

cargo-release 自动化整个发布流程:bump 版本号、更新 changelog、创建 git tag、发布到 crates.io、推送 git tag。

# Cargo.toml 中的 release 配置
[package.metadata.release]
pre-release-hook = ["cargo", "test"] # 发布前跑测试
tag-prefix = "" # tag 格式:v0.5.0
consolidate-commits = true # 多个变更合并为一个 commit

典型流程

Terminal window
# 发布 patch 版本(0.5.0 → 0.5.1)
cargo release patch
# 发布 minor 版本(0.5.0 → 0.6.0)
cargo release minor
# dry-run 模式(只预览,不实际执行)
cargo release patch --dry-run

Conventional Commits + 自动版本推断#

Conventional Commits 约定提交消息格式:

feat: add streaming support → minor bump
fix: correct timeout handling → patch bump
feat!: change API signature → major bump (breaking)

配合 git-cliff 生成 changelog:

Terminal window
cargo install git-cliff
git cliff --tag v0.6.0 > CHANGELOG.md

git-cliff 配置文件 cliff.toml

[changelog]
header = """# Changelog\n"""
body = """
{% if version %}## {{ version }} ({{ date | date(format="%Y-%m-%d") }}){% else %}## Unreleased{% endif %}
{% for group in commits | group_by(attribute="group") %}
### {{ group.key | capitalize }}
{% for commit in group.commits %}
- {{ commit.message | upper_first }}{% endfor %}
{% endfor %}\n
"""
[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactor" },
{ message = "^chore", skip = true },
]

经验谈:Conventional Commits 的价值不在于格式本身,而在于让版本号变更有据可查。当用户问”为什么 0.6.0 有 breaking change?“,你可以直接指向 feat!: change API signature 这个 commit。但不要过度追求——如果团队不习惯这个格式,强制推行只会增加摩擦。我建议先在 CI 中用 commitlint 做可选检查,而非强制拒绝。

crates.io 发布流程与最佳实践#

发布前 Checklist#

  1. cargo semver-checks:确保 API 兼容性
  2. cargo test --workspace:全部测试通过
  3. cargo clippy -- -D warnings:无 clippy 警告
  4. cargo doc --no-deps:文档无警告
  5. cargo audit:无已知安全漏洞
  6. 检查 Cargo.toml 元数据:description、license、repository、keywords、categories 完整

Cargo.toml 元数据#

[package]
name = "my-crate"
version = "0.6.0"
edition = "2024"
description = "A concise description of what this crate does"
license = "MIT OR Apache-2.0" # Rust 社区标准双许可
repository = "https://github.com/user/my-crate"
homepage = "https://user.github.io/my-crate"
documentation = "https://docs.rs/my-crate" # 通常自动生成,无需显式设置
keywords = ["async", "stream", "backpressure"]
categories = ["asynchronous", "network-programming"]
rust-version = "1.85.0"
include = [ # 仅包含必要文件,减小 crate 体积
"src/**/*.rs",
"Cargo.toml",
"README.md",
"LICENSE-*",
]

踩坑include 字段很关键。默认情况下 cargo publish 包含仓库根目录的所有文件(除了 gitignore 排除的)。如果你的仓库有大型测试数据、benchmark 数据、文档源文件等,它们会被不必要地包含在发布的 crate 中,增加用户下载时间。

发布命令#

Terminal window
# 首次发布需要登录
cargo login <API_TOKEN> # 从 crates.io/me 获取
# 发布(dry-run 先预览)
cargo publish --dry-run
cargo publish
# 撤回发布(仅阻止新下载,已下载的不受影响)
cargo yank --vers 0.6.0

重要规则

  • crates.io 不允许删除已发布的版本,只能 yank
  • yank 后该版本仍然可下载(已有 Cargo.lock 的用户不受影响),但不允许新的 Cargo.lock 选用它
  • 版本号一旦发布就不能重复使用,即使 yank 了也不行

发布自动化 Workflow#

name: Release
on:
push:
tags: ['v*']
jobs:
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
environment: release # GitHub Environment,保护 secrets
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Publish
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

安全配置:把 CARGO_REGISTRY_TOKEN 存在 GitHub Environment 的 secrets 中,配合 environment protection rules(要求手动审批)可以防止误发布。

文档部署#

docs.rs 定制#

docs.rs 是 Rust 社区的文档托管服务,cargo publish 后自动构建和部署。你可以通过 Cargo.toml 定制构建行为:

[package.metadata.docs.rs]
features = ["full"] # 启用所有 feature 以展示完整文档
rustdoc-args = ["--cfg", "docsrs"] # 传递 cfg 标志
targets = ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-musl"]

在代码中使用 docsrs cfg:

#[cfg(docsrs)]
/// 此文档仅在 docs.rs 上可见完整内容。
/// 在本地 `cargo doc` 中,此段可能被省略。
pub fn advanced_feature() {}

rustdoc 配置最佳实践#

  1. 启用 deny(rustdoc::broken_intra_doc_links):文档内链接失效即报错,防止文档中引用了不存在的项。
[lints.rustdoc]
broken_intra_doc_links = "deny"
  1. 为 public item 编写 doc comment:用 /////!,而非 /* */。doc comment 支持 Markdown。

  2. 使用 #[doc(alias = "...")]:为类型/函数添加搜索别名。

#[doc(alias = "Future")]
#[doc(alias = "promise")]
pub struct AsyncTask { /* ... */ }
  1. 文档测试(doc test):doc comment 中的代码块会作为测试运行。对于不需要运行的代码块,标注 no_runignore
/// ```
/// // 需要网络连接,CI 中不运行
/// let result = my_crate::fetch("https://example.com").await;
/// ```
///
/// ```no_run
/// let result = my_crate::fetch("https://example.com").await;
/// ```
pub async fn fetch(url: &str) -> String { /* ... */ }
  1. 隐藏实现细节:用 #[doc(hidden)] 标记不想暴露在文档中的 public item。

CI 中的安全审计#

cargo-audit:已知漏洞检测#

audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}

rustsec/audit-check action 会检查 Cargo.lock 中的依赖是否包含已知安全漏洞(基于 RustSec 数据库),并在发现漏洞时创建 GitHub Issue。

cargo-vet:供应链验证#

cargo-vet 是 Mozilla 开发的供应链审计工具,用于验证依赖的特定版本是否经过人工审查:

Terminal window
cargo install cargo-vet
# 初始化(创建 supply-chain/ 目录)
cargo vet init
# 审查未验证的依赖
cargo vet suggest
# 标记已审查的依赖
cargo vet certify <crate> <version> <criteria>

cargo-vet 的核心思想是每个依赖的每个版本都必须经过某种形式的人工审查,才能进入项目。审查标准(criteria)可自定义,如 “safe-to-deploy”、“safe-to-run”。

supply-chain/audits.toml 示例

[[audits.serde]]
who = "Alice <alice@example.com>"
criteria = "safe-to-deploy"
version = "1.0.195"
notes = "Reviewed diff from 1.0.194, only performance improvement"
[[audits.serde_json]]
who = "Alice <alice@example.com>"
criteria = "safe-to-deploy"
delta = "1.0.112 -> 1.0.113" # 仅审查 diff

何时用 cargo-vet:如果你的项目对安全性有较高要求(金融、医疗、基础设施),cargo-vet 提供了比 cargo-audit 更细粒度的控制。对于一般项目,cargo-audit 足矣。

每日定时审计#

name: Daily Audit
on:
schedule:
- cron: '0 6 * * 1' # 每周一 06:00 UTC
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2

经验谈:把审计放在定时任务而非每次 push 中,因为漏洞数据库是独立更新的——你的代码没变,但某个依赖可能新被披露了漏洞。定时检查能及时捕获这种情况。

完整 CI/CD Workflow 组合#

把上述所有环节组合,推荐的 workflow 结构:

Workflow触发条件内容
ci.ymlpush / PRcheck + test + clippy + fmt + docs
ci-matrix.ymlpush to main多平台 × 多版本测试
audit.yml定时 + push to maincargo-audit
release.ymltag pushpublish to crates.io
msrv.ymlpush to mainMSRV 兼容性检查

这种分层设计的好处:PR 时跑快速 CI(1-3 分钟),合并到 main 时跑完整矩阵测试(10-15 分钟),发布时跑发布流程。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Rust 2026 经验谈 - 从零到发布 CI/CD 全链路
https://tinyzzh.github.io/posts/rust-2026/2026-06-04-rust_2026_004_cicd_publish/
作者
TinyZ Zzh
发布于
2026-06-04
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TinyZ Zzh
专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
218
分类
38
标签
221
总字数
356,121
运行时长
0
最后活动
0 天前

文章目录