Dist-Zilla - distribution simplifiée pour auteur CPAN

Article publié dans Linux Magazine 128, juin 2010.

Copyright © 2010 - Jérôme Quelin

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

Chapeau

Si vous utilisez Perl, vous connaissez déjà le CPAN, proposant plus de 10000 modules réutilisables pour gagner en temps et en qualité. Dans cet article, nous allons nous mettre dans la peau d'un auteur ayant publié des modules sur CPAN et voir comment simplifier le processus de maintenance associée.

Le travail d'un auteur CPAN

Qu'est-ce qu'une distribution ?

Lorsqu'un auteur publie un module sur CPAN [0], il publie une distribution. Cette distribution est en fait :

Que trouve-t-on dans une distribution ?

Regardons plus en détails le contenu d'une distribution ...

Du code

Le but premier d'une distribution est de fournir un ou des modules réutilisables. Une distribution va donc comprendre un répertoire lib/ qui contiendra les modules à installer, proprement hiérarchisés. Ainsi, le module Foo::Bar sera implémenté dans le fichier lib/Foo/Bar.pm.

Une distribution peut aussi fournir un programme qui est l'intérêt principal du module - par exemple, le module SVK fournit l'utilitaire svk. Ces programmes sont localisés dans le répertoire bin/ de la distribution (certains auteurs préfèrent utiliser le répertoire scripts/).

Mais ce n'est pas tout : en effet, un auteur CPAN va devoir fournir d'autres choses pour que cette distribution s'installe facilement.

Script d'installation

Une distribution fournit aussi un script d'installation. Ce script est traditionnellement appelé selon son fonctionnement Makefile.PL (utilisation de ExtUtils::MakeMaker) ou Build.PL (utilisation de Module::Build).

Ce script est utilisé de la manière suivante :

    $ perl Makefile.PL
    $ make
    $ make test
    $ make install

Si un script Build.PL est fourni, alors les incantations deviennent :

    $ perl Build.PL
    $ ./Build
    $ ./Build test
    $ ./Build install

Bien sûr, cette séquence est lancée pour vous par les utilitaires d'installation automatisés, mais c'est la présence de ces scripts qui garantit une installation facile.

Avant toute chose, le script d'installation va s'assurer que la distribution est complète. Pour cela, il s'appuie sur un fichier MANIFEST contenant la liste des fichiers devant être présents dans l'archive.

Fichier META.yml

Pour permettre d'indexer la distribution et l'analyser facilement, mais aussi pour que les scripts d'installation automatique puissent calculer les dépendances, une distribution se doit d'avoir un fichier META.yml qui contiendra des méta-informations :

Ce fichier répond à des spécifications précises [1], comprises par les utilitaires d'installation tels que cpan.

Tests

Perl est connu pour l'importance qu'il accorde aux tests. C'est pourquoi une distribution incorpore un répertoire t/ contenant les tests de la distribution. Suivant l'auteur et la distribution, on trouvera des tests s'assurant de la bonne compilation des modules, des tests unitaires sur les fonctions et méthodes des modules, des tests fonctionnels ... mais aussi des tests pour s'assurer que la documentation (écrite en Pod) est bien formée et couvre bien l'ensemble de l'API, des tests lançant perlcritic [2] sur les modules pour s'assurer que le code respecte certains standards de programmation, et bien d'autres destinés à s'assurer de la qualité d'une distribution.

Documentation

Une distribution fournit aussi des fichiers permettant d'en savoir plus à son sujet : les traditionnels README et LICENSE, mais aussi un fichier Changes listant les versions et les changements apportés.

Ces fichiers ne sont pas nécessaires pour l'installation automatisée, mais ils sont toutefois importants pour les utilisateurs et les personnes packageant ces modules.

Et bien d'autres choses encore !

Enfin, une distribution peut fournir d'autres fichiers, selon son but final. Ainsi, les applications peuvent fournir des fichiers .po et .mo pour leur internationalisation / localisation, des icônes, des images ou des données... Tout dépend de la distribution !

Autres conventions

À cela s'ajoutent les conventions auxquelles le code doit se plier.

