cargo-workflows
Cargo 工作流
用途
引导代理使用 Cargo 工作区、特性管理、构建脚本(build.rs)、CI 集成、增量编译,以及 Cargo 工具生态系统。
触发场景
- "如何设置包含多个 crate 的 Cargo 工作区?"
- "Cargo 中的特性如何工作?"
- "如何编写 build.rs 脚本?"
- "如何加速 CI 中的 Cargo 构建?"
- "如何审计 Rust 依赖?"
- "什么是 cargo nextest,我应该使用它吗?"
工作流程
1. 工作区设置
my-project/
├── Cargo.toml # 工作区根
├── Cargo.lock # 所有成员共享一个锁文件
├── crates/
│ ├── core/
│ │ └── Cargo.toml
│ ├── cli/
│ │ └── Cargo.toml
│ └── server/
│ └── Cargo.toml
└── tools/
└── codegen/
└── Cargo.toml
# 工作区根 Cargo.toml
[workspace]
members = [
"crates/core",
"crates/cli",
"crates/server",
"tools/codegen",
]
resolver = "2" # 特性解析器 v2(edition 2021 必须)
# 共享依赖版本(workspace.dependencies)
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
# 共享 profile 设置
[profile.release]
lto = "thin"
codegen-units = 1
# 成员 Cargo.toml
[package]
name = "myapp-core"
version.workspace = true
edition.workspace = true
[dependencies]
serde.workspace = true # 从工作区继承
anyhow.workspace = true
2. 特性标志
[features]
default = ["std"]
# 简单标志
std = []
# 启用其他特性的特性
full = ["std", "async", "serde-support"]
# 带可选依赖的特性
async = ["dep:tokio"]
serde-support = ["dep:serde", "serde/derive"]
[dependencies]
tokio = { version = "1", optional = true }
serde = { version = "1", optional = true }
# 使用指定特性构建
cargo build --features "async,serde-support"
# 不使用默认特性构建
cargo build --no-default-features
# 使用所有特性构建
cargo build --all-features
# 检查特性组合
cargo check --no-default-features
cargo check --all-features
特性注意事项:
- 特性是累加的:一旦在依赖图任何地方启用,就会保持启用状态
resolver = "2"防止 dev-dependencies 和常规依赖之间的特性泄漏- 使用
dep:optional_dep语法(edition 2021)避免隐式特性创建
3. 构建脚本(build.rs)
// build.rs(位于 crate 根目录,在编译前运行)
use std::env;
use std::path::PathBuf;
fn main() {
// 这些文件变化时重新运行
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-env-changed=MY_LIB_PATH");
// 链接系统库
println!("cargo:rustc-link-lib=mylib");
println!("cargo:rustc-link-search=/usr/local/lib");
// 向 Rust 代码传递 cfg 标志
let target = env::var("TARGET").unwrap();
if target.contains("linux") {
println!("cargo:rustc-cfg=target_os_linux");
}
// 为下游 crate 设置环境变量
println!("cargo:rustc-env=MY_GENERATED_VAR=value");
// 使用 bindgen 生成绑定
let bindings = bindgen::Builder::default()
.header("wrapper.h")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings.write_to_file(out_path.join("bindings.rs")).unwrap();
}
println! 指令 |
效果 |
|---|---|
cargo:rerun-if-changed=FILE |
文件变化时重新运行构建脚本 |
cargo:rerun-if-env-changed=VAR |
环境变量变化时重新运行 |
cargo:rustc-link-lib=NAME |
链接库 |
cargo:rustc-link-search=PATH |
添加库搜索路径 |
cargo:rustc-cfg=FLAG |
在代码中启用 #[cfg(FLAG)] |
cargo:rustc-env=KEY=VAL |
在编译时设置 env!("KEY") |
cargo:warning=MSG |
发出构建警告 |
4. 增量构建与 CI 缓存
# 使用 sccache 的 GitHub Actions
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: "release-build"
# 或手动缓存
- uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# 本地预热缓存
cargo fetch # 下载所有依赖但不构建
cargo build --tests # 构建所有内容,包括测试二进制
# 检查增量构建是否影响 release(通常会影响)
[profile.release]
incremental = false # 默认;release 版本保持为 false
5. cargo nextest(更快的测试运行器)
# 安装
cargo install cargo-nextest
# 运行测试(默认并行,输出更清晰)
cargo nextest run
# 按过滤条件运行
cargo nextest run test_name_pattern
# 列出测试而不运行
cargo nextest list
# 在 CI 中使用(JUnit 输出)
cargo nextest run --profile ci
nextest.toml:
[profile.ci]
fail-fast = false
test-threads = "num-cpus"
retries = { backoff = "exponential", count = 2, delay = "1s" }
[profile.default]
test-threads = "num-cpus"
6. 依赖管理与审计
# 检查安全公告
cargo install cargo-audit
cargo audit
# 拒绝特定许可证、重复依赖、公告
cargo install cargo-deny
cargo deny check
# 检查未使用的依赖
cargo install cargo-machete
cargo machete
# 更新依赖
cargo update # 更新到兼容版本
cargo update -p serde # 更新单个包
cargo upgrade # 更新到最新版本(cargo-edit)
deny.toml:
[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause"]
deny = ["GPL-2.0", "AGPL-3.0"]
[bans]
multiple-versions = "warn"
deny = [{ name = "openssl", reason = "Use rustls instead" }]
[advisories]
ignore = [] # 列出要忽略的公告 ID
7. 常用 cargo 命令
# 仅构建指定二进制
cargo build --bin myapp
# 仅构建指定示例
cargo build --example myexample
# 带参数运行
cargo run -- --flag arg1 arg2
# 展开宏(用于调试过程宏)
cargo install cargo-expand
cargo expand module::path
# 依赖树
cargo tree
cargo tree --duplicates # 显示有多个版本的 crate
cargo tree -i serde # 谁依赖 serde?
# Cargo.toml 元数据
cargo metadata --format-version 1 | jq '.packages[].name'
工作区模式和依赖解析详情,参见 references/workspace-patterns.md。
相关技能
- 使用
skills/rust/rustc-basics了解编译器标志和 profile 配置 - 使用
skills/rust/rust-debugging调试 Cargo 构建的二进制文件 - 使用
skills/rust/rust-ffi了解带 C 库绑定的build.rs - 使用
skills/build-systems/cmake将 Rust 集成到 CMake 构建中
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cmake
C/C++ 项目的 CMake 构建系统技能。适用场景:编写或重构 CMakeLists.txt、配置源外构建、选择生成器(Ninja、Make、VS)、使用 target_link_libraries 管理目标和依赖、通过 find_package 或 FetchContent 集成外部包、启用 Sanitizer、为交叉编译配置工具链文件,或导出 CMake 包。触发条件:涉及 CMakeLists.txt、cmake 配置错误、目标属性、安装规则、CPack 或 CMake Presets 的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1