[ADD] install: uv to place Python packages, pip as fallback

uv replaces pip where it can: the tools venv and the Poetry bootstrap. The
choice lives in EL_PIP_PROVIDER -- auto, uv or pip -- and in a single
file, lib_pip_provider.sh, on the exact model of the mise/pyenv switch. A
uv failure falls back to pip: it is stricter on metadata and rejects
packages pip accepts.

Three guards. The target always goes through "--python" rather than being
inferred from the active venv: poetry.toml declares a "./.venv" uv would
also look for. Python 3.7, still used by Odoo 12 and 13, stays on pip. And
"uv pip sync" is never used: it would delete pip itself and everything
Poetry laid down.

One bug falls along the way: the idempotence guard aimed at
.venv.erplibre/bin/poetry while Poetry installs into the Odoo venv, so a
successful replay reported failure all the same.

--- FR ---

uv remplace pip là où il le peut : le venv d'outils et l'amorçage de
Poetry. Le choix vit dans EL_PIP_PROVIDER — auto, uv ou pip — et dans un
seul fichier, lib_pip_provider.sh, sur le modèle exact de l'aiguillage
mise/pyenv. Un échec d'uv retombe sur pip : il est plus strict sur les
métadonnées et refuse des paquets que pip accepte.

Trois gardes. La cible passe toujours par « --python » plutôt que d'être
déduite du venv actif : poetry.toml déclare un « ./.venv » qu'uv
chercherait aussi. Python 3.7, encore utilisé par Odoo 12 et 13, reste sur
pip. Et « uv pip sync » n'est jamais employé : il supprimerait pip
lui-même et tout ce que Poetry a posé.

Un défaut tombe au passage : le garde d'idempotence visait
.venv.erplibre/bin/poetry alors que Poetry s'installe dans le venv Odoo,
si bien qu'un rejeu réussi rapportait quand même un échec.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-11 23:16:26 -04:00
parent e4aea8b8a6
commit e20d8d9baa
5 changed files with 166 additions and 23 deletions

View file

@ -24,3 +24,16 @@ compile.
Un seul fichier décide : `script/install/lib_python_provider.sh`. mise n'est
jamais installé automatiquement — `make install_mise` porte cette décision.
Pas de binaire mise pour s390x à ce jour : cette architecture reste sur pyenv.
## Paquets Python
`EL_PIP_PROVIDER` (dans `env_var.sh`) vaut `auto`, `uv` ou `pip`. `auto` prend
uv s'il est présent et si le venv visé est en Python ≥ 3.8, sinon pip ; un
échec d'uv retombe sur pip. Un seul fichier décide :
`script/install/lib_pip_provider.sh`. uv n'est jamais installé
automatiquement — `make install_uv` porte cette décision.
Portée réelle : le venv d'outils et l'amorçage de Poetry. **`poetry install`
n'est pas concerné** — uv ne lit pas `poetry.lock` — et c'est pourtant l'étape
qui domine. Sur s390x le gain est quasi nul : une trentaine de paquets sans
roue se compilent, et uv n'enlève pas une seconde de gcc.

View file

@ -105,6 +105,25 @@ pyenv_update:
#
# Pas de binaire s390x publie a ce jour : sur cette architecture, la cible
# le dit et n'installe rien.
# uv installe les paquets Python nettement plus vite que pip, et met en cache
# les roues qu'il CONSTRUIT — ce qui compte la ou rien n'a de roue publiee.
# Contrairement a mise, uv publie une roue s390x : un « pip install uv » dans
# le venv d'outils suffit, sans « curl | sh ». Reste EXPLICITE quand meme,
# comme install_mise : EL_PIP_PROVIDER=auto s'en sert des qu'il est la.
#
# Attention a l'attente : « poetry install », qui domine le temps
# d'installation, n'est PAS accelere — uv ne lit pas poetry.lock.
.PHONY: install_uv
install_uv:
@if command -v uv >/dev/null 2>&1; then \
echo "uv deja present : $$(uv --version)"; \
else \
echo "Installation de uv dans le venv d outils"; \
./$$(cat conf/python-erplibre-venv | xargs)/bin/pip install uv; \
echo "uv : ./$$(cat conf/python-erplibre-venv | xargs)/bin/uv"; \
echo "Ajoutez ce repertoire au PATH, ou installez uv globalement."; \
fi
.PHONY: install_mise
install_mise:
@if command -v mise >/dev/null 2>&1; then \

View file

@ -19,6 +19,12 @@ EL_ERPLIBRE_VERSION=$(cat ".erplibre-version" | xargs)
# precompile, quelques secondes), sinon pyenv (compilation). Voir
# script/install/lib_python_provider.sh.
EL_PYTHON_PROVIDER="${EL_PYTHON_PROVIDER:-auto}"
# Installateur de paquets Python : auto | uv | pip.
# « auto » prend uv s'il est present et si le venv vise est en Python >= 3.8,
# sinon pip ; un echec d'uv retombe sur pip. Voir
# script/install/lib_pip_provider.sh. Note : « poetry install » n'est PAS
# concerne, uv ne lit pas poetry.lock.
EL_PIP_PROVIDER="${EL_PIP_PROVIDER:-auto}"
# The default port where this Odoo instance will run under (provided you use the command -c in the terminal)
# Set to true if you want to install it, false if you don't need it or have it already installed.
EL_INSTALL_WKHTMLTOPDF="True"

