[couverture de Linux Magazine 69]

Créer une distribution pour le CPAN

Article publié dans Linux Magazine 69, février 2005.

Copyright © 2004 - Sébastien Aperghis-Tramoni.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau de l'article

Après bien des articles sur l'utilisation de modules divers et variés, nous allons passer de l'autre côté du use et vous montrer comment créer votre propre module correctement empaqueté.

Introduction

Comme vous avez bien appris vos leçons, vous avez commencé à écrire un module pour encapsuler de manière un tant soit peu propre un ensemble de fonctionnalités. Nous ne nous étendrons pas ici sur l'interface fournie (procédurale, objet) mais sur un autre point qui est la manière d'en faire une distribution apte à aller sur le CPAN.

Plus précisément, même si vous ne comptez (ou ne pouvez) pas publier votre module sur le CPAN, tout programmeur digne de ce nom vous conseillera de créer votre paquetage proprement pour des raisons de qualité et de maintenance. En effet, même (en fait surtout) si votre module, développé dans un cadre professionnel, est destiné à ne jamais sortir de la société où il a vu le jour, ceux qui devront le maintenir ou le déployer vous remercieront d'avoir fait les choses proprement.

Quelques définitions

Commençons déjà par définir quelques termes importants, afin de lever certaines ambiguïtés pour le reste de cet article.

Un module est un package qui est normalement défini dans un fichier portant le même nom terminé par .pm. Nous précisons normalement car Perl n'impose pas de créer un fichier par package. Un même fichier peut donc contenir du code déclarant plusieurs packages que le programmeur a jugé préférable de ne pas séparer (même si ce n'est pas conseillé). Inversement, un package peut être déclaré dans plusieurs fichiers différents. C'est la méthode utilisée par AutoSplit pour éclater un module en plusieurs fichiers afin de réduire le temps de chargement. Le terme module désigne donc le ou les fichiers qui définissent le package correspondant.

Un module d'extension est un module qui apporte dans l'espace Perl les fonctionnalités d'un logiciel externe (typiquement une bibliothèque en C). Ces modules sont programmés en partie en Perl et en partie dans un langage permettant de lier le logiciel externe à l'interpréteur perl(1) comme XS ou SWIG. Ne seront traités dans cet article que les modules pur Perl, l'empaquetage de modules d'extension comportant quelques subtilités.

Une distribution est une archive qui contient un ou plusieurs modules ainsi que les fichiers et programmes permettant son installation. Par convention, les archives sur le CPAN sont au format tar(1) compressées avec GNUzip (gzip(1)).

Un bundle est une distribution virtuelle permettant de regrouper plusieurs distributions. C'est utile pour installer d'un seul coup un ensemble de modules avec des dépendances multiples. Toutefois, depuis que le module CPAN.pm gère mieux les dépendances, ils sont encore moins utilisés qu'ils ne l'étaient déjà.

Malgré tout, il reste que la plupart des programmeurs vont parler de module ou de package en désignant indifféremment le module en lui-même, le fichier qui contient le module, ou la distribution (qui peut contenir plusieurs modules ou sous-modules)...

Survol d'un module Perl

Comme précisé dans l'introduction, nous ne nous attarderons pas sur l'écriture d'un module Perl, cet aspect ayant déjà été couvert par l'article de Sylvain Lhullier (cf. [1]).

Rappelons simplement qu'un module peut comporter plusieurs fichiers pour différentes raisons : il peut avoir été écrit ainsi par l'auteur ou comporter des fichiers de données, ou être chargé par AutoSplit qui l'a préalablement découpé en plusieurs petits fichiers, ou encore c'est un module dont une partie est compilée et qui comporte donc des binaires, dépendants de l'architecture. Dans tous les cas, ces fichiers doivent être rangés de manière précise dans l'arborescence de Perl pour qu'il puisse recoller les morceaux. Pour rappel, vous pouvez exécuter la commande perl -V pour voir le contenu de la variable @INC qui contient les répertoires de recherche de perl(1).

Comme cette arborescence est spécifique à chaque plate-forme, voire à chaque machine, vous ne pouvez pas deviner où doivent se placer les différents fichiers. Mais pas de panique : perl(1) « sait » comment il a été installé et dispose des informations nécessaires pour une installation correcte. Votre rôle dans la création d'une distribution consiste donc principalement à fournir des informations sur votre module et à laisser des programmes faire le reste.

Dissection d'une distribution

Examinons un peu la hiérarchie d'une distribution Perl. Elle comporte plusieurs ensembles de fichiers aux vocations et aux buts différents.

Les fichiers destinés aux humains

Il s'agit des fichiers que l'on retrouve classiquement dans toute distribution de logiciel libre (et non-libre) et qui donnent des informations sur le logiciel, son installation et son utilisation. Leurs noms sont variables, d'où les différentes possibilités présentées ici.

Les fichiers destinés aux programmes

Ce sont les fichiers qui vont être utilisés pour l'installation du module. Ils contiennent des informations formatées et sont nommés en suivant des règles assez précises.

Les fichiers à installer

