Rust:Cargo workspace多项目管理

Rust项目复杂了之后,单个crate放不下所有代码。Cargo workspace可以把多个crate放在一个仓库里统一管理,共享依赖版本,一起编译测试。

workspace基本配置

在项目根目录创建Cargo.toml,声明workspace成员:

# 根 Cargo.toml
[workspace]
members = [
    "core",         # 核心库
    "api",          # HTTP API
    "cli",          # 命令行工具
    "worker",       # 后台任务
    "shared",       # 共享类型和工具
]
resolver = "2"

每个成员是一个独立的crate,有自己的Cargo.tomlsrc/目录:

my-project/
+-- Cargo.toml          # workspace根配置
+-- Cargo.lock          # 统一的lock文件
+-- core/
|   +-- Cargo.toml
|   +-- src/lib.rs
+-- api/
|   +-- Cargo.toml
|   +-- src/main.rs
+-- cli/
|   +-- Cargo.toml
|   +-- src/main.rs
+-- worker/
|   +-- Cargo.toml
|   +-- src/main.rs
+-- shared/
    +-- Cargo.toml
    +-- src/lib.rs

成员间依赖

成员crate之间通过path依赖引用:

# api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2021"

[dependencies]
core = { path = "../core" }
shared = { path = "../shared" }
axum = "0.6"
tokio = { version = "1", features = ["full"] }

统一依赖版本

Cargo 1.64+ 支持workspace级别的依赖声明,避免各crate版本不一致:

# 根 Cargo.toml
[workspace]
members = ["core", "api", "cli", "worker", "shared"]

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
anyhow = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }

成员crate引用时用workspace = true

# core/Cargo.toml
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
sqlx = { workspace = true }
anyhow = { workspace = true }

这样所有crate用的serde版本一定相同,升级时只改根Cargo.toml一个地方。

构建和测试

# 构建所有成员
cargo build

# 构建指定成员
cargo build -p api

# 测试所有成员
cargo test

# 测试指定成员
cargo test -p core

# 运行指定binary
cargo run -p api
cargo run -p cli -- --help

# 检查所有成员
cargo clippy --workspace

Cargo.lock在workspace根目录统一管理,保证所有成员用相同版本的依赖。

小技巧

  • 把共享的类型定义放在shared crate里,避免循环依赖
  • [workspace.metadata]可以存自定义信息,配合cargo-make等工具使用
  • CI里用cargo test --workspace一次跑完所有测试
  • 如果某个crate不想发布到crates.io,加publish = false

workspace是Rust项目超过一定规模后的标配,用好了代码组织清晰很多。