Par exemple, les modules doivent fournir une variable $VERSION. Cette version doit être celle de la distribution pour le module principal. Il est recommandé que les autres modules fournissent la même version (cela permet de savoir si un module n'est plus fourni par la distribution, car sa version ne sera plus incrémentée).

De plus, chaque module comporte également des paragraphes AUTHOR et LICENSE (au moins pour le module principal), une documentation idoine...

Enfin, si l'auteur veut suivre les recommandations de la FSF [3], il lui faut ajouter un en-tête à tous ses fichiers avec le copyleft.

Travailler sur votre distribution

Voyons maintenant certaines opérations qu'un auteur est amené à faire.

Ajouter un module dans votre distribution ?

Pour ajouter un module, il vous faut d'abord créer un fichier, copier les en-têtes utilisés dans votre projet, puis ajouter le Pod contenant licence, auteur et autres méta-informations. Bien sûr, n'oubliez pas d'ajouter une variable $VERSION égale à la version de la distribution. Et n'oublions pas de rajouter le fichier dans le MANIFEST de votre distribution !

Fournir une nouvelle version ?

Admettons maintenant que vous êtes prêt à publier une nouvelle version de votre distribution... Voyons ce que vous devez faire.

Il vous faut tout d'abord modifier la version de tous vos modules et s'assurer que les pré-requis listés sont toujours les bons (certains peuvent avoir été rajoutés ou supprimés).

Puis vous lancerez les tests, mettrez à jour les fichiers Changes et META.yml, pour enfin créer une distribution que vous pourrez télécharger sur le site des auteurs CPAN.

Ouf ! C'est bien compliqué ! Voyons s'il est possible de se simplifier la vie.

Se faciliter la tâche

Au vu des tâches requises au-delà de la programmation pure, différentes solutions sont apparues pour aider les auteurs.

ExtUtils::MakeMaker et Module::Build

Puisque l'auteur fournit un script d'installation qui contient un certain nombre de méta-informations, ces scripts permettent de générer les fichiers MANIFEST et META.yml.

Cependant, il faut bien penser à lancer la commande mettant à jour ces fichiers, et mis à part la création de l'archive tar, ils n'aident pas pour le reste.

Module::Starter

L'un des premiers modules pour aider les auteurs fut Module::Starter. Il permet de créer une distribution avec les différents fichiers nécessaires et fournit un module squelette à remplir par l'auteur.

Cependant, Module::Starter ne fournit qu'un point de départ (d'où son nom). Il ne permet pas de rajouter des fichiers à votre distribution, ni de modifier les fichiers existants si vous décidez de changer la licence ou autre méta-information... ou même la version de tous vos modules !

Bref, la solution n'est pas idéale.

Module::Release

A l'autre bout du spectre, Module::Release aide à pousser votre distribution sur différents sites. Cela peut être PAUSE (pour le CPAN), ou SourceForge, ou autres sites FTP. Il permet aussi une intégration minimale avec votre gestionnaire de source. Bien que pratique pour cette phase de publication, il ne résout pas les problèmes rencontrés lors de l'écriture de votre distribution.

Autres

Il existe d'autres modules qui aident sur un point particulier (e.g., le module Perl::Version fournit un exemple permettant de modifier les numéros de version de tous les modules d'une distribution), mais aucune solution ne sort du lot pour aider les auteurs.

La solution Dist::Zilla

Partant du constat que programmer c'est fun, mais qu'écrire des licences et déclamations légales ne l'est pas, Ricardo Signes (dit rjbs) a donc créé Dist::Zilla, avec le slogan Roooar !

Ce module fournit en fait une application nommée dzil pour aider les auteurs. Voici comment...

Principes

Dist::Zilla va être utilisé par l'auteur du module, et par l'auteur uniquement, pour créer sa distribution. La distribution créée par Dist::Zilla sera exactement la même, suivra les mêmes conventions (cf. plus haut) et s'installera de la même manière qu'une distribution classique.

Par contre, Dist::Zilla va créer des fichiers et en réécrire d'autres à la volée sans que l'auteur n'ait à s'en préoccuper.

Dist::Zilla a énormément de pré-requis à installer, mais ce n'est pas un problème car Dist::Zilla n'est lancé que par l'auteur. Il n'y a aucun pré-requis additionnel pour la distribution finale et donc pour l'utilisateur de votre distribution.

Fonctionnement

Pour utiliser Dist::Zilla pour votre distribution, vous allez créer un nouveau fichier dist.ini contenant les informations statiques de votre projet. Ce fichier suit le format .ini [4] popularisé par Windows, avec des sections définies entre crochets et des paires clef / valeur.

Vous allez me dire (à raison) que cela fait un fichier supplémentaire à gérer... C'est vrai, mais attendez de voir ce que ce simple fichier va vous permettre de faire !

