Obszary robocze Cargo
W Rozdziale 12 zbudowaliśmy pakiet, który zawierał binarny crate i biblioteczny crate. W miarę rozwoju projektu może się okazać, że biblioteczny crate staje się coraz większy i chcesz podzielić swój pakiet na wiele bibliotecznych crate’ów. Cargo oferuje funkcję o nazwie workspaces (obszary robocze), która może pomóc w zarządzaniu wieloma powiązanymi pakietami, które są rozwijane równocześnie.
Tworzenie obszaru roboczego
Obszar roboczy to zestaw pakietów, które współdzielą ten sam plik Cargo.lock i katalog wyjściowy. Stwórzmy projekt z wykorzystaniem obszaru roboczego — użyjemy trywialnego kodu, abyśmy mogli skupić się na strukturze obszaru roboczego. Istnieje wiele sposobów na strukturę obszaru roboczego, więc pokażemy tylko jeden powszechny. Będziemy mieć obszar roboczy zawierający binarny pakiet i dwie biblioteki. Binarny pakiet, który zapewni główną funkcjonalność, będzie zależał od dwóch bibliotek. Jedna biblioteka zapewni funkcję add_one, a druga biblioteka funkcję add_two. Te trzy pakiety będą częścią tego samego obszaru roboczego. Zaczniemy od utworzenia nowego katalogu dla obszaru roboczego:
$ mkdir add
$ cd add
Następnie, w katalogu add, tworzymy plik Cargo.toml, który skonfiguruje cały obszar roboczy. Ten plik nie będzie miał sekcji [package]. Zamiast tego, zacznie się od sekcji [workspace], która pozwoli nam dodawać członków do obszaru roboczego. Zwracamy również uwagę na użycie najnowszej i najlepszej wersji algorytmu resolvera Cargo w naszym obszarze roboczym, ustawiając wartość resolver na "3":
Nazwa pliku: Cargo.toml
[workspace]
resolver = "3"
Następnie utworzymy binarny pakiet adder poprzez uruchomienie cargo new w katalogu add:
$ cargo new adder
Utworzono pakiet binarny (aplikacja) `adder`
Dodawanie `adder` jako członka obszaru roboczego pod `file:///projects/add`
Uruchomienie cargo new wewnątrz obszaru roboczego automatycznie dodaje nowo utworzony pakiet do klucza members w definicji [workspace] w pliku Cargo.toml obszaru roboczego, w następujący sposób:
[workspace]
resolver = "3"
members = ["adder"]
W tym momencie możemy zbudować obszar roboczy, uruchamiając cargo build. Pliki w katalogu add powinny wyglądać następująco:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Obszar roboczy posiada jeden katalog target na najwyższym poziomie, do którego zostaną umieszczone skompilowane artefakty; pakiet adder nie ma własnego katalogu target. Nawet gdybyśmy uruchomili cargo build z wnętrza katalogu adder, skompilowane artefakty nadal znalazłyby się w add/target, a nie add/adder/target. Cargo strukturyzuje katalog target w obszarze roboczym w ten sposób, ponieważ pakiety w obszarze roboczym mają od siebie zależeć. Gdyby każdy pakiet miał własny katalog target, każdy pakiet musiałby rekompilować każdy z pozostałych pakietów w obszarze roboczym, aby umieścić artefakty w swoim własnym katalogu target. Dzięki współdzieleniu jednego katalogu target pakiety mogą uniknąć niepotrzebnej ponownej kompilacji.
Tworzenie drugiego pakietu w obszarze roboczym
Następnie stwórzmy kolejny pakiet członkowski w obszarze roboczym i nazwijmy go add_one. Wygeneruj nowy pakiet biblioteczny o nazwie add_one:
$ cargo new add_one --lib
Utworzono bibliotekę `add_one`
Dodawanie `add_one` jako członka obszaru roboczego pod `file:///projects/add`
Plik Cargo.toml na najwyższym poziomie będzie teraz zawierał ścieżkę add_one na liście members:
Nazwa pliku: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Twój katalog add powinien teraz zawierać następujące katalogi i pliki:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
W pliku add_one/src/lib.rs dodajmy funkcję add_one:
Nazwa pliku: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Teraz pakiet adder z naszym plikiem binarnym może zależeć od pakietu add_one, który zawiera naszą bibliotekę. Najpierw będziemy musieli dodać zależność ścieżkową do add_one do pliku adder/Cargo.toml.
Nazwa pliku: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo nie zakłada, że pakiety w obszarze roboczym będą od siebie zależeć, więc musimy jawnie określić relacje zależności.
Następnie, użyjmy funkcji add_one (z pakietu add_one) w pakiecie adder. Otwórz plik adder/src/main.rs i zmień funkcję main, aby wywoływała funkcję add_one, jak w Listing 14-7.
fn main() {
let num = 10;
println!("Witaj, świecie! {num} plus jeden to {}!", add_one::add_one(num));
}
Zbudujmy obszar roboczy, uruchamiając cargo build w katalogu add na najwyższym poziomie!
$ cargo build
Kompilowanie add_one v0.1.0 (file:///projects/add/add_one)
Kompilowanie adder v0.1.0 (file:///projects/add/adder)
Zakończono `dev` profil [unoptimized + debuginfo] cel(e) w 0.22s
Aby uruchomić binarny pakiet z katalogu add, możemy określić, który pakiet w obszarze roboczym chcemy uruchomić, używając argumentu -p i nazwy pakietu z cargo run:
$ cargo run -p adder
Zakończono `dev` profil [unoptimized + debuginfo] cel(e) w 0.00s
Uruchamianie `target/debug/adder`
Witaj, świecie! 10 plus jeden to 11!
To uruchamia kod z adder/src/main.rs, który zależy od pakietu add_one.
Zależność od zewnętrznego pakietu
Zauważ, że obszar roboczy ma tylko jeden plik Cargo.lock na najwyższym poziomie, zamiast mieć plik Cargo.lock w katalogu każdego pakietu. To zapewnia, że wszystkie pakiety używają tej samej wersji wszystkich zależności. Jeśli dodamy pakiet rand do plików adder/Cargo.toml i add_one/Cargo.toml, Cargo rozwiąże oba do jednej wersji rand i zapisze to w jednym pliku Cargo.lock. Sprawienie, by wszystkie pakiety w obszarze roboczym używały tych samych zależności, oznacza, że pakiety zawsze będą ze sobą kompatybilne. Dodajmy pakiet rand do sekcji [dependencies] w pliku add_one/Cargo.toml, abyśmy mogli używać pakietu rand w pakiecie add_one:
Nazwa pliku: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Możemy teraz dodać use rand; do pliku add_one/src/lib.rs, a zbudowanie całego obszaru roboczego przez uruchomienie cargo build w katalogu add spowoduje zaimportowanie i skompilowanie pakietu rand. Otrzymamy jedno ostrzeżenie, ponieważ nie odwołujemy się do rand, które wprowadziliśmy do zakresu:
$ cargo build
Aktualizowanie indeksu crates.io
Pobrano rand v0.8.5
--snip--
Kompilowanie rand v0.8.5
Kompilowanie add_one v0.1.0 (file:///projects/add/add_one)
warning: nieużywany import: `rand`
--> add_one/src/lib.rs:1:5
|
1 | use rand;
| ^^^^
|
= note: `#[warn(unused_imports)]` domyślnie włączone
warning: `add_one` (lib) wygenerował 1 ostrzeżenie (uruchom `cargo fix --lib -p add_one`, aby zastosować 1 sugestię)
Kompilowanie adder v0.1.0 (file:///projects/add/adder)
Zakończono `dev` profil [unoptimized + debuginfo] cel(e) w 0.95s
Plik Cargo.lock na najwyższym poziomie zawiera teraz informacje o zależności add_one od rand. Jednakże, mimo że rand jest używane gdzieś w obszarze roboczym, nie możemy go używać w innych pakietach w obszarze roboczym, chyba że dodamy rand również do ich plików Cargo.toml. Na przykład, jeśli dodamy use rand; do pliku adder/src/main.rs dla pakietu adder, otrzymamy błąd:
$ cargo build
--snip--
Kompilowanie adder v0.1.0 (file:///projects/add/adder)
error[E0432]: nierozwiązany import `rand`
--> adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ brak zewnętrznego pakietu `rand`
Aby to naprawić, edytuj plik Cargo.toml pakietu adder i wskaż, że rand jest również jego zależnością. Budowanie pakietu adder doda rand do listy zależności dla adder w Cargo.lock, ale nie zostaną pobrane żadne dodatkowe kopie rand. Cargo zapewni, że każdy pakiet w obszarze roboczym używający pakietu rand będzie używał tej samej wersji, o ile określą kompatybilne wersje rand, oszczędzając nam miejsce i zapewniając, że pakiety w obszarze roboczym będą ze sobą kompatybilne.
Jeśli pakiety w obszarze roboczym określają niekompatybilne wersje tej samej zależności, Cargo rozwiąże każdą z nich, ale nadal będzie starało się rozwiązać jak najmniej wersji.
Dodawanie testu do obszaru roboczego
Dla kolejnego ulepszenia, dodajmy test funkcji add_one::add_one w pakiecie add_one:
Nazwa pliku: 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));
}
}
Teraz uruchom cargo test w katalogu add najwyższego poziomu. Uruchomienie cargo test w tak skonstruowanym obszarze roboczym uruchomi testy dla wszystkich pakietów w obszarze roboczym:
$ cargo test
Kompilowanie add_one v0.1.0 (file:///projects/add/add_one)
Kompilowanie adder v0.1.0 (file:///projects/add/adder)
Zakończono `test` profil [unoptimized + debuginfo] cel(e) w 0.20s
Uruchamianie 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
Uruchamianie 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
Pierwsza sekcja wyjścia pokazuje, że test it_works w pakiecie add_one przeszedł. Następna sekcja pokazuje, że w pakiecie adder nie znaleziono żadnych testów, a następnie ostatnia sekcja pokazuje, że w pakiecie add_one nie znaleziono żadnych testów dokumentacji.
Możemy również uruchomić testy dla jednego konkretnego pakietu w obszarze roboczym z katalogu najwyższego poziomu, używając flagi -p i określając nazwę pakietu, który chcemy przetestować:
$ cargo test -p add_one
Zakończono `test` profil [unoptimized + debuginfo] cel(e) w 0.00s
Uruchamianie 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
To wyjście pokazuje, że cargo test uruchomił tylko testy dla pakietu add_one i nie uruchomił testów pakietu adder.
Jeśli opublikujesz pakiety w obszarze roboczym na crates.io, każdy pakiet w obszarze roboczym będzie musiał zostać opublikowany osobno. Podobnie jak cargo test, możemy opublikować konkretny pakiet w naszym obszarze roboczym, używając flagi -p i określając nazwę pakietu, który chcemy opublikować.
Dodatkowo, spróbuj dodać pakiet add_two do tego obszaru roboczego w podobny sposób jak pakiet add_one!
W miarę wzrostu projektu rozważ użycie obszaru roboczego: pozwala on na pracę z mniejszymi, łatwiejszymi do zrozumienia komponentami niż jedna duża bryła kodu. Ponadto, utrzymywanie pakietów w obszarze roboczym może ułatwić koordynację między pakietami, jeśli często są zmieniane w tym samym czasie.