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
         Benoît Sibaud, alberic89 🐧, L'intendant zonard, nonas, palm123 et gUI
Date:    2023-10-31T10:16:22+01:00
License: CC By-SA
Tags:    python, installation, distribution et packaging
Score:   92


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.0/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 les 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 les 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.
