Journal upt: l'outil parfait pour empaqueter TapTempo

Posté par  . Licence CC By‑SA.
35
13
mar.
2018

Sommaire

Bonjour tout le monde,

Je vais faire (éhontément) un peu de pub pour mon dernier projet, upt : the Universal Packaging Tool, ou encore "l'outil d'empaquetage universel", comme on dit en France, de Bretagne en Provence.

Obtenir TapTempo depuis une archive de paquets spécifique à un langage

La communauté récemment formée autour de TapTempo a la possibilité d'utiliser diverses implémentations du logiciel, écrites dans de nombreux langages différents. Toutes ces versions de TapTempo seront sans doute bientôt disponibles sur des archives de paquets telles que PyPI, CPAN ou encore RubyGems. Il sera ensuite possible d'installer TapTempo grâce aux commandes suivantes :

# pip3 install taptempo
# cpan install App::TapTempo
# gem install taptempo

Obtenir TapTempo /via/ son OS

De nombreux utilisateurs souhaiteraient toutefois installer TapTempo grâce au gestionnaire de paquets de leur distribution. Ce sera sans doute chose aisée, puisqu'à n'en pas douter, des hordes de mainteneurs vont s'efforcer d'empaqueter TapTempo dans les jours qui viennent. Il sera ensuite possible de taper une des commandes suivantes dans son terminal :

# apt install python3-taptempo
# dnf install python3-taptempo
# guix package -i python-taptempo

Créer un paquet pour Debian, Fedora ou encore Guix à la main serait un peu fastidieux. Il est donc sans doute probable que les empaqueteurs s'aideront de scripts permettant de convertir un paquet Python/Perl/Ruby/… en un paquet Debian/Fedora/Guix/… Il existe d'ailleurs de nombreux scripts de ce genre :

Toutefois, cette multiplication d'outils pose plusieurs problèmes:

  • leur interface n'est pas unifiée : chaque outil a sa propre syntaxe, ses propres options, et il est peu évident de passer de l'un à l'autre;
  • leur comportement n'est pas unifié : certains outils vont créer des fichiers quand d'autres se contenteront d'écrire sur la sortie standard; certains lanceront la compilation et pas d'autres; etc.
  • tous les outils qui permettent de ré-empaqueter un paquet disponible sur PyPI réimplémentent le même type de code; tous les outils qui génèrent un paquet Debian (par exemple) réimplémentent du code similaire. On a donc un beau cas du syndrôme de "pas inventé ici". Certains outils (guix import, PortGen) permettent cependant de factoriser une partie du code.

upt : une conception modulaire

En se penchant bien sur le problème, on peut voir que l'on cherche à "traduire" la représentation d'un paquet en amont (celle disponible sur PyPI/CPAN/RubyGems, etc.) en une sa représentation en aval (celle disponible dans nos distributions).

C'est un problème finalement classique en informatique. Deux exemples :

  • un compilateur tel que GCC sait compiler de N langages vers P architectures, mais il n'implémente pas N*P compilateurs : il fournit N frontends qui convertissent le code en entrée vers une représentation interne, et P backends, qui convertissent cette représentation interne en code valide pour chaque architecture;
  • pandoc sait convertir des documents d'un langage de balisage à un autre : pour ce faire, il implémente des lecteurs et des écrivains, qui sont rendus compatibles en partageant une représentation interne des documents.

L'outil que j'ai développé récemment, upt (the Universal Packaging Tool) fonctionne de façon similaire, en implémentant deux concepts :

  • des frontends transforment un paquet présent sur une archive telle que PyPI/CPAN/RubyGems en une réprésentation interne à upt;
  • des backends transforment cette représentation interne en une définition de paquet (presque) valide pour un gestionnaire de paquets traditionnel.