Ce fichier dist.ini permet aussi de personnaliser ce que vous voulez que la commande dzil fasse. En effet, Dist::Zilla utilise des plugins, et c'est à vous de choisir quels plugins vous souhaitez utiliser et dans quel ordre. Chaque plugin est indiqué par une nouvelle section dans le fichier, de cette manière :

    [MyPlugin]
    ; commentaire si on veut
    parametre = valeur

La commande dzil

Une fois Dist::Zilla installé et votre fichier dist.ini créé, la commande dzil sera votre nouvelle interface.

Là où vous utilisiez :

    $ perl Makefile.PL
    $ make disttest

Vous utiliserez maintenant :

    $ dzil test

Et pour remplacer :

    $ perl Makefile.PL
    $ make dist

Vous utiliserez :

    $ dzil build

Vous remarquerez déjà que nous gagnons une commande lors de l'invocation...

Enfin, la commande :

    $ dzil new Foo::Bar

va créer un répertoire Foo-Bar/ contenant un fichier dist.ini de base pour démarrer une nouvelle distribution. Il ne vous restera qu'à créer les modules et fichiers nécessaires, en prenant en compte bien sûr les particularités des plugins que vous allez choisir d'utiliser.

Dist::Zilla par l'exemple

Dist::Zilla propose de nombreux plugins. Pour montrer leur fonctionnement, nous allons prendre dans la suite de cet article l'exemple d'un module (fictif) App::Frobnizer, que nous allons migrer pour utiliser Dist::Zilla. Voici les fichiers contenus la distribution de ce module, tels qu'ils sont dans notre système de gestion de source :

    $ cd ~/code/app-frobnizer
    $ find .
    ./Changes
    ./LICENSE
    ./MANIFEST
    ./MANIFEST.SKIP
    ./Makefile.PL
    ./META.yml
    ./README
    ./bin/frobnizer
    ./lib/App/Frobnizer.pm
    ./lib/App/Frobnizer/Reticulator.pm
    ./lib/App/Frobnizer/Util/mtfnpy.pm
    ./t/0-compile.t
    ./t/1-unit-test.t
    ./t/2-integration.t
    ./t/9-critic.t
    ./t/9-pod.t
    ./t/9-pod-coverage.t

Création de dist.ini

Nous allons donc tout d'abord créer notre fichier dist.ini avec les informations statiques suivantes :

    name    = App-Frobnizer
    author  = Jerome Quelin
    version = 1.23
    license = Perl_5
    copyright_holder = Jerome Quelin
    copyright_year   = 2009

La ligne auteur peut être répétée plusieurs fois et la personne possédant le copyright peut être différente.

La licence doit être l'une des licences répertoriées dans Software::License [5].

Plugins de base

Tel quel, notre fichier de configuration ne nous sert à rien... Il faut ajouter des plugins pour que dzil comprenne ce qu'il faut faire.

Indiquons tout d'abord à dzil quels fichiers feront partie de la distribution. Le plus simple pour cela est donc d'ajouter le plugin GatherDir qui va, comme son nom l'indique, parcourir l'arborescence et ajouter tous les fichiers qu'il trouvera dans la distribution.

Comme nous ne souhaitons pas inclure les anciennes distributions ni les fichiers générés durant les builds précédents, nous allons les exclure grâce au plugin PruneCruft. Cela supprimera aussi tous les fichiers cachés (qui commencent par un point), afin que votre distribution ne contienne pas les fichiers de votre gestionnaire de source.

Enfin, pour ne pas inclure certains fichiers ou répertoires, nous allons utiliser le plugin ManifestSkip qui va utiliser le fichier MANIFEST.SKIP, contenant une liste d'expressions régulières, pour ignorer les fichiers correspondants. Ainsi, pour ne pas inclure le répertoire private/ et tout son contenu dans notre distribution, il suffira de rajouter dans MANIFEST.SKIP la ligne suivante :

    private/

Il est aussi possible d'utiliser le plugin PruneFiles, qui prend en paramètre les fichiers à supprimer de la distribution. Cependant, il n'accepte que des noms de fichier exacts, ce qui le rend moins pratique pour supprimer un répertoire entier ou un ensemble de fichiers correspondant à un motif.

Notre fichier dist.ini contient donc les plugins suivants :

    [GatherDir]
    [PruneCruft]
    [ManifestSkip]

Aucun de ces modules ne prend d'arguments, ils apparaissent donc tous comme une section vide.

Maintenant que nous avons rajouté ces trois plugins, la commande :

    $ dzil test