C'est la « charge utile » de la distribution, les fichiers qui vont être installés. Il s'agit des fichiers nommés en .pm à la racine de l'archive ou des fichiers (.pm ou autres) situés dans l'arborescence lib/. Ainsi le module Encode de Dan Kogai inclut les tables de codage des caractères Unicode. Des programmes, situés dans scripts/, peuvent aussi être installés dans les répertoires d'exécutables comme /usr/local/bin/. De la documentation spécifique peut aussi être comprise dans le lot sous la forme de fichiers au format Pod (extension .pod).

Les fichiers de tests

L'un des points forts des distributions de modules Perl est la possibilité de réaliser très simplement des batteries de tests unitaires afin de vérifier que le module va fonctionner comme attendu sur la machine cible. Nous reviendrons plus tard sur le fonctionnement de ces tests. Précisons simplement pour le moment que les tests doivent se trouver soit dans un fichier test.pl, soit dans des fichiers situés dans le répertoire t/ et dont le nom se termine par l'extension .t.

Fichiers divers

L'auteur d'un module peut inclure dans sa distribution d'autres fichiers qu'il a jugés utiles, comme des fichiers d'exemples d'utilisation, des patches ou des sources pour des programmes externes optionnels, etc.

Installation

L'installation d'un module packagé dans une distribution proprement réalisée est tout ce qu'il y a de plus simple en Perl. Et bien que ExtUtils::MakeMaker reste à l'heure actuelle la méthode canonique pour installer un module, d'autres systèmes existent, que nous allons présenter ici.

ExtUtils::MakeMaker

Extutils::MakeMaker est le module officiel utilisé pour installer une distribution. Cette opération se résume généralement à exécuter ces quelques commandes :

    $ tar zxf Theory-Strings-3.54.tar.gz
    $ cd Theory-Strings-3.54/
    $ perl Makefile.PL
    $ make
    $ make test
    $ sudo make install

Vous pouvez aussi installer des modules dans un répertoire privé en indiquant un paramètre PREFIX :

    $ make install PREFIX=/home/myself/perl5lib

Les modules installés dans ce répertoire pourront être utilisés en affectant la variable d'environnement PERL5LIB ou en utilisant l'option -I :

    $ perl -I/home/myself/perl5lib program.pl

L'avantage de Extutils::MakeMaker est qu'il est standard et intégré à la distribution Perl depuis très longtemps. Il dérive en effet d'un script shell, Makefile.SH, écrit par Andy Dougherty et qui permettait l'installation des modules à l'époque de Perl 4. Avec le développement de Perl 5 et sa bien meilleure gestion des packages, ce script a été réécrit en Perl par Andy Dougherty, Andreas Koenig et Tim Bunce. Cela a permis d'éliminer la dépendance vis-à-vis du shell, qui était un facteur limitant pour la personnalisation du fichier Makefile généré.

Aujourd'hui, ExtUtils::MakeMaker semble souffrir des mêmes défauts que Perl lui-même : il est capable de tout faire, mais son architecture rend certaines choses difficiles à réaliser. Par ailleurs, le rôle de Makefile.PL reste avant tout de générer un fichier Makefile. Cela signifie que Perl conserve donc une dépendance vis-à-vis de make(1), ce qui est une source de problèmes car suivant les systèmes, la syntaxe reconnue par make(1) n'est pas exactement la même (voir par exemple les différences entre les versions GNU et BSD), et certains systèmes n'ont pas nativement d'utilitaire make(1) (Windows) voire pas de shell (Mac OS Classic).

Module::Build

C'est afin de s'affranchir de cette dépendance d'utilitaires externes que Ken Williams a créé Module::Build, le futur successeur d'ExtUtils::MakeMaker. La philosophie de base est de ne dépendre de rien d'autre que Perl, ce qui permet de remplacer tout utilitaire externe dont on aurait pu dépendre.

Actuellement encore en développement, il est néanmoins déjà utilisable, et il est recommandé de s'en servir pour le connaître et le tester. Il a par conséquent les défauts de sa jeunesse, à savoir qu'il n'est pas encore intégré à la distribution standard de Perl et qu'il faut donc l'installer depuis le CPAN.

Son utilisation est aussi simple qu'avec ExtUtils::MakeMaker :

    $ tar zxf Theory-Strings-3.54.tar.gz
    $ cd Theory-Strings-3.54/
    $ perl Build.PL
    $ ./Build
    $ ./Build test
    $ ./Build install

Comme avec ExtUtils::MakeMaker, la première étape est d'exécuter le script Build.PL qui génère le fichier Build, qui est le programme d'installation de la distribution. Celui-ci s'utilise avec les mêmes commandes qu'avant.

Petite note pour les infortunés utilisateurs d'un système d'exploitation édité par Microsoft, leur interpréteur de commandes n'autorisant pas l'exécution de ./Build, ils devront à la place taper perl Build.

Créer une distribution

Après ce rapide tour d'horizon, passons aux choses sérieuses et aux moyens pour créer une distribution.

h2xs

