ch14-03-cargo-workspaces.md
commit 7ddc28cfe0bfa6c531a6475c7fa41dfa66e8943c
第十二章中,我們構(gòu)建一個(gè)包含二進(jìn)制 crate 和庫(kù) crate 的包。你可能會(huì)發(fā)現(xiàn),隨著項(xiàng)目開發(fā)的深入,庫(kù) crate 持續(xù)增大,而你希望將其進(jìn)一步拆分成多個(gè)庫(kù) crate。對(duì)于這種情況,Cargo 提供了一個(gè)叫 工作空間(workspaces)的功能,它可以幫助我們管理多個(gè)相關(guān)的協(xié)同開發(fā)的包。
工作空間 是一系列共享同樣的 Cargo.lock 和輸出目錄的包。讓我們使用工作空間創(chuàng)建一個(gè)項(xiàng)目 —— 這里采用常見的代碼以便可以關(guān)注工作空間的結(jié)構(gòu)。有多種組織工作空間的方式;我們將展示一個(gè)常用方法。我們的工作空間有一個(gè)二進(jìn)制項(xiàng)目和兩個(gè)庫(kù)。二進(jìn)制項(xiàng)目會(huì)提供主要功能,并會(huì)依賴另兩個(gè)庫(kù)。一個(gè)庫(kù)會(huì)提供 add_one
方法而第二個(gè)會(huì)提供 add_two
方法。這三個(gè) crate 將會(huì)是相同工作空間的一部分。讓我們以新建工作空間目錄開始:
$ mkdir add
$ cd add
接著在 add 目錄中,創(chuàng)建 Cargo.toml 文件。這個(gè) Cargo.toml 文件配置了整個(gè)工作空間。它不會(huì)包含 [package]
或其他我們?cè)?nbsp;Cargo.toml 中見過(guò)的元信息。相反,它以 [workspace]
部分作為開始,并通過(guò)指定 adder 的路徑來(lái)為工作空間增加成員,如下會(huì)加入二進(jìn)制 crate:
文件名: Cargo.toml
[workspace]
members = [
"adder",
]
接下來(lái),在 add 目錄運(yùn)行 cargo new
新建 adder
二進(jìn)制 crate:
$ cargo new adder
Created binary (application) `adder` package
到此為止,可以運(yùn)行 cargo build
來(lái)構(gòu)建工作空間。add 目錄中的文件應(yīng)該看起來(lái)像這樣:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
工作空間在頂級(jí)目錄有一個(gè) target 目錄;adder
并沒有自己的 target 目錄。即使進(jìn)入 adder 目錄運(yùn)行 cargo build
,構(gòu)建結(jié)果也位于 add/target 而不是 add/adder/target。工作空間中的 crate 之間相互依賴。如果每個(gè) crate 有其自己的 target 目錄,為了在自己的 target 目錄中生成構(gòu)建結(jié)果,工作空間中的每一個(gè) crate 都不得不相互重新編譯其他 crate。通過(guò)共享一個(gè) target 目錄,工作空間可以避免其他 crate 多余的重復(fù)構(gòu)建。
接下來(lái),讓我們?cè)诠ぷ骺臻g中指定另一個(gè)成員 crate。這個(gè) crate 位于 add-one 目錄中,所以修改頂級(jí) Cargo.toml 為也包含 add-one 路徑:
文件名: Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]
接著新生成一個(gè)叫做 add-one
的庫(kù):
$ cargo new add_one --lib
Created library `add_one` package
現(xiàn)在 add 目錄應(yīng)該有如下目錄和文件:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
在 add-one/src/lib.rs 文件中,增加一個(gè) add_one
函數(shù):
文件名: add-one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
現(xiàn)在工作空間中有了一個(gè)庫(kù) crate,讓 adder
依賴庫(kù) crate add-one
。首先需要在 adder/Cargo.toml 文件中增加 add-one
作為路徑依賴:
文件名: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
cargo并不假定工作空間中的Crates會(huì)相互依賴,所以需要明確表明工作空間中 crate 的依賴關(guān)系。
接下來(lái),在 adder
crate 中使用 add-one
crate 的函數(shù) add_one
。打開 adder/src/main.rs 在頂部增加一行 use
將新 add-one
庫(kù) crate 引入作用域。接著修改 main
函數(shù)來(lái)調(diào)用 add_one
函數(shù),如示例 14-7 所示。
文件名: adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!(
"Hello, world! {} plus one is {}!",
num,
add_one::add_one(num)
);
}
示例 14-7:在 adder
crate 中使用 add-one
庫(kù) crate
在 add 目錄中運(yùn)行 cargo build
來(lái)構(gòu)建工作空間!
$ 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 目錄運(yùn)行二進(jìn)制 crate,可以通過(guò) -p
參數(shù)和包名稱來(lái)運(yùn)行 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!
這會(huì)運(yùn)行 adder/src/main.rs 中的代碼,其依賴 add-one
crate
還需注意的是工作空間只在根目錄有一個(gè) Cargo.lock,而不是在每一個(gè) crate 目錄都有 Cargo.lock。這確保了所有的 crate 都使用完全相同版本的依賴。如果在 Cargo.toml 和 add-one/Cargo.toml 中都增加 rand
crate,則 Cargo 會(huì)將其都解析為同一版本并記錄到唯一的 Cargo.lock 中。使得工作空間中的所有 crate 都使用相同的依賴意味著其中的 crate 都是相互兼容的。讓我們?cè)?nbsp;add-one/Cargo.toml 中的 [dependencies]
部分增加 rand
crate 以便能夠在 add-one
crate 中使用 rand
crate:
文件名: add-one/Cargo.toml
[dependencies]
rand = "0.8.3"
現(xiàn)在就可以在 add-one/src/lib.rs 中增加 use rand;
了,接著在 add 目錄運(yùn)行 cargo build
構(gòu)建整個(gè)工作空間就會(huì)引入并編譯 rand
crate:
$ 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
現(xiàn)在頂級(jí)的 Cargo.lock 包含了 add-one
的 rand
依賴的信息。然而,即使 rand
被用于工作空間的某處,也不能在其他 crate 中使用它,除非也在他們的 Cargo.toml 中加入 rand
。例如,如果在頂級(jí)的 adder
crate 的 adder/src/main.rs 中增加 use rand;
,會(huì)得到一個(gè)錯(cuò)誤:
$ 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`
為了修復(fù)這個(gè)錯(cuò)誤,修改頂級(jí) adder
crate 的 Cargo.toml 來(lái)表明 rand
也是這個(gè) crate 的依賴。構(gòu)建 adder
crate 會(huì)將 rand
加入到 Cargo.lock 中 adder
的依賴列表中,但是這并不會(huì)下載 rand
的額外拷貝。Cargo 確保了工作空間中任何使用 rand
的 crate 都采用相同的版本。在整個(gè)工作空間中使用相同版本的 rand
節(jié)省了空間,因?yàn)檫@樣就無(wú)需多個(gè)拷貝并確保了工作空間中的 crate 將是相互兼容的。
作為另一個(gè)提升,讓我們?yōu)?nbsp;add_one
crate 中的 add_one::add_one
函數(shù)增加一個(gè)測(cè)試:
文件名: 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));
}
}
在頂級(jí) add 目錄運(yùn)行 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 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 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
輸出的第一部分顯示 add-one
crate 的 it_works
測(cè)試通過(guò)了。下一個(gè)部分顯示 adder
crate 中找到了 0 個(gè)測(cè)試,最后一部分顯示 add-one
crate 中有 0 個(gè)文檔測(cè)試。在像這樣的工作空間結(jié)構(gòu)中運(yùn)行 cargo test
會(huì)運(yùn)行工作空間中所有 crate 的測(cè)試。
也可以選擇運(yùn)行工作空間中特定 crate 的測(cè)試,通過(guò)在根目錄使用 -p
參數(shù)并指定希望測(cè)試的 crate 名稱:
$ cargo test -p add_one
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running 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
只運(yùn)行了 add-one
crate 的測(cè)試而沒有運(yùn)行 adder
crate 的測(cè)試。
如果你選擇向 crates.io發(fā)布工作空間中的 crate,每一個(gè)工作空間中的 crate 需要單獨(dú)發(fā)布。cargo publish
命令并沒有 --all
或者 -p
參數(shù),所以必須進(jìn)入每一個(gè) crate 的目錄并運(yùn)行 cargo publish
來(lái)發(fā)布工作空間中的每一個(gè) crate。
現(xiàn)在嘗試以類似 add-one
crate 的方式向工作空間增加 add_two
crate 來(lái)作為更多的練習(xí)!
隨著項(xiàng)目增長(zhǎng),考慮使用工作空間:每一個(gè)更小的組件比一大塊代碼要容易理解。如果它們經(jīng)常需要同時(shí)被修改的話,將 crate 保持在工作空間中更易于協(xié)調(diào)他們的改變。
更多建議: