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

Les espaces de travail Cargo

Dans le chapitre 12, nous avons construit un paquet qui incluait une crate binaire et une crate de bibliothèque. Au fur et à mesure que votre projet se développe, vous pourriez constater que la crate de bibliothèque continue de grossir et que vous souhaitez diviser votre paquet en plusieurs crates de bibliothèque. Cargo offre une fonctionnalité appelée workspaces (espaces de travail) qui peut aider à gérer plusieurs paquets liés développés en tandem.

Créer un espace de travail

Un workspace est un ensemble de paquets qui partagent le même Cargo.lock et le même répertoire de sortie. Créons un projet utilisant un workspace – nous utiliserons du code trivial pour pouvoir nous concentrer sur la structure du workspace. Il y a plusieurs façons de structurer un workspace, donc nous n’en montrerons qu’une courante. Nous aurons un workspace contenant un binaire et deux bibliothèques. Le binaire, qui fournira la fonctionnalité principale, dépendra des deux bibliothèques. Une bibliothèque fournira une fonction add_one et l’autre bibliothèque une fonction add_two. Ces trois crates feront partie du même workspace. Nous commencerons par créer un nouveau répertoire pour le workspace :

$ mkdir add
$ cd add

Ensuite, dans le repertoire add, nous créons le fichier Cargo.toml qui configurera l’ensemble du workspace. Ce fichier n’aura pas de section [package]. A la place, il commencera par une section [workspace] qui nous permettra d’ajouter des membres au workspace. Nous prenons également soin d’utiliser la version la plus récente de l’algorithme de resolution de Cargo dans notre workspace en definissant la valeur resolver a "3" :

Fichier : Cargo.toml

[workspace]
resolver = "3"

Ensuite, nous allons créer la crate binaire adder en exécutant cargo new dans le repertoire add :

$ cargo new adder
     Created binary (application) `adder` package
      Adding `adder` as member of workspace at `file:///projects/add`

Exécuter cargo new à l’intérieur d’un workspace ajouté également automatiquement le paquet nouvellement crée à la clé members dans la définition [workspace] du Cargo.toml du workspace, comme ceci :

[workspace]
resolver = "3"
members = ["adder"]

A ce stade, nous pouvons compiler le workspace en exécutant cargo build. Les fichiers dans votre repertoire add devraient ressembler a ceci :

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Le workspace a un seul répertoire target au niveau supérieur dans lequel les artefacts compilés seront placés ; le paquet adder n’a pas son propre répertoire target. Même si nous exécutons cargo build depuis l’intérieur du répertoire adder, les artefacts compilés finiraient quand même dans add/target plutôt que dans add/adder/target. Cargo structure le répertoire target dans un workspace de cette façon parce que les crates dans un workspace sont destinés à dépendre les uns des autres. Si chaque crate avait son propre répertoire target, chaque crate devrait recompiler chacun des autres crates du workspace pour placer les artefacts dans son propre répertoire target. En partageant un seul répertoire target, les crates peuvent éviter les recompilations inutiles.

Créer le deuxième paquet dans le workspace

Ensuite, créons un autre paquet membre dans le workspace et appelons-le add_one. Generez un nouveau crate de bibliothèque nomme add_one :

$ cargo new add_one --lib
     Created library `add_one` package
      Adding `add_one` as member of workspace at `file:///projects/add`

Le Cargo.toml de niveau supérieur inclura maintenant le chemin add_one dans la liste members :

Fichier : Cargo.toml

[workspace]
resolver = "3"
members = ["adder", "add_one"]

Votre repertoire add devrait maintenant avoir ces repertoires et fichiers :

├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Dans le fichier add_one/src/lib.rs, ajoutons une fonction add_one :

Fichier : add_one/src/lib.rs

pub fn add_one(x: i32) -> i32 {
    x + 1
}

Maintenant, nous pouvons faire dependre le paquet adder avec notre binaire du paquet add_one qui contient notre bibliothèque. D’abord, nous devrons ajouter une dépendance de chemin vers add_one dans adder/Cargo.toml.

Fichier : adder/Cargo.toml

[dependencies]
add_one = { path = "../add_one" }

Cargo ne suppose pas que les crates dans un workspace dependront les uns des autres, nous devons donc être explicites sur les relations de dépendance.

Ensuite, utilisons la fonction add_one (de la crate add_one) dans la crate adder. Ouvrez le fichier adder/src/main.rs et modifiez la fonction main pour appeler la fonction add_one, comme dans l’encart 14-7.

Filename: adder/src/main.rs
fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
Listing 14-7: Using the add_one library crate from the adder crate