va créer un répertoire temporaire dans lequel seront copiés tous les fichiers de la distribution, et lancer le fameux couple de commandes :

    $ perl Makefile.PL
    $ make test

Quant à la commande :

    $ dzil build

elle va maintenant créer un répertoire App-Frobnizer-1.23/ ainsi qu'une archive App-Frobnizer-1.23.tar.gz prête à être publiée sur CPAN.

Aide à la publication

Puisqu'on parle de publication, dzil va nous aider avec deux plugins sympathiques. Le premier, CheckChangeLog, va s'assurer que nous avons bien rajouté les notes de version dans le fichier Changes. Si ce fichier ne contient pas un paragraphe ressemblant à ceci :

    1.23 2009-09-17
      - added blort option
      - fixed crash when called with slurp

alors les appels à dzil échoueront avec le message suivant :

    [CheckChangeLog] No Change Log in Changes
    [CheckChangeLog] Please edit

Ceci est intéressant pour s'assurer que la documentation de notre distribution est bien à jour. Mais le plugin UploadToCPAN est encore mieux. Il accepte les paramètres user et password :

    [UploadToCPAN]
    user     = myusername
    password = S3kr3t

Une fois cette section renseignée, nous allons pouvoir utiliser la commande :

    $ dzil release

qui va créer la distribution (ce que fait dzil build) puis la publier automatiquement pour vous sur CPAN. Voilà du temps gagné !

Si vous souhaitez publier une version de test, qui sera donc disponible sur CPAN mais qui ne sera pas automatiquement installée par les clients, nous allons utiliser l'option:

    $ dzil release --trial

Bien sûr, laisser ses identifiant et mot de passe dans le fichier dist.ini de votre distribution n'est pas la meilleure chose à faire. En effet, en tant qu'auteur, vous publiez sans doute plusieurs distributions et dupliquer ces informations dans le fichier dist.ini de chacune d'entre elle n'est pas efficace. Mais surtout, ce fichier sera ajouté à votre SCM (git, svn ou celui que vous utilisez pour votre distribution), donc tout le monde y aura accès.

Heureusement, rjbs y a pensé et la commande dzil accepte un fichier de configuration global pour l'utilisateur. Il vous suffit donc de créer un fichier ~/.dzil/config.ini dans lequel vous ajouterez :

    [!release]
    user     = myusername
    password = S3kr3t

(le point d'exclamation indique à Dist::Zilla qu'on parle de la commande release)

Dans votre fichier dist.ini, vous ne référencerez maintenant que le plugin UploadToCPAN sans paramètres : ceux-ci seront récupérés dans votre fichier de configuration utilisateur.

Enfin, si vous utilisez git comme SCM, certains plugins peuvent vous aider. En ajoutant :

    [Git::Check]

dans dist.ini, la phase de publication échouera si votre copie de travail n'est pas propre. Il faut que votre index soit vide, ne pas avoir de fichier non gérés par git, et ne pas avoir de modification dans votre copie de travail (hormis le changelog et dist.ini, qui contient la version de votre distribution).

Après la publication, le plugin Git::Commit va committer automatiquement votre changelog et dist.ini dans git, en prenant comme message de commit l'entrée correspondante de votre fichier changelog. Le plugin Git::Tag va créer un tag dans git correspondant à cette version juste publiée, et enfin Git::Push va pousser les commits et tags de la branche courante vers sa branche distante.

Tous ces plugins peuvent être instanciés en une seule ligne avec un bundle (regroupement de plugins) dans votre dist.ini :

    [@Git]

(le arobase dénote un bundle de plugins)

Génération spontanée

Jusque là, dzil nous aide un peu dans notre tâche d'auteur. Mais, mis à part la publication automatisée, cela reste marginal et à la hauteur de ce que Module::Release apporte déjà.

Nous allons donc aborder les plugins permettant d'injecter des fichiers dans notre distribution.

Tests génériques

Commençons par les tests. Beaucoup d'entre eux sont présents pour tester la forme de votre distribution, sa documentation, etc. Ces tests sont donc les mêmes d'une distribution sur l'autre - et qui dit répétition dit automatisation possible !

dzil propose donc les plugins suivants pour vous aider :

Ainsi, grâce à 8 lignes ajoutées dans notre fichier dist.ini, vous avez fait l'économie d'autant fichiers, qu'il vous faudrait normalement écrire et maintenir ! Et ce, sans aucune perte de fonctionnalité - on commence à voir là toute la puissance de Dist::Zilla.

