URL: https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-1-4 Title: L’installation et la distribution de paquets Python (1/4) Authors: jeanas alberic89 🐧, L'intendant zonard, nonas, palm123, gUI et BenoĂźt Sibaud Date: 2023-10-31T10:16:22+01:00 License: CC By-SA Tags: python, packaging, installation et distribution Score: 4 Quelques dĂ©pĂȘches prĂ©cĂ©dentes ont parlĂ© des outils de *packaging* Python, comme [ici](https://linuxfr.org/news/environnement-moderne-de-travail-python), [lĂ ](https://linuxfr.org/news/python-partie-6-pip-et-pipx) ou encore [lĂ ](https://linuxfr.org/news/python-partie-8-pipenv). Je vais chercher Ă  faire un tour complet de la question, non seulement du point de vue de l’utilisateur qui cherche Ă  comprendre quelle est « la bonne » solution (← ha ha ha *rire moqueur*
), mais aussi en expliquant les choix qui ont Ă©tĂ© faits, les Ă©volutions, la structure de la communautĂ© autour des outils, et les critiques qui leur sont souvent adressĂ©es, Ă  tort ou Ă  raison. ---- ---- Il est question ici de *packaging*, terme pris dans un sens trĂšs large : - L’installation de paquets, - L’installation de Python lui-mĂȘme, - L’administration d’environnements isolĂ©s, - La gestion de dĂ©pendances (*lock files* notamment), - La distribution de son code, en tant qu’auteur d’un paquet Python, - La compilation de code natif (Ă©crit en C, C++, Rust
) pour ĂȘtre utilisĂ© depuis Python. Le langage Python, avec son Ă©crasante popularitĂ© (premier au classement TIOBE), est vantĂ© pour sa simplicitĂ©. HĂ©las, c’est devenu un lieu commun que cette simplicitĂ© ne s’étend pas aux outils de *packaging*, qui sont tout sauf faciles Ă  maĂźtriser. Personnellement, j’y ai Ă©tĂ© confrontĂ© en voulant soulager la situation de [Frescobaldi](https://frescobaldi.org), une application Ă©crite avec Python et PyQt. Frescobaldi possĂšde une dĂ©pendance qui [se](https://github.com/frescobaldi/python-poppler-qt5/issues/51) [trouve](https://github.com/frescobaldi/python-poppler-qt5/issues/2) [concentrer](https://github.com/frescobaldi/python-poppler-qt5/issues/58) [quelques](https://github.com/frescobaldi/python-poppler-qt5/issues/57) [difficultĂ©s](https://github.com/frescobaldi/python-poppler-qt5/issues/56) [de](https://github.com/frescobaldi/python-poppler-qt5/issues/53) [distribution](https://github.com/frescobaldi/python-poppler-qt5/issues/52) [et](https://github.com/frescobaldi/python-poppler-qt5/issues/48) [d’installation](https://github.com/frescobaldi/python-poppler-qt5/issues/46) [coriaces](https://github.com/frescobaldi/python-poppler-qt5/issues/29), [en](https://github.com/frescobaldi/python-poppler-qt5/issues/27) [raison](https://github.com/frescobaldi/python-poppler-qt5/issues/20) [de](https://github.com/frescobaldi/python-poppler-qt5/issues/15) [code](https://github.com/frescobaldi/python-poppler-qt5/issues/14) [C++](https://github.com/frescobaldi/python-poppler-qt5/issues/13). Ce problĂšme d’ensemble a conduit Ă  des Ă©volutions rapides au cours des derniĂšres annĂ©es, qui tentent de garder un Ă©quilibre fragile entre l’introduction de nouvelles mĂ©thodes et la compatibilitĂ© avec l’existant. Parmi les utilisateurs, une confusion certaine a Ă©mergĂ© avec cette cadence des changements, qui fait que des tutoriels Ă©crits il y a quelques annĂ©es voire quelques mois Ă  peine sont complĂštement obsolĂštes, de mĂȘme que des outils encore rĂ©cemment recommandĂ©s. Bien que de nombreux progrĂšs soient indĂ©niables, il existe un scepticisme trĂšs rĂ©pandu concernant le degrĂ© de fragmentation dĂ©concertant de l’écosystĂšme, qui met l’utilisateur face Ă  un labyrinthe d’outils qui se ressemblent. Pour illustrer ce labyrinthe, voici une liste des outils que, personnellement, j’utilise ou j’ai utilisĂ©, ou dont j’ai au moins lu sĂ©rieusement une partie de la documentation : pip, pipx, virtualenv, venv, ensurepip, conda, condax, conda-lock, tox, build, twine, setuptools, setuptools-scm, flit, hatch, poetry, pdm, rye, pip-tools, maturin, setuptools-rust, meson-python, scikit-build, sip, pyinstaller, py2app, py2exe, cx_freeze, pyoxidizer, pynsist, briefcase, wheel, repairwheel, auditwheel, delocate, delvewheel, cibuildwheel Encore une fois, je n’ai mis ceux que je connais. On en trouve encore d’autres [ici](https://packaging.python.org/en/latest/key_projects). Sans compter quelques outils dont j’ai connaissance mais qui sont aujourd’hui obsolĂštes ou non-maintenus : distutils, distutils2, distribute, pyflow, bento, pipenv Et quelques librairies de plus bas niveau : importlib.metadata, packaging, distlib, installer Face Ă  cette profusion, le classique aujourd’hui est de citer [le XKCD qui va bien](https://xkcd.com/927), et de renchĂ©rir en comparant au langage Rust, connu pour la simplicitĂ© de ses outils, Ă  savoir : cargo, rustup *Et c’est tout.* Alors, pourquoi a-t-on besoin de plusieurs douzaines d’outils diffĂ©rents pour Python ? Cela s’explique largement par des facteurs techniques, que j’expliquerai, qui font Ă  la fois que le *packaging* Python est intrinsĂšquement plus compliquĂ©, et qu’on lui demande beaucoup plus. En vĂ©ritĂ©, dans ces outils, on trouve des projets qui ont des cas d’utilisation complĂštement diffĂ©rents et ne s’adressent pas au mĂȘme public (comme `pip` et `repairwheel`). Le hic, c’est qu’il y a, aussi, beaucoup de projets dont les fonctionnalitĂ©s se recouvrent partiellement, voire totalement, comme entre `pip` et `conda`, entre `venv` et `virtualenv`, entre `hatch` et `poetry`, entre `pyinstaller` et `briefcase`, etc. Ces projets sont en concurrence et ont chacun leurs adeptes religieux et leurs dĂ©tracteurs, Ă  la maniĂšre des vieilles querelles Vim/Emacs et compagnie. Cette dĂ©pĂȘche est la premiĂšre d’une sĂ©rie de quatre, qui ont pour but de dĂ©cortiquer comment tout cela fonctionne, de retracer comment on en est arrivĂ©s lĂ , et de parler des perspectives actuelles : 1. **L’histoire du *packaging* Python** 2. Tour de l’écosystĂšme actuel 3. Le casse-tĂȘte du code compilĂ© 4. La structure de la communautĂ© en question # Les outils historiques : distutils et setuptools Guido van Rossum a créé le langage Python en 1989. Rappelons qu’à l’époque, le World Wide Web n’existait pas encore, ni d’ailleurs le noyau Linux, les deux ayant Ă©tĂ© créés en 1991. D’aprĂšs WikipĂ©dia ([source](https://en.wikipedia.org/wiki/Package_manager)), le premier logiciel comparable aux gestionnaires de paquets actuels a Ă©tĂ© CPAN ­— destinĂ© au langage Perl — qui ne date que de 1995. Python a donc grandi en mĂȘme temps que l’idĂ©e des paquets, des gestionnaires de paquets et des dĂ©pĂŽts sur Internet prenait racine. C’est en 1998 ([source](https://gerg.ca/blog/post/2013/distutils-history)) que le module **distutils** est nĂ© pour rĂ©pondre au mĂȘme besoin que CPAN pour Perl, dans le monde Python encore balbutiant. Ce module servait Ă  compiler et installer des paquets, y compris des paquets Ă©crits en C. Historiquement, il permettait aussi de gĂ©nĂ©rer des paquets au format RPM de Red Hat, et d’autres formats pour des machines de l’époque comme Solaris et HP-UX ([source](https://docs.python.org/3/distutils/introduction.html)). `distutils` faisait partie de la bibliothĂšque standard du langage. Il Ă©tait configurĂ© avec un fichier Ă©crit en Python, nommĂ© conventionnellement `setup.py`, qui prenait gĂ©nĂ©ralement cette forme : ```python from distutils.core import setup setup(name='nom-paquet', version='x.y.z', ...) ``` On pouvait alors exĂ©cuter le `setup.py` comme un script, en lui passant le nom d’une commande : ```shell $ python setup.py install # installe le paquet $ python setup.py bdist_rpm # gĂ©nĂšre un RPM ``` Le tĂ©lĂ©chargement de paquets depuis un dĂ©pĂŽt partagĂ© ou la rĂ©solution des dĂ©pendances n’ont jamais fait partie des fonctions de `distutils`. De façon peut-ĂȘtre plus surprenante, `distutils` n’offre pas de moyen simple ou fiable pour dĂ©sinstaller un paquet. Le projet **setuptools** est arrivĂ© en 2004 ([source](https://mail.python.org/pipermail/distutils-sig/2004-March/003758.html)) pour pallier aux limitations de `distutils`. Contrairement Ă  `distutils`, `setuptools` a toujours Ă©tĂ© dĂ©veloppĂ© en dehors de la bibliothĂšque standard. Cependant, il Ă©tait fortement couplĂ© Ă  `distutils`, le modifiant par des sous-classes
 et par une bonne dose *monkey-patching* ([source](https://github.com/pypa/setuptools/blob/main/setuptools/monkey.py)). Lesdites limitations ont Ă©galement conduit Ă  un fork de `distutils`, nommĂ© **distutils2**, dĂ©marrĂ© en 2010. Parmi les raisons citĂ©es par son initiateur figure le dĂ©sir de faire Ă©voluer `distutils` Ă  un rythme plus soutenu que ce qui Ă©tait raisonnable pour un module de la bibliothĂšque standard, surtout un module largement utilisĂ© au travers de `setuptools` et son monkey-patching ([source](https://tarekziade.wordpress.com/2010/03/03/the-fate-of-distutils-pycon-summit-packaging-sprint-detailed-report)). Mais `distutils2` a cessĂ© d’ĂȘtre dĂ©veloppĂ© en 2012 ([source](https://pypi.org/project/Distutils2/#history)). De mĂȘme, entre 2011 et 2013 ([source](https://pypi.org/project/distribute/#history)), il a existĂ© un fork de `setuptools` appelĂ© **distribute**. Ses changements ont fini par ĂȘtre fusionnĂ©s dans `setuptools`. Pour finir, `distutils` a Ă©tĂ© supprimĂ© de la bibliothĂšque standard dans la version 3.12 de Python, sortie en octobre 2023, au profit de `setuptools`. Mais cela ne signifie pas que `distutils` n’est plus utilisĂ© du tout : `setuptools` continue d’en contenir une copie qui peut ĂȘtre utilisĂ©e (bien que ce ne soit pas recommandĂ©). Il est aussi Ă  noter que NumPy, la bibliothĂšque Python quasi universelle de calcul scientifique, maintient elle aussi son propre fork de `distutils`, `numpy.distutils`, qui est en train d’ĂȘtre remplacĂ© ([source](https://numpy.org/doc/stable/reference/distutils.html)). Cela montre Ă  quel point le code de `distutils` s’est rĂ©vĂ©lĂ© Ă  la fois essentiel, omniprĂ©sent Ă  travers divers avatars ou forks, et difficile Ă  faire Ă©voluer. # La montĂ©e en puissance de `pip` et PyPI Avec le dĂ©veloppement d’Internet et la croissance de la communautĂ© Python, il devenait nĂ©cessaire d’avoir un registre centralisĂ© oĂč pouvaient ĂȘtre dĂ©posĂ©s et tĂ©lĂ©chargĂ©s les paquets. C’est dans ce but qu’a Ă©tĂ© créé **PyPI**, le **Py**thon **P**ackage **I**ndex, en 2002 ([source](https://peps.python.org/pep-0301)). (Ne pas confondre PyPI avec PyPy, une implĂ©mentation alternative de Python. PyPy se prononce « paille paille » alors que PyPI se prononce « paille pie aĂŻe »). `distutils` pouvait installer un paquet une fois le code source tĂ©lĂ©chargĂ©, mais pas installer le paquet depuis PyPI directement, en rĂ©solvant les dĂ©pendances. Le premier outil Ă  en ĂȘtre capable a Ă©tĂ© `setuptools` avec la commande **easy_install** ([source](https://packaging.python.org/en/latest/discussions/pip-vs-easy-install/#pip-vs-easy-install)), apparue en 2004. Puis est arrivĂ© **pip** en 2008. Entre autres, `pip` Ă©tait capable, contrairement Ă  tout ce qui existait jusque lĂ , de dĂ©sinstaller un paquet ! En quelques annĂ©es, `easy_install` est devenu obsolĂšte et `pip` s’est imposĂ© comme l’outil d’installation standard. En 2013, la [PEP 453](https://peps.python.org/pep-0453) a ajoutĂ© Ă  la bibliothĂšque standard un module `ensurepip`, qui *bootstrape* `pip` en une simple commande : `python -m ensurepip`. De cette maniĂšre, `pip` a pu continuer d’ĂȘtre dĂ©veloppĂ© indĂ©pendamment de Python tout en bĂ©nĂ©ficiant d’une installation facile (puisqu’on ne peut Ă©videmment pas installer `pip` avec `pip`) et d’une reconnaissance officielle. # L’introduction du format « wheel » Sans rentrer trop dans les dĂ©tails Ă  ce stade, le format principal pour distribuer un paquet jusqu’en 2012 Ă©tait le code source dans une archive `.tar.gz`. Pour installer un paquet, `pip` devait dĂ©compresser l’archive et exĂ©cuter le script `setup.py`, utilisant `setuptools`. Bien sĂ»r, cette exĂ©cution de code arbitraire posait un problĂšme de fiabilitĂ©, car il Ă©tait critique de contrĂŽler l’environnement, par exemple la version de `setuptools`, au moment d’exĂ©cuter le `setup.py`. `setuptools` avait bien un format de distribution prĂ©compilĂ©, le format *egg* (le nom est une rĂ©fĂ©rence aux Monty Python). Un paquet en `.egg` pouvait ĂȘtre installĂ© sans exĂ©cuter de `setup.py`. Malheureusement, ce format Ă©tait mal standardisĂ© et spĂ©cifique Ă  `setuptools`. Il Ă©tait conçu comme un format importable, c’est Ă  dire que l’interprĂ©teur pouvait directement lire l’archive (sans qu’elle soit dĂ©compressĂ©e) et exĂ©cuter le code dedans. C’était essentiellement une façon de regrouper tout le code en un seul fichier, mais il n’y avait pas vraiment d’intention de distribuer les `.egg`. La [PEP 427](https://peps.python.org/pep-0427) a dĂ©fini le format *wheel* pour que les paquets puissent ĂȘtre distribuĂ©s sous forme prĂ©compilĂ©e sur PyPI, Ă©vitant l’exĂ©cution du `setup.py` durant l’installation. Parmi les innovations, les fichiers *wheels* sont nommĂ©s d’aprĂšs une convention qui indique avec quelle plateforme ils sont compatibles. Ceci a grandement facilitĂ© la distribution de modules Python codĂ©s en C ou C++. Jusqu’ici, ils Ă©taient toujours compilĂ©s sur la machine de l’utilisateur, durant l’exĂ©cution du `setup.py`. Avec les wheels, il est devenu possible de distribuer le code dĂ©jĂ  compilĂ©, avec un wheel par systĂšme d’exploitation et par version de Python (voire moins, mais c’est pour la troisiĂšme dĂ©pĂȘche). # Les dĂ©buts des environnements virtuels Voici un problĂšme trĂšs banal : vous voulez utiliser sur la mĂȘme machine les projets `foo` et `bar`, or `foo` nĂ©cessite `numpy` version 3.14 alors que `bar` nĂ©cessite `numpy` version 2.71. Malheureusement, en Python, il n’est pas possible d’installer deux versions du mĂȘme paquet Ă  la fois dans un mĂȘme environnement, alors que d’autres langages le permettent. (Plus prĂ©cisĂ©ment, il est possible d’installer deux versions en parallĂšle si elles ont des noms de module diffĂ©rents, le nom de module Python n’étant pas forcĂ©ment le mĂȘme que le nom de paquet, cf. Pillow qui s’installe avec `pip install pillow` mais s’importe avec `import PIL`. Mais tant que le module Python n’est pas renommĂ©, les deux sont en conflit.) La solution est alors de se servir d’un **environnement virtuel**, qui est un espace isolĂ© oĂč on peut installer des paquets indĂ©pendamment du reste du systĂšme. Cette technique a Ă©tĂ© dĂ©veloppĂ©e dans le projet `virtualenv`, créé par Ian Bicking, qui est aussi l’auteur originel de `pip`. La premiĂšre version date de 2007 ([source](https://pypi.org/project/virtualenv/0.8)). Plus tard, en 2012 ([source](https://peps.python.org/pep-0405)), un module `venv` a Ă©tĂ© ajoutĂ© Ă  la bibliothĂšque standard. C’est une version rĂ©duite de `virtualenv`. On peut toutefois regretter que `virtualenv` soit encore nĂ©cessaire dans des cas avancĂ©s, ce qui fait que les deux sont utilisĂ©s aujourd’hui, mĂȘme si `venv` est largement prĂ©dominant. # La crĂ©ation de la *Python Packaging Authority* (PyPA) La premiĂšre rĂ©action naturelle en entendant le nom « Python Packaging Authority » est bien sĂ»r de penser qu’à partir de 2012, date de sa crĂ©ation, ce groupe a Ă©tĂ© l’acteur d’une unification, d’une standardisation, d’une mise en commun des efforts, etc. par contraste avec la multiplication des *forks* de `distutils`. Sauf que
 **le mot « Authority » Ă©tait au dĂ©part une blague** (sĂ©rieusement !). La preuve sur [l’échange de mails](https://gist.github.com/jezdez/6222d1ba8b10d734d003492e58041687) oĂč le nom a Ă©tĂ© choisi, les alternatives proposĂ©es allant de « ianb-ng » Ă  « Ministry of Installation » (rĂ©fĂ©rence aux Monty Python), en passant par « Politburo ». La Python Packaging Authority a dĂ©marrĂ© comme un groupe informel de successeurs Ă  Ian Bicking (d’oĂč le « ianb ») pour maintenir les outils qu’il avait créés, `pip` et `virtualenv`. Elle est un ensemble de projets, reconnus comme importants et maintenus. Au fil du temps, la partie « Authority » son nom a Ă©voluĂ© progressivement de la blague vers une autoritĂ© semi-sĂ©rieuse. La PyPA d’aujourd’hui dĂ©veloppe des **standards d’interopĂ©rabilitĂ©**, proposĂ©s avec le mĂȘme processus que les changements au langage Python lui-mĂȘme, sous forme de PEP (Python Enhancement Proposals), des propositions qui ressemblent aux RFC, JEP et autres SRFI. La PyPA est Ă©galement une [organisation GitHub](https://github.com/pypa), sous l’égide de laquelle vivent les dĂ©pĂŽts de divers projets liĂ©s au *packaging*, dont la plupart des outils que je mentionne dans cette dĂ©pĂȘche, aux exceptions notables de `conda`, `poetry` et `rye`. La PyPA *n’est pas* : - Un ensemble *cohĂ©rent* d’outils. Il y a *beaucoup* d’outils redondants (mais chacun ayant ses adeptes) dans la PyPA. Pour ne donner qu’un exemple, tout ce que peut faire `flit`, `hatch` peut le faire aussi. - Une vĂ©ritable autoritĂ©. Elle reste composĂ©e de projets indĂ©pendants, avec chacun ses mainteneurs. Le fait qu’un standard soit acceptĂ© ne garantit pas formellement qu’il sera implĂ©mentĂ©. J’ai lu quelque part une allusion Ă  un exemple rĂ©cent de « transgression » dans `setuptools`, mais je n’ai pas retrouvĂ© de quel standard il s’agissait. ConcrĂštement, cela ne veut pas dire que les transgressions sont frĂ©quentes, mais plutĂŽt qu’une PEP risque de ne pas ĂȘtre acceptĂ©e s’il n’est pas clair que les mainteneurs des outils concernĂ©s sont d’accord. La dĂ©cision finale sur une PEP est prise par un « dĂ©lĂ©gué », et normalement, cette personne ne fait que formaliser le consensus atteint (mĂȘme s’il peut y avoir des [exceptions](https://discuss.python.org/t/pronouncement-on-peps-660-and-662-editable-installs/9450)). Il n’y a donc pas de place dans la PyPA actuelle pour des dĂ©cisions du type de « `flit` devient officiellement dĂ©prĂ©ciĂ© au profit de `hatch` ». # Le dĂ©veloppement d’un Ă©cosystĂšme alternatif, `conda` et Anaconda Pour complĂ©ter la liste de tout ce qui s’est passĂ© en 2012, c’est aussi l’annĂ©e de la premiĂšre version de `conda`. Cet outil a Ă©tĂ© créé pour pallier aux graves lacunes des outils classiques concernant la distribution de paquets Ă©crits en C ou C++ (Rust Ă©tait un langage confidentiel Ă  l’époque). Jusque lĂ , on ne pouvait redistribuer que le code source, et il fallait que chaque paquet soit compilĂ© sur la machine oĂč il Ă©tait installĂ©. Le format *wheel* introduit plus haut a commencĂ© Ă  rĂ©soudre ce problĂšme, mais toutes ces nouveautĂ©s sont concomitantes. Contrairement Ă  tous les autres outils mentionnĂ©s dans cette dĂ©pĂȘche, Conda, bien qu’*open source*, est dĂ©veloppĂ© par une entreprise (d’abord appelĂ©e Continuum Analytics, devenue Anaconda Inc.). C’est un univers parallĂšle Ă  l’univers PyPA : un installeur de paquets diffĂ©rent (`conda` plutĂŽt que `pip`), un format de paquet diffĂ©rent, un gestionnaire d’environnements virtuel diffĂ©rent (`conda` plutĂŽt que `virtualenv` ou `venv`), une communautĂ© largement sĂ©parĂ©e. Il est aussi beaucoup plus unifiĂ©. `conda` adopte un modĂšle qui se rapproche davantage de celui d’une distribution Linux que de PyPI, en mettant le *packaging* dans les mains de mainteneurs sĂ©parĂ©s des auteurs des paquets. Il est possible de publier ses propres paquets indĂ©pendamment d’une organisation, mais ce n’est pas le cas le plus frĂ©quent. On pourrait comparer cela Ă  ArchLinux, avec son dĂ©pĂŽt principal coordonnĂ© auquel s’ajoute un dĂ©pĂŽt non coordonnĂ©, l’AUR. Cette organisation permet Ă  `conda` de faciliter Ă©normĂ©ment la distribution de modules C ou C++. En pratique, mĂȘme avec les *wheels*, cela reste une source infernale de casse-tĂȘtes avec les outils PyPA (ce sera l’objet de la troisiĂšme dĂ©pĂȘche), alors que tout devient beaucoup plus simple avec `conda`. VoilĂ  son gros point fort et sa raison d’ĂȘtre. Il faut bien distinguer `conda`, l’outil, d’Anaconda, qui est une *distribution* de Python contenant, bien sĂ»r, `conda`, mais aussi une flopĂ©e de paquets scientifiques ultra-populaires comme NumPy, SciPy, matplotlib, etc. Anaconda est trĂšs largement utilisĂ©e dans le monde scientifique (analyse numĂ©rique, *data science*, intelligence artificielle, etc.). Il existe aussi Miniconda3, qui est une distribution plus minimale avec seulement `conda` et quelques autres paquets essentiels. Enfin, `conda-forge` est un projet communautaire (contrairement Ă  Anaconda, dĂ©veloppĂ© au sein de l’entreprise Anaconda Inc.) qui distribue des milliers de paquets. On peut, dans une installation d’Anaconda, installer un paquet depuis `conda-forge` avec `conda - c conda-forge` (le plus courant), ou bien (moins courant) installer une distribution nommĂ©e « Miniforge », qui est un Ă©quivalent de Miniconda3 fondĂ© entiĂšrement sur `conda-forge`. # Les PEP 517 et 518, une petite rĂ©volution S’il y a deux PEP qui ont vraiment changĂ© le *packaging*, ce sont les PEP 517 et 518. La [PEP 518](https://peps.python.org/pep-0518) constate le problĂšme que le fichier de configuration de `setuptools`, le `setup.py`, est Ă©crit en Python. Comme il faut `setuptools` pour exĂ©cuter ce fichier, il n’est pas possible pour lui, par exemple, de spĂ©cifier la version de `setuptools` dont il a besoin. Il n’est pas possible non plus d’avoir des dĂ©pendances dans le `setup.py` autres que `setuptools` (sauf avec un hack nommĂ© `setup_requires` proposĂ© par `setuptools`, qui joue alors un rĂŽle d’installeur de paquets comme `pip` en plus de son rĂŽle normal
 ce pour quoi `setuptools` n’excellait pas). **Elle dĂ©crit aussi explicitement comme un problĂšme le fait qu’il n’y ait pas de moyen pour une alternative Ă  setuptools de s’imposer.** En effet, le projet `setuptools` souffrait, et souffre toujours gravement, d’une surcomplication liĂ©e Ă  toutes ses fonctionnalitĂ©s dĂ©prĂ©ciĂ©es. Pour remĂ©dier Ă  cela, la PEP 518 a introduit un nouveau fichier de configuration nommĂ© **pyproject.toml**. Ce fichier est Ă©crit en [TOML](https://toml.io), un format de fichier de configuration (comparable au YAML). C’est l’embryon d’un mouvement pour adopter une configuration qui soit **dĂ©clarative** plutĂŽt qu’exĂ©cutable. Le TOML avait dĂ©jĂ  Ă©tĂ© adoptĂ© par l’outil Cargo de Rust. Le `pyproject.toml` contient une table `build-system` qui dĂ©clare quels paquets doivent ĂȘtre installĂ©s pour exĂ©cuter le `setup.py`. Elle se prĂ©sente ainsi : ```toml [build-system] requires = ["setuptools>=61"] ``` Cet exemple pourrait ĂȘtre utilisĂ© dans un projet dont le `setup.py` a besoin de `setuptools` version 61 au moins. Mais ce n’est pas tout. La PEP 518 dĂ©finit aussi une table `tool` avec des sous-tables arbitraires `tool.nom-d-outil`, qui permet Ă  des outils arbitraires de lire des options de configuration, qu’ils soient des outils de *packaging*, des reformateurs de code, des linters, des gĂ©nĂ©rateurs de documentation,
 **pyproject.toml est donc devenu un fichier de configuration universel pour les projets Python.** (Anecdote : au dĂ©part, il n’était pas prĂ©vu pour cela, mais il a commencĂ© Ă  ĂȘtre utilisĂ© comme tel, si bien que la PEP 518 a Ă©tĂ© modifiĂ©e a posteriori pour structurer cela dans une table `tool`.) La deuxiĂšme Ă©tape a Ă©tĂ© la [PEP 517](https://peps.python.org/pep-0517). Elle va plus loin, en standardisant une interface entre deux outils appelĂ©s **build frontend** et **build backend**. Le *build frontend* est l’outil appelĂ© par l’utilisateur, par exemple `pip`, qui a besoin de compiler le paquet pour en faire un wheel installable (ou une *sdist*, soit une archive du code source avec quelques mĂ©tadonnĂ©es, mais c’est un dĂ©tail). Le *build backend* est un outil comme `setuptools` qui va crĂ©er le wheel. Le rĂŽle du *build frontend* est assez simple. Pour schĂ©matiser, il lit la valeur `build-system.requires` du `pyproject.toml`, installe cette liste de dĂ©pendances, puis lit la valeur `build-system.build-backend`, importe le *build backend* en Python, et appelle la fonction `backend.build_wheel()`. Voici un exemple de `pyproject.toml` utilisant les PEP 517 et 518 avec `setuptools` comme *build backend* : ```toml [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" ``` Ainsi, il est devenu possible d’écrire et utiliser des *build backends* complĂštement diffĂ©rents de `setuptools`, sans mĂȘme de fichier `setup.py`. L’un des premiers *build backends* alternatifs a Ă©tĂ© **flit**, qui cherche Ă  ĂȘtre l’opposĂ© de `setuptools` : le plus simple possible, avec le moins de configuration possible. La PEP 517 dĂ©crit bien son objectif : > While distutils / setuptools have taken us a long way, they suffer from three serious problems : (a) they’re missing important features like usable build-time dependency declaration, autoconfiguration, and even basic ergonomic niceties like DRY-compliant version number management, and (b) extending them is difficult, so while there do exist various solutions to the above problems, they’re often quirky, fragile, and expensive to maintain, and yet (c) it’s very difficult to use anything else, because distutils/setuptools provide the standard interface for installing packages expected by both users and installation tools like pip. > > Previous efforts (e.g. distutils2 or setuptools itself) have attempted to solve problems (a) and/or (b). This proposal aims to solve (c). > > The goal of this PEP is get distutils-sig out of the business of being a gatekeeper for Python build systems. If you want to use distutils, great ; if you want to use something else, then that should be easy to do using standardized methods. # La PEP 621, un peu de standardisation Suite aux PEP 518 et 517, plusieurs *build backends* alternatifs ont Ă©mergĂ©. Bien sĂ»r, tous les *build backends* avaient des maniĂšres diffĂ©rentes de spĂ©cifier les mĂ©tadonnĂ©es du projet comme le nom, la version, la description, etc. La [PEP 621](https://peps.python.org/pep-0621) a standardisĂ© cette partie de la configuration, en dĂ©finissant une nouvelle section du `pyproject.toml`, la table `project`. ConcrĂštement, elle peut ressembler à : ```toml [project] name = "my-project" version = "0.1" description = "My project does awesome things." requires-python = ">=3.8" authors = [{name = "Me", email = "me@example.com"}] ... ``` En effet, Ă©crire les mĂ©tadonnĂ©es n’est pas la partie la plus intĂ©ressante d’un *build backend*. Les vraies diffĂ©rences se trouvent, par exemple, dans la prise en charge des extensions C ou C++, ou bien dans des options de configuration plus avancĂ©es. La PEP 621 permet de changer plus facilement de *build backend* en gardant l’essentiel de la configuration de base. De plus, elle encourage la configuration statique, par opposition au `setup.py` de `setuptools`. L’avantage d’une configuration statique est sa fiabilité : aucune question ne se pose sur l’environnement d’exĂ©cution du `setup.py`, sa portabilitĂ©, etc. Pour donner un exemple concret, on apprend dans [cette section](https://peps.python.org/pep-0597/#using-the-default-encoding-is-a-common-mistake) de la PEP 597 que les dĂ©veloppeurs Ă©crivaient souvent dans le `setup.py` un code qui lit le README avec l’encodage systĂšme au lieu de l’UTF-8, ce qui peut rendre le paquet impossible Ă  installer sous Windows. C’est le genre de problĂšmes systĂ©miques qui sont Ă©liminĂ©s par la configuration statique. MalgrĂ© tout, la configuration dynamique reste utile. C’est typiquement le cas pour la valeur de `version`, qui est avantageusement calculĂ©e en consultant le systĂšme de contrĂŽle de version (par exemple avec `git describe` pour Git). Dans ces situations, on peut marquer la valeur comme Ă©tant calculĂ©e dynamiquement avec ```toml [project] dynamic = ["version"] ``` C’est alors au *build backend* de dĂ©terminer la valeur par tout moyen appropriĂ© (Ă©ventuellement configurĂ© dans la table `tool`). # L’émergence d’outils tout-en-un alternatifs Cet historique est trĂšs loin d’ĂȘtre exhaustif, et pourtant on sent dĂ©jĂ  la prolifĂ©ration d’outils diffĂ©rents. Face Ă  la confusion qui en rĂ©sulte, des dĂ©veloppeurs ont tentĂ© d’écrire des outils « tout-en-un » qui rassemblent Ă  peu prĂšs toutes les fonctionnalitĂ©s en une seule interface cohĂ©rente : installation, *build frontend*, *build backend*, gestion des environnements virtuels, installation d’une nouvelle version de Python, mise Ă  jour d’un *lock file*, etc. Parmi eux, on peut notamment citer [poetry](https://python-poetry.org), dĂ©veloppĂ© depuis 2018 ([source](https://python-poetry.org/history)), qui se distingue en ne participant pas Ă  la PyPA et en rĂ©implĂ©mentant bien plus de choses que d’autres (notamment en ayant son propre rĂ©solveur de dĂ©pendances distinct de celui de `pip`). On peut penser aussi Ă  [hatch](https://hatch.pypa.io), qui, lui, fait partie de la PyPA et ne fait pas autant de choses, mais s’intĂšgre mieux Ă  l’existant. Et pour mentionner le dernier-nĂ©, il y a Ă©galement [rye](https://rye-up.com), qui cherche Ă  modifier la façon dont Python est *boostrapĂ©*, en utilisant exclusivement des Pythons gĂ©rĂ©s par lui-mĂȘme, qui ne viennent pas du systĂšme, et en Ă©tant Ă©crit lui-mĂȘme en Rust plutĂŽt qu’en Python. # Conclusion J’espĂšre que cet historique permet de mieux comprendre pourquoi le *packaging* est tel qu’il est aujourd’hui. L’un des facteurs majeurs est l’omniprĂ©sence des extensions C, C++ ou maintenant Rust qui doivent ĂȘtre prĂ©compilĂ©es. C’est la raison essentielle pour laquelle `conda` et tout son Ă©cosystĂšme existent et sont sĂ©parĂ©s du monde de la PyPA. Un autre facteur est Ă  chercher dans les problĂšmes de conception de `distutils`, un code qui date de l’époque des premiers gestionnaires de paquets et qui n’était pas prĂȘt Ă  accompagner Python pour vingt ans. C’est pour cela qu’il a Ă©tĂ© forkĂ© si souvent, et c’est pour en finir avec l’hĂ©gĂ©monie de son fork `setuptools` que les PEP 518 et 517 ont volontairement ouvert le jeu aux outils alternatifs. Il faut enfin voir que la PyPA n’a jamais Ă©tĂ© un groupe unifiĂ© autour d’un outil, et qu’il est difficile de changer de modĂšle social. Dans la deuxiĂšme dĂ©pĂȘche, je ferai un tour complet de l’état actuel, en prĂ©sentant tous les outils et les liens entre eux.