Cargo-Arbeitsbereiche
In Kapitel 12 haben wir ein Paket erstellt, das eine binäre Crate und eine Bibliotheks-Crate enthält. Während dein Projekt entwickelt wird, wirst du möglicherweise feststellen, dass die Bibliotheks-Crate immer größer wird und du dein Paket weiter in mehrere Bibliotheks-Crates aufteilen möchtest. Cargo bietet eine Funktion namens Arbeitsbereiche (workspaces), mit denen mehrere verwandte Pakete verwaltet werden können, die gemeinsam entwickelt werden.
Einen Arbeitsbereich erstellen
Ein Arbeitsbereich ist eine Reihe von Paketen, die dieselbe Datei Cargo.lock
sowie dasselbe Ausgabeverzeichnis (output directory) verwenden. Lass uns
mithilfe eines Arbeitsbereiches ein Projekt erstellen. Wir verwenden einfachen
Programmcode, damit wir uns auf die Struktur des Arbeitsbereiches konzentrieren
können. Es gibt verschiedene Möglichkeiten, einen Arbeitsbereich zu
strukturieren. Wir werden nur einen üblichen Weg zeigen. Wir haben einen
Arbeitsbereich mit einer Binärdatei und zwei Bibliotheken. Die Binärdatei stellt
die Hauptfunktion bereit und hängt von den beiden Bibliotheken ab. Eine
Bibliothek stellt die Funktion add_one und eine andere Bibliothek die Funktion
add_two zur Verfügung. Diese drei Crates werden Teil desselben Arbeitsbereichs
sein. Zunächst erstellen wir ein neues Verzeichnis für den Arbeitsbereich:
$ mkdir add
$ cd add
Als Nächstes erstellen wir im Verzeichnis add die Datei Cargo.toml, mit der
der gesamte Arbeitsbereich konfiguriert wird. Diese Datei enthält keinen
Abschnitt [package]. Stattdessen beginnt sie mit einem Abschnitt
[workspace], in dem wir Mitglieder zum Arbeitsbereich hinzufügen können. Wir
stellen außerdem sicher, dass wir die neueste und beste Version des
Cargo-Auflösungsalgorithmus in unserem Arbeitsbereich verwenden, indem wir den
Wert von resolver auf "3" setzen:
Dateiname: Cargo.toml
[workspace]
resolver = "3"
Als nächstes erstellen wir die binäre Crate adder, indem wir cargo new im
Verzeichnis add ausführen:
$ cargo new adder
Created binary (application) `adder` package
Adding `adder` as member of workspace at `file:///projects/add`
Wenn du cargo new innerhalb eines Arbeitsbereichs ausführst, wird das neu
erstellte Paket automatisch zum Schlüssel members in der Definition
[workspace] der Datei Cargo.toml hinzugefügt, etwa so:
[workspace]
resolver = "3"
members = ["adder"]
An dieser Stelle können wir den Arbeitsbereich erstellen, indem wir cargo build ausführen. Die Dateien in deinem add-Verzeichnis sollten
folgendermaßen aussehen:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Der Arbeitsbereich verfügt auf der obersten Ebene über ein Zielverzeichnis
(target), in das die kompilierten Artefakte abgelegt werden; das Paket adder
hat kein eigenes Zielverzeichnis. Selbst wenn wir cargo build aus dem
Verzeichnis adder heraus ausführen würden, landen die kompilierten Artefakte
noch immer in add/target und nicht in add/adder/target. Cargo strukturiert
das Zielverzeichnis in einem derartigen Arbeitsverzeichnis, da die Crates
voneinander abhängig sein sollen. Wenn jede Crate ihr eigenes Zielverzeichnis
hätte, müssten für jede Crate die anderen Crates im Arbeitsbereich neu
kompiliert werden, um die Artefakte in ihrem eigenen Zielverzeichnis
abzulegen. Durch die gemeinsame Nutzung eines einzigen Verzeichnisses können die
Crates unnötiges Neuaufbauen vermeiden.
Erstellen des zweiten Pakets im Arbeitsbereich
Als Nächstes erstellen wir ein weiteres, dem Arbeitsbereich zugehöriges Paket
und nennen es add_one. Erzeuge eine neue Bibliotheks-Crate namens add_one:
$ cargo new add_one --lib
Created library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
Die Datei Cargo.toml auf der obersten Ebene enthält nun den Pfad add_one in
der Liste members:
Dateiname: Cargo.toml
[workspace]
resolver = "3"
members = ["adder", "add_one"]
Dein Verzeichnis add sollte nun so aussehen:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Lass uns in der Datei add_one/src/lib.rs eine Funktion add_one hinzufügen.
Dateiname: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Nun können wir das adder-Paket von unserem add_one-Paket, das unsere
Bibliothek enthält, abhängig machen. Zuerst müssen wir adder/Cargo.toml einen
Pfad zur Abhängigkeit von add_one hinzufügen.
Dateiname: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo geht nicht davon aus, dass Crates in einem Arbeitsbereich voneinander abhängen, daher müssen wir die Abhängigkeit explizit angeben.
Als nächstes verwenden wir die Funktion add_one (aus der Crate add_one) in
der Crate adder. Öffne die Datei adder/src/main.rs und ändere die Funktion
main, um die Funktion add_one aufzurufen, siehe Listing 14-7.
Dateiname: adder/src/main.rs
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
Listing 14-7: Die add_one-Bibliotheks-Crate in der
Crate adder verwenden
Erstellen wir den Arbeitsbereich, indem wir cargo build im obersten
Verzeichnis add ausführen!
$ 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.68s
Um die binäre Crate aus dem Verzeichnis add auszuführen, können wir mithilfe
des Arguments -p und des Paketnamens mit cargo run angeben, welches Paket im
Arbeitsbereich ausgeführt werden soll:
$ cargo run -p adder
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
Dadurch wird der Programmcode in adder/src/main.rs ausgeführt, der von der
Crate add_one abhängt.
Abhängigkeiten zu externen Paketen
Beachte, dass der Arbeitsbereich nur eine Datei Cargo.lock auf der obersten
Ebene enthält, anstatt einer in jeder Crate. Dies stellt sicher, dass alle
Crates dieselbe Version aller Abhängigkeiten verwenden. Wenn wir das Paket
rand zu den Dateien adder/Cargo.toml und add_one/Cargo.toml hinzufügen,
löst Cargo beide dieser Versionen zu einer auf und fügt diese in der
Cargo.lock-Datei hinzu. Wenn alle Crates im Arbeitsbereich dieselben
Abhängigkeiten verwenden, sind die Crates immer miteinander kompatibel. Lass uns
die Crate rand in der Datei add_one/Cargo.toml zum Abschnitt
[dependencies] hinzufügen, damit wir die Crate rand in der Crate add_one
verwenden können:
Dateiname: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Wir können nun use rand; zur Datei add_one/src/lib.rs hinzufügen, und wenn
du den gesamten Arbeitsbereich durch Ausführen von cargo build im Verzeichnis
add erstellst, wird die Crate rand eingefügt und kompiliert. Wir erhalten
eine Warnung, weil wir nicht auf rand referenzieren, das wir in den
Gültigkeitsbereich gebracht haben:
$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--abschneiden--
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 10.18s
Die Datei Cargo.lock der obersten Ebene enthält nun Informationen über die
Abhängigkeit von add_one von rand. Obwohl rand irgendwo im Arbeitsbereich
verwendet wird, können wir es nicht in anderen Crates im Arbeitsbereich
verwenden, es sei denn, wir fügen rand zu ihren Cargo.toml-Dateien hinzu.
Wenn wir beispielsweise use rand; zur Datei adder/src/main.rs für das Paket
adder hinzufügen, wird folgende Fehlermeldung angezeigt:
$ cargo build
--abschneiden--
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`
Um dies zu beheben, bearbeiten wir die Datei Cargo.toml für das Paket adder
und geben an, dass rand auch eine Abhängigkeit davon ist. Durch das Erstellen
des Pakets adder wird rand zur Liste der Abhängigkeiten für adder in
Cargo.lock hinzugefügt, es werden jedoch keine zusätzlichen Kopien von rand
heruntergeladen. Cargo stellt sicher, dass jede Crate in jedem Paket im
Arbeitsbereich, das das rand-Paket verwendet, die gleiche Version verwendet,
solange sie kompatible Versionen von rand angeben, was uns Platz spart und
sicherstellt, dass die Crates im Arbeitsbereich miteinander kompatibel sind.
Wenn Crates im Arbeitsbereich inkompatible Versionen der gleichen Abhängigkeit angeben, löst Cargo jede von ihnen auf, versucht aber trotzdem, so wenige Versionen wie möglich aufzulösen.
Hinzufügen eines Tests zu einem Arbeitsbereich
Füge für eine weitere Verbesserung innerhalb der Crate add_one einen Test der
Funktion add_one::add_one hinzu:
Dateiname: add_one/src/lib.rs
#![allow(unused)]
fn main() {
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));
}
}
}
Führen wir nun cargo test in der obersten Ebene im Verzeichnis add aus. Die
Ausführung von cargo test in einem Arbeitsbereich, der wie dieser strukturiert
ist, führt die Tests für alle Crates im Arbeitsbereich aus:
$ cargo test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.27s
Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841)
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-49979ff40686fa8e)
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
Der erste Abschnitt der Ausgabe zeigt, dass der Test it_works in der Crate
add_one bestanden wurde. Der nächste Abschnitt zeigt, dass in der Crate
adder keine Tests gefunden wurden, und der letzte Abschnitt zeigt, dass in der
Crate add_one keine Dokumentationstests gefunden wurden.
Wir können auch Tests für eine bestimmte Crate in einem Arbeitsbereich aus dem
Verzeichnis der obersten Ebene ausführen, indem wir die Option -p verwenden
und den Namen der Crate angeben, die wir testen möchten:
$ cargo test -p add_one
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74)
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
Die Ausgabe zeigt, dass cargo test nur die Tests der Crate add_one, aber
nicht der Crate adder ausgeführt hat.
Wenn du die Crates im Arbeitsbereich unter crates.io veröffentlichst,
muss jede Crate im Arbeitsbereich separat veröffentlicht werden. Der Befehl
cargo publish hat keine Option --all oder -p, daher musst du in das
Verzeichnis jeder Crate wechseln und cargo publish für jede Crate im
Arbeitsbereich ausführen, um die Crates zu veröffentlichen.
Als zusätzliche Übung, füge ähnlich der Crate add_one diesem Arbeitsbereich
eine Crate add-two hinzu!
Wenn dein Projekt wächst, solltest du einen Arbeitsbereich verwenden: Es ermöglicht dir, mit kleineren, leichter zu verstehenden Komponenten zu arbeiten als mit einem großen Klumpen von Code. Darüber hinaus kann die Verwaltung von Crates in einem Arbeitsbereich die Koordination zwischen Crates erleichtern, wenn sie häufig zur gleichen Zeit verändert werden.