Et ce n'est que le début : il y a 8 autres plugins fournissant des tests génériques (correction orthographique du Pod, vérification de la version minimum de Perl demandée, etc.). Gageons que d'autres plugins générant divers tests vont faire leur apparition.

Méta-fichiers

Mais les tests ne sont pas les seuls candidats à la génération. En particulier, comme vous avez déjà spécifié un certain nombre d'informations sur votre travail en début de dist.ini, beaucoup de méta-fichiers qui permettent à votre distribution d'être de bons citoyens CPAN peuvent être générés. Voyons donc cela...

Commençons par le plus simple : le fichier LICENSE va être créé automatiquement grâce au plugin License. Il contiendra le texte complet de la licence choisie et, dans le cas d'une double licence (comme Perl 5, qui est disponible soit sous licence Artistic soit sous licence GPL), il contiendra un chapeau avec les termes de l'alternative, suivi par le texte intégral des deux licences.

Le fichier README sera lui généré grâce au plugin Readme. Il contiendra un bref rappel du nom de la distribution, la version considérée et son but, ainsi que la mention du copyright et de la licence utilisée. Si vous préférez que le fichier README soit plus conséquent, le plugin ReadmeFromPod le créera en convertissant la documentation de votre module principal en texte. Bien sûr, ces deux modules ne peuvent pas être utilisés en même temps.

Continuons notre génération de fichiers... Le fichier INSTALL sera généré par le plugin InstallGuide et contiendra un rappel des commandes à lancer pour installer la distribution. Le plugin MetaYAML va générer pour vous le fichier META.yml, et le plugin MakeMaker quant à lui s'occupera du fichier Makefile.PL. Si vous souhaitez fournir aussi un fichier Build.PL (utilisant Module::Build), alors le plugin ModuleBuild s'en chargera pour vous. Si vous avez des besoins très particuliers, les modules OverridableMakeMaker et ModuleBuild::Custom permettront de les personnaliser.

Pour terminer sur la génération de ce genre de fichiers, citons le plugin Manifest qui va créer automatiquement le fichier MANIFEST. Ainsi, plus besoin de devoir lancer (quand on y pense !) à la main :

    $ perl Makefile.PL
    $ make manifest

Votre fichier MANIFEST sera maintenant toujours à jour. Bien sûr, ce plugin doit être listé après tous les autres plugins générant des fichiers, sinon le manifeste de votre distribution ne contiendra pas les nouveaux fichiers créés.

Résultats

Avec tous ces fichiers générés pour vous, le code que vous avez à gérer s'est maintenant réduit à ce qui est réellement spécifique à votre application :

    $ cd ~/code/app-frobnizer
    $ find .
    ./Changes
    ./dist.ini
    ./bin/frobnizer
    ./lib/App/Frobnizer.pm
    ./lib/App/Frobnizer/Reticulator.pm
    ./lib/App/Frobnizer/Util/mtfnpy.pm
    ./t/1-unit-test.t
    ./t/2-integration.t

Vous pouvez maintenant vraiment vous concentrer sur votre code, et oublier les à-côtés, nécessaires mais ennuyeux !

Méta-informations

Pré-requis

En tant qu'auteur averti, vous avez certainement tiqué au paragraphe sur les méta-fichiers générés : les fichiers META.yml, Makefile.PL et Build.PL ont besoin de méta-informations supplémentaires. En effet, il leur faut savoir quels sont les pré-requis de votre distribution... Pour cela, il vous faut rajouter une section pour le plugin Prereq, listant ces pré-requis :

    [Prereq]
    Foo::Bar     = 2.43
    Blah::Compat = 0

Vous pouvez spécifier la version minimum que vous souhaitez, ou laisser 0 pour indiquer que n'importe quelle version convient, du moment que le module est disponible.

Cependant, dzil propose encore mieux : trouver automatiquement les pré-requis pour vous ! Il vous suffit pour cela de lister le plugin AutoPrereq, qui tentera de trouver les modules que vous utilisez. L'analyse est statique mais devrait récupérer la majorité d'entre eux, ainsi que les versions minimales des modules que vous souhaitez. Il ne trouvera cependant pas les utilisations conditionnelles et/ou obfusquées. Par exemple, si votre code teste la présence d'un module en faisant :

    eval "use My::Module";

