Không gian làm việc Cargo
Trong Chương 12, chúng ta đã xây dựng một gói bao gồm một crate nhị phân và một crate thư viện. Khi dự án của bạn phát triển, bạn có thể thấy rằng crate thư viện tiếp tục trở nên lớn hơn và bạn muốn chia gói của mình thành nhiều crate thư viện hơn. Cargo cung cấp một tính năng gọi là workspaces (không gian làm việc) có thể giúp quản lý nhiều gói liên quan được phát triển cùng nhau.
Tạo một Workspace
Một workspace là một tập hợp các gói chia sẻ cùng một Cargo.lock và thư mục
đầu ra. Hãy tạo một dự án sử dụng workspace—chúng ta sẽ sử dụng mã đơn giản để
có thể tập trung vào cấu trúc của workspace. Có nhiều cách để cấu trúc một
workspace, vì vậy chúng ta sẽ chỉ trình bày một cách phổ biến. Chúng ta sẽ có
một workspace chứa một crate nhị phân và hai crate thư viện. Crate nhị phân,
cung cấp chức năng chính, sẽ phụ thuộc vào hai thư viện. Một thư viện sẽ cung
cấp hàm add_one
và thư viện khác cung cấp hàm add_two
. Ba crate này sẽ là
một phần của cùng một workspace. Chúng ta sẽ bắt đầu bằng cách tạo một thư mục
mới cho workspace:
$ mkdir add
$ cd add
Tiếp theo, trong thư mục add, chúng ta tạo tệp Cargo.toml sẽ cấu hình toàn
bộ workspace. Tệp này sẽ không có phần [package]
. Thay vào đó, nó sẽ bắt đầu
với phần [workspace]
cho phép chúng ta thêm thành viên vào workspace. Chúng ta
cũng lưu ý sử dụng phiên bản mới nhất và tốt nhất của thuật toán resolver của
Cargo trong workspace của chúng ta bằng cách đặt resolver
thành "3"
.
Tên tệp: Cargo.toml
[workspace]
resolver = "3"
Tiếp theo, chúng ta sẽ tạo crate nhị phân adder
bằng cách chạy cargo new
trong thư mục add:
$ cargo new adder
Creating binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
Chạy cargo new
bên trong một workspace cũng tự động thêm gói mới tạo vào khóa
members
trong định nghĩa [workspace]
trong Cargo.toml
của workspace, như
thế này:
[workspace]
resolver = "3"
members = ["adder"]
Tại thời điểm này, chúng ta có thể xây dựng workspace bằng cách chạy
cargo build
. Các tệp trong thư mục add của bạn sẽ trông như thế này:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Workspace có một thư mục target ở cấp cao nhất nơi các sản phẩm biên dịch sẽ
được đặt vào; gói adder
không có thư mục target riêng. Ngay cả khi chúng ta
chạy cargo build
từ bên trong thư mục adder, các sản phẩm biên dịch vẫn sẽ
kết thúc trong add/target thay vì add/adder/target. Cargo cấu trúc thư mục
target trong một workspace như thế này vì các crate trong một workspace được
thiết kế để phụ thuộc vào nhau. Nếu mỗi crate có thư mục target riêng, mỗi
crate sẽ phải biên dịch lại từng crate khác trong workspace để đặt các sản phẩm
vào thư mục target riêng của nó. Bằng cách chia sẻ một thư mục target, các
crate có thể tránh việc xây dựng lại không cần thiết.
Tạo Gói Thứ Hai trong Workspace
Tiếp theo, hãy tạo một gói thành viên khác trong workspace và gọi nó là
add_one
. Tạo một crate thư viện mới có tên add_one
:
$ cargo new add_one --lib
Creating library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
Cargo.toml cấp cao nhất sẽ bao gồm đường dẫn add_one trong danh sách
members
:
Tên tệp: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Thư mục add của bạn bây giờ sẽ có các thư mục và tệp này:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Trong tệp add_one/src/lib.rs, hãy thêm một hàm add_one
:
Tên tệp: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Bây giờ chúng ta có thể để gói adder
với chương trình nhị phân của chúng ta
phụ thuộc vào gói add_one
chứa thư viện của chúng ta. Trước tiên, chúng ta sẽ
cần thêm một phụ thuộc đường dẫn vào add_one
trong adder/Cargo.toml.
Tên tệp: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo không giả định rằng các crate trong một workspace sẽ phụ thuộc vào nhau, vì vậy chúng ta cần chỉ định rõ ràng mối quan hệ phụ thuộc.
Tiếp theo, hãy sử dụng hàm add_one
(từ crate add_one
) trong crate adder
.
Mở tệp adder/src/main.rs và thay đổi hàm main
để gọi hàm add_one
, như
trong Listing 14-7.
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
Hãy xây dựng workspace bằng cách chạy cargo build
trong thư mục add cấp cao
nhất!
$ 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
Để chạy crate nhị phân từ thư mục add, chúng ta có thể chỉ định gói nào trong
workspace chúng ta muốn chạy bằng cách sử dụng đối số -p
và tên gói với
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!
Điều này chạy mã trong adder/src/main.rs, mã này phụ thuộc vào crate
add_one
.
Phụ thuộc vào một Gói Bên ngoài trong một Workspace
Lưu ý rằng workspace chỉ có một tệp Cargo.lock ở cấp cao nhất, thay vì có
Cargo.lock trong thư mục của mỗi crate. Điều này đảm bảo rằng tất cả các crate
đều sử dụng cùng phiên bản của tất cả các phụ thuộc. Nếu chúng ta thêm gói
rand
vào tệp adder/Cargo.toml và add_one/Cargo.toml, Cargo sẽ giải quyết
cả hai gói này thành một phiên bản của rand
và ghi lại trong Cargo.lock duy
nhất. Việc làm cho tất cả các crate trong workspace sử dụng cùng các phụ thuộc
có nghĩa là các crate sẽ luôn tương thích với nhau. Hãy thêm crate rand
vào
phần [dependencies]
trong tệp add_one/Cargo.toml để chúng ta có thể sử dụng
crate rand
trong crate add_one
:
Tên tệp: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Bây giờ chúng ta có thể thêm use rand;
vào tệp add_one/src/lib.rs, và việc
xây dựng toàn bộ workspace bằng cách chạy cargo build
trong thư mục add sẽ
mang rand
crate vào và biên dịch nó. Chúng ta sẽ nhận được một cảnh báo vì
chúng ta không tham chiếu đến rand
mà chúng ta đã đưa vào phạm vi:
$ 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 cấp cao nhất bây giờ chứa thông tin về phụ thuộc của add_one
vào
rand
. Tuy nhiên, mặc dù rand
được sử dụng ở đâu đó trong workspace, chúng ta
không thể sử dụng nó trong các crate khác trong workspace trừ khi chúng ta thêm
rand
vào tệp Cargo.toml của họ. Ví dụ: nếu chúng ta thêm use rand;
vào tệp
adder/src/main.rs cho gói adder
, chúng ta sẽ nhận được lỗi:
$ 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`
Để sửa điều này, hãy chỉnh sửa tệp Cargo.toml cho gói adder
và chỉ ra rằng
rand
cũng là một phụ thuộc cho nó. Việc xây dựng gói adder
sẽ thêm rand
vào danh sách phụ thuộc cho adder
trong Cargo.lock, nhưng không có bản sao
bổ sung nào của rand
sẽ được tải xuống. Cargo sẽ đảm bảo rằng mọi crate trong
mọi gói trong workspace sử dụng gói rand
sẽ sử dụng cùng một phiên bản miễn là
họ chỉ định các phiên bản tương thích của rand
, tiết kiệm không gian cho chúng
ta và đảm bảo rằng các crate trong workspace sẽ tương thích với nhau.
Nếu các crate trong workspace chỉ định các phiên bản không tương thích của cùng một phụ thuộc, Cargo sẽ giải quyết từng crate, nhưng vẫn cố gắng giải quyết càng ít phiên bản càng tốt.
Thêm một Bài Kiểm thử vào Workspace
Để một cải tiến khác, hãy thêm một bài kiểm thử cho hàm add_one::add_one
trong
crate add_one
:
Tên tệp: 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));
}
}
Bây giờ chạy cargo test
trong thư mục add cấp cao nhất. Chạy cargo test
trong một workspace có cấu trúc như thế này sẽ chạy các bài kiểm thử cho tất cả
các crate trong 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
Phần đầu tiên của đầu ra cho thấy bài kiểm thử it_works
trong crate add_one
đã thành công. Phần tiếp theo cho thấy không tìm thấy bài kiểm thử nào trong
crate adder
, và sau đó phần cuối cùng cho thấy không tìm thấy bài kiểm thử tài
liệu nào trong crate add_one
.
Chúng ta cũng có thể chạy các bài kiểm thử cho một crate cụ thể trong workspace
từ thư mục cấp cao nhất bằng cách sử dụng cờ -p
và chỉ định tên của crate mà
chúng ta muốn kiểm thử:
$ 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
Đầu ra này hiển thị cargo test
chỉ chạy các bài kiểm thử cho crate add_one
và không chạy các bài kiểm thử crate adder
.
Nếu bạn xuất bản các crate trong workspace lên crates.io,
mỗi crate trong workspace sẽ cần được xuất bản riêng. Giống như cargo test
,
chúng ta có thể xuất bản một crate cụ thể trong workspace của chúng ta bằng cách
sử dụng cờ -p
và chỉ định tên của crate mà chúng ta muốn xuất bản.
Để thực hành thêm, hãy thêm một crate add_two
vào workspace này theo cách
tương tự như crate add_one
!
Khi dự án của bạn phát triển, hãy cân nhắc sử dụng một workspace: nó cho phép bạn làm việc với các thành phần nhỏ hơn, dễ hiểu hơn so với một đống mã lớn. Hơn nữa, việc giữ các crate trong một workspace có thể làm cho việc phối hợp giữa các crate dễ dàng hơn nếu chúng thường xuyên thay đổi cùng một lúc.