Cargo Workspace
ในบทที่ 12 เราสร้าง package ที่รวม binary crate และ library crate เมื่อโปรเจกต์ของคุณพัฒนา คุณอาจพบว่า library crate ใหญ่ขึ้นต่อเนื่อง และคุณต้องการแยก package ของคุณเพิ่มเข้า library crate หลายตัว Cargo เสนอฟีเจอร์ที่เรียกว่า workspace ที่ช่วยจัดการ package ที่เกี่ยวข้อง หลายตัวที่ถูกพัฒนาควบคู่กัน
สร้าง Workspace
workspace คือชุดของ package ที่แชร์ Cargo.lock และ output
directory เดียวกัน มาสร้างโปรเจกต์โดยใช้ workspace — เราจะใช้โค้ดเล็ก
น้อยเพื่อให้เราโฟกัสที่โครงสร้างของ workspace มีหลายวิธีในการจัด
โครงสร้าง workspace ดังนั้นเราจะแสดงเพียงวิธีทั่วไปหนึ่ง เราจะมี
workspace ที่บรรจุ binary และสอง library โดย binary ซึ่งจะให้
functionality หลัก จะขึ้นกับสอง library นั้น library หนึ่งจะให้ฟังก์ชัน add_one และ
library อื่นฟังก์ชัน add_two สาม crate เหล่านี้จะเป็นส่วนของ
workspace เดียวกัน เราจะเริ่มโดยสร้าง directory ใหม่สำหรับ workspace:
$ mkdir add
$ cd add
ถัดไป ใน directory add เราสร้างไฟล์ Cargo.toml ที่จะ configure
workspace ทั้งหมด ไฟล์นี้จะไม่มี section [package] แทน มันจะเริ่ม
ด้วย section [workspace] ที่จะอนุญาตให้เราเพิ่ม member ให้
workspace เรายังพยายามใช้ version ล่าสุดและดีที่สุดของอัลกอริทึม
resolver ของ Cargo ใน workspace ของเราโดยตั้งค่า resolver เป็น "3":
Filename: Cargo.toml
[workspace]
resolver = "3"
ถัดไป เราจะสร้าง binary crate adder โดยรัน cargo new ภายใน
directory add:
$ cargo new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
การรัน cargo new ภายใน workspace ยังเพิ่ม package ที่เพิ่งสร้าง
อัตโนมัติให้ key members ในนิยาม [workspace] ใน Cargo.toml ของ
workspace แบบนี้:
[workspace]
resolver = "3"
members = ["adder"]
ในจุดนี้ เรา build workspace ได้โดยรัน cargo build ไฟล์ใน
directory add ของคุณควรดูแบบนี้:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
workspace มี directory target หนึ่งที่ระดับสูงที่ artifact ที่
คอมไพล์จะถูกวาง — package adder ไม่มี directory target ของตัวเอง
แม้เราจะรัน cargo build จากภายใน directory adder artifact ที่
คอมไพล์จะยังลงเอยที่ add/target ไม่ใช่ add/adder/target Cargo จัด
โครงสร้าง directory target ใน workspace แบบนี้เพราะ crate ใน
workspace มีไว้ขึ้นต่อกัน ถ้าแต่ละ crate มี directory target ของ
ตัวเอง แต่ละ crate จะต้อง recompile แต่ละ crate อื่นใน workspace เพื่อ
วาง artifact ใน directory target ของตัวเอง โดยแชร์ directory
target เดียว crate หลีกเลี่ยงการ rebuild ที่ไม่จำเป็นได้
สร้าง Package ที่สองใน Workspace
ถัดไป มาสร้าง package member อีกตัวใน workspace และเรียกมัน
add_one สร้าง library crate ใหม่ชื่อ add_one:
$ cargo new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
Cargo.toml ระดับสูงตอนนี้จะรวม path add_one ใน list members:
Filename: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
directory add ของคุณตอนนี้ควรมี directory และไฟล์เหล่านี้:
├── 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:
Filename: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
ตอนนี้เรามี package adder กับ binary ของเราขึ้นกับ package
add_one ที่มี library ของเรา ก่อนอื่น เราจะต้องเพิ่ม path
dependency บน add_one ให้ adder/Cargo.toml
Filename: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo ไม่สมมติว่า crate ใน workspace จะขึ้นต่อกัน ดังนั้นเราต้อง ชัดเจนเกี่ยวกับความสัมพันธ์ของ dependency
ถัดไป มาใช้ฟังก์ชัน add_one (จาก crate add_one) ใน crate
adder เปิดไฟล์ adder/src/main.rs และเปลี่ยนฟังก์ชัน main ให้
เรียกฟังก์ชัน add_one ดังใน Listing 14-7
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
add_one จาก crate adderมา build workspace โดยรัน cargo build ใน directory add ระดับสูง!
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
เพื่อรัน binary crate จาก directory add เราระบุว่า package ไหนใน
workspace ที่เราต้องการรันโดยใช้อาร์กิวเมนต์ -p และชื่อ package
กับ cargo run ได้:
$ cargo run -p adder
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
นี่รันโค้ดใน adder/src/main.rs ซึ่งขึ้นกับ crate add_one
ขึ้นกับ Package ภายนอก
สังเกตว่า workspace มีไฟล์ Cargo.lock เพียงไฟล์เดียวที่ระดับสูง
ไม่ใช่มี Cargo.lock ใน directory ของแต่ละ crate นี่รับประกันว่า
crate ทั้งหมดใช้ version เดียวกันของ dependency ทั้งหมด ถ้าเราเพิ่ม
package rand ในไฟล์ adder/Cargo.toml และ add_one/Cargo.toml
Cargo จะ resolve ทั้งสองเป็น version หนึ่งของ rand และบันทึกใน
Cargo.lock เดียว การทำให้ crate ทั้งหมดใน workspace ใช้ dependency
เดียวกันแปลว่า crate จะเข้ากันได้กับกันและกันเสมอ มาเพิ่ม crate
rand ที่ section [dependencies] ในไฟล์ add_one/Cargo.toml
เพื่อให้เราใช้ crate rand ใน crate add_one ได้:
Filename: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
ตอนนี้เราเพิ่ม use rand; ในไฟล์ add_one/src/lib.rs ได้ และ
build workspace ทั้งหมดโดยรัน cargo build ใน directory add จะ
นำมาและคอมไพล์ crate rand เราจะได้ warning หนึ่งเพราะเราไม่ได้อ้าง
ถึง rand ที่เรานำเข้า scope:
$ 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 (run `cargo fix --lib -p add_one` to apply 1 suggestion)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
Cargo.lock ระดับสูงตอนนี้บรรจุข้อมูลเกี่ยวกับ dependency ของ
add_one บน rand อย่างไรก็ตาม แม้ rand ถูกใช้ที่ใดที่หนึ่งใน
workspace เราใช้มันใน crate อื่นใน workspace ไม่ได้ยกเว้นเราเพิ่ม
rand ในไฟล์ Cargo.toml ของพวกมันด้วย ตัวอย่างเช่น ถ้าเราเพิ่ม
use rand; ในไฟล์ adder/src/main.rs สำหรับ package adder เรา
จะได้ error:
$ 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`
เพื่อแก้นี้ แก้ไฟล์ Cargo.toml สำหรับ package adder และระบุว่า
rand เป็น dependency สำหรับมันด้วย การ build package adder จะ
เพิ่ม rand ใน list ของ dependency สำหรับ adder ใน Cargo.lock
แต่ไม่มี copy เพิ่มของ rand ที่จะถูก download Cargo จะรับประกันว่า
ทุก crate ในทุก package ใน workspace ที่ใช้ package rand จะใช้
version เดียวกันตราบใดที่พวกมันระบุ version ที่เข้ากันได้ของ rand
ประหยัดพื้นที่ของเราและรับประกันว่า crate ใน workspace จะเข้ากันได้
กับกันและกัน
ถ้า crate ใน workspace ระบุ version ที่ไม่เข้ากันได้ของ dependency เดียวกัน Cargo จะ resolve แต่ละตัว แต่ยังพยายาม resolve version น้อยที่สุดเท่าที่จะเป็นไปได้
เพิ่มเทสให้ Workspace
สำหรับการปรับปรุงอื่น มาเพิ่มเทสของฟังก์ชัน add_one::add_one ภายใน
crate add_one:
Filename: 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));
}
}
ตอนนี้รัน cargo test ใน directory add ระดับสูง การรัน
cargo test ใน workspace ที่จัดโครงสร้างแบบนี้จะรันเทสสำหรับ crate
ทั้งหมดใน workspace:
$ cargo test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
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-3a47283c568d2b6a)
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
section แรกของ output แสดงว่าเทส it_works ใน crate add_one ผ่าน
section ถัดไปแสดงว่าพบเทสศูนย์ใน crate adder และ section สุดท้าย
แสดงว่าพบ documentation test ศูนย์ใน crate add_one
เรายังรันเทสสำหรับ crate ตัวใดตัวหนึ่งใน workspace จาก directory
ระดับสูงได้โดยใช้ flag -p และระบุชื่อของ crate ที่เราต้องการ
ทดสอบ:
$ cargo test -p add_one
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
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
output นี้แสดงว่า cargo test รันเฉพาะเทสสำหรับ crate add_one
และไม่ได้รันเทส crate adder
ถ้าคุณ publish crate ใน workspace ไปยัง
crates.io แต่ละ crate ใน
workspace จะต้องถูก publish แยก เช่นเดียวกับ cargo test เรา
publish crate ตัวใดตัวหนึ่งใน workspace ของเราได้โดยใช้ flag -p
และระบุชื่อของ crate ที่เราต้องการ publish
สำหรับการฝึกเพิ่ม เพิ่ม crate add_two ให้ workspace นี้ในแบบ
คล้ายกับ crate add_one!
เมื่อโปรเจกต์ของคุณเติบโต พิจารณาใช้ workspace — มันช่วยให้คุณทำงาน กับ component เล็กลง เข้าใจง่ายขึ้น มากกว่าหนึ่ง blob ใหญ่ของโค้ด นอกจากนี้ การเก็บ crate ใน workspace ทำให้การประสานงานระหว่าง crate ง่ายขึ้นได้ถ้าพวกมันมักถูกเปลี่ยนพร้อมกัน