alors AutoPrereq ne le trouvera pas. Mais si vous testez sa présence ainsi, cela veut dire que vous vous attendez à son absence et que ce module est optionnel. Il est donc logique de ne pas le lister dans les pré-requis de votre distribution. AutoPrereq va aussi filtrer automatiquement les modules se trouvant dans la hiérarchie de votre distribution. Ainsi, pour notre distribution App::Frobnizer, il ne va pas lister App::Frobnizer::Reticulator comme pré-requis, même s'il utilisé dans votre code. Enfin, il est possible de supprimer des pré-requis trouvés grâce au paramètre skip qui accepte une expression régulière en paramètre :

    [AutoPrereq]
    skip = ^Private::

Tous les modules commençant par Private seront filtrés et ne seront donc pas listés comme pré-requis de votre distribution.

Enfin, notons qu'il est possible d'utiliser Prereq en complément du plugin AutoPrereq pour lister manuellement des pré-requis non trouvés automatiquement.

Autres méta-informations

Il est possible d'ajouter d'autres informations sur votre distribution, qui seront agrégées dans le META.yml ou utilisées dans le script d'installation.

Citons d'abord donc le plugin MetaResources qui permet d'ajouter des URL se rapportant à votre projet :

    [MetaResources]
    homepage    = http://search.cpan.org/dist/App-Frobnizer
    repository  = http://github.com/jquelin/app-frobnizer
    MailingList = http://groups.google.com/group/app-frobnizer

Si la distribution utilise les valeurs par défaut de CPAN, les plugins HomePage et BugTracker permettront de remplir ces valeurs pour vous. Et le plugin Repository quant à lui fournira tout seul l'url de votre gestionnaire de source, en allant la chercher dans votre copie locale.

Comme notre magnifique module est en fait une application, il faut indiquer au script d'installation de s'occuper du ou des scripts fournissant l'interface de notre projet. Cela se fait avec le plugin ExecDir qui permet d'installer des programmes supplémentaires à des endroits particuliers. Par défaut, le plugin ExecDir va automatiquement considérer le répertoire bin/ comme contenant les programmes à installer, mais si vos scripts sont dans scripts/, alors il vous faut l'indiquer ainsi :

    [ExecDir]
    dir = scripts

Le plugin ShareDir permet d'installer des fichiers additionnels parmi les modules et autres fichiers utilisés par Perl, qui pourront ensuite être trouvés facilement pendant l'exécution du programme via le module File::ShareDir.

Enfin, pour que le fichier META.yml contienne tous les modules fournis par votre distribution, ajoutez le plugin MetaProvides::Package qui va les extraire automatiquement pour vous. Ils seront intégrés de manière transparente lors de la génération de ce fichier.

Modification à la volée

Nous avons réussi à réduire drastiquement le nombre de fichiers que vous devez maintenir dans votre distribution. Mais le rapport code / non-code dans les fichiers module reste peu élevé. Heureusement, Dist::Zilla va nous aider là aussi.

Prenons notre fabuleux module App::Frobnizer comme exemple :

    #
    # This file is part of App-Frobnizer
    #
    # This software is copyright (c) 2009 by Jerome Quelin.
    #
    # This is free software; you can redistribute it and/or modify it under
    # the same terms as the Perl 5 programming language system itself.
    #
    package App::Frobnizer;

    =head1 NAME

    App::Frobnizer - my awesome app

    =head1 VERSION

    version 1.23

    =cut

    our $VERSION = 1.23;

    =head1 DESCRIPTION

    This app is awesome.

    =head1 METHODS

    =head2 this_method

    This method does stuff.

    =cut

    sub this_method { ... }

    =head2 that_method

    Also stuff.

    =cut

    sub that_method { ... }

    =head1 AUTHOR

    Jerome Quelin <jquelin@cpan.org>

    =head1 COPYRIGHT AND LICENSE

    Copyright (c) 2010, Jerome Quelin.

    This is free software; you can redistribute it and/or modify
    it under the same terms as the Perl 5 programming language
    system itself.

    =cut

    1;

    __END__

Versions

Le premier plugin qui va nous simplifier la vie est PkgVersion. Il va automatiquement rajouter à vos modules une ligne :

    our $VERSION = '1.23';

Bien sûr, la version réelle sera celle spécifiée dans le fichier de configuration dist.ini. Fini la modification de tous vos fichiers pour mettre à jour la version lorsque vous souhaitez publier !

Et si par flemme vous ne versionniez que le module principal, vous pouvez maintenant fournir un numéro de version pour tous vos modules, sans plus d'effort...

En-têtes légaux

Pour s'assurer de notre copyright, la FSF recommande d'ajouter un en-tête à tous les fichiers du projet. Ces en-têtes sont toutefois toujours les mêmes - le plugin Prepender permet donc de les ajouter pour vous !

