写点什么

Rust 从 0 到 1-Cargo-Workspaces

用户头像
关注
发布于: 2 小时前
Rust从0到1-Cargo-Workspaces

随着项目规模的增大,library crate 会持续增大,这时我们会希望将 library crate 进一步拆分成多个。对于这种情况,Cargo 提供了一个叫 workspaces 的功能,帮助我们管理多个相关联的 packages 。

创建 Workspace

Workspace 是一组共享同一个 Cargo.lock 和 输出目录的 packages 。下面我们将使用 workspace 创建一个项目。workspace 的组织方式有多种,我们将使用最为常用的一种方式。我们的 workspace 将包含一个 binary crate 和 2 个 library crate,并且它们分别属于不同的 package。binary crate 实现主要功能,并依赖于其它 2 个 library crate,它们分别提供了 add_one 和 add_two 两个方法。这三个 crate 共同组成了一个 workspace。下面让我们从新建 workspace 的目录开始:

$ mkdir add$ cd add
复制代码

接着在 add 目录中,创建 Cargo.toml 文件,用于配置 workspace。在 workspace 的 Cargo.toml 文件中并不包含 [package] 或之前我们在 Cargo.toml 中见过的元信息。它通常以 [workspace] 部分开始,我们可以在其后通过指定 package 的目录来增加 workspace 的成员,譬如我们 binary crate 所属的 package ,adder:

[workspace]
members = [ "adder",]
复制代码

然后,我们在 add 目录下运行 cargo new 创建它:

$ cargo new adder     Created binary (application) `adder` package
复制代码

现在我们可以试试运行 cargo build 来构建 workspace。add 目录看起来应该类似下面这样:


├── Cargo.lock├── Cargo.toml├── adder│ ├── Cargo.toml│ └── src│ └── main.rs└── target
复制代码

在 workspace 的顶级目录有一个 target 目录;adder 没有自己的 target 目录。即使我们在 adder 目录下运行 cargo build,构建结果也是输出至 add/target ,而不是 add/adder/target。之所以在 workspace 中会这样,是因为,如果每个 crate 都有自己的 target 目录,为了在目录中生成各自的制品(artifacts),workspace 中的每个 crate 都需要重新编译其它 crate。通过共享 target 目录,可以避免进行重复的构建。

创建第二个 package

现在我们在 workspace 中创建另一个 package:add_one。首先在 Cargo.toml 的 workspace 成员中加入 add_one 目录:

[workspace]
members = [ "adder", "add_one",]
复制代码

接着在 add 目录下运行 cargo new --lib 创建 add_one package:

$ 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 函数:

pub fn add_one(x: i32) -> i32 {    x + 1}
复制代码

现在我们在 workspace 中创建了一个 library crate,接下来我们把它添加为 adder 的依赖库。首先我们在 adder/Cargo.toml 中增加 add_one 作为依赖:

[dependencies]
add_one = { path = "../add_one" }
复制代码

Cargo 并不会实现假设 workspace 中的 Crates 会相互依赖,所以需要我们显示的指定它们的依赖关系。现在,我们可以在 adder 中使用  add_one 函数了,如下所示:

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

现在,让我们在 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
复制代码

如果需要运行 workspace 中指定的 binary crate,我们可以使用 -p 参数:

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

引入外部依赖

需要我们注意的是 workspace 只在根目录有一个 Cargo.lock,而不是在每一个成员目录都有 Cargo.lock。这是为了确保所有 crate 用到的依赖都保持相同的版本。如果我们在顶层的 Cargo.toml 和 add-one/Cargo.toml 中都添加了 rand package 作为依赖,Cargo 会将它们解析为同一版本并记录到根目录的 Cargo.lock 中。确保 workspac 中的所有 crate 都使用相同版本的依赖意味着其中所有 crate 的外部依赖具备兼容性。下面,让我们在 add-one/Cargo.toml 的 [dependencies] 部分增加 rand crate 作为外部依赖:

[dependencies]rand = "0.8.3"
复制代码

现在我们可以在 add-one/src/lib.rs 中使用 use rand; 了。尝试在 add 目录运行 cargo build 构建整个 workspace 我们可以看到类似下面的结果:

$ cargo build    Updating crates.io index  Downloaded rand v0.8.3   --snip--   Compiling rand v0.8.3   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: 1 warning emitted
Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 10.18s
复制代码

现在根目录的 Cargo.lock 包含了 add_one 的外部依赖 rand 。虽然我们在 workspace 的其中一个 crate 中引入了 rand 作为依赖,但是在其它 crate 中是无法使用的,除非也在它们的 Cargo.toml 中添加 rand 作为依赖。譬如,如果我们在 adder/src/main.rs 中使用 use rand;,就会会得到类似下面的错误:

$ 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 作为依赖, rand 会加入到 Cargo.lock 中 adder 的依赖列表中,但是并不会重复下载 rand 。Cargo 确保了 workspace 中任何使用 rand 的 crate 都会使用相同的版本。这样做不仅节省了空间,同时也确保了 workspace 中 crate 外部依赖的兼容性。

增加测试

让我们为 add_one crate 中的 add_one::add_one 函数增加单元测试:

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   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 target/debug/deps/add_one-f0253159197f7841
running 1 testtest tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running 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
复制代码

在 workspace 中运行 cargo test 会运行其中所有 crate 的测试。如果我们希望运行 workspace 中指定 crate 的测试,可以使用 -p 参数加上 crate 的名称,参考下面的例子:

$ cargo test -p add-one    Finished test [unoptimized + debuginfo] target(s) in 0.00s     Running target/debug/deps/add_one-b3235fea9a156f74
running 1 testtest 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
复制代码

此外,如果我们希望向 crates.io 发布 workspace 中的 crate,每个 crate 需要单独发布。cargo publish 命令并没有 --all 或者 -p 参数,所以需要在每一个 crate 的目录下运行 cargo publish 来进行发布。

随着项目增长,我们需要考虑使用 workspace 对代码进行组织:独立的小组件比大块代码要更加容易理解。此外,如果它们经常需要同时被修改,使用 workspace 进行管理对于保持它们的协调一致也更加方便。

发布于: 2 小时前阅读数: 2
用户头像

关注

公众号"山 顽石"欢迎大家关注;) 2021.03.23 加入

IT老兵,关注Rust、Java、前端、架构、大数据、AI、算法等;平时喜欢美食、旅游、宠物、健身、篮球、乒乓球等。希望能和大家交流分享。

评论

发布
暂无评论
Rust从0到1-Cargo-Workspaces