Publier une crate sur crates.io

Nous avons déjà utilisé des paquets provenant de crates.io comme dépendance de notre projet, mais vous pouvez aussi partager votre code avec d'autres personnes en publiant vos propres paquets. Le registre des crates disponible sur crates.io distribue le code source de vos paquets, donc il héberge principalement du code qui est open source.

Rust et cargo ont des fonctionnalités qui aident les développeurs à trouver et utiliser les paquets que vous publiez. Nous allons voir certaines de ces fonctionnalités puis nous allons expliquer comment publier un paquet.

Créer des commentaires de documentation utiles

Documenter correctement vos paquets aidera les autres utilisateurs à savoir comment et quand les utiliser, donc ça vaut la peine de consacrer du temps à la rédaction de la documentation. Dans le chapitre 3, nous avons vu comment commenter du code Rust en utilisant deux barres obliques //. Rust a aussi un type particulier de commentaire pour la documentation, aussi connu sous le nom de commentaire de documentation, qui va générer de la documentation en HTML. Le HTML affiche le contenu des commentaires de documentation pour les éléments public de votre API à destination des développeurs qui s'intéressent à la manière d'utiliser votre crate et non pas à la manière dont elle est implémentée.

Les commentaires de documentation utilisent trois barres obliques /// au lieu de deux et prend en charge la notation Markdown pour mettre en forme le texte. Placez les commentaires de documentation juste avant l'élément qu'ils documentent. L'encart 14-1 montre des commentaires de documentation pour une fonction ajouter_un dans une crate nommée ma_crate.

Fichier : src/lib.rs

/// Ajoute 1 au nombre donné.
///
/// # Exemples
///
/// ```
/// let argument = 5;
/// let reponse = ma_crate::ajouter_un(argument);
///
/// assert_eq!(6, reponse);
/// ```
pub fn ajouter_un(x: i32) -> i32 {
    x + 1
}

Encart 14-1 : un commentaire de documentation pour une fonction

Ici nous avons écrit une description de ce que fait la fonction ajouter_un, débuté une section avec le titre Exemples puis fourni du code qui montre comment utiliser la fonction ajouter_un. Nous pouvons générer la documentation HTML à partir de ces commentaires de documentation en lançant cargo doc. Cette commande lance l'outil rustdoc qui est distribué avec Rust et place la documentation HTML générée dans le dossier target/doc.

Pour plus de facilité, lancer cargo doc --open va générer le HTML pour la documentation de votre crate courante (ainsi que la documentation pour toutes les dépendances de la crate) et ouvrir le résultat dans un navigateur web. Rendez-vous à la fonction ajouter_one et vous découvrirez comment le texte dans les commentaires de la documentation a été interprété, ce qui devrait ressembler à l'illustration 14-1 :

Documentation HTML générée pour la fonction `ajouter_un` de `ma_crate`

Illustration 14-1 : documentation HTML pour la fonction ajouter_un

Les sections utilisées fréquemment

Nous avons utilisé le titre en Markdown # Exemples dans l'encart 14-1 afin de créer une section dans le HTML avec le titre “Exemples”. Voici d'autres sections que les auteurs de crate utilisent fréquemment dans leur documentation :

  • Panics : les scénarios dans lesquels la fonction qui est documentée peut paniquer. Ceux qui utilisent la fonction et qui ne veulent pas que leur programme panique doivent s'assurer qu'ils n'appellent pas la fonction dans ce genre de situation.
  • Errors : si la fonction retourne un Result, documenter les types d'erreurs qui peuvent survenir ainsi que les conditions qui mènent à ces erreurs sera très utile pour ceux qui utilisent votre API afin qu'ils puissent écrire du code pour gérer ces différents types d'erreurs de manière à ce que cela leur convienne.
  • Safety : si la fonction fait un appel à unsafe (que nous verrons au chapitre 19), il devrait exister une section qui explique pourquoi la fonction fait appel à unsafe et quels sont les paramètres que la fonction s'attend à recevoir des utilisateurs de l'API.

La plupart des commentaires sur la documentation n'ont pas besoin de ces sections, mais c'est une bonne liste de vérifications à avoir pour vous rappeler les éléments importants à signaler aux utilisateurs.

Les commentaires de documentation pour faire des tests

L'ajout des blocs de code d'exemple dans vos commentaires de documentation peut vous aider à montrer comment utiliser votre bibliothèque, et faire ceci apporte un bonus supplémentaire : l'exécution de cargo test va lancer les codes d'exemples présents dans votre documentation comme étant des tests ! Il n'y a rien de mieux que de la documentation avec des exemples. Mais il n'y a rien de pire que des exemples qui ne fonctionnent plus car le code a changé depuis que la documentation a été écrite. Si nous lançons cargo test avec la documentation de la fonction ajouter_un de l'encart 14-1, nous verrons une section dans les résultats de tests comme celle-ci :

   Doc-tests ma_crate

