引言
单体仓库(monorepo)是一种源代码管理方法,它将多个项目(库、应用程序、服务)存储在一个单一的仓库中。在 Rust 生态系统中,这种方法通过 Cargo Workspace 机制获得了强大的支持。本文将分析如何在 Rust 中有效组织单体仓库,探讨其结构、依赖管理和最佳实践。
为什么单体仓库对 Rust 项目有用?它允许您:
- 在多个 crate(库和二进制文件)之间复用代码。
- 简化版本管理和 CI/CD。
- 确保变更的原子性(一次提交可以更新多个 crate)。
- 减少依赖管理的复杂性。
Cargo Workspace 基础知识
Cargo Workspace 是 Rust 内置的用于管理单体仓库的机制。它将多个 crate 整合在同一个顶层 Cargo.toml 文件之下。
创建工作区
首先,创建一个项目目录,并在根目录中创建 Cargo.toml 文件:
# Cargo.toml (root)[workspace]members = [ "core", "cli", "web-api", "utils"]
[workspace.dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1.37", features = ["full"] }这里我们定义了四个 crate:core、cli、web-api 和 utils。关键点在于 [workspace.dependencies]。它允许您为工作区中的所有 crate 指定公共依赖项。每个 crate 都可以引用这些依赖项,而无需重复指定版本。
目录结构
典型的 Rust 单体仓库结构如下所示:
my-monorepo/├── Cargo.toml # 根工作区├── Cargo.lock # 公共锁文件├── core/│ ├── Cargo.toml│ └── src/│ └── lib.rs├── cli/│ ├── Cargo.toml│ └── src/│ └── main.rs├── web-api/│ ├── Cargo.toml│ └── src/│ └── main.rs├── utils/│ ├── Cargo.toml│ └── src/│ └── lib.rs└── README.md每个子目录都是一个独立的 crate,拥有自己的 Cargo.toml。crate 可以是库(lib.rs)或可执行应用程序(main.rs)。
依赖管理与代码复用
内部依赖
工作区内的 crate 可以通过本地路径相互引用。例如,cli 可以依赖于 core 和 utils:
# cli/Cargo.toml[package]name = "cli"version = "0.1.0"edition = "2021"
[dependencies]core = { path = "../core" }utils = { path = "../utils" }serde = { workspace = true }tokio = { workspace = true }请注意语法 { workspace = true }——它允许您使用在根目录 Cargo.toml 中声明的公共依赖项。这确保了所有 crate 使用相同版本的库。
示例:共享核心库
我们来创建一个简单的 core 库,它将包含业务逻辑:
// core/src/lib.rsuse serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct User { pub id: u64, pub name: String, pub email: String,}
pub fn create_user(name: String, email: String) -> User { User { id: rand::random(), name, email, }}
pub fn greet(user: &User) -> String { format!("Hello, {}!", user.name)}现在,cli crate 可以使用这个库:
// cli/src/main.rsuse core::{create_user, greet};
fn main() { let user = create