La manière officielle de créer un nouveau module est d'utiliser le programme h2xs(1). Il est présent depuis fort longtemps dans la distribution Perl, mais il est en réalité assez mal employé. En effet, comme son nom l'indique, h2xs sert avant tout à créer des modules d'extensions en XS à partir des fichiers d'en-têtes d'une bibliothèque C (fichiers .h). Son intérêt est qu'il est capable de générer la majeure partie du code XS de manière automatique, libérant ainsi le programmeur Perl de cette pénible tâche.

Toutefois comme les auteurs de modules pur Perl voulaient bénéficier du squelette de distribution qu'il construisait pour les modules XS, une option a été ajoutée afin de ne pas générer de code XS. Son utilisation dans ce cas-ci ressemble à ceci :

    $ h2xs -XAn Acme::GreatModule
    Writing Acme/GreatModule/GreatModule.pm
    Writing Acme/GreatModule/Makefile.PL
    Writing Acme/GreatModule/test.pl
    Writing Acme/GreatModule/Changes
    Writing Acme/GreatModule/MANIFEST

Examinons un peu le début de distribution qu'il vient de nous créer. La première constatation est que l'arborescence Acme/GreatModule/ est un peu inutile, on s'attend plutôt à trouver Acme-GreatModule/. De plus, il place le fichier GreatModule.pm à la racine de la distribution, alors qu'il est plus propre de le placer dans une arborescence lib/Acme/. Pour un seul module, ce n'est pas très grave, mais quand d'autres modules viennent s'ajouter au premier, cela peut rapidement devenir fouillis !

Autre défaut, le code d'exemple dans le fichier GreatModule.pm a tendance à inclure beaucoup de choses inutiles comme une contrainte sur la version de Perl et l'utilisation d'Exporter alors que nous ne lui avons rien demandé. Encore une fois, rien de dramatique, mais comme la plupart des modules Perl développés aujourd'hui offrent une interface objet, un auteur passe finalement déjà trop de temps à effacer tous ces trucs inutiles (et à modifier Makefile.PL pour prendre en compte ces modifications). Enfin il n'y a pas de fichier README ni d'information de droit d'auteur et de licence.

Le h2xs des versions récentes de Perl (5.8.x) a un peu amélioré les choses : un fichier README est généré avec le squelette pour ajouter le copyright et la licence, et le test de base (le chargement du module) est déplacé dans le répertoire t/. Toutefois il génère toujours par défaut une contrainte sur la version de Perl en cours (donc 5.8). Cela peut se changer avec l'option -b, mais il faut y penser.

Pour résumer, h2xs a l'avantage d'être présent dans toute distribution de Perl, et s'il peut suffire dans le cas de modules simples pur Perl, il peut rapidement fatiguer l'auteur de nombreux modules.

Script personnalisé

Après un tel réquisitoire contre h2xs, certains vont demander si la solution est d'écrire son petit script Perl ou shell qui génère les fichiers et qui... La réponse est : NON !

Non, parce que ce script sera forcément faux ou incomplet.

Non, parce qu'il y a déjà une meilleure solution.

module-starter

Pour pallier cette épine qui se faisait de plus en plus sentir dans la communauté Perl, Andy Lester a développé un programme qui fait ça de manière propre et complète, module-starter. Ses avantages par rapport à h2xs sont qu'il est prévu pour créer le squelette d'une distribution, qu'il le fait de manière propre et moderne, et qu'en plus il gère Module::Build. Son inconvénient est qu'il n'est pas installé par défaut, mais comme il n'est utile que pour l'auteur d'un module et non pas son utilisateur, il ne faut pas hésiter à l'installer :

    $ sudo perl -MCPAN -e'install Module::Starter'

Son utilisation est tout aussi simple que ne l'est h2xs, si ce n'est qu'on fournit le nom et l'adresse email de l'auteur dès la création de la distribution (ce qui évite d'éditer moults fichiers) et qu'on peut spécifier plusieurs modules :

    $ module-starter --module=Acme::GreatModule,Acme::SecondGreat \
        --author="Jean Suifier" --email="jean@suifier.net"
    Created starter directories and files

module-starter crée cette arborescence :

  Acme-GreatModule/
    Changes
    MANIFEST
    Makefile.PL
    README
    lib/
      Acme/
        GreatModule.pm
        SecondGreat.pm
    t/
      00.load.t
      pod-coverage.t
      pod.t

On constate que c'est déjà bien mieux rangé, les fichiers .pm sont dans une arborescence dans lib/, et plusieurs tests sont présents dans t/. On peut éventuellement regretter le fait que les tests utilisent Test::More (qui n'a été intégré à Perl qu'à partir de 5.8), mais on peut facilement remédier à cela.

Les squelettes de modules générés utilisent le mode de documentation mixé, c'est-à-dire que la documentation de chaque fonction est située juste à côté de sa définition. Le code Perl et la documentation Pod s'entremêlent donc tout au long du fichier. Certains apprécient le fait que la documentation soit aussi près du code concerné, d'autres préfèrent la conserver tout à la fin, après __END__ pour des raisons de performances. En effet, l'interpréteur doit sauter chaque passage entre les balises =pod et =cut, mais d'un autre côté, ce coût est très minime et n'est payé qu'à la compilation, pas à l'exécution. Au final, il s'agit donc surtout d'une question de goût personnel.