Ce plugin est en fait plus générique : il permet d'insérer automatiquement des lignes au début de votre fichier (ou juste après la ligne shebang dans le cas de scripts). Cependant, l'ajout de copyright étant une opération commune, vous pouvez le spécifier grâce au paramètre booléen copyright.

Si vous adjoignez ceci à votre fichier de configuration :

    [Prepender]
    copyright = 1
    line = use strict;
    line = use warnings;

tous vos fichiers se retrouveront alors avec un commentaire contenant les en-têtes légaux, mais auront aussi les pragmas strict et warnings activés.

PodWeaver

Si vous êtes consciencieux, vos modules comportent beaucoup de documentation. Cependant, celle-ci comporte beaucoup de redites.

De plus, si vous voulez que la documentation de vos méthodes et fonctions soit près de leur objet, cela vous oblige à mettre les informations de votre module au début de votre code, ce qui n'est pas idéal. Damian Conway le déconseille d'ailleurs dans son livre Perl Best Practices.

C'est pourquoi le plugin PodWeaver a été créé : il pallie ces inconvénients. Il va récupérer le Pod de vos modules, le torturer dans tous les sens et le recréer pour le remettre d'aplomb.

La première section à sauter sera NAME, qui sera générée pour vous. Afin que la description de haut niveau de votre module soit présente, il vous faut ajouter un commentaire juste après la définition de votre package :

    package App::Frobnizer;
    # ABSTRACT: my awesome app

Il va aussi rajouter automatiquement une section VERSION étant donné qu'il dispose de cette information.

De même, comme l'auteur, le copyright et la licence ne changent pas d'un module sur l'autre, vous pouvez les supprimer et PodWeaver va les créer automatiquement pour vous.

Enfin, les méthodes seront automatiquement groupées dans une section METHODS, en utilisant le nouveau marqueur Pod =method. Les attributs d'une classe peuvent de même être indiqués avec le marqueur Pod =attr, et seront regroupés dans une section ATTRIBUTES.

Et comme le Pod est automatiquement regroupé, vous pouvez garder la documentation de vos méthodes près de celles-ci, tout en mettant la documentation générale de votre module à la fin...

Résultats

Voici donc notre code tel que nous allons l'écrire et le stocker dans notre SCM :

    package App::Frobnizer;
    # ABSTRACT: my awesome app

    =method this_method

    This method does stuff.

    =cut

    sub this_method { ... }

    =method that_method

    Also stuff.

    =cut

    sub that_method { ... }

    1;
    __END__

    =head1 DESCRIPTION

    This app is awesome.

    =cut

Nous avons réduit sa taille de moitié, en supprimant toutes les redites. Et encore une fois, nous n'avons rien perdu, car dzil va rajouter pour vous ces redites lors des tests, ou création de la distribution finale !

Il faut toutefois garder à l'esprit un inconvénient découlant des capacités de réécriture de Dist::Zilla : si vous recevez un rapport de bug, les lignes fautives ne seront sans doute pas celles pointées par le numéro de ligne remonté dans l'erreur. Cependant, cet inconvénient reste mineur comparé aux gains de temps et de clarté que nous apporte l'utilisation de Dist::Zilla. Il est toutefois de votre ressort de comprendre cela et de décider en conscience si vous souhaitez utiliser les fonctionnalités de modification à la volée de Dist::Zilla.

Tests

Un dernier plugin permet de modifier à la volée des fichiers - des tests cette fois.

Avec la prolifération de tests dits d'auteur (testant la documentation, la qualité du code, etc.), des voix se sont élevées pour qu'ils ne soient pas activés par défaut lors de l'installation des modules, mais uniquement lorsque l'auteur teste sa distribution avant publication.

Ces tests se sont donc vus déplacer dans le répertoire xt/ contenant les sous-répertoires suivants :

Et pour éviter que les tests ne soient lancés lors de l'installation de la distribution par un utilisateur, ces tests doivent s'assurer que les variables d'environnement respectives AUTHOR_TESTING, RELEASE_TESTING ou AUTOMATED_TESTING soient positionnées. Si elles sont absentes, les tests ne seront pas exécutés.

Cependant, écrire la logique de saut de ces tests en fonction des variables d'environnement est répétitive... Le plugin ExtraTests va donc réécrire les tests de xt/ pour automatiquement les sauter si les variables d'environnement idoines ne sont pas présentes.

Bien sûr, en tant qu'auteur, nous souhaitons lancer ces tests. Les variables d'environnement sont donc positionnées pour nous lorsque nous lançons la commande dzil test.

