Rust Cargo 工作空間

2023-03-22 15:12 更新
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ā)的包。

創(chuàng)建工作空間

工作空間 是一系列共享同樣的 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)建。

在工作空間中創(chuàng)建第二個(gè)包

接下來(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 將是相互兼容的。

為工作空間增加測(cè)試

作為另一個(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)他們的改變。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)