Autre aspect intéressant, module-starter tente dès le départ de vous forcer à fournir un module propre et documenté en incluant les tests de vérification du Pod (fichier t/pod.t) et de couverture de documentation (fichier t/pod-coverage.t). Le premier vérifie simplement que votre documentation Pod ne contient pas d'erreurs de balisage, alors que le second vérifie que vous avez bien documenté toutes les fonctions et méthodes définies dans chaque module, sous réserve que vous installiez Test::Pod et Test::Pod::Coverage, ce que vous devriez faire dès maintenant.

En résumé, module-starter est vraiment l'outil que vous devez utiliser. Mais pour être tout à fait honnête, la comparaison entre module-starter et h2xs est nécessairement défavorable à ce dernier : la création d'un squelette de distribution n'est pas son véritable rôle, qui est la création de fichiers XS (rôle qu'il remplit très bien d'ailleurs), alors que module-starter a été écrit spécifiquement pour ça.

Modules de construction et d'installation

L'introduction de Module::Build a créé une certaine agitation dans la communauté Perl, car CPAN.pm (le module, pas le réseau) ne connaissant que ExtUtils::MakeMaker, les distributions ne comportant qu'un fichier Build.PL ne pouvaient pas être installées par ce biais si commode. Il s'agit en réalité d'un faux problème qui montre que beaucoup d'auteurs ne se sont pas rendus compte que l'utilisation de l'un n'est pas exclusive de l'autre. Plus précisément, il est parfaitement possible pour l'auteur d'utiliser le moderne Module::Build pour programmer ses modules et construire sa distribution tout en fournissant un Makefile.PL pour que CPAN.pm puisse l'installer sans se poser de questions.

Voyons immédiatement comment faire en reprenant l'exemple de tout à l'heure. Comme nous ne lui avions pas spécifié un module de construction particulier, un builder, il a automatiquement utilisé ExtUtils::MakeMaker. On peut lui indiquer le builder à utiliser avec les options suivantes :

  --builder=module Build with 'ExtUtils::MakeMaker' or 'Module::Build'
  --eumm           Same as --build=ExtUtils::MakeMaker
  --mb             Same as --build=Module::Build

On peut même lui indiquer d'utiliser les deux en même temps. La création d'une distribution utilisant les deux builders devient alors triviale :

    $ module-starter --module=Acme::GreatModule,Acme::SecondGreat \
        --author="Jean Suifier" --email="jean@suifier.net" --eumm --mb

module-starter crée alors les deux fichiers Build.PL et Makefile.PL avec les bons paramètres (et l'astuce permettant aux deux de cohabiter).

En réalité, module-starter ne permettait pas au départ d'utiliser les deux builders, mais suite aux suggestions de l'auteur, Andy Lester a gentiment accepté d'ajouter ces fonctionnalités dans module-starter version 1.00, ce qui vous permet, chers lecteurs, de rester paresseux et d'éviter de devoir bidouiller des trucs inutiles et ennuyeux ;-).

Notez que vous pouvez déjà construire votre distribution (avec Build.PL ou Makefile.PL, au choix) sans devoir éditer aucun fichier ! Pas question de distribuer une archive ne contenant rien d'utile, mais cela vous montre que tout le travail ennuyeux de préparation de la distribution a déjà été effectué, vous pouvez donc vous concentrer sur l'écriture de votre module. La première exécution de l'un des builders affichera un avertissement sur le fait que META.yml est manquant. C'est normal, il sera automatiquement créé lorsque vous construirez la distribution. Voyons d'ailleurs comment faire.

Lorsque vous exécutez perl Makefile.PL ou perl Build.PL, ces programmes créent un builder, respectivement Makefile et Build. Les deux offrent les mêmes actions, exécutées avec make action ou ./Build action.

Quelques détails en plus sur l'action manifest. Elle permet de mettre à jour automatiquement le fichier MANIFEST en examinant l'arborescence courante, et en vérifiant si le nom ne correspond pas aux expressions régulières présentes dans MANIFEST.SKIP. Voici un bon point de départ pour un fichier MANIFEST.SKIP :

    # fichiers et répertoires de CVS, RCS, et autres
    /CVS/
    ^.cvsignore$

    # fichiers et répertoires générés par les builders
    ^blib
    ^_build
    ^Build$
    ^cover_db
    ^Makefile(\.old)?$
    ^MANIFEST\.
    ^pm_to_blib$

    # fichiers temporaires, de backup, etc
    ~$
    \.bak$
    \.old$
    \.sw[a-z]$

Lors de la construction d'une distribution, un auteur va typiquement exécuter les commandes suivantes (l'exemple est ici donné avec Module::Build) :

    $ perl Build.PL
    $ ./Build
    $ ./Build test
    $ ./Build disttest
    $ ./Build dist

Vous devez alors obtenir une archive correctement nommée, dans notre exemple Acme-GreatModule-0.01.tar.gz, prête à être déposée sur le CPAN ou à être installée sur une autre machine par les commandes habituelles.

La documentation