Encore plus de paresse...

Si vous ne souhaitez pas vous embarrasser par la gestion des versions, alors le plugin AutoVersion va générer la version de votre distribution automatiquement pour vous. Ceci n'est possible principalement que pour les versions basées sur la date de publication, mais comme vous pouvez utiliser n'importe quel code Perl pour formater ce numéro de version, rien ne vous empêche de faire plus compliqué, comme une intégration avec votre SCM... (Sauf que dans ce cas, vous pouvez même utiliser le plugin BumpVersionFromGit.)

Par exemple, si vous ajoutez ceci à votre dist.ini (en supprimant la ligne version au début du fichier, bien sûr) :

    [AutoVersion]
    major  = 1
    format = {{ $major }}.{{ cldr('yyDDD') }}{{ sprintf '%01u', ($ENV{N} || 0) }}

alors la prochaine version de votre distribution sera 1.yydddn avec :

Ce schéma de version est d'ailleurs le schéma par défaut du plugin AutoVersion, il n'est donc pas nécessaire de spécifier le paramètre format s'il vous convient. De même, le paramètre major vaut 1 par défaut.

Les plugins BumpVersion, VersionFromPrev et AutoVersion::Relative permettent aussi de générer la prochaine version automatiquement selon une autre logique, je vous renvoie à leur documentation pour en savoir plus.

Si vous utilisez l'un de ces plugins, alors écrire les notes de version dans le fichier Changes devient plus difficile, car la version n'est pas connue avant le moment de la publication. Le plugin NextRelease permet donc d'utiliser la notation :

    {{$NEXT}}
      - added blort option
      - fixed crash when called with slurp

dans votre fichier Changes, et la ligne {{$NEXT}} sera remplacée par la version et la date à laquelle vous aurez publié votre distribution.

Configuration finalisée

Pour récapituler, voici le fichier dist.ini complet correspondant à notre exemple, trié et commenté :

    name    = App-Frobnizer
    author  = Jerome Quelin
    license = Perl_5
    copyright_holder = Jerome Quelin
    copyright_year   = 2009

    ; -- static meta-information
    [AutoVersion]
    [HomePage]
    [BugTracker]
    [Repository]
    [MetaResources]
    MailingList = http://groups.google.com/group/app-frobnizer

    ; -- fetch & generate files
    [GatherDir]
    [CompileTests]
    [CriticTests]
    [MetaTests]
    [KwaliteeTests]
    [PodSyntaxTests]
    [PodCoverageTests]
    [PortabilityTests]
    [ReportVersions]

    ; -- remove files
    [PruneCruft]
    [ManifestSkip]

    ; -- get prereqs
    [AutoPrereq]

    ; -- munge files
    [ExtraTests]
    [NextRelease]
    [PkgVersion]
    [PodWeaver]
    [Prepender]
    copyright = 1

    ; -- dynamic meta information
    [ExecDir]
    [ShareDir]
    [MetaProvides::Package]

    ; -- generate meta files
    [License]
    [MakeMaker]
    [ModuleBuild]
    [MetaYAML]
    [Readme]
    [InstallGuide]
    [Manifest] ; should come last

    ; -- release
    [CheckChangeLog]
    [@Git]
    [UploadToCPAN]

L'ordre des plugins est bien sûr important, pour s'assurer qu'ils ont tous l'effet escompté.

Si vous utilisez couramment un ensemble de plugins, il est possible de définir un ensemble (bundle) de plugins et de remplacer leurs lignes dans dist.ini par :

    [@MyBundle]

Je vous renvoie à la documentation correspondante [6] pour la définition de ces bundles.

Conclusion

dzil est un outil actuellement en plein essor, et gagne rapidement de nouveaux adeptes. La fondation Perl a d'ailleurs fourni une bourse à Ricardo pour améliorer l'outil et sa documentation (cet article reflète bien les dernières nouveautés).

Cet outil puissant remplace make dist et bien d'autres choses encore. Votre distribution reste une distribution standard, utilisant les outils et conventions du CPAN. Vos utilisateurs ne se rendent pas compte pas que vous utilisez Dist::Zilla, tout est transparent pour eux. Ils s'aperçoivent juste que vous êtes plus productifs...

Bref, je vous conseille d'essayer Dist::Zilla... Roooar !

Liens

Auteur

Jérôme Quelin <jquelin@gmail.com>

Merci aux Mongueurs de Perl pour leur relecture attentive.

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