View file

@ -19,15 +19,27 @@ Color_Off='\033[0m' # Text Reset
PYTHON_VERSION_MAJOR=$(echo "$EL_PYTHON_ODOO_VERSION" | sed 's/\.[^\.]*$//')
VENV_ERPLIBRE_PATH=$(cat "conf/python-erplibre-venv" | xargs)
VENV_ODOO_PATH=".venv.${EL_ERPLIBRE_VERSION}"
POETRY_ODOO_PATH=${VENV_ERPLIBRE_PATH}/bin/poetry
# Poetry est installé dans le venv ODOO, pas dans celui des outils : le garde
# d'idempotence visait le mauvais chemin, donc il était TOUJOURS vrai. Sans
# conséquence visible — poetry se réinstallait pour rien — mais le jour où ce
# fichier aurait existé, « poetry install » aurait été purement sauté.
POETRY_ODOO_PATH=${VENV_ODOO_PATH}/bin/poetry
# Choix pip/uv : un seul endroit décide, comme pour mise/pyenv.
# shellcheck source=script/install/lib_pip_provider.sh
. ./script/install/lib_pip_provider.sh
export WITH_POETRY_INSTALLATION=1
# Verbosité de l'installation Poetry : silencieuse (-q) par défaut, les logs
# détaillés (-vvv) reviennent avec la variable d'environnement EL_VERBOSE=1.
# Verbosité de l'installation Poetry. « -q » a été retiré du défaut : dans Cleo,
# le mode silencieux coupe AUSSI la sortie d'erreur. Mesuré — « poetry -q
# commande-inexistante » rend 1 sans imprimer un caractère, quand la même sans
# « -q » nomme le problème. Un échec devenait donc parfaitement muet, et c'est
# pour cela qu'il avait fallu ajouter un rejeu de diagnostic plus bas.
# La verbosité par défaut de Poetry tient en une ligne par paquet.
if [[ "${EL_VERBOSE:-0}" == "1" ]]; then
POETRY_VERBOSE="-vvv"
else
POETRY_VERBOSE="-q"
POETRY_VERBOSE=""
fi
# EL_PHASE controls which steps to execute. Used by install_locally_dev.sh
@ -72,7 +84,8 @@ if [[ "${EL_PHASE}" != "poetry" ]]; then
source ./${VENV_ERPLIBRE_PATH}/bin/activate
echo -e "Upgrade pip to ${VENV_ERPLIBRE_PATH}"
pip install --upgrade pip
pip install -r requirement/erplibre_require-ments.txt
el_pip_install "${VENV_ERPLIBRE_PATH}" \
-r requirement/erplibre_require-ments.txt
./script/install/install_git_repo.sh
fi
@ -121,28 +134,50 @@ if [[ "${EL_PHASE}" != "setup" ]]; then
esac
fi
# Delete artifacts created by pip, cause error in next "poetry install"
if [[ ! -f "${POETRY_ODOO_PATH}" ]]; then
# Le garde ne couvre QUE l'amorçage de Poetry. « poetry install » doit
# rejouer à chaque fois : c'est lui qui applique un lock régénéré par
# « make poetry_update ». L'englober rendrait la mise à jour sans effet
# dès la seconde installation.
if [[ ! -x "${POETRY_ODOO_PATH}" ]]; then
echo -e "Install Poetry ${POETRY_ODOO_PATH}"
pip install ${PIP_CONSTRAINT_CRYPTO} poetry==${EL_POETRY_VERSION}
poetry --version
# Fix broken poetry by installing ignored dependence
# poetry lock --no-update
# To fix keyring problem when installation is blocked, use
export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then
poetry install --no-root ${POETRY_VERBOSE}
fi
el_pip_install "${VENV_ODOO_PATH}" \
${PIP_CONSTRAINT_CRYPTO} "poetry==${EL_POETRY_VERSION}"
fi
# Chemin EXPLICITE, jamais le « poetry » du PATH. Le script active pourtant
# le bon venv juste avant, mais poetry.toml porte « virtualenvs.create =
# false » : hors activation, Poetry installe dans l'interpréteur de BASE.
# Vérifié à la dure sur une VM — 772 paquets posés dans le CPython de mise,
# invisibles du venv qui a « include-system-site-packages = false », et
# promis à polluer tout autre venv bâti sur le même interpréteur.
if [[ ! -x "${POETRY_ODOO_PATH}" ]]; then
echo "Poetry introuvable a ${POETRY_ODOO_PATH}, arret."
exit 1
fi
"${POETRY_ODOO_PATH}" --version
# To fix keyring problem when installation is blocked, use
export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
# « poetry install » reste à Poetry : uv ne lit pas poetry.lock
# (astral-sh/uv#1804, « not planned ») et Poetry 2.1.3 n'a plus « export ».
if [[ ${WITH_POETRY_INSTALLATION} -ne 0 ]]; then
"${POETRY_ODOO_PATH}" install --no-root ${POETRY_VERBOSE}
retVal=$?
if [[ $retVal -ne 0 ]]; then
echo "Poetry installation error with status ${retVal}"
# « -q » masque la CAUSE. On rejoue en « -vvv » (debug) car c'est
# le SEUL niveau où Poetry affiche la sortie des sous-processus
# (git clone/checkout, build pip) — donc l'erreur réelle d'une
# dépendance VCS/build. Capturé dans le log pour diagnostic.
echo "---- Poetry: rejeu -vvv pour diagnostic ----"
poetry install --no-root -vvv 2>&1 || true
exit 1
# Un rejeu, parce qu'une partie des échecs sont des aléas de
# téléchargement : le lock est figé, rien ne dépend de l'instant.
# « -vvv » est le SEUL niveau où Poetry montre la sortie des
# sous-processus (git clone, build pip), donc l'erreur réelle.
#
# Le « exit 1 » était INCONDITIONNEL : un rejeu réussi laissait une
# installation complète... et la déclarait quand même en échec.
# C'est ce qui a arrêté openSUSE Leap 16 sur un pdfminer-six posé
# correctement au second essai.
echo "---- Poetry: rejeu -vvv (diagnostic et seconde chance) ----"
if "${POETRY_ODOO_PATH}" install --no-root -vvv 2>&1; then
echo "Le rejeu a REUSSI : le premier echec etait transitoire."
else
exit 1
fi
fi
fi