Un peu d'art ASCII vaut mieux qu'un long journal:

   Archive   |   Frontends   |    upt   |     Backends     |  Définitions       
   en amont  |      upt      |          |       upt        |   de paquet        
             |               |          |                  |                    
  +------+   |  +----------+ |  +-----+ |  +-------------+ |  +----------+   
  | PyPI |---|->| upt-pypi |-|->|     |-|->|  upt-guix   |-|->| Guix pkg |   
  +------+   |  +----------+ |  |     | |  +-------------+ |  +----------+   
             |               |  | upt | |                  |                    
  +------+   |  +----------+ |  |     | |  +-------------+ |  +----------+   
  | CPAN |---|->| upt-cpan |-|->|     |-|->| upt-openbsd |-|->| Makefile |   
  +------+   |  +----------+ |  +-----+ |  +-------------+ |  +----------+   

Le "cœur" du projet est "upt", qui permet de faire travailler ensemble les frontends et les backends. On peut ainsi générer un paquet pour GNU Guix qu'il soit distribué sur CPAN ou sur PyPI. De même, on peut facilement distribuer python-taptempo sur GNU Guix et OpenBSD en utilisant upt-pypi, upt-openbsd et upt-guix.

Exemples

Voyons comment utiliser upt. Il faut tout d'abord l'installer (attention, il ne fonctionne qu'avec Python 3) avec l'une des commandes suivantes :

$ pip3 install upt
$ pip3 install upt[frontends] # upt et tous ses frontends
$ pip3 install upt[backends] # upt et tous ses backends
$ pip3 install upt[frontends,backends] # upt et tous ses frontends/backends

Nous pouvons ensuite lister les frontends et backends disponibles :

$ upt list-frontends
rubygems
pypi
cpan

$ upt list-backends
openbsd
guix

Essayons maintenant d'empaqueter requests_mock pour GNU Guix :

$ upt package -f pypi -b guix requests_mock
(define-public python-requests_mock
  (package
    (name "python-requests_mock")
    (version "1.4.0")
    (source (origin
              (method url-fetch)
              (uri (pypi-uri "requests_mock" version))
              (sha256
               (base32
                "XXX")))))
    (build-system python-build-system)
    (inputs
     `())
    (native-inputs
     `(("python-fixtures" ,python-fixtures)
       ("python-mock" ,python-mock)
       ("python-sphinx" ,python-sphinx)
       ("python-testrepository" ,python-testrepository)
       ("python-testtools" ,python-testtools)))
    (propagated-inputs
     `(("python-requests" ,python-requests)
       ("python-six" ,python-six)))
    (home-page "https://requests-mock.readthedocs.org/")
    (synopsis "Mock out responses from the requests package")
    (description "XXX")
    (license license:asl2.0)))

(define-public python2-requests_mock
  (package-with-python2 python-requests_mock))

Et pour OpenBSD :

$ upt package -f pypi -b openbsd requests_mock
COMMENT =       mock out responses from the requests package

MODPY_EGG_VERSION = 1.4.0
DISTNAME =      requests_mock-${MODPY_EGG_VERSION}
PKGNAME =       py-requests_mock-${MODPY_EGG_VERSION}

CATEGORIES =        XXX

HOMEPAGE =      https://requests-mock.readthedocs.org/

MAINTAINER =        XXX XXX <xxx@xxx.xxx>

# Apache-2.0
PERMIT_PACKAGE_CDROM =  Yes

MODULES =       lang/python
MODPY_SETUPTOOLS =  Yes # Probably
MODPY_PI =      Yes

RUN_DEPENDS =       xxx/py-requests${MODPY_FLAVOR} \
            xxx/py-six${MODPY_FLAVOR}
TEST_DEPENDS =      xxx/py-fixtures${MODPY_FLAVOR} \
            xxx/py-mock${MODPY_FLAVOR} \
            xxx/py-sphinx${MODPY_FLAVOR} \
            xxx/py-testrepository${MODPY_FLAVOR} \
            xxx/py-testtools${MODPY_FLAVOR}


.include <bsd.port.mk>

On voit que les définitions générées ne sont pas utilisables en l'état, mais 80% du travail de l'empaqueteur est effectué automatiquement.

Nous avons résolu nos problèmes :
* la CLI est désormais unifiée;
* le comportement est similaire d'un backend à l'autre;
* on n'implémente une fois seulement la récupération des informations concernant un paquet en amont; de même, on implémente qu'une seule fois la génération d'un paquet en aval.

Le dernier point semble trop beau pour être vrai. En effet, le langage utilisé influe très légèrement sur le contenu du paquet en aval. Les backends ne sont donc pas tout à fait "génériques"; toutefois, le code spécifique à un langage est en général très court.

Travaux futurs

Pour l'instant, trois frontends et deux backends sont disponibles. Si upt vous intéresse, je vous invite à le tester et à :

  • participer au développement : le projet utilise framagit, mais vous pouvez vous y connecter grâce à vos identifiants Github, youhou ! L'équipe d'upt est très sympathique, bien que principalement constituée de ma personne;
  • ajouter votre propre frontend ou backend : même pas besoin de m'envoyer une requête de tirage (une "pull request" comme on dit dans le reste du monde), puisqu'upt utilise les points d'entrée de setuptools. Tous les modules peuvent donc être développés dans leur propre dépôt.

Et si le projet ne vous intéresse pas assez pour y contribuer, bah j'f'rai tout moi-même, et pis tant pis, je s'rai tout triste.

Liens

  • # Intéressant mais ....

    Posté par  . Évalué à -8.

    … dommage que ce soit écrit en python.

  • # À suivre…

    Posté par  (site web personnel) . Évalué à 6.

    C’est un bon projet. Et tu as raison, cette présentation arrive à propos.
    Pour que ce soit vraiment abouti, il faudrait je pense enlever le « presque », c’est à dire que le paquet final soit installable, comme le propose checkinstall.

    Si certains formats demandent des informations spécifiques impossibles à inclure dans le format–pivot, je vois 3 solutions, utilisables ensemble ou séparément, au choix :

    • les enregistrer au préalable dans un fichier de configuration (comme checkinstall)
    • les passer en paramètre (comme avec les archétypes Maven)
    • les demander interactivement (les deux outils font ça)
    • [^] # Re: À suivre…

      Posté par  . Évalué à 2.

      Merci pour ton retour.

      Oui, idéalement, il faudrait pouvoir lancer upt, puis compiler et pousser. Je pense qu'aucun outil ne fait ça sans implémenter une des trois idées que tu donnes : ça s'explique par le fait que l'empaquetage, c'est toujours un bordel sans nom. Ayant beaucoup utilisé "guix import", je trouve que la solution à 80% est déjà pas mal : il me suffit de changer 2/3 trucs avec vim, et ensuite, soit le paquet compile, soit je débugge, mais je n'ai pas à me taper toutes les étapes ennuyeuses de la création du paquet.

      J'aime bien l'idée de demander interactivement des infos, je me garde ça sous le coude. Une petite option "--interactive" pourrait être sympa. À voir dans les prochaines versions :)

  • # Quel est l'intéret ?

    Posté par  . Évalué à 2.

    De nombreux utilisateurs souhaiteraient toutefois installer TapTempo grâce au gestionnaire de paquets de leur distribution.

    En pratique, il y a toujours des paquets qui ne sont pas fournis par le gestionnaire de paquet de la distribution. Ou alors ce sont de vieilles version. Gênant par excemple quand on installe un paquet python avec la distrib et qu'un pip installe une version plus récente parce qu'un autre truc en a besoin comme dépendance: on a une référence à un rpm ou un deb qui n'est pas réellement celle qui est utilisée car mise à jour.

    En pratique il sest bien plus efficace d'utiliser l'outil communément utilisé par le langage (gem, pip) pour être sur d'avoir une version à peu près à jour.

    • [^] # Re: Quel est l'intéret ?

      Posté par  . Évalué à 4.

      On peut en effet se poser la question du modèle de "distribution" que l'on souhaite utiliser. Il en existe plusieurs :

      • la distribution GNU/Linux (ou le dérivé de BSD) "classique";
      • le "tout dockerisé" (mais Docker, c'est juste des softs installés dans des Debian/Fedora conteneurisées…);
      • le "virtualisé" (comme les environnements Vagrant "prêts à l'emploi" pour le dév de certains logiciels);
      • le "par langage" avec pip/gem etc.

      Ce journal n'est effectivement intéressant que si l'on considère qu'il faut conserver le modèle classique. C'est une autre discussion, à avoir dans un autre journal :)

      • [^] # Re: Quel est l'intéret ?

        Posté par  . Évalué à 2.

        L'os de référence dans docker c'est alpine (basé sur la libc musl et le gestionnaire de paquets opkg) afin d'avoir des images la plus légère possible. Le "must" étant l application standalone avec libc statique (très simple à faire pour une application golang). Après effectivement il y a une dérive dans l utilisation de docker dû à son succès où tu te retrouves avec des debians bloated remplies de commande Apt mais c'est/c'était pas la philosophie première de la technologie. Les devs docker depuis le premier jour ont critiqué l idée d avoir un daemon ssh dans une image docker par exemple.

        • [^] # Re: Quel est l'intéret ?

          Posté par  . Évalué à 2.

          critiqué l idée d avoir un daemon ssh dans une image docker par exemple.

          Pourquoi ?

          • [^] # Re: Quel est l'intéret ?

            Posté par  (site web personnel) . Évalué à 2.

            Parce qu’un conteneur façon Docker (ici, je ne parle pas de LXC/D, nspawn, etc.), c’est une application, pas un OS.

            • [^] # Re: Quel est l'intéret ?

              Posté par  . Évalué à 2.

              Ben moi ça ne me choque pas : on trouve des frameworks applicatifs (erlang/otp par exemple) qui fournissent un "shell" permettant de faire plein de choses intéressantes à l' "intérieur" de ton application. Après, je suis d'accord qu'une distribution complète dans un container doker c'est pas forcément une bonne idée, mais un daemon sshd avec quelques outils bien choisis ne me choque pas du tout.

    • [^] # Re: Quel est l'intéret ?

      Posté par  . Évalué à 2.

      Pour pip il faut autant que possible passer par un virtualenv.

      • [^] # Re: Quel est l'intéret ?

        Posté par  . Évalué à 2.

        Pourquoi?

        • [^] # Re: Quel est l'intéret ?

          Posté par  (site web personnel) . Évalué à 4.

          Parce ce que si ton installation mets à jour un module installé et utilisé par le système,
          et qu'il n'y a pas compatibilité ascendante totale, tu as un risque non négligeable de te retrouver avec un système instable.
          Ceux qui ont connu windows 95 comprendront …

          • [^] # Re: Quel est l'intéret ?

            Posté par  . Évalué à 2. Dernière modification le 14 mars 2018 à 20:56.

            C'est pas un peu détourner la raison d'être de virtualenv ça ?

            Il me semble qu'à l'origine c'était prévu pour faire du développement non? De plus ça signifie que tu reproduis 1 environnement python par outil installé, ce qui risque de devenir difficilement gérable.

            Celà dit, pourquoi les modules utilisés par le système n'auraient-ils pas un virtualenv? Ou alors, ne devraient-ils pas avoir leur propre installation en dehors de l'installation effectuée par le gestionnaire de paquets ?

            • [^] # Re: Quel est l'intéret ?

              Posté par  . Évalué à 1. Dernière modification le 14 mars 2018 à 21:41.

              Un outil peut être conçu pour une tâche à l’origine et être utilisé pour d’autres par la suite.

              Gérés correctement, genre outil de déploiement Ansible/Puppet/Chef/QueSaisJe il y a des modules pour gérer ça : tu précises que tu veux utiliser un virtualenv qui se trouve à tel endroit, ou le créer dans la foulée, et y installer tel paquet avec ses dépendances, et c’est fini.

              Si ta distro ne te propose pas les dépendances suffisantes, comment comptes-tu t’en sortir ? En installant dans le système… en écrasant les dépendances… eurk… ça risque bien de casser un paquet de trucs insoupçonnés.

              Les paquets Python de la distro servent à faire fonctionner les paquets proposés par la distro : ils sont parfaitement intégrés dans la distro, pas besoin alors d’un venv.

              Il y a une autre solution, beaucoup plus lourde, concistant à compiler et installer Python dans des dossiers différents à chaque fois.

              Les paquets Python seront alors installés dans l’arborescence de ton python compilé.

              Mais il est rare qui tu ais besoin d’un interpréteur particulier, sauf si ta distro ne le propose pas.

              C’était très régulièrement le cas lorsque Python 3 n’était pas encore répandu ni disponible dans des dépôts tiers.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.