diff --git a/.claude/rules/08-deployment.md b/.claude/rules/08-deployment.md index bd6c86a..354d4ce 100644 --- a/.claude/rules/08-deployment.md +++ b/.claude/rules/08-deployment.md @@ -8,8 +8,19 @@ Plateformes supportées : Ubuntu 24.04 / 25.10 / 26.04, Linux Mint 22.3, Debian 12, AlmaLinux 9+, Rocky Linux 9+, openSUSE Tumbleweed, Arch Linux, -macOS (pyenv), +macOS (mise ou pyenv), Windows (WSL/Docker). Ubuntu 20.04 et 22.04 sont abandonnées : pikepdf exige qpdf >= 12.2, compilé en C++20, quand focal livre GCC 9. + +## Interpréteur Python + +`EL_PYTHON_PROVIDER` (dans `env_var.sh`) vaut `auto`, `mise` ou `pyenv`. +`auto` reprend un interpréteur DÉJÀ posé, quel que soit le fournisseur ; sinon +il préfère mise, qui pose un CPython précompilé, et retombe sur pyenv, qui le +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. diff --git a/Makefile b/Makefile index 465190a..59b417e 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,28 @@ version: pyenv_update: ~/.pyenv/bin/pyenv update +# mise pose un CPython PRECOMPILE la ou pyenv le compile : quelques secondes +# contre une a trois minutes, et aucun paquet -dev requis. Volontairement +# EXPLICITE : « curl | sh » engage, et ce n'est pas a un script d'installation +# de le decider a la place de l'utilisateur. Une fois mise present, +# EL_PYTHON_PROVIDER=auto s'en sert tout seul. +# +# Pas de binaire s390x publie a ce jour : sur cette architecture, la cible +# le dit et n'installe rien. +.PHONY: install_mise +install_mise: + @if command -v mise >/dev/null 2>&1; then \ + echo "mise deja present : $$(mise --version)"; \ + elif command -v pacman >/dev/null 2>&1; then \ + sudo pacman -S --needed --noconfirm mise; \ + elif [ "$$(uname -m)" = "s390x" ]; then \ + echo "mise ne publie pas de binaire s390x : on reste sur pyenv."; \ + else \ + echo "Installation de mise depuis https://mise.run"; \ + curl -fsSL https://mise.run | sh; \ + echo "Ajoutez ~/.local/bin a votre PATH, puis relancez l installation."; \ + fi + .PHONY: db_create_db_test db_create_db_test: ./script/make.sh db_drop_db_test diff --git a/README.base.md b/README.base.md index af2bd49..d79149d 100644 --- a/README.base.md +++ b/README.base.md @@ -95,7 +95,7 @@ Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc. # Supported platforms - **Linux** : Ubuntu 24.04, 25.10, 26.04; Debian 12; AlmaLinux 9+; Rocky Linux 9+; Arch Linux -- **macOS** : via pyenv +- **macOS** : via mise ou pyenv - **Windows** : via WSL or Docker - **Mainframe** : 390x architecture @@ -104,7 +104,7 @@ Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc. # Plateformes supportées - **Linux** : Ubuntu 24.04, 25.10, 26.04; Debian 12; AlmaLinux 9+; Rocky Linux 9+; Arch Linux -- **macOS** : via pyenv +- **macOS** : via mise ou pyenv - **Windows** : via WSL ou Docker - **Mainframe** : architecture 390x diff --git a/README.fr.md b/README.fr.md index 1af228c..5c80a26 100644 --- a/README.fr.md +++ b/README.fr.md @@ -48,7 +48,7 @@ Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc. # Plateformes supportées - **Linux** : Ubuntu 24.04, 25.10, 26.04; Debian 12; AlmaLinux 9+; Rocky Linux 9+; Arch Linux -- **macOS** : via pyenv +- **macOS** : via mise ou pyenv - **Windows** : via WSL ou Docker - **Mainframe** : architecture 390x diff --git a/README.md b/README.md index bf6847e..90bbb1b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Switch between versions with `make switch_odoo_18`, `make switch_odoo_16`, etc. # Supported platforms - **Linux** : Ubuntu 24.04, 25.10, 26.04; Debian 12; AlmaLinux 9+; Rocky Linux 9+; Arch Linux -- **macOS** : via pyenv +- **macOS** : via mise ou pyenv - **Windows** : via WSL or Docker - **Mainframe** : 390x architecture diff --git a/env_var.sh b/env_var.sh index c899ad9..d338d3e 100755 --- a/env_var.sh +++ b/env_var.sh @@ -14,6 +14,11 @@ EL_POETRY_VERSION=$(cat ".poetry-version" | xargs) EL_PYTHON_ODOO_VERSION=$(cat ".python-odoo-version" | xargs) EL_PYTHON_ERPLIBRE_VERSION=$(cat "./conf/python-erplibre-version" | xargs) EL_ERPLIBRE_VERSION=$(cat ".erplibre-version" | xargs) +# Fournisseur d'interpreteur Python : auto | mise | pyenv. +# « auto » prend ce qui est DEJA pose, sinon mise s'il est present (Python +# precompile, quelques secondes), sinon pyenv (compilation). Voir +# script/install/lib_python_provider.sh. +EL_PYTHON_PROVIDER="${EL_PYTHON_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" diff --git a/script/install/install_dev.sh b/script/install/install_dev.sh index 3e6f8f7..5f3228d 100755 --- a/script/install/install_dev.sh +++ b/script/install/install_dev.sh @@ -1,4 +1,9 @@ #!/usr/bin/env bash +# Le code de retour du script de distribution DOIT remonter : il est aujourd'hui +# la derniere commande executee, donc propage par accident. « set -e » le rend +# explicite, et update_env_version.py:818 s'en sert pour arreter l'installation +# au lieu d'enchainer sur un systeme sans compilateur. +set -e # « $OSTYPE » ne decrit PAS le systeme : bash le fige au triplet avec lequel il # a ete compile. openSUSE batit bash en « x86_64-suse-linux » et le renseigne diff --git a/script/install/install_locally.sh b/script/install/install_locally.sh index 585fe26..38609e4 100755 --- a/script/install/install_locally.sh +++ b/script/install/install_locally.sh @@ -50,10 +50,21 @@ if [[ "${EL_PHASE}" != "poetry" ]]; then if [[ ! -n "${DOCKER_BUILD}" ]]; then # Install ERPLibre venv echo -e "Install ${VENV_ERPLIBRE_PATH} with ${EL_PYTHON_ERPLIBRE_VERSION}" - ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}" + # Le code de retour n'était PAS regardé : quand l'interpréteur ne + # pouvait pas être obtenu, le script continuait, et tout ce qui suit + # échouait à son tour sur un venv inexistant. Le log devenait une + # cascade de « No such file or directory » répartie sur trois fichiers, + # où la cause réelle — pas de compilateur C — se perdait tout en haut. + if ! ./script/install/install_venv.sh "ERPLibre" "${VENV_ERPLIBRE_PATH}" "${EL_PYTHON_ERPLIBRE_VERSION}"; then + echo "Echec de creation de ${VENV_ERPLIBRE_PATH}, arret." + exit 1 + fi # Install Odoo venv echo -e "Install ${VENV_ODOO_PATH} with ${EL_PYTHON_ODOO_VERSION}" - ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}" + if ! ./script/install/install_venv.sh "Odoo" "${VENV_ODOO_PATH}" "${EL_PYTHON_ODOO_VERSION}"; then + echo "Echec de creation de ${VENV_ODOO_PATH}, arret." + exit 1 + fi else mkdir .venv fi diff --git a/script/install/install_suse_dependency.sh b/script/install/install_suse_dependency.sh index a70a252..e9c86cc 100755 --- a/script/install/install_suse_dependency.sh +++ b/script/install/install_suse_dependency.sh @@ -273,6 +273,18 @@ if [[ ${retVal} -ne 0 ]]; then fi # Dépendances de build pour pyenv (compilation de CPython) — CRITIQUE. +# Python de la DISTRIBUTION d'abord : Tumbleweed livre python312 en 3.12.13, +# qui satisfait « >=3.12.10,<3.13 ». lib_python_provider.sh le prefere alors a +# toute compilation — decisif sur s390x, ou gcc 15.2 s'arrete sur une erreur +# interne en batissant Parser/parser.c de CPython 3.12.10. +# Le nom suit la version demandee : python312 pour 3.12.x. +PY_WANT="$(cat .python-odoo-version 2>/dev/null | xargs)" +PY_PKG="python$(echo "${PY_WANT}" | awk -F. '{print $1 $2}')" +if [ -n "${PY_WANT}" ]; then + echo -e "\n---- Python ${PY_WANT} depuis la distribution (${PY_PKG}) ----" + zyp_soft "${PY_PKG}" "${PY_PKG}-devel" "${PY_PKG}-pip" +fi + echo -e "\n---- Dependances pyenv (compilation Python) ----" zyp_in \ make gcc zlib-devel libbz2-devel readline-devel sqlite3-devel \ diff --git a/script/install/install_venv.sh b/script/install/install_venv.sh index 3019feb..4f4dc8f 100755 --- a/script/install/install_venv.sh +++ b/script/install/install_venv.sh @@ -17,52 +17,25 @@ echo "Context: $CONTEXT" echo "Venv Path: $VENV_PATH" echo "Python Version: $PYTHON_VERSION" -PYENV_PATH=~/.pyenv -PYENV_VERSION_PATH=${PYENV_PATH}/versions/${PYTHON_VERSION} -PYTHON_EXEC=${PYENV_VERSION_PATH}/bin/python -echo "Python path version home" -echo ${PYENV_VERSION_PATH} -#echo "Python path version local" -#echo ${LOCAL_PYTHON_EXEC} +# Le CHOIX du fournisseur (mise ou pyenv) vit dans la bibliothèque : ce script +# ne connaît qu'un chemin d'interpréteur. C'est ce qui permet d'en ajouter un +# troisième sans toucher ici. +# shellcheck source=script/install/lib_python_provider.sh +. ./script/install/lib_python_provider.sh -if [[ ! -d "${PYENV_PATH}" ]]; then - echo -e "\n---- Installing pyenv in ${PYENV_PATH} ----" - # export PYENV_GIT_TAG=v2.3.35 - # To change version - # rm ~/.pyenv to uninstall it - curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash +PYTHON_EXEC="$(el_python_exec "${PYTHON_VERSION}")" +if [[ -z "${PYTHON_EXEC}" ]] || [[ ! -x "${PYTHON_EXEC}" ]]; then + echo "Aucun interpreteur Python ${PYTHON_VERSION} n'a pu etre obtenu." + echo " Fournisseur demande : ${EL_PYTHON_PROVIDER:-auto}" + echo " Voir 'make install_mise', ou installez pyenv." + exit 1 fi - -echo -e "\n---- Export pyenv in ${PYENV_PATH} ----" -export PATH="${PYENV_PATH}/bin:$PATH" -eval "$(pyenv init -)" -eval "$(pyenv virtualenv-init -)" - -if [[ ! -d "${PYENV_VERSION_PATH}" ]]; then - echo -e "\n---- Installing python ${PYTHON_VERSION} with pyenv in ${PYENV_VERSION_PATH} ----" - # Update all python version list - cd "${PYENV_PATH}" && git pull && cd - - yes n|pyenv install ${PYTHON_VERSION} - if [[ $retVal -ne 0 ]]; then - echo -e "${Red}Error${Color_Off} when installing pyenv" - exit 1 - fi -fi - -# This will write .python-version -# pyenv local ${PYTHON_VERSION} +echo "Interpreteur retenu : ${PYTHON_EXEC}" if [[ ! -d ${VENV_PATH} ]]; then - echo -e "\n---- Create Virtual environment Python ----" - if [[ -e ${PYTHON_EXEC} ]]; then - ${PYTHON_EXEC} -m venv "${VENV_PATH}" - retVal=$? - if [[ $retVal -ne 0 ]]; then - echo "Virtual environment, error when creating ${VENV_PATH}" - exit 1 - fi - else - echo "Missing pyenv, please refer installation guide. Check variable '${PYTHON_EXEC}'." - exit 1 - fi -fi \ No newline at end of file + echo -e "\n---- Create Virtual environment Python ----" + if ! "${PYTHON_EXEC}" -m venv "${VENV_PATH}"; then + echo "Virtual environment, error when creating ${VENV_PATH}" + exit 1 + fi +fi diff --git a/script/install/lib_python_provider.sh b/script/install/lib_python_provider.sh new file mode 100755 index 0000000..211d62e --- /dev/null +++ b/script/install/lib_python_provider.sh @@ -0,0 +1,196 @@ +#!/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 : « une version de Python » -> « un interpréteur ». +# +# C'est le seul endroit du dépôt qui décide COMMENT un interpréteur est +# obtenu. Tout le reste ne consomme que des chemins de venv, jamais pyenv ni +# mise. Ajouter un fournisseur se fait donc ici, et nulle part ailleurs. +# +# Deux fournisseurs : +# pyenv historique. Compile CPython depuis les sources : 1 à 3 min sur une +# machine récente, bien plus sous émulation, et il lui faut une +# douzaine de -dev (openssl, zlib, readline, sqlite, bzip2, xz, tk…). +# mise pose un CPython PRÉCOMPILÉ (astral python-build-standalone) : +# quelques secondes, aucun compilateur requis. +# +# EL_PYTHON_PROVIDER choisit : auto (défaut), mise, ou pyenv. +# +# En mode « auto », un interpréteur DÉJÀ présent gagne, quel que soit le +# fournisseur qui l'a posé. Une installation pyenv qui marche n'est donc +# jamais doublée par un second CPython de 150 Mo. + +# Ne jamais laisser mise compiler en silence : sans build précompilée pour la +# plateforme, il retomberait sur python-build — le moteur de pyenv — et on +# aurait la lenteur de pyenv sans son outillage. « false » = télécharger ou +# échouer ; l'échec nous fait basculer proprement sur le repli. +export MISE_PYTHON_COMPILE=false + +el_pyenv_root() { + echo "${PYENV_ROOT:-${HOME}/.pyenv}" +} + +# Chemin de l'interpréteur pyenv d'une version, qu'il existe ou non. +el_pyenv_exec_path() { + echo "$(el_pyenv_root)/versions/$1/bin/python" +} + +# Interpréteur déjà posé par mise, sans le moindre accès réseau. +el_mise_exec_path() { + command -v mise > /dev/null 2>&1 || return 1 + local out + out="$(mise where "python@$1" 2> /dev/null)" || return 1 + [ -n "${out}" ] && [ -x "${out}/bin/python" ] || return 1 + echo "${out}/bin/python" +} + +# Vrai si l'exécutable rend EXACTEMENT la version demandée. Un interpréteur +# présent mais d'une autre version casserait Poetry, dont le pyproject exige +# le patch au près (>=3.12.10,<3.13). +el_python_is_version() { + local exe="$1" want="$2" got + [ -x "${exe}" ] || return 1 + got="$("${exe}" -c 'import platform;print(platform.python_version())' 2> /dev/null)" + [ "${got}" = "${want}" ] +} + +# Vrai si l'exécutable CONVIENT : même majeure.mineure, et patch au moins +# égal au demandé. C'est exactement ce qu'exige le pyproject — « >=3.12.10, +# <3.13 » — et non l'égalité stricte que testait el_python_is_version. +# +# La distinction n'est pas théorique. Tumbleweed s390x livre python312 en +# 3.12.13 : parfaitement utilisable, mais rejeté par l'égalité, ce qui forçait +# pyenv à COMPILER CPython — et gcc 15.2 s'y arrête sur une erreur interne +# dans Parser/parser.c, un fichier généré de quarante mille lignes. +el_python_is_compatible() { + local exe="$1" want="$2" got + [ -x "${exe}" ] || return 1 + got="$("${exe}" -c 'import platform;print(platform.python_version())' \ + 2> /dev/null)" + [ -n "${got}" ] || return 1 + # Même majeure.mineure : 3.13 ne convient pas à un pyproject borné à <3.13. + [ "${got%.*}" = "${want%.*}" ] || return 1 + # Patch au moins égal, comparé en version et non en chaîne (3.12.9 < 3.12.10). + [ "$(printf '%s\n%s\n' "${want}" "${got}" | sort -V | head -1)" = "${want}" ] +} + +# Interpréteur de la DISTRIBUTION qui conviendrait, s'il y en a un. +# +# Cherché avant toute compilation : sur une architecture émulée, bâtir CPython +# prend des dizaines de minutes quand il aboutit. Le nom suit la convention de +# toutes les distributions, « python3.12 ». +el_distro_python_exec() { + local want="$1" exe + # Des chemins SYSTEME, jamais « command -v » : dans un venv activé celui-ci + # rend le python DU VENV, et l'on bâtirait un venv depuis un venv. Le PATH + # d'une session interactive n'a rien à faire dans cette décision. + for exe in "/usr/bin/python${want%.*}" "/usr/local/bin/python${want%.*}"; do + if el_python_is_compatible "${exe}" "${want}"; then + echo "${exe}" + return 0 + fi + done + return 1 +} + +# Installe la version via mise. Renvoie le chemin, ou échoue. +el_mise_install() { + local version="$1" exe + command -v mise > /dev/null 2>&1 || return 1 + echo "---- Python ${version} via mise (precompile) ----" >&2 + mise install "python@${version}" >&2 || return 1 + exe="$(el_mise_exec_path "${version}")" || return 1 + el_python_is_version "${exe}" "${version}" || return 1 + echo "${exe}" +} + +# Installe la version via pyenv, en posant pyenv lui-même au besoin. +el_pyenv_install() { + local version="$1" root exe + root="$(el_pyenv_root)" + exe="$(el_pyenv_exec_path "${version}")" + if [[ ! -d "${root}" ]]; then + echo "---- Installation de pyenv dans ${root} ----" >&2 + curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer \ + | bash >&2 || return 1 + fi + export PATH="${root}/bin:$PATH" + eval "$(pyenv init - 2> /dev/null)" || true + eval "$(pyenv virtualenv-init - 2> /dev/null)" || true + if [[ ! -d "${root}/versions/${version}" ]]; then + # pyenv COMPILE CPython : sans compilateur C, il télécharge l'archive, + # lance configure et s'arrête sur « no acceptable C compiler found », après + # avoir gaspillé le téléchargement. On le dit AVANT, et on nomme le paquet. + if ! command -v cc > /dev/null 2>&1 \ + && ! command -v gcc > /dev/null 2>&1; then + echo "Aucun compilateur C : pyenv ne peut pas compiler Python." >&2 + echo " Installez le necessaire de compilation (build-essential," >&2 + echo " gcc/gcc-c++, ou le motif devel_basis) puis relancez." >&2 + return 1 + fi + echo "---- Python ${version} via pyenv (compilation) ----" >&2 + # La liste des versions connues vient du dépôt git de pyenv : sans ce + # « pull », une version récente est « not a known version ». + (cd "${root}" && git pull) >&2 || true + # Le contrôle d'erreur d'origine testait un $retVal jamais affecté ici : + # un échec de compilation passait inaperçu jusqu'au test d'existence. + if ! yes n | pyenv install "${version}" >&2; then + echo "pyenv install ${version} a echoue." >&2 + return 1 + fi + fi + el_python_is_version "${exe}" "${version}" || return 1 + echo "${exe}" +} + +# API publique : imprime le chemin absolu d'un interpréteur de cette version, +# ou rien (et rend non nul) si aucun fournisseur n'y parvient. +el_python_exec() { + local version="$1" provider="${EL_PYTHON_PROVIDER:-auto}" exe + + # 1) Déjà présent ? On ne réinstalle rien et on ne touche pas au réseau. + # C'est ce qui rend le changement sans effet pour une installation + # pyenv existante. + if [ "${provider}" != "mise" ]; then + exe="$(el_pyenv_exec_path "${version}")" + if el_python_is_version "${exe}" "${version}"; then + echo "${exe}" + return 0 + fi + fi + if [ "${provider}" != "pyenv" ]; then + if exe="$(el_mise_exec_path "${version}")" \ + && el_python_is_version "${exe}" "${version}"; then + echo "${exe}" + return 0 + fi + fi + + # 2) Un interpréteur de la DISTRIBUTION qui convient ? Rien à installer, + # rien à compiler. Vérifié avant mise et pyenv : c'est le seul chemin + # qui ne coûte rien, et sur s390x le seul qui aboutisse partout — mise + # n'y publie aucun binaire, et pyenv doit compiler. + # Uniquement en mode « auto » : demander mise ou pyenv explicitement doit + # être respecté, sinon le réglage ne veut plus rien dire. + if [ "${provider}" = "auto" ] \ + && exe="$(el_distro_python_exec "${version}")"; then + echo "Python $("${exe}" -V 2>&1 | awk '{print $2}') de la distribution :" \ + "aucune compilation." >&2 + echo "${exe}" + return 0 + fi + + # 3) Rien de posé : il faut provisionner. mise d'abord quand il est là, + # parce qu'il télécharge au lieu de compiler. + if [ "${provider}" != "pyenv" ]; then + if exe="$(el_mise_install "${version}")"; then + echo "${exe}" + return 0 + fi + [ "${provider}" = "mise" ] && return 1 + command -v mise > /dev/null 2>&1 \ + && echo "mise n'a pas pu fournir Python ${version} : repli sur pyenv." >&2 + fi + el_pyenv_install "${version}" +} diff --git a/script/todo/source_todo.sh b/script/todo/source_todo.sh index 84082d2..4a31863 100755 --- a/script/todo/source_todo.sh +++ b/script/todo/source_todo.sh @@ -7,10 +7,23 @@ DIR_VENV_ERPLIBRE=".venv.erplibre" if [[ ! -d "$DIR_VENV_ERPLIBRE" ]]; then DIR_VENV_ERPLIBRE_EXIST=0 echo "$DIR_VENV_ERPLIBRE does not exist." - if [[ ! -d "~/.pyenv/versions/$(< ./conf/python-erplibre-version)/bin/python" ]]; then - "~/.pyenv/versions/$(< ./conf/python-erplibre-version)/bin/python" -m venv $DIR_VENV_ERPLIBRE + # Le test d'origine etait inoperant : le tilde entre guillemets n'est pas + # etendu, et « -d » testait un FICHIER comme un repertoire. La condition + # etait donc toujours vraie, la commande echouait en « No such file or + # directory », et le repli « python -m venv » ne servait jamais. + # shellcheck source=script/install/lib_python_provider.sh + . ./script/install/lib_python_provider.sh + EL_PY_WANT="$(< ./conf/python-erplibre-version)" + # Ici on ne PROVISIONNE pas : ce script s'execute au demarrage de TODO, il + # doit rester rapide et hors reseau. On prend ce qui est deja pose, sinon + # le python du systeme. + EL_PY_EXEC="$(el_pyenv_exec_path "${EL_PY_WANT}")" + el_python_is_version "${EL_PY_EXEC}" "${EL_PY_WANT}" \ + || EL_PY_EXEC="$(el_mise_exec_path "${EL_PY_WANT}" 2> /dev/null)" + if [[ -n "${EL_PY_EXEC}" ]] && [[ -x "${EL_PY_EXEC}" ]]; then + "${EL_PY_EXEC}" -m venv $DIR_VENV_ERPLIBRE else - python -m venv $DIR_VENV_ERPLIBRE + python3 -m venv $DIR_VENV_ERPLIBRE fi fi