14.5 cargo 工作空间 (Workspace)
14.5.1 为什么需要 Cargo Workspace
假如说我们构建了一个二进制 crate,里面既有库又有应用程序。随着项目规模不断增长,库 crate 可能不断变大。在这种情况下通常会把它拆为多个包。针对这种需求,Rust 提供了 Cargo 工作空间,也就是 cargo workspace。
Cargo workspace 会帮助管理多个相互关联且需要协同开发的 crate。其本质是一套共享同一个 Cargo.lock 和输出文件的包。
14.5.2 使用 Workspace
有多种方式可以创建工作空间(workspace)。
做一个例子,这个工作空间里有 1 个二进制 crate 和 1 个库 crate:
- 二进制 crate 里有
main函数,依赖于库 crate。 - 其中一个库 crate 提供一个叫
add_one的函数。
1. 创建 Workspace 目录
首先为工作空间创建一个目录,我取名叫 add。在终端输入以下命令:
$ mkdir add
$ cd add
2. 在主项目中使用 Workspace
接下来,在 add 目录中,创建将配置整个工作区的 Cargo.toml 文件。该文件不会有 [package] 部分。相反,它将以 [workspace] 部分开头:
[workspace]
resolver = "2"
members = [
"adder",
]
adder 就是我给二进制 crate 取的名,这个列表可以继续添加更多成员。
3. 添加库
$ cargo new adder
Creating binary (application) `adder` package
通过这个命令创建了 adder crate,位于目录 add/adder 下。
此时整个项目的结构如下:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
需要注意的是,这时候我们既可以对 add 这个项目使用 cargo build,也可以对 add 下的 adder crate 使用 cargo build。但是生成的 target 目录和 Cargo.lock 文件只会有一个,都在 add 目录下,而 adder crate 的构建产出物也会存放在这里。因为工作空间中的各个 crate 往往是相互依赖的,每个目录都有自己的 target 就会导致开发者不得不反复编译工作空间里的其余 crate。
接下来添加其它 crate:
另一个 crate 叫 add_one,修改工作空间信息:
[workspace]
resolver = "2"
members = [
"adder",
"add_one",
]
使用 cargo new 添加库,记得使用 --lib 标志来把它声明为 library crate:
$ cargo new add_one --lib
Creating library `add_one` package
现在整个项目的结构是:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
4. 编写代码
在 add_one/src/lib.rs 文件中,添加一个 add_one 函数和一个简单的单元测试:
#![allow(unused)]
fn main() {
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
}
现在我们可以让 adder 包和我们的二进制文件依赖于 add_one。首先,需要添加路径依赖 add_one 到 adder/Cargo.toml,因为 Cargo 并不假设工作区中的 crate 会相互依赖,因此我们需要明确依赖关系。在 adder/Cargo.toml 中这么写:
[dependencies]
add_one = { path = "../add_one" }
接下来,让我们使用来自 add_one crate 的 add_one 函数。打开 adder/src/main.rs,在顶部添加 use 来把 add_one 引入作用域,然后修改 main 函数来调用 add_one 函数。
use add_one;
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
5. 编译
对 add 这个项目使用 cargo build:
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
没有报错,正常运行。
6. 测试
我们还可以通过使用 -p 标志并指定我们要测试的包的名称,从顶级目录中对工作区中的一个特定包运行测试。比如说仅测试 add_one 函数:
$ cargo test -p add_one
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-bd89fda78e7f92a7)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
如果将工作区中的 crate 发布到 crates.io,则工作区中的每个 crate 都需要单独发布。与 cargo test 一样,我们可以使用 -p 指定工作区中要发布的特定包,并写出想要发布的包名称。