Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.