running 1 test
test src/lib.rs - ajouter_un (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s

Maintenant, si nous changeons la fonction ou l'exemple de telle sorte que le assert_eq! de l'exemple panique et que nous lançons cargo test à nouveau, nous verrons que les tests de documentation vont découvrir que l'exemple et le code sont désynchronisés l'un de l'autre !

Commenter l'élément qui contient l'élément courant

Un autre style de commentaire de documentation, //!, ajoute de la documentation à l'élément qui contient ce commentaire plutôt que d'ajouter la documentation à l'élément qui suit ce commentaire. Nous utilisons habituellement ces commentaires de documentation dans le fichier de la crate racine (qui est src/lib.rs par convention) ou à l'intérieur d'un module afin de documenter la crate ou le module dans son ensemble.

Par exemple, si nous souhaitons ajouter de la documentation qui décrit le rôle de la crate ma_crate qui contient la fonction ajouter_un, nous pouvons ajouter des commentaires de documentation qui commencent par //! au début du fichier src/lib.rs, comme dans l'encart 14-2 :

Fichier : src/lib.rs

//! # Ma crate
//!
//! `ma_crate` est un regroupement d'utilitaires pour rendre plus pratique
//! certains calculs.

/// Ajoute 1 au nombre donné.
// -- partie masquée ici --
///
/// # Exemples
///
/// ```
/// let argument = 5;
/// let reponse = ma_crate::ajouter_un(argument);
///
/// assert_eq!(6, reponse);
/// ```
pub fn ajouter_un(x: i32) -> i32 {
    x + 1
}

Encart 14-2 : documentation portant sur la crate ma_crate

Remarquez qu'il n'y a pas de code après la dernière ligne qui commence par //!. Comme nous commençons les commentaires par //! au lieu de ///, nous documentons l'élément qui contient ce commentaire plutôt que l'élément qui suit ce commentaire. Dans notre cas, l'élément qui contient ce commentaire est le fichier src/lib.rs, qui est la racine de la crate. Ces commentaires vont décrire l'intégralité de la crate.

Lorsque nous lançons cargo doc --open, ces commentaires vont s'afficher sur la page d'accueil de la documentation de ma_crate, au-dessus de la liste des éléments publics de la crate, comme montré dans l'illustration 14-2 :

Documentation HTML générée avec un commentaire pour toute la crate

Illustration 14-2 : Documentation générée pour ma_crate, qui contient le commentaire qui décrit l'intégralité de la crate

Les commentaires de la documentation placés à l'intérieur des éléments sont particulièrement utiles pour décrire les crates et les modules. Utilisez-les pour expliquer globalement le rôle du conteneur pour aider vos utilisateurs à comprendre l'organisation de votre crate.

Exporter une API publique conviviale avec pub use

Dans le chapitre 7, nous avons vu comment organiser notre code en modules en utilisant le mot-clé mod, comment faire pour rendre des éléments publics en utilisant le mot-clé pub, et comment importer des éléments dans la portée en utilisant le mot-clé use. Cependant, la structure qui a un sens pour vous pendant que vous développez une crate peut ne pas être pratique pour vos utilisateurs. Vous pourriez vouloir organiser vos structures dans une hiérarchie qui a plusieurs niveaux, mais les personnes qui veulent utiliser un type que vous avez défini dans un niveau profond de la hiérarchie pourraient rencontrer des difficultés pour savoir que ce type existe. Ils peuvent aussi être agacés d'avoir à écrire use ma_crate::un_module::un_autre_module::TypeUtile; plutôt que use ma_crate::TypeUtile;.

La structure de votre API publique est une question importante lorsque vous publiez une crate. Les personnes qui utilisent votre crate sont moins familiers avec la structure que vous l'êtes et pourraient avoir des difficultés à trouver les éléments qu'ils souhaitent utiliser si votre crate a une hiérarchie de module imposante.

La bonne nouvelle est que si la structure n'est pas pratique pour ceux qui l'utilisent dans une autre bibliothèque, vous n'avez pas à réorganiser votre organisation interne : à la place, vous pouvez ré-exporter les éléments pour créer une structure publique qui est différente de votre structure privée en utilisant pub use. Ré-exporter prend un élément public d'un endroit et le rend public dans un autre endroit, comme s'il était défini dans l'autre endroit.

Par exemple, disons que nous avons créé une bibliothèque art pour modéliser des concepts artistiques. A l'intérieur de cette bibliothèque nous avons deux modules : un module types qui contient deux énumérations CouleurPrimaire et CouleurSecondaire, et un module utilitaires qui contient une fonction mixer, comme dans l'encart 14-3 :

Fichier : src/lib.rs

//! # Art
//!
//! Une bibliothèque pour modéliser des concepts artistiques.

pub mod types {
    /// Les couleurs primaires du modèle RJB.
    pub enum CouleurPrimaire {
        Rouge,
        Jaune,
        Bleu,
    }

    /// Les couleurs secondaires du modèle RJB.
    pub enum CouleurSecondaire {
        Orange,
        Vert,
        Violet,
    }
}

pub mod utilitaires {
    use crate::types::*;

    /// Combine deux couleurs primaires dans les mêmes quantités pour
    /// créer une couleur secondaire.
    pub fn mixer(c1: CouleurPrimaire, c2: CouleurPrimaire) -> CouleurSecondaire {
        // -- partie masquée ici --
        unimplemented!();
    }
}

Encart 14-3 : une bibliothèque art avec des éléments organisés selon les modules types et utilitaires

L'illustration 14-3 montre la page d'accueil de la documentation de cette crate générée par cargo doc qui devrait ressembler à cela :

Documentation générée pour la crate `art` qui liste les modules `types` et `utilitaires`

Illustration 14-3 : Page d'accueil de la documentation de art qui liste les modules types et utilitaires

Notez que les types CouleurPrimaire et CouleurSecondaire ne sont pas listés sur la page d'accueil, pas plus que la fonction mixer. Nous devons cliquer sur types et utilitaires pour les voir.

Une autre crate qui dépend de cette bibliothèque va avoir besoin d'utiliser l'instruction use pour importer les éléments de art dans sa portée, en suivant la structure du module qui est actuellement définie. L'encart 14-4 montre un exemple d'une crate qui utilise les éléments CouleurPrimaire et mixer de la crate art :

Fichier : src/main.rs

use art::types::CouleurPrimaire;
use art::utilitaires::mixer;

fn main() {
    let rouge = CouleurPrimaire::Rouge;
    let jaune = CouleurPrimaire::Jaune;
    mixer(rouge, jaune);
}

Encart 14-4 : une crate qui utilise les éléments de la crate art avec sa structure interne exportée

L'auteur du code de l'encart 14-4, qui utilise la crate art, doit comprendre que CouleurPrimaire est dans le module types et que mixer est dans le module utilitaires. La structure du module de la crate art est bien plus pratique pour les développeurs qui travaillent sur la crate art que pour les développeurs qui utilisent la crate art. La structure interne qui divise les éléments de la crate dans le module types et le module utilitaires ne contient aucune information utile à quelqu'un qui essaye de comprendre comment utiliser la crate art. Au lieu de cela, la structure du module de la crate art génère de la confusion car les développeurs doivent découvrir où trouver les éléments, et la structure n'est pas pratique car les développeurs doivent renseigner les noms des modules dans les instructions use.

Pour masquer l'organisation interne de l'API publique, nous pouvons modifier le code de la crate art de l'encart 14-3 pour ajouter l'instruction pub use pour ré-exporter les éléments au niveau supérieur, comme montré dans l'encart 14-5 :

Fichier : src/lib.rs

//! # Art
//!
//! Une bibliothèque pour modéliser des concepts artistiques.

pub use self::types::CouleurPrimaire;
pub use self::types::CouleurSecondaire;
pub use self::utilitaires::mixer;

pub mod types {
    // -- partie masquée ici --
    /// Les couleurs primaires du modèle RJB.
    pub enum CouleurPrimaire {
        Rouge,
        Jaune,
        Bleu,
    }

    /// Les couleurs secondaires du modèle RJB.
    pub enum CouleurSecondaire {
        Orange,
        Vert,
        Violet,
    }
}

pub mod utilitaires {
    // -- partie masquée ici --
    use crate::types::*;

    /// Combine deux couleurs primaires dans les mêmes quantités pour
    /// créer une couleur secondaire.
    pub fn mixer(c1: CouleurPrimaire, c2: CouleurPrimaire) -> CouleurSecondaire {
        CouleurSecondaire::Orange
    }
}

Encart 14-5 : ajout de l'instruction pub use pour ré-exporter les éléments

La documentation de l'API que cargo doc a générée pour cette crate va maintenant lister et lier les ré-exportations sur la page d'accueil, comme dans l'illustration 14-4, ce qui rend les types CouleurPrimaire et CouleurSecondaire plus faciles à trouver.

Documentation générée pour la crate `art` avec les ré-exports sur la page d'accueil

Illustration 14-4 : la page d'accueil de la documentation pour art qui liste les ré-exports

Les utilisateurs de la crate art peuvent toujours voir et utiliser la structure interne de l'encart 14-3 comme ils l'utilisaient dans l'encart 14-4, mais ils peuvent maintenant utiliser la structure plus pratique de l'encart 14-5, comme montré dans l'encart 14-6 :

Fichier : src/main.rs

use art::mixer;
use art::CouleurPrimaire;

fn main() {
    // -- partie masquée ici --
    let rouge = CouleurPrimaire::Rouge;
    let jaune = CouleurPrimaire::Jaune;
    mixer(rouge, jaune);
}

Encart 14-6 : un programme qui utilise les éléments ré-exportés de la crate art

Dans les cas où il y a de nombreux modules imbriqués, ré-exporter les types au niveau le plus haut avec pub use peut faire une différence significative dans l'expérience utilisateur de ceux qui utilisent cette crate.

Créer une structure d'API publique utile est plus un art qu'une science, et vous pouvez itérer plusieurs fois pour trouver une API qui fonctionne mieux pour vos utilisateurs. Choisir pub use vous donne de la flexibilité pour l'organisation interne de votre crate et découple la structure interne de ce que vous présentez aux utilisateurs. N'hésitez pas à regarder le code source des crates que vous avez installées pour voir si leur structure interne est différente de leur API publique.

Mise en place d'un compte crates.io

Avant de pouvoir publier une crate, vous devez créer un compte sur crates.io et obtenir un jeton d'API. Pour pouvoir faire cela, visitez la page d'accueil de crates.io et connectez-vous avec votre compte GitHub (le compte GitHub est actuellement une obligation, mais crates.io pourra permettre de créer un compte d'une autre manière un jour). Une fois identifié, consultez les réglages de votre compte à l'adresse https://crates.io/me/ et récupérez votre jeton d'API (NdT : API key). Ensuite, lancez la commande cargo login avec votre clé d'API, comme ceci :

$ cargo login abcdefghijklmnopqrstuvwxyz012345

Cette commande informera cargo de votre jeton d'API et l'enregistrera localement dans ~/.cargo/credentials. Notez que ce jeton est un secret : ne le partagez avec personne d'autre. Si vous le donnez à quelqu'un pour une quelconque raison, vous devriez le révoquer et générer un nouveau jeton sur crates.io.

Ajouter des métadonnées à une nouvelle crate

Maintenant que vous avez un compte, imaginons que vous avez une crate que vous souhaitez publier. Avant de la publier, vous aurez besoin d'ajouter quelques métadonnées à votre crate en les ajoutant à la section [package] du fichier Cargo.toml de votre crate.

Votre crate va avoir besoin d'un nom unique. Tant que vous travaillez en local, vous pouvez nommer un crate comme vous le souhaitez. Cependant, les noms des crates sur crates.io sont accordés selon le principe du premier arrivé, premier servi. Une fois qu'un nom de crate est accordé, personne d'autre ne peut publier une crate avec ce nom. Avant d'essayer de publier une crate, recherchez sur le site le nom que vous souhaitez utiliser. Si le nom a été utilisé par une autre crate, vous allez devoir trouver un autre nom et modifier le champ name dans le fichier Cargo.toml sous la section [package] pour utiliser le nouveau nom pour la publication, comme ceci :

Fichier : Cargo.toml

[package]
name = "jeu_du_plus_ou_du_moins"

Même si vous avez choisi un nom unique, lorsque vous lancez cargo publish pour publier la crate à ce stade, vous allez avoir un avertissement suivi par une erreur :

$ cargo publish
    Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
-- partie masquée ici --
error: failed to publish to registry at https://crates.io

Caused by:
  the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata

La raison est qu'il manque quelques informations essentielles : une description et une licence sont nécessaires pour que les gens puissent savoir ce que fait votre crate et sous quelles conditions ils peuvent l'utiliser. Pour corriger cette erreur, vous devez rajouter ces informations dans le fichier Cargo.toml.

Ajoutez une description qui ne fait qu'une phrase ou deux, car elle va s'afficher à proximité de votre crate dans les résultats de recherche. Pour le champ license, vous devez donner une valeur d'identification de la licence. La Linux Foundation’s Software Package Data Exchange (SPDX) liste les identifications que vous pouvez utiliser pour cette valeur. Par exemple, pour stipuler que votre crate est sous la licence MIT, ajoutez l'identifiant MIT :

Fichier : Cargo.toml

[package]
name = "jeu_du_plus_ou_du_moins"
license = "MIT"

Si vous voulez utiliser une licence qui n'apparaît pas dans le SPDX, vous devez placer le texte de cette licence dans un fichier, inclure ce fichier dans votre projet puis utiliser licence-file pour renseigner le nom de ce fichier plutôt que d'utiliser la clé licence.

Les conseils sur le choix de la licence appropriée pour votre projet sortent du cadre de ce livre. De nombreuses personnes dans la communauté Rust appliquent à leurs projets la même licence que Rust qui utilise la licence double MIT OR Apache-2.0. Cette pratique montre que vous pouvez également indiquer plusieurs identificateurs de licence séparés par OR pour avoir plusieurs licences pour votre projet.

Une fois le nom unique, la version, la description et la licence ajoutés, le fichier Cargo.toml de ce projet qui est prêt à être publié devrait ressembler à ceci :

Fichier : Cargo.toml

[package]
name = "jeu_du_plus_ou_du_moins"
version = "0.1.0"
edition = "2021"
description = "Un jeu où vous devez deviner quel nombre l'ordinateur a choisi."
license = "MIT OR Apache-2.0"

[dependencies]

La documentation de cargo décrit d'autres métadonnées que vous pouvez renseigner pour vous assurer que les autres développeurs puissent découvrir et utiliser votre crate plus facilement.

Publier sur crates.io

Maintenant que vous avez créé un compte, sauvegardé votre jeton de clé, choisi un nom pour votre crate, et précisé les métadonnées requises, vous êtes prêt à publier ! Publier une crate téléverse une version précise sur crates.io pour que les autres puissent l'utiliser.

Faites attention lorsque vous publiez une crate car une publication est permanente. La version ne pourra jamais être remplacée, et le code ne pourra jamais être effacé. Le but majeur de crates.io est de fournir une archive durable de code afin que les compilations de tous les projets qui dépendent des crates de crates.io puissent toujours continuer à fonctionner. Si la suppression de version était autorisée, cela rendrait ce but impossible. Cependant, il n'y a pas de limites au nombre de versions de votre crate que vous pouvez publier.

Lancez la commande cargo publish à nouveau. Elle devrait fonctionner à présent :

$ cargo publish
    Updating crates.io index
   Packaging jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
   Verifying jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)
   Compiling jeu_du_plus_ou_du_moins v0.1.0
(file:///projects/jeu_du_plus_ou_du_moins/target/package/jeu_du_plus_ou_du_moins-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
   Uploading jeu_du_plus_ou_du_moins v0.1.0 (file:///projects/jeu_du_plus_ou_du_moins)

Félicitations ! Vous venez de partager votre code avec la communauté Rust, et désormais tout le monde peut facilement ajouter votre crate comme une dépendance de son projet.

Publier une nouvelle version d'une crate existante

Lorsque vous avez fait des changements sur votre crate et que vous êtes prêt à publier une nouvelle version, vous devez changer la valeur de version renseignée dans votre fichier Cargo.toml et la publier à nouveau. Utilisez les règles de versionnage sémantique pour choisir quelle sera la prochaine version la plus appropriée en fonction des changements que vous avez faits. Lancez ensuite cargo publish pour téléverser la nouvelle version.

Retirer des versions de crates.io avec cargo yank

Bien que vous ne puissiez pas enlever des versions précédentes d'une crate, vous pouvez prévenir les futurs projets de ne pas l'ajouter comme une nouvelle dépendance. Cela s'avère pratique lorsqu'une version de crate est défectueuse pour une raison ou une autre. Dans de telles circonstances, cargo permet de déprécier une version de crate.

Déprécier une version évite que les nouveaux projets ajoutent une dépendance à cette version tout en permettant à tous les projets existants de continuer à en dépendre en leur permettant toujours de télécharger et dépendre de cette version. En gros, une version dépréciée permet à tous les projets avec un Cargo.lock de ne pas échouer, mais tous les futurs fichiers Cargo.lock générés n'utiliseront pas la version dépréciée.

Pour déprécier une version d'une crate, lancez cargo yank et renseignez quelle version vous voulez déprécier :

$ cargo yank --vers 1.0.1

Si vous ajoutez --undo à la commande, vous pouvez aussi annuler une dépréciation et permettre à nouveaux aux projets de dépendre de cette version :

$ cargo yank --vers 1.0.1 --undo

Une dépréciation ne supprime pas du code. Par exemple, la fonctionnalité de dépréciation n'est pas conçue pour supprimer des secrets téléversés par mégarde. Si cela arrive, vous devez régénérer immédiatement ces secrets.