Si vous voulez que votre module soit apprécié, vous devez le documenter correctement. C'est d'ailleurs une conséquence logique de la première vertu du programmeur, la Paresse, qui pousse à écrire une documentation complète afin d'éviter de devoir répondre inlassablement aux mêmes questions (ou de pouvoir se contenter de jeter un « lisez ce foutu manuel » aux utilisateurs les plus ennuyeux). Comme vous avez été de bons élèves, vous avez utilisé module-starter pour créer votre distribution, et celui-ci, respectant votre paresse, a déjà écrit un squelette de documentation. Vous n'avez plus qu'à compléter le fichier README et à écrire la documentation de votre module au fur et à mesure de son développement.

Gardez à l'esprit que quand votre module sera diffusé (en particulier sur search.cpan.org), des liens seront créés au sein de la documentation, permettant de relier les différentes pages entre elles. En particulier, n'hésitez pas à indiquer les modules similaires ou associés (ou les dépendances) dans la section SEE ALSO. Cela facilitera la vie de l'utilisateur de votre module, et donc la vôtre.

Pensez à préciser de manière explicite votre droit d'auteur et la licence d'utilisation de votre module dans tous les fichiers qui seront installés. Par défaut, module-starter (ainsi que h2xs) ont déjà rempli pour vous la partie sur le droit d'auteur (section COPYRIGHT), mais vous pouvez être amenés à la modifier ou la compléter suivant les cas (développement au sein d'une société par exemple).

La licence est par défaut celle de Perl, qui est constituée de la disjonction de l'Artistic License (licence artistique) et de la GNU General Public License (licence publique générale du GNU). Vous pouvez spécifier une autre licence libre (LGPL, BSD révisée) ou non-libre. Dans tous les cas, vous devez ajouter un fichier LICENSE qui spécifie explicitement les termes de la licence d'utilisation. Un petit rappel : licence (avec un c) est le mot français, license (avec un s) est le mot anglais.

D'ailleurs, souvenez-vous que la communauté Perl est mondiale, et que la plupart des personnes s'attendront donc à pouvoir lire la documentation en anglais. Cela n'interdit pas d'inclure aussi une version française, mais la politesse conseille toutefois d'écrire un minimum de documentation (au moins le README) en anglais, afin que les non-francophones aient une chance de savoir à quoi sert votre module.

Le numéro de version

Comme tout logiciel qui se respecte, votre module doit avoir un numéro de version. En Perl, il est toutefois demandé d'utiliser des numéros de versions à deux composantes plutôt qu'à trois, ceci pour des raisons techniques. En effet un numéro de version à deux composantes peut être vu comme un nombre flottant, ce qui simplifie la comparaison. Pour cette raison, les versions des modules Perl suivent généralement un format V.RR, où V est le numéro de version majeur et RR (ou RRR) est le numéro de release, de distribution. Vous pouvez aussi ajouter le suffixe _DD pour signaler une version de développement.

Prenons quelques exemples pour mieux comprendre. Notre ami Jean Suifier distribue une première version de son module Acme::GreatModule, qu'il numérote 0.01. Ayant un bon retour de la part des utilisateurs, il continue et distribue une version de développement 0.01_01, qui indique qu'elle est basée sur la version 0.01. Il peut ainsi distribuer plusieurs versions beta 0.01_02, 0.01_03, avant de distribuer une version 0.02 finale.

Pour une distribution comportant plusieurs modules, il est généralement conseillé de donner un numéro de version séparé à chaque module, la distribution ayant comme version celle du module principal. Vous pouvez complètement décorréler la version de la distribution de la version des modules inclus en supprimant le paramètre dist_version_from de Build.PL et en le remplaçant par dist_version, avec comme valeur la version. Pour Makefile.PL on fait de même en remplaçant VERSION_FROM par VERSION.