Compilons le workspace en exécutant cargo build dans le repertoire add de niveau supérieur !

$ 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

Pour exécuter la crate binaire depuis le repertoire add, nous pouvons spécifier quel paquet dans le workspace nous voulons exécuter en utilisant l’argument -p et le nom du paquet avec 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!

Cela exécute le code dans adder/src/main.rs, qui depend de la crate add_one.

Dépendre d’un paquet externe

Remarquez que le workspace n’a qu’un seul fichier Cargo.lock au niveau supérieur, plutôt qu’un Cargo.lock dans le répertoire de chaque crate. Cela garantit que toutes les crates utilisent la même version de toutes les dépendances. Si nous ajoutons le paquet rand aux fichiers adder/Cargo.toml et add_one/Cargo.toml, Cargo résoudra les deux vers une seule version de rand et l’enregistrera dans l’unique Cargo.lock. Faire en sorte que toutes les crates du workspace utilisent les mêmes dépendances signifie que les crates seront toujours compatibles entre elles. Ajoutons la crate rand à la section [dependencies] dans le fichier add_one/Cargo.toml afin de pouvoir utiliser la crate rand dans la crate add_one :

Fichier : add_one/Cargo.toml

[dependencies]
rand = "0.10.1"

Nous pouvons maintenant ajouter use rand; au fichier add_one/src/lib.rs, et compiler l’ensemble du workspace en exécutant cargo build dans le répertoire add intégrera et compilera la crate rand. Nous obtiendrons un avertissement car nous ne faisons pas référence au rand que nous avons amené dans la portée :

$ cargo build
    Updating crates.io index
  Downloaded rand v0.10.1
   --snip--
   Compiling rand v0.10.1
   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)]` (part of `#[warn(unused)]`) 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

Le Cargo.lock de niveau supérieur contient maintenant des informations sur la dépendance d’add_one envers rand. Cependant, même si rand est utilise quelque part dans le workspace, nous ne pouvons pas l’utiliser dans d’autres crates du workspace a moins d’ajouter également rand à leurs fichiers Cargo.toml. Par exemple, si nous ajoutons use rand; au fichier adder/src/main.rs pour le paquet adder, nous obtiendrons une erreur :

$ 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`

Pour corriger cela, editez le fichier Cargo.toml du paquet adder et indiquez que rand est également une dépendance pour celui-ci. Compiler le paquet adder ajoutera rand à la liste des dépendances pour adder dans Cargo.lock, mais aucune copie supplementaire de rand ne sera téléchargée. Cargo s’assurera que chaque crate dans chaque paquet du workspace utilisant le paquet rand utilisera la même version tant qu’ils specifient des versions compatibles de rand, nous faisant economiser de l’espace et garantissant que les crates du workspace seront compatibles entre eux.

Si les crates du workspace spécifient des versions incompatibles de la même dépendance, Cargo résoudra chacune d’entre elles mais essaiera quand même de résoudre le moins de versions possible.

Ajouter un test à un workspace

Pour une autre amélioration, ajoutons un test de la fonction add_one::add_one dans la crate add_one :

Fichier : 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));
    }
}

Maintenant, exécutez cargo test dans le repertoire add de niveau supérieur. Exécuter cargo test dans un workspace structure comme celui-ci exécutera les tests pour tous les crates du 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

La première section de la sortie montre que le test it_works dans la crate add_one a réussi. La section suivante montre que zéro test a été trouvé dans la crate adder, puis la dernière section montre que zéro test de documentation a été trouvé dans la crate add_one.

Nous pouvons également exécuter les tests pour une crate particulière dans un workspace depuis le répertoire de niveau supérieur en utilisant le drapeau -p et en spécifiant le nom de la crate que nous voulons tester :

$ 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

Cette sortie montre que cargo test n’a exécute que les tests de la crate add_one et n’a pas exécute les tests de la crate adder.

Si vous publiez les crates du workspace sur crates.io, chaque crate du workspace devra être publiée séparément. Comme pour cargo test, nous pouvons publier une crate particulière dans notre workspace en utilisant le drapeau -p et en spécifiant le nom de la crate que nous voulons publier.

Pour vous exercer davantage, ajoutez une crate add_two à ce workspace de la même manière que la crate add_one !

Au fur et à mesure que votre projet grandit, envisagez d’utiliser un workspace : il vous permet de travailler avec des composants plus petits et plus faciles à comprendre plutôt qu’un seul gros bloc de code. De plus, garder les crates dans un workspace peut faciliter la coordination entre les crates s’ils sont souvent modifiés en même temps.