View file

@ -0,0 +1,70 @@
#!/usr/bin/env bash
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
#
# Bibliothèque SOURÇABLE : installer des paquets Python dans un venv donné.
#
# Seul endroit du dépôt qui décide COMMENT les paquets sont posés. Deux
# fournisseurs, même contrat de sortie :
# pip historique, toujours présent dans un venv.
# uv nettement plus rapide sur la résolution, le téléchargement et la
# pose ; il met aussi en cache les roues qu'il CONSTRUIT, ce qui
# compte là où rien n'a de roue publiée.
#
# EL_PIP_PROVIDER choisit : auto (défaut), uv, ou pip.
#
# CE QUE ÇA NE FAIT PAS — et il faut le savoir avant d'en attendre trop :
# « poetry install --no-root » n'est PAS concerné. uv ne lit pas poetry.lock
# (astral-sh/uv#1804, fermé « not planned ») et Poetry 2.1.3 n'a plus la
# commande « export ». Or c'est cette étape qui domine : 354 paquets contre
# 153 pour le venv d'outils. Le gain porte donc sur le venv d'outils et
# l'amorçage de Poetry, pas sur le gros morceau.
#
# Et sur s390x il est quasi nul : une trentaine de paquets binaires n'ont
# aucune roue (numpy, pandas, pillow, lxml, pymupdf…) et se COMPILENT. uv
# n'enlève pas une seconde de gcc ; il parallélise seulement entre paquets.
# uv ne supporte pas officiellement Python 3.7, encore utilisé par Odoo 12
# et 13. En dessous de ce seuil on reste sur pip, sans discuter.
EL_UV_MIN_PY_MINOR=8
el_uv_usable() {
# $1 = interpréteur du venv visé.
local py="$1" minor
command -v uv > /dev/null 2>&1 || return 1
[ -x "${py}" ] || return 1
minor="$("${py}" -c 'import sys;print(sys.version_info[1])' 2> /dev/null)"
[ -n "${minor}" ] || return 1
[ "${minor}" -ge "${EL_UV_MIN_PY_MINOR}" ] 2> /dev/null || return 1
}
# API publique : installe dans le venv `$1`, avec les arguments qui suivent.
#
# La CIBLE est toujours explicite, jamais déduite de l'environnement. uv
# cherche sinon un « .venv » dans le répertoire courant ou un parent, et
# poetry.toml en déclare justement un — le dépôt porte déjà la trace de cette
# confusion, des dépendances du pyproject Odoo s'étant retrouvées dans
# .venv.erplibre. « --python » ferme la question.
#
# « uv pip sync » n'est jamais utilisé : il SUPPRIME ce qui n'est pas dans le
# fichier d'entrée, donc pip lui-même et tout ce que Poetry a posé.
el_pip_install() {
local venv="$1"
shift
local py="${venv}/bin/python"
local provider="${EL_PIP_PROVIDER:-auto}"
if [ "${provider}" != "pip" ] && el_uv_usable "${py}"; then
echo " installation par uv ($(uv --version 2>&1 | head -1))"
if uv pip install --python "${py}" "$@"; then
return 0
fi
if [ "${provider}" = "uv" ]; then
return 1
fi
# uv est plus strict que pip sur les métadonnées : il refuse des paquets
# que pip accepte. Le repli n'est donc pas théorique.
echo " uv a echoue : reprise avec pip." >&2
fi
"${py}" -m pip install "$@"
}