货物工作区


在第 12 章中,我们构建了一个包含二进制 crate 和库 crate 的包。随着项目的发展,您可能会发现 library crate 不断变大,并且您希望将包进一步拆分为多个 library crate。Cargo 提供了一项名为 workspaces 的功能,可以帮助管理协同开发的多个相关软件包。


创建工作区


工作区是一组共享相同 Cargo.lock 和 output 目录的包。让我们使用工作区创建一个项目 - 我们将使用简单的代码,以便我们可以专注于工作区的结构。有多种方法可以构建工作区,因此我们只展示一种常见的方法。我们将有一个包含一个二进制文件和两个库的工作区。将提供主要功能的二进制文件将取决于这两个库。一个库将提供 add_one 函数,另一个库将提供 add_two 函数。这三个 crate 将成为同一工作区的一部分。首先,我们将为工作区创建一个新目录:


$ mkdir 添加 $ cd 添加


接下来,在 add 目录中,我们创建 Cargo.toml 文件,该文件将配置整个工作区。此文件将不包含 [package] 部分。相反,它将以 [workspace] 部分开始,该部分将允许我们通过使用二进制 crate 指定包的路径来将成员添加到工作区;在本例中,该路径为 adder


文件名: Cargo.toml

[workspace]

members = [
    "adder",
]


接下来,我们将通过在 add 目录:

$ cargo new adder
     Created binary (application) `adder` package


此时,我们可以通过运行 cargo build 来构建工作区。add 目录中的文件应如下所示:

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target


工作区在顶层有一个 target 目录,编译后的工件将被放入该目录;adder 包没有自己的 target 目录中。即使我们从 adder 目录中,编译后的工件仍将位于 add/target 中 而不是 add/adder/target。Cargo 在像这样的工作空间中构建目标目录,因为工作空间中的 crate 是相互依赖的。如果每个 crate 都有自己的 target 目录,则每个 crate 都必须重新编译工作区中的其他每个 crate,才能将工件放在自己的 target 目录中。通过共享一个目标目录,crate 可以避免不必要的重建。


在工作区中创建第二个包


接下来,让我们在工作区中创建另一个成员包并调用它 add_one。更改顶级 Cargo.toml 以指定 members 列表中的 add_one 路径:


文件名: Cargo.toml

[workspace]

members = [
    "adder",
    "add_one",
]


然后生成一个名为 add_one 的新库 crate:

$ cargo new add_one --lib
     Created library `add_one` package


您的 add 目录现在应该包含以下目录和文件:

├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target


add_one/src/lib.rs 文件中,让我们添加一个 add_one 函数:


文件名: add_one/src/lib.rs

pub fn add_one(x: i32) -> i32 {
    x + 1
}


现在我们可以让带有二进制文件的 adder 包依赖于 add_one 包含我们的库的包。首先,我们需要在 add_one adder/Cargo.toml 中。


文件名: adder/Cargo.toml

[dependencies]
add_one = { path = "../add_one" }


Cargo 并不假设 workspace 中的 crate 会相互依赖,因此我们需要明确依赖关系。


接下来,让我们add_one add_oneadder crate 的打开 adder/src/main.rs 文件并在顶部添加 use 行,以将新的 add_one 库 crate 引入范围。然后更改 main 函数调用 add_one 函数,如示例 14-7 所示。


文件名: adder/src/main.rs
use add_one;

fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

示例 14-7:使用 adder crate 中的 add_one 库 crate


让我们通过在顶级 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 [unoptimized + debuginfo] target(s) in 0.68s


要从 add 目录运行二进制 crate,我们可以通过使用 -p 参数和带有 cargo run 的包名称来指定要在工作区中运行的包:

$ cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!


这将运行 adder/src/main.rs 中的代码,这取决于 add_one crate。


取决于工作区中的外部包


请注意,工作区在顶层只有一个 Cargo.lock 文件,而不是在每个 crate 的目录中都有一个 Cargo.lock。这可确保所有 crate 使用所有依赖项的相同版本。如果我们将 rand package 添加到 adder/Cargo.tomladd_one/Cargo.toml 文件中,Cargo 会将这两个文件解析为一个版本的 rand,并将其记录在一个版本中 使工作区中的所有 crate 使用相同的依赖项 意味着 crate 将始终彼此兼容。让我们添加 rand crate 复制到 add_one/Cargo.toml 文件中的 [dependencies] 部分,以便我们可以在 add_one crate 中使用 rand crate:


文件名: add_one/Cargo.toml

[dependencies]
rand = "0.8.5"


我们现在可以将 use rand; 添加到 add_one/src/lib.rs 文件中,通过在 add 目录中运行 cargo build 来构建整个工作区,这将引入并编译 rand crate。我们将收到一个警告,因为我们所指的不是我们纳入范围的 rand

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   --snip--
   Compiling rand v0.8.5
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
 --> add_one/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `add_one` (lib) generated 1 warning
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 10.18s


顶级 Cargo.lock 现在包含有关 add_one。但是,即使 rand 在 workspace 中,我们不能在 workspace 中的其他 crate 中使用它,除非我们添加 rand 也添加到他们的 Cargo.toml 文件中。例如,如果我们添加 use rand; 对于 adder 包的 adder/src/main.rs 文件,我们将收到一个错误:

$ cargo build
  --snip--
   Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
 --> adder/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`


要解决此问题,请编辑 adder 包的 Cargo.toml 文件,并指示 rand 也是它的依赖项。构建 adder 包会将 rand 添加到 Cargo.lockadder 的依赖项列表中,但不会下载 rand 的其他副本。Cargo 将确保使用 rand 软件包的工作区中每个软件包中的每个 crate 都将使用相同的版本,只要它们指定了 rand 的兼容版本,从而节省了我们的空间并确保工作区中的 crate 彼此兼容。


如果工作区中的 crate 指定了同一依赖项的不兼容版本,Cargo 将解析每个版本,但仍然会尝试解析尽可能少的版本。


将测试添加到工作区


对于另一个增强功能,让我们在 add_one crate 中添加 add_one::add_one 函数的测试:


文件名: add_one/src/lib.rs

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));
    }
}


现在在顶级 add 目录中运行 cargo test。在结构如下的工作区中运行 cargo test 将对工作区中的所有 crate 运行测试:

$ cargo test
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.27s
     Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841)

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

     Running unittests src/main.rs (target/debug/deps/adder-49979ff40686fa8e)

running 0 tests

test result: ok. 0 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


输出的第一部分显示 it_worksadd_one 板条箱通过。下一部分显示在 adder 中未找到任何测试 crate,然后最后一部分显示 add_one 个板条箱。


我们还可以从顶级目录对工作区中的一个特定 crate 运行测试,方法是使用 -p 标志并指定要测试的 crate 的名称:

$ cargo test -p add_one
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74)

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


此输出显示 cargo test 仅运行了 add_one crate 的测试,没有运行 adder crate 测试。


如果您将工作区中的 crate 发布到 crates.io,则工作区中的每个 crate 都需要单独发布。与 cargo test 一样,我们可以在工作区中使用 -p flag 并指定要发布的 crate 的名称。


如需进一步练习,请以与 add_one crate 类似的方式将 add_two crate 添加到此工作区!


随着项目的增长,请考虑使用工作区:与一大堆代码相比,它更容易理解较小的单个组件。此外,如果 crate 经常同时更换,将 crate 放在工作区中可以更轻松地协调 crate。