Cargo Workspaces
Di Bab 12, kita udah bikin sebuah package yang isinya satu binary crate sama satu library crate. Seiring berkembangnya project kita, kita mungkin menemukan bahwa library crate kita terus jadi makin besar dan kita mau membagi package kita lebih jauh lagi jadi beberapa library crates. Cargo menawarkan fitur bernama workspaces (ruang kerja) yang bisa membantu mengelola beberapa packages yang saling terkait yang dikembangkan secara beriringan (in tandem).
Membuat Workspace
Sebuah workspace adalah sekumpulan packages yang berbagi Cargo.lock dan
direktori output yang sama. Mari kita bikin project yang memakai
workspace—kita bakal pakai kode yang sepele biar kita bisa fokus ke
struktur dari workspace tersebut. Ada banyak cara buat menata struktur
sebuah workspace, jadi kita cuma bakal nunjukin satu cara yang umum.
Kita bakal punya sebuah workspace yang berisi satu binary dan dua
libraries. Si binary, yang bakal menyediakan fungsionalitas utama,
bakal bergantung pada (depend on) kedua libraries itu. Satu library
bakal menyediakan fungsi add_one dan library yang satunya lagi
menyediakan fungsi add_two. Ketiga crates ini bakal jadi bagian dari
workspace yang sama. Kita bakal memulainya dengan membuat direktori baru
buat workspace tersebut:
$ mkdir add
$ cd add
Berikutnya, di dalam direktori add, kita bikin file Cargo.toml yang
bakal mengkonfigurasi seluruh workspace. File ini tidak bakal punya
bagian [package]. Sebaliknya, file ini bakal diawali dengan bagian
[workspace] yang bakal memungkinkan kita buat menambahkan anggota
(members) ke dalam workspace. Kita juga sengaja menentukan buat memakai
algoritma resolver (pemecah) Cargo versi yang paling baru dan paling
bagus di workspace kita dengan menge-set nilai resolver ke "3".
Nama file: Cargo.toml
[workspace]
resolver = "3"
Selanjutnya, kita bakal membikin binary crate adder dengan menjalankan
cargo new di dalam direktori add:
$ cargo new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
Menjalankan cargo new di dalam sebuah workspace juga secara otomatis
menambahkan package yang baru dibuat itu ke dalam key members di
definisi [workspace] yang ada di Cargo.toml tingkat workspace, kayak gini:
[workspace]
resolver = "3"
members = ["adder"]
Pada titik ini, kita bisa mem-build workspace ini dengan menjalankan
cargo build. File-file di direktori add kita seharusnya kelihatan
seperti ini:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Workspace ini cuma punya satu direktori target di tingkat teratas
(top level) tempat artefak hasil kompilasi bakal ditaruh; package
adder tidak punya direktori target-nya sendiri. Bahkan kalau pun kita
menjalankan cargo build dari dalam direktori adder, artefak
hasil kompilasinya bakal tetap berujung di add/target bukannya di
add/adder/target. Cargo menata struktur direktori target di sebuah
workspace seperti ini karena crates di dalam sebuah workspace itu
memang ditujukan buat bergantung satu sama lain. Kalau setiap crate punya
direktori target-nya sendiri, setiap crate harus men-compile ulang
setiap crate lainnya di dalam workspace itu buat menaruh artefaknya di
direktori target-nya sendiri-sendiri. Dengan berbagi satu direktori
target, crates bisa menghindari kompilasi ulang yang tidak diperlukan.
Membuat Package Kedua di dalam Workspace
Berikutnya, mari kita buat member package (paket anggota) lain di dalam
workspace ini dan namakan dia add_one. Generate sebuah library crate
baru bernama add_one:
$ cargo new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
File Cargo.toml tingkat teratas sekarang bakal menyertakan path add_one
ke dalam daftar members:
Nama file: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Direktori add kita seharusnya sekarang punya direktori dan file berikut ini:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Di dalam file add_one/src/lib.rs, mari kita tambahkan sebuah fungsi add_one:
Nama file: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Sekarang kita bisa bikin package adder dengan binary kita bergantung pada
package add_one yang punya library kita. Pertama-tama, kita harus
menambahkan path dependency pada add_one ke dalam adder/Cargo.toml.
Nama file: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo tidak mengasumsikan kalau crates di dalam sebuah workspace bakal bergantung satu sama lain, jadi kita harus secara eksplisit mendefinisikan hubungan dependensi (ketergantungan) mereka.
Selanjutnya, mari kita pakai fungsi add_one (dari crate add_one) di dalam
crate adder. Buka file adder/src/main.rs dan ubah fungsi main buat
memanggil fungsi add_one, seperti di Listing 14-7.
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
add_one dari dalam crate adderMari kita build workspace ini dengan menjalankan cargo build di direktori
tingkat teratas 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
Buat menjalankan binary crate tersebut dari direktori add, kita bisa
menentukan package mana di dalam workspace yang mau kita jalankan dengan
memakai argumen -p beserta nama package-nya dengan 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!
Ini bakal menjalankan kode di adder/src/main.rs, yang mana bergantung pada
crate add_one.
Bergantung pada Package Eksternal di dalam Workspace
Perhatikan bahwa workspace ini cuma punya satu file Cargo.lock di tingkat
teratas, bukannya punya file Cargo.lock di setiap direktori crate. Ini
memastikan kalau semua crates memakai versi yang persis sama buat semua
dependensinya. Kalau kita menambahkan package rand ke dalam file
adder/Cargo.toml dan add_one/Cargo.toml, Cargo bakal me-resolve keduanya
ke satu versi dari rand dan mencatat hal itu di dalam satu file Cargo.lock
tersebut. Membuat semua crates di workspace memakai dependensi yang sama
berarti semua crates tersebut bakal selalu kompatibel satu sama lain.
Mari kita tambahkan crate rand ke bagian [dependencies] di file
add_one/Cargo.toml supaya kita bisa memakai crate rand di dalam crate
add_one:
Nama file: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Sekarang kita bisa menambahkan use rand; ke dalam file add_one/src/lib.rs,
dan saat mem-build seluruh workspace dengan menjalankan cargo build di
direktori add bakal ikut membawa dan men-compile crate rand. Kita bakal
dapat satu peringatan (warning) karena kita belum memakai rand yang sudah kita
bawa ke dalam scope tersebut:
$ 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
File Cargo.lock di tingkat teratas sekarang berisi informasi mengenai
dependensi dari add_one terhadap rand. Namun, meskipun rand sudah dipakai
di suatu tempat di dalam workspace, kita tidak bisa memakainya di crates
lainnya di workspace ini kecuali kita menambahkan rand ke dalam file
Cargo.toml mereka juga. Misalnya, kalau kita menambahkan use rand; ke dalam
file adder/src/main.rs untuk package adder, kita bakal dapat 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`
Buat memperbaikinya, edit file Cargo.toml untuk package adder dan
indikasikan kalau rand juga adalah sebuah dependensi buatnya. Mem-build
package adder bakal menambahkan rand ke dalam daftar dependensi untuk
adder di dalam Cargo.lock, tapi tidak akan ada salinan tambahan dari
rand yang bakal di-download. Cargo bakal memastikan kalau setiap crate di
setiap package di dalam workspace yang memakai package rand bakal
memakai versi yang persis sama selama mereka menentukan versi dari rand yang
kompatibel, hal ini menghemat kapasitas penyimpanan kita dan memastikan kalau
semua crates di workspace ini bakal kompatibel satu sama lain.
Kalau crates di workspace menentukan versi yang tidak kompatibel dari dependensi yang sama, Cargo bakal mencoba me-resolve masing-masing dari mereka, tapi tetap bakal berusaha me-resolve ke sesedikit mungkin versi.
Menambahkan Pengujian ke Workspace
Untuk peningkatan selanjutnya, mari kita tambahkan sebuah pengujian buat fungsi
add_one::add_one di dalam crate add_one:
Nama file: 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));
}
}
Sekarang jalankan cargo test di dalam direktori add di tingkat teratas.
Menjalankan cargo test di sebuah workspace yang ditata seperti ini bakal
menjalankan pengujian untuk semua crates di dalam workspace tersebut:
$ 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
Bagian pertama dari outputnya menunjukkan kalau pengujian it_works di dalam
crate add_one itu sukses (passed). Bagian selanjutnya menunjukkan kalau ada
nol pengujian yang ditemukan di dalam crate adder, dan lalu bagian terakhir
menunjukkan kalau ada nol pengujian dokumentasi yang ditemukan di dalam crate
add_one.
Kita juga bisa menjalankan pengujian buat satu crate tertentu di dalam
sebuah workspace dari direktori tingkat teratas dengan memakai flag -p
dan menentukan nama dari crate yang mau kita uji:
$ 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 ini menunjukkan kalau cargo test cuma menjalankan pengujian untuk
crate add_one dan tidak menjalankan pengujian untuk crate adder.
Kalau kita mempublikasikan crates yang ada di dalam workspace ke
crates.io, setiap crate di dalam workspace itu
harus dipublikasikan secara terpisah. Sama seperti cargo test, kita bisa
mempublikasikan satu crate tertentu di dalam workspace kita dengan memakai
flag -p dan menentukan nama dari crate yang mau kita publikasikan.
Sebagai latihan tambahan, coba tambahkan crate add_two ke dalam workspace
ini dengan cara yang sama seperti crate add_one!
Seiring project kita bertambah besar, pertimbangkanlah buat memakai sebuah workspace: ini memungkinkan kita buat bekerja dengan komponen-komponen yang lebih kecil dan lebih gampang dipahami ketimbang bekerja dengan satu gumpalan kode (blob of code) yang super besar. Selain itu, menyimpan crates di dalam sebuah workspace bisa bikin koordinasi antar crates jadi lebih mudah kalau mereka sering diubah secara bersamaan.