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 的目录开始:
接着在 add 目录中,创建 Cargo.toml 文件,用于配置 workspace。在 workspace 的 Cargo.toml 文件中并不包含 [package] 或之前我们在 Cargo.toml 中见过的元信息。它通常以 [workspace] 部分开始,我们可以在其后通过指定 package 的目录来增加 workspace 的成员,譬如我们 binary crate 所属的 package ,adder:
然后,我们在 add 目录下运行 cargo new 创建它:
现在我们可以试试运行 cargo build 来构建 workspace。add 目录看起来应该类似下面这样:
在 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 目录:
接着在 add 目录下运行 cargo new --lib 创建 add_one package:
现在 add 目录应该类似下面这样:
我们在 add_one/src/lib.rs 文件中创建 add_one 函数:
现在我们在 workspace 中创建了一个 library crate,接下来我们把它添加为 adder 的依赖库。首先我们在 adder/Cargo.toml 中增加 add_one 作为依赖:
Cargo 并不会实现假设 workspace 中的 Crates 会相互依赖,所以需要我们显示的指定它们的依赖关系。现在,我们可以在 adder 中使用 add_one 函数了,如下所示:
现在,让我们在 add 目录中运行 cargo build 进行构建,我们可以看到类似下面的结果:
如果需要运行 workspace 中指定的 binary crate,我们可以使用 -p 参数:
引入外部依赖
需要我们注意的是 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 作为外部依赖:
现在我们可以在 add-one/src/lib.rs 中使用 use rand; 了。尝试在 add 目录运行 cargo build 构建整个 workspace 我们可以看到类似下面的结果:
现在根目录的 Cargo.lock 包含了 add_one 的外部依赖 rand 。虽然我们在 workspace 的其中一个 crate 中引入了 rand 作为依赖,但是在其它 crate 中是无法使用的,除非也在它们的 Cargo.toml 中添加 rand 作为依赖。譬如,如果我们在 adder/src/main.rs 中使用 use rand;,就会会得到类似下面的错误:
为了修复这个错误,我们需要在 adder/Cargo.toml 添加 rand 作为依赖, rand 会加入到 Cargo.lock 中 adder 的依赖列表中,但是并不会重复下载 rand 。Cargo 确保了 workspace 中任何使用 rand 的 crate 都会使用相同的版本。这样做不仅节省了空间,同时也确保了 workspace 中 crate 外部依赖的兼容性。
增加测试
让我们为 add_one crate 中的 add_one::add_one 函数增加单元测试:
在 add 目录运行 cargo test 命令:
在 workspace 中运行 cargo test 会运行其中所有 crate 的测试。如果我们希望运行 workspace 中指定 crate 的测试,可以使用 -p 参数加上 crate 的名称,参考下面的例子:
此外,如果我们希望向 crates.io 发布 workspace 中的 crate,每个 crate 需要单独发布。cargo publish 命令并没有 --all 或者 -p 参数,所以需要在每一个 crate 的目录下运行 cargo publish 来进行发布。
随着项目增长,我们需要考虑使用 workspace 对代码进行组织:独立的小组件比大块代码要更加容易理解。此外,如果它们经常需要同时被修改,使用 workspace 进行管理对于保持它们的协调一致也更加方便。
版权声明: 本文为 InfoQ 作者【山】的原创文章。
原文链接:【http://xie.infoq.cn/article/e3d060296658b276cb877c93d】。文章转载请联系作者。
评论