À noter toutefois qu'il est déconseillé d'utiliser le numéro de révision CVS (ou de votre système de gestion de versions) comme valeur pour $VERSION car cela peut engendrer des résultats bizarres (en particulier lorsqu'un programme existe dans plusieurs branches).

Nous verrons plus loin en quoi attribuer un numéro de version unique à chaque nouvelle release est important.

Les tests automatisés

L'un des points forts du système d'installation des modules Perl est le support, lors de la procédure d'installation, d'une phase de tests automatiques permettant de vérifier le bon fonctionnement du ou des modules sur la machine cible avant l'installation proprement dite. Ces tests unitaires ont un protocole de déroulement très simple et codifié dans la documentation du module Test::Harness. Ce module est utilisé par le builder pour exécuter les tests qui doivent se trouver dans des fichiers .t dans le répertoire t/. Test::Harness attend de ces programmes qu'ils affichent simplement l'état de chaque test, "ok" ou "not ok", suivi du numéro du test. Ces programmes peuvent être réalisés de manière complètement indépendante de tout module Perl, mais il est conseillé d'utiliser l'un des modules Test ou Test::More, qui offrent des fonctions pour faciliter grandement le travail et évite de devoir connaître les subtilités du protocole de dialogue.

Ces deux modules offrent comme fonctionnalités de base la fonction ok() dont le but est d'afficher "ok" ou "not ok" en fonction de la valeur des arguments. Voyons immédiatement un exemple :

    use Test;
    use Acme::GreatModule;

    plan tests => 2;

    # est-ce que $VERSION est défini ?
    ok( $Acme::GreatModule::VERSION );

    # est-ce que $VERSION est un numéro de version valide ?
    ok( $Acme::GreatModule::VERSION =~ /^\d+\.\d+(?:_\d{2})?$/ );

Juste après l'utilisation du module Test (ou Test::More) on trouve la déclaration du plan prévu, qui indique le nombre de tests qui seront effectués. Ensuite on charge le module qui doit être testé et on vérifie qu'il a bien défini un numéro de version, puis que celui-ci correspond bien à un numéro de version Perl. Vous l'aurez deviné, ok() affiche "ok" si l'expression qu'on lui donne est vraie, et "not ok" dans le cas contraire. On peut aussi utiliser la forme à deux arguments de ok(), pour lui laisser faire la comparaison :

    # est-ce que $PI a la bonne valeur ?
    ok( $PI, 4 * atan2(1,1) );

Test fournit aussi une fonction skip() pour passer des tests qui ne peuvent pas être exécutés (parce qu'il manque un module, parce que la plate-forme ne supporte pas certaines fonctionnalités, etc). Test::More fournit d'autres fonctions qui offrent des sémantiques plus pointues que ok(), telles que is() et isnt() pour tester si une valeur est égale ou différente d'une autre, like() pour vérifier qu'une valeur correspond à une expression régulière, is_deeply() pour vérifier si deux structures complexes sont identiques à tous les niveaux.

À noter que le répertoire t/ permet d'inclure des modules et des fichiers qui ne seront utilisés que pour les tests, et qui ne seront pas indexés (ce point sera développé plus loin). En particulier, cela signifie que vous pouvez sans risque inclure une copie des modules Test et Test::More dans t/, afin de ne pas créer de dépendances artificielles.

Dernier point intéressant à connaître pour ce rapide tour des tests de votre module : la couverture de code, c'est-à-dire vérifier que les tests mis en place exécutent réellement l'ensemble du code des modules testés. Le module Perl Devel::Cover permet justement de vérifier cet aspect. Et son utilisation est rendue encore plus simple qu'elle ne l'est naturellement grâce à Module::Build qui offre depuis la version 0.25_01 une action testcover. L'exécution de ./Build testcover exécutera donc la suite de tests en chargeant Devel::Cover et générera un rapport HTML très lisible dans cover_db/coverage.html. Le programmeur orgueilleux sera alors tenté (avec raison) d'écrire des tests pour avoir une couverture maximale.

Tester la distribution

Les scripts de tests peuvent (et doivent) être utilisés pour tester tous les aspects possibles de votre distribution. Cela concerne aussi bien le code du module fourni (vérifier qu'il se comporte comme attendu), que des aspects plus extérieurs : présence de tous les fichiers, vérification du POD, des dépendances, etc. module-starter a déjà créé certains scripts de tests, t/00.load.t pour vérifier que le module se charge bien, t/pod.t pour vérifier que le POD est correct et t/pod-coverage.t pour vérifier que la documentation couvre bien toutes les fonctions définies.

On peut ensuite ajouter les vérifications fournies par Test::Distribution qui examine plusieurs points. Pour l'utiliser, il suffit d'ajouter le script t/distchk.t :

    use strict;
    use Test::More;
    eval "use Test::Distribution";
    plan skip_all => "Test::Distribution required for checking distribution" if $@;

Évidemment, il est plus que fortement conseillé d'installer tous les modules utilisés par ces scripts de tests pour qu'ils soient utiles, à savoir Test::Pod, Test::Pod::Coverage, Test::Distribution, ainsi que toutes leurs dépendances. Avec le shell cpan, il suffit de confirmer leur installation.

Portabilité

Un auteur avisé doit garder ce point à l'esprit : Perl est portable, et porté sur beaucoup de systèmes aux caractéristiques très différentes. Il doit donc en tenir compte lorsqu'il écrit un module. Nous ne nous attarderons pas ici sur les problèmes de portabilité liés aux fonctionnalités de Perl (par exemple la disponibilité de fonctions comme fork()), mais aux problèmes sur les noms de fichiers, cet article traitant de la manière de créer une distribution. Pour les autres aspects de portabilité, le lecteur est invité à consulter perlport[3].

En effet, les systèmes d'exploitation ne sont pas tous égaux (et sont même très différents) sur les noms de fichiers valides. Voici donc quelques conseils pour éviter de vous attirer les récriminations d'utilisateurs mécontents.

Vous trouvez ces limitations effrayantes ? C'est bien, c'était le but : vous rappeler qu'Unix n'est pas le seul système (ou architecture de système) au monde. D'autres systèmes existent, avec leurs caractéristiques propres qu'il s'agit de respecter dans la mesure du possible.

Vous pourriez écrire un script pour vérifier que les fichiers sont nommés en respectant ces conseils mais comme ça ne présente que peu d'intérêt et que vous n'avez pas envie de tout vous retaper à chaque fois, autant utiliser directement le module Test::Portability::Files qui est à votre disposition sur le CPAN. Le script de test, t/portfs.t, se résume alors à :

    use Test::More;
    eval "use Test::Portability::Files";
    plan skip_all => "Test::Portability::Files required for testing filenames portability" if $@;
    run_tests();

Et vous pouvez même copier-coller le synopsis du module, voire directement copier le fichier t/portfs.t inclus pour vous éviter même cette saisie ;-).

Notez que vous pouvez appeler la fonction options() pour sélectionner les tests à exécuter. La documentation vous expliquera le reste.

Le CPAN

Le CPAN est le Comprehensive Perl Archive Network, le réseau complet d'archives Perl. Il est l'une des pierres angulaires de la communauté Perl, et l'une des principales raisons du succès de ce langage, car il offre aux utilisateurs et aux programmeurs un réseau distribué où sont centralisés quasiment tous les modules Perl existants. C'est la raison pour laquelle il est demandé à tout programmeur qui désire contribuer de vérifier d'abord s'il n'y a pas déjà un module offrant les fonctionnalités désirées (ce qui est généralement le cas). On peut noter que tant sur la structure que sur le nom, le CPAN s'est inspiré du CTAN (Comprehensive TeX Archive Network), mais a dépassé son modèle tant en fonctionnalités qu'en popularité.

CPAN est aussi le nom du module Perl du même nom qui permet d'installer des modules de manière simplifiée, en gérant automatiquement les dépendances.

PAUSE

PAUSE est le Perl Authors Upload SErver, le serveur d'upload pour les auteurs Perl[4]. C'est un service qui est mis à la disposition des auteurs de modules Perl qui leur permet de déposer sur un serveur les modules qu'ils ont écrits (ceux-ci devant bien sûr être proprement emballés). Ces archives sont ensuite copiées en miroir sur les serveurs formant le CPAN. PAUSE est donc le point d'entrée des modules sur le CPAN.

Pour vous connecter sur le serveur PAUSE, vous devez au préalable obtenir un identifiant CPAN. Par ce login, un répertoire à votre nom sera créé sur les serveurs du CPAN, où vous pourrez ensuite déposer les modules et programmes que vous désirez partager avec la communauté.

Obtenir un identifiant

Pour obtenir votre identifiant CPAN, connectez-vous sur le site web de PAUSE https://pause.perl.org/ et cliquez sur le lien Request PAUSE account. Vous arrivez sur un formulaire où on vous demande de remplir votre nom, votre adresse mail, votre site web, l'identifiant que vous désirez obtenir, et une description de ce que vous comptez contribuer. Une fois validé, ce formulaire envoie un mail sur la liste modules@perl.org. Cette liste est un peu spéciale car ce n'est pas une vraie liste de diffusion mais juste un alias vers les mainteneurs du CPAN. Vous pouvez néanmoins en consulter les archives[5] pour suivre l'avancement de votre requête. Vous devriez recevoir votre identifiant dans la semaine qui suit.

Une fois votre identifiant créé, vous pouvez vous authentifier sur le site de PAUSE. Comme cela vous sera recommandé par mail, vous avez alors la possibilité d'éditer les informations de votre compte, et notamment de définir une adresse de courrier publique et une adresse confidentielle. Vous pouvez aussi définir un alias identifiant@cpan.org pointant vers l'une de ces adresses.

Choisir un nom

Encore plus que pour les autres logiciels libres, le choix du nom d'un module Perl est souvent un sujet sensible. La raison en est que le nom d'un module définit un « espace de noms », mais que contrairement à Java par exemple, les espaces de noms sont en quelque sorte globaux, alors qu'en Java ils commencent souvent par le nom de domaine inversé (comme org.w3c.dom ou org.apache.xerces), afin de garantir une unicité dès les premiers niveaux. Si le module n'introduit pas une fonctionnalité ou le support d'un logiciel tiers qui n'existait pas encore, il est conseillé qu'il s'inscrive dans un espace de noms déjà existant. Une règle commune est que le nom commence par la catégorie dans laquelle s'inscrit le module, et se termine par l'implémentation spécifique, s'il y a lieu.

Par exemple, un module implémentant un protocole réseau Proto devrait s'appeler Net::Proto, alors qu'un module interrogeant un site web s'inscrirait plutôt dans WWW:: ou WebService::. À noter que l'espace de noms Acme:: vous permet d'exprimer votre créativité en vous autorisant à y inclure des modules quelque peu fantaisistes (mais qui doivent néanmoins être fonctionnels).

À propos des espaces de noms

Les débats sur les espaces de noms sont souvent à l'origine de discussions au lance-flammes sur les listes de diffusions où le sujet est abordé. En résumé, si la notation par domaine inversée semble intéressante, elle pose plus de problème qu'elle n'en résout. Imaginez que Gisle Aas ait nommé son module Org::Aas::LWP au lieu de LWP ! Outre le fait que c'est très laid, ça pose de nouveaux problèmes : comment faire quand le module change de mainteneur ? En particulier, comment faire si le module est intégré à la distribution standard de Perl ? Les problèmes de compatibilité que cela induit ne sont pas mineurs, et la plupart des programmeurs ont autre chose à faire qu'à perdre du temps à s'occuper de ce genre de futilités.

Le plus simple est de chercher sur http://search.cpan.org/ si d'autres modules similaires à celui que vous voulez écrire existent déjà. D'ailleurs, vous devriez chercher sur ce site avant même de commencer un module ! Vous pouvez aussi, c'est même très fortement recommandé, demander conseil sur la liste modules-authors@perl.org[6] qui est prévue pour ça. C'est bien sûr une liste internationale donc anglophone, mais vous pouvez aussi demander sur la liste francophone perl@mongueurs.net[7].

En fait, l'attribution de l'espace de nom se fait dès le dépôt de la distribution sur le CPAN sur la base du « premier arrivé, premier servi ». Vous pouvez ensuite l'enregistrer de manière plus officielle dans la liste des modules : connectez-vous sur PAUSE, cliquez sur Register Namespace, puis remplissez les champs demandés et validez.

Charger une archive sur le CPAN

Ca y est ! Tout est prêt. Vous avez écrit votre module, vous avez demandé conseil pour le nom, vous avez obtenu votre identifiant CPAN, votre archive est prête (surtout, ne changez pas le nom de l'archive créée avec l'action dist, car il est formaté de la manière attendue pour la suite des opérations). Vous êtes paré pour charger votre module sur le CPAN, et comble de chance, l'opération est on ne peut plus simple. Connectez-vous sur PAUSE, puis cliquez sur Upload a file to CPAN. Cette page vous indique tous les moyens de charger une archive sur le CPAN : envoi par formulaire, téléchargement depuis un site distant ou dépôt sur un serveur FTP. Une fois que PAUSE a récupéré votre archive, elle est automatiquement décompressée pour être examinée et indexée.

En particulier les fichiers README et META.yml sont extraits et stockés à côté de l'archive .tar.gz mais avec les extensions .readme et .meta. Les informations contenues dans META.yml et dans Build.PL ou Makefile.PL sont ensuite extraites pour être stockées dans une base de données qui servira à créer le fichier http://www.cpan.org/modules/03modlist.data.gz, utilisé par le module CPAN.pm pour installer les modules. Ensuite, quand votre archive arrive sur http://search.cpan.org/, l'archive est décompressée pour en indexer les différents fichiers, sauf ceux situés dans le répertoire t/, et générer les pages HTML correspondantes.

Il faut par ailleurs connaître quelques particularités du fonctionnement de CPAN. La principale est que chaque version doit être impérativement unique. En fait, vous ne pouvez pas déposer deux archives avec le même numéro de version, par exemple pour corriger une erreur idiote que vous n'avez vue qu'au dernier moment. Dans ce genre de cas, il vous faudra charger la nouvelle archive avec un nouveau numéro de version. Cela peut paraître curieux, mais c'est une mesure de sécurité qui a été mûrement réfléchie.

À noter que vous pouvez supprimer les vieilles versions de vos modules afin de ne pas encombrer les disques des miroirs du CPAN. Pour cela, connectez-vous sur PAUSE, puis cliquez sur Delete Files. Vous pouvez alors sélectionner les fichiers à supprimer. N'hésitez pas, il y aura toujours une version archivée dans BackPAN[8], et cela permettra d'améliorer le Facteur Schwartz[9].

Conclusion

Voici un petit résumé pour les programmeurs impatients :

Cette présentation sur les bonnes pratiques pour créer votre distribution de modules Perl est volontairement restée assez générale, pour montrer l'ensemble des étapes de l'idée au CPAN. Il y a encore bien à dire à ce sujet, mais ce sera abordé dans de futurs articles consacrés aux modules s'interfaçant avec du code C (les modules XS), à la configuration avancée des constructeurs Module::Build et ExtUtils::MakeMaker, et à la programmation de tests complexes avec Test::More et Test::Builder.

Références

[1] Introduction à Perl - Partie 7, Écrire un module, par Sylvain Lhullier, in Linux Magazine n°46, janvier 2003; in Les Dossiers de Linux Magazine n°2, printemps 2004 ; https://formation-perl.fr/guide-perl.html

[2] YAML (YAML Ain't Markup Language) est un format destiné à la sérialisation de données. Il est similaire dans ses fonctionnalités à XML mais contrairement à ce dernier, il reste toujours lisible pour un humain. Pour plus d'informations, voir http://www.yaml.org/

[3] perlport: Writing portable Perl - http://search.cpan.org/dist/perl/pod/perlport.pod (version anglaise), http://www.mongueurs.net/perlfr/perlport.html (version française)

[4] The Perl Authors Upload Server - http://www.cpan.org/modules/04pause.html

[5] Archives de modules@perl.org - http://www.xray.mpe.mpg.de/mailing-lists/modules

[6] The module-authors Mailing List - http://lists.perl.org/showlist.cgi?name=module-authors

[7] Liste de diffusion perl@mongueurs.net - http://paris.mongueurs.net/mail.html

[8] BackPAN - http://backpan.cpan.org/

[9] Schwartz Factor - http://use.perl.org/~brian_d_foy/journal/8314

Auteur

Sébastien Aperghis-Tramoni <sebastien@aperghis.net>, "Maddingue" - de Marseille.pm

Bricoleur de Perl depuis 1994, Sébastien est actuellement administrateur système à Marseille, où il essaye de devenir un véritable BOFH.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]