[UPD] install : le venv d'outillage passe en Python 3.14.7

install_erplibre.sh bâtissait .venv.erplibre avec le python3 du système, quelle
que soit sa version : un 3.10 y donnait un venv que l'outillage ne sait pas
charger. Il délègue à install_venv.sh, qui obtient la version par mise, pyenv
ou la distribution, et rebâtit un venv hors service. Le PATCH ne borne que le
venv d'Odoo, dont le pyproject exige « >=3.12.10,<3.13 » : l'exiger ailleurs
écartait un Python de distribution d'un cran en retard — NixOS 25.11 livre
3.14.2 — et faisait compiler CPython pour rien. Le module NixOS déclare
désormais les DEUX Python, substitués depuis les fichiers de version.
Vérifié : 15 tests sur des venvs factices, conservés, rebâtis ou refusés selon
leur état, et un chemin sans pyvenv.cfg garde son contenu.

--- EN ---

install_erplibre.sh built .venv.erplibre with the system python3, whatever its
version: a 3.10 gave a venv the tooling cannot load. It delegates to
install_venv.sh, which obtains the version through mise, pyenv or the
distribution, and rebuilds a venv out of service. The PATCH bounds only Odoo's
venv, whose pyproject requires ">=3.12.10,<3.13": requiring it elsewhere turned
away a distribution Python one step behind — NixOS 25.11 ships 3.14.2 — and
compiled CPython for nothing. The NixOS module now declares BOTH Pythons,
substituted from the version files.
Checked: 15 tests on stub venvs, kept, rebuilt or refused by their state, and a
path without pyvenv.cfg keeps its content.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-09-24 13:31:32 -04:00
parent 7cdcd8a07a
commit 42e10bcced
25 changed files with 739 additions and 78 deletions

View file

@ -36,9 +36,31 @@ Un seul fichier décide : `script/install/lib_python_provider.sh`. mise n'est
jamais installé automatiquement — `make install_mise` porte cette décision. jamais installé automatiquement — `make install_mise` porte cette décision.
Pas de binaire mise pour s390x à ce jour : cette architecture reste sur pyenv. Pas de binaire mise pour s390x à ce jour : cette architecture reste sur pyenv.
## Le Python de l'outillage
`conf/python-erplibre-version` (3.14.7) donne l'interpréteur de
`.venv.erplibre`, le venv d'outillage, distinct du venv Odoo (3.12.10 pour
Odoo 18.0). C'est la seule version que `script/` vise : là où une distribution
ne la porte pas, pyenv la compile.
Le PATCH ne borne que le venv d'Odoo, dont le pyproject exige
`>=3.12.10,<3.13`. Pour l'outillage, la majeure.mineure suffit : exiger le
patch écarterait le Python d'une distribution d'un cran en retard — NixOS 25.11
livre 3.14.2 — et ferait compiler CPython pour rien.
`install_erplibre.sh` passe par `install_venv.sh`, donc par
`EL_PYTHON_PROVIDER`. Un venv dont `bin/python` n'est pas compatible (même
majeure.mineure, patch au moins égal) est DÉTRUIT puis rebâti : ce qui y avait
été posé à la main part avec lui. Le venv d'Odoo, lui, n'est rebâti que s'il
est HORS SERVICE : une simple différence de version le laisse en place, parce
que le rebâtir refait une installation Poetry entière. Un répertoire sans
`pyvenv.cfg` n'est jamais effacé.
Le hook `pre-commit` relaie `script/analyse/check_python_version.py` : il Le hook `pre-commit` relaie `script/analyse/check_python_version.py` : il
signale le source qui ne parse pas sous cette version, sans bloquer, et dit signale le source qui ne parse pas sous cette version, sans bloquer, et dit
quand aucun interpréteur de cette version n'était là pour vérifier. quand aucun interpréteur de cette version n'était là pour vérifier.
L'image Docker de production bâtit `.venv.erplibre` sur le Python d'Odoo de son
image de base, et s'arrête si ce Python ne sait pas lire `script/`.
## Paquets Python ## Paquets Python

View file

@ -156,6 +156,10 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- A VM deployed with the cache upstream cut — QEMU form, Proxmox VE, or `deploy_qemu.py --offline` — has npm's security audit turned off (`NPM_CONFIG_AUDIT=false`): it queries a remote service no cache can replay, and failed on every offline install. An online VM keeps its audit - A VM deployed with the cache upstream cut — QEMU form, Proxmox VE, or `deploy_qemu.py --offline` — has npm's security audit turned off (`NPM_CONFIG_AUDIT=false`): it queries a remote service no cache can replay, and failed on every offline install. An online VM keeps its audit
- Verifying a downloaded image no longer needs `--verify`: it runs by default for every distribution that publishes a sum, and `--no-verify` is what skips it — to be kept for offline runs, where a substituted image would otherwise pass unremarked - Verifying a downloaded image no longer needs `--verify`: it runs by default for every distribution that publishes a sum, and `--no-verify` is what skips it — to be kept for offline runs, where a substituted image would otherwise pass unremarked
- `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere - `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere
- The tooling virtual environment `.venv.erplibre` runs Python 3.14.7, independently of the Odoo one (3.12.10 for Odoo 18.0). `install_erplibre.sh` builds it through `install_venv.sh` and `EL_PYTHON_PROVIDER` instead of the system `python3`
- `.venv.erplibre` on an incompatible Python is DELETED and rebuilt; whatever was installed in it by hand goes with it. Odoo's venv is kept when it merely differs in version, and rebuilt only when it is unusable: rebuilding it redoes a whole Poetry install. A directory without `pyvenv.cfg` is never deleted
- The production Docker image builds `.venv.erplibre` on Odoo's Python and stops when that Python cannot parse `script/`
- The PATCH bounds only Odoo's venv, whose pyproject requires `>=3.12.10,<3.13`. For the tooling one, the major.minor is enough: requiring the patch turned away a distribution's Python one step behind — NixOS 25.11 ships 3.14.2 where conf asks 3.14.7 — and made pyenv COMPILE CPython for a difference nothing needs
- `make format` picks the formatter from each file's context: an Odoo module keeps isort and black on `py37`, the series still supported going down that far, while this repository's own tooling goes through ruff, configured once in `.ruff.toml`. ruff follows CPython's versions, where black 24.8.0 stops at `py313`, and its import sorting replaces isort; it is also what the OCA standard uses since it left black - `make format` picks the formatter from each file's context: an Odoo module keeps isort and black on `py37`, the series still supported going down that far, while this repository's own tooling goes through ruff, configured once in `.ruff.toml`. ruff follows CPython's versions, where black 24.8.0 stops at `py313`, and its import sorting replaces isort; it is also what the OCA standard uses since it left black
- The repositories that Google Repo checks out under `script/` are excluded from that formatting, a named path included: reformatting them would write in someone else's history. `target-version` stays at `py310` there, because the git hooks carry `#!/usr/bin/env python3` and a distribution still ships 3.10 — from 3.14 on, ruff would write `except A, B:` without parentheses - The repositories that Google Repo checks out under `script/` are excluded from that formatting, a named path included: reformatting them would write in someone else's history. `target-version` stays at `py310` there, because the git hooks carry `#!/usr/bin/env python3` and a distribution still ships 3.10 — from 3.14 on, ruff would write `except A, B:` without parentheses
@ -174,6 +178,10 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Une VM déployée l'amont du cache coupé — formulaire QEMU, Proxmox VE, ou `deploy_qemu.py --offline` — a l'audit de sécurité de npm désactivé (`NPM_CONFIG_AUDIT=false`) : il interroge un service qu'aucun cache ne rejoue, et échouait à chaque installation hors ligne. Une VM en ligne garde son audit - Une VM déployée l'amont du cache coupé — formulaire QEMU, Proxmox VE, ou `deploy_qemu.py --offline` — a l'audit de sécurité de npm désactivé (`NPM_CONFIG_AUDIT=false`) : il interroge un service qu'aucun cache ne rejoue, et échouait à chaque installation hors ligne. Une VM en ligne garde son audit
- Vérifier une image téléchargée ne demande plus `--verify` : c'est le défaut pour toute distribution qui publie une somme, et `--no-verify` est ce qui la saute — à réserver aux essais hors ligne, où une image substituée passerait autrement sans un mot - Vérifier une image téléchargée ne demande plus `--verify` : c'est le défaut pour toute distribution qui publie une somme, et `--no-verify` est ce qui la saute — à réserver aux essais hors ligne, où une image substituée passerait autrement sans un mot
- `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs - `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs
- L'environnement virtuel d'outillage `.venv.erplibre` tourne en Python 3.14.7, indépendamment de celui d'Odoo (3.12.10 pour Odoo 18.0). `install_erplibre.sh` le bâtit par `install_venv.sh` et `EL_PYTHON_PROVIDER` plutôt qu'avec le `python3` du système
- `.venv.erplibre` sur un Python incompatible est SUPPRIMÉ puis rebâti ; ce qu'on y avait posé à la main part avec lui. Le venv d'Odoo est conservé quand seule sa version diffère, et rebâti seulement s'il est hors service : le rebâtir refait une installation Poetry entière. Un répertoire sans `pyvenv.cfg` n'est jamais effacé
- L'image Docker de production bâtit `.venv.erplibre` sur le Python d'Odoo et s'arrête quand ce Python ne sait pas lire `script/`
- Le PATCH ne borne que le venv d'Odoo, dont le pyproject exige `>=3.12.10,<3.13`. Pour celui de l'outillage, la majeure.mineure suffit : exiger le patch écartait le Python d'une distribution d'un cran en retard — NixOS 25.11 livre 3.14.2 quand conf demande 3.14.7 — et faisait COMPILER CPython à pyenv pour une différence que rien ne réclame
- `make format` choisit le formateur d'après le contexte de chaque fichier : un module Odoo garde isort et black en `py37`, la série la plus ancienne encore supportée, quand l'outillage de ce dépôt passe par ruff, réglé une fois dans `.ruff.toml`. ruff suit les versions de CPython, là où black 24.8.0 s'arrête à `py313`, et son tri d'imports remplace isort ; c'est aussi ce qu'emploie la norme OCA depuis qu'elle a quitté black - `make format` choisit le formateur d'après le contexte de chaque fichier : un module Odoo garde isort et black en `py37`, la série la plus ancienne encore supportée, quand l'outillage de ce dépôt passe par ruff, réglé une fois dans `.ruff.toml`. ruff suit les versions de CPython, là où black 24.8.0 s'arrête à `py313`, et son tri d'imports remplace isort ; c'est aussi ce qu'emploie la norme OCA depuis qu'elle a quitté black
- Les dépôts que Google Repo rapatrie sous `script/` sont écartés de ce formatage, chemin nommé compris : les reformater écrirait dans l'historique d'autrui. `target-version` y reste à `py310`, parce que les hooks git portent `#!/usr/bin/env python3` et qu'une distribution livre encore 3.10 — à partir de 3.14, ruff écrirait `except A, B:` sans parenthèses - Les dépôts que Google Repo rapatrie sous `script/` sont écartés de ce formatage, chemin nommé compris : les reformater écrirait dans l'historique d'autrui. `target-version` y reste à `py310`, parce que les hooks git portent `#!/usr/bin/env python3` et qu'une distribution livre encore 3.10 — à partir de 3.14, ruff écrirait `except A, B:` sans parenthèses

View file

@ -78,6 +78,10 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Une VM déployée l'amont du cache coupé — formulaire QEMU, Proxmox VE, ou `deploy_qemu.py --offline` — a l'audit de sécurité de npm désactivé (`NPM_CONFIG_AUDIT=false`) : il interroge un service qu'aucun cache ne rejoue, et échouait à chaque installation hors ligne. Une VM en ligne garde son audit - Une VM déployée l'amont du cache coupé — formulaire QEMU, Proxmox VE, ou `deploy_qemu.py --offline` — a l'audit de sécurité de npm désactivé (`NPM_CONFIG_AUDIT=false`) : il interroge un service qu'aucun cache ne rejoue, et échouait à chaque installation hors ligne. Une VM en ligne garde son audit
- Vérifier une image téléchargée ne demande plus `--verify` : c'est le défaut pour toute distribution qui publie une somme, et `--no-verify` est ce qui la saute — à réserver aux essais hors ligne, où une image substituée passerait autrement sans un mot - Vérifier une image téléchargée ne demande plus `--verify` : c'est le défaut pour toute distribution qui publie une somme, et `--no-verify` est ce qui la saute — à réserver aux essais hors ligne, où une image substituée passerait autrement sans un mot
- `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs - `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs
- L'environnement virtuel d'outillage `.venv.erplibre` tourne en Python 3.14.7, indépendamment de celui d'Odoo (3.12.10 pour Odoo 18.0). `install_erplibre.sh` le bâtit par `install_venv.sh` et `EL_PYTHON_PROVIDER` plutôt qu'avec le `python3` du système
- `.venv.erplibre` sur un Python incompatible est SUPPRIMÉ puis rebâti ; ce qu'on y avait posé à la main part avec lui. Le venv d'Odoo est conservé quand seule sa version diffère, et rebâti seulement s'il est hors service : le rebâtir refait une installation Poetry entière. Un répertoire sans `pyvenv.cfg` n'est jamais effacé
- L'image Docker de production bâtit `.venv.erplibre` sur le Python d'Odoo et s'arrête quand ce Python ne sait pas lire `script/`
- Le PATCH ne borne que le venv d'Odoo, dont le pyproject exige `>=3.12.10,<3.13`. Pour celui de l'outillage, la majeure.mineure suffit : exiger le patch écartait le Python d'une distribution d'un cran en retard — NixOS 25.11 livre 3.14.2 quand conf demande 3.14.7 — et faisait COMPILER CPython à pyenv pour une différence que rien ne réclame
- `make format` choisit le formateur d'après le contexte de chaque fichier : un module Odoo garde isort et black en `py37`, la série la plus ancienne encore supportée, quand l'outillage de ce dépôt passe par ruff, réglé une fois dans `.ruff.toml`. ruff suit les versions de CPython, là où black 24.8.0 s'arrête à `py313`, et son tri d'imports remplace isort ; c'est aussi ce qu'emploie la norme OCA depuis qu'elle a quitté black - `make format` choisit le formateur d'après le contexte de chaque fichier : un module Odoo garde isort et black en `py37`, la série la plus ancienne encore supportée, quand l'outillage de ce dépôt passe par ruff, réglé une fois dans `.ruff.toml`. ruff suit les versions de CPython, là où black 24.8.0 s'arrête à `py313`, et son tri d'imports remplace isort ; c'est aussi ce qu'emploie la norme OCA depuis qu'elle a quitté black
- Les dépôts que Google Repo rapatrie sous `script/` sont écartés de ce formatage, chemin nommé compris : les reformater écrirait dans l'historique d'autrui. `target-version` y reste à `py310`, parce que les hooks git portent `#!/usr/bin/env python3` et qu'une distribution livre encore 3.10 — à partir de 3.14, ruff écrirait `except A, B:` sans parenthèses - Les dépôts que Google Repo rapatrie sous `script/` sont écartés de ce formatage, chemin nommé compris : les reformater écrirait dans l'historique d'autrui. `target-version` y reste à `py310`, parce que les hooks git portent `#!/usr/bin/env python3` et qu'une distribution livre encore 3.10 — à partir de 3.14, ruff écrirait `except A, B:` sans parenthèses

View file

@ -78,6 +78,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- A VM deployed with the cache upstream cut — QEMU form, Proxmox VE, or `deploy_qemu.py --offline` — has npm's security audit turned off (`NPM_CONFIG_AUDIT=false`): it queries a remote service no cache can replay, and failed on every offline install. An online VM keeps its audit - A VM deployed with the cache upstream cut — QEMU form, Proxmox VE, or `deploy_qemu.py --offline` — has npm's security audit turned off (`NPM_CONFIG_AUDIT=false`): it queries a remote service no cache can replay, and failed on every offline install. An online VM keeps its audit
- Verifying a downloaded image no longer needs `--verify`: it runs by default for every distribution that publishes a sum, and `--no-verify` is what skips it — to be kept for offline runs, where a substituted image would otherwise pass unremarked - Verifying a downloaded image no longer needs `--verify`: it runs by default for every distribution that publishes a sum, and `--no-verify` is what skips it — to be kept for offline runs, where a substituted image would otherwise pass unremarked
- `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere - `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere
- The tooling virtual environment `.venv.erplibre` runs Python 3.14.7, independently of the Odoo one (3.12.10 for Odoo 18.0). `install_erplibre.sh` builds it through `install_venv.sh` and `EL_PYTHON_PROVIDER` instead of the system `python3`
- `.venv.erplibre` on an incompatible Python is DELETED and rebuilt; whatever was installed in it by hand goes with it. Odoo's venv is kept when it merely differs in version, and rebuilt only when it is unusable: rebuilding it redoes a whole Poetry install. A directory without `pyvenv.cfg` is never deleted
- The production Docker image builds `.venv.erplibre` on Odoo's Python and stops when that Python cannot parse `script/`
- The PATCH bounds only Odoo's venv, whose pyproject requires `>=3.12.10,<3.13`. For the tooling one, the major.minor is enough: requiring the patch turned away a distribution's Python one step behind — NixOS 25.11 ships 3.14.2 where conf asks 3.14.7 — and made pyenv COMPILE CPython for a difference nothing needs
- `make format` picks the formatter from each file's context: an Odoo module keeps isort and black on `py37`, the series still supported going down that far, while this repository's own tooling goes through ruff, configured once in `.ruff.toml`. ruff follows CPython's versions, where black 24.8.0 stops at `py313`, and its import sorting replaces isort; it is also what the OCA standard uses since it left black - `make format` picks the formatter from each file's context: an Odoo module keeps isort and black on `py37`, the series still supported going down that far, while this repository's own tooling goes through ruff, configured once in `.ruff.toml`. ruff follows CPython's versions, where black 24.8.0 stops at `py313`, and its import sorting replaces isort; it is also what the OCA standard uses since it left black
- The repositories that Google Repo checks out under `script/` are excluded from that formatting, a named path included: reformatting them would write in someone else's history. `target-version` stays at `py310` there, because the git hooks carry `#!/usr/bin/env python3` and a distribution still ships 3.10 — from 3.14 on, ruff would write `except A, B:` without parentheses - The repositories that Google Repo checks out under `script/` are excluded from that formatting, a named path included: reformatting them would write in someone else's history. `target-version` stays at `py310` there, because the git hooks carry `#!/usr/bin/env python3` and a distribution still ships 3.10 — from 3.14 on, ruff would write `except A, B:` without parentheses

View file

@ -129,6 +129,8 @@ install_uv:
echo "Ajoutez ce repertoire au PATH, ou installez uv globalement."; \ echo "Ajoutez ce repertoire au PATH, ou installez uv globalement."; \
fi fi
# Telecharge puis execute : dans « curl | sh », le statut est celui de sh,
# qui rend 0 sur une entree vide et masque un telechargement rate.
.PHONY: install_mise .PHONY: install_mise
install_mise: install_mise:
@if command -v mise >/dev/null 2>&1; then \ @if command -v mise >/dev/null 2>&1; then \
@ -139,7 +141,19 @@ install_mise:
echo "mise ne publie pas de binaire s390x : on reste sur pyenv."; \ echo "mise ne publie pas de binaire s390x : on reste sur pyenv."; \
else \ else \
echo "Installation de mise depuis https://mise.run"; \ echo "Installation de mise depuis https://mise.run"; \
curl -fsSL https://mise.run | sh; \ installateur="$$(mktemp)" || exit 1; \
if ! curl -fsSL -o "$$installateur" https://mise.run; then \
rm -f "$$installateur"; \
echo "Telechargement de l installateur mise impossible" \
"(reseau ou cache) : mise n est pas pose." >&2; \
exit 1; \
fi; \
if ! sh "$$installateur"; then \
rm -f "$$installateur"; \
echo "L installateur de mise a echoue (voir ci-dessus)." >&2; \
exit 1; \
fi; \
rm -f "$$installateur"; \
echo "Ajoutez ~/.local/bin a votre PATH, puis relancez l installation."; \ echo "Ajoutez ~/.local/bin a votre PATH, puis relancez l installation."; \
fi fi

View file

@ -86,10 +86,21 @@ Suivez-nous sur Mastodon : https://fosstodon.org/@erplibre
Switch between versions with `make switch_odoo_18`, `make switch_odoo_16`, etc. Switch between versions with `make switch_odoo_18`, `make switch_odoo_16`, etc.
The Python in that table is the one of the Odoo virtual environment. The
tooling virtual environment `.venv.erplibre` (TODO, `repo`, formatters) runs
its own interpreter, **3.14.7**, set by `conf/python-erplibre-version`. Where a
distribution does not carry it, pyenv compiles it.
<!-- [fr] --> <!-- [fr] -->
Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc. Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc.
Le Python de ce tableau est celui de l'environnement virtuel Odoo.
L'environnement virtuel d'outillage `.venv.erplibre` (TODO, `repo`, formateurs)
tourne sur son propre interpréteur, **3.14.7**, fixé par
`conf/python-erplibre-version`. Là où une distribution ne le porte pas, pyenv
le compile.
<!-- [en] --> <!-- [en] -->
# Supported platforms # Supported platforms

View file

@ -44,6 +44,12 @@ Suivez-nous sur Mastodon : https://fosstodon.org/@erplibre
Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc. Changez de version avec `make switch_odoo_18`, `make switch_odoo_16`, etc.
Le Python de ce tableau est celui de l'environnement virtuel Odoo.
L'environnement virtuel d'outillage `.venv.erplibre` (TODO, `repo`, formateurs)
tourne sur son propre interpréteur, **3.14.7**, fixé par
`conf/python-erplibre-version`. Là où une distribution ne le porte pas, pyenv
le compile.
# Plateformes supportées # Plateformes supportées

View file

@ -43,6 +43,11 @@ Follow us on Mastodon : https://fosstodon.org/@erplibre
Switch between versions with `make switch_odoo_18`, `make switch_odoo_16`, etc. Switch between versions with `make switch_odoo_18`, `make switch_odoo_16`, etc.
The Python in that table is the one of the Odoo virtual environment. The
tooling virtual environment `.venv.erplibre` (TODO, `repo`, formatters) runs
its own interpreter, **3.14.7**, set by `conf/python-erplibre-version`. Where a
distribution does not carry it, pyenv compiles it.
# Supported platforms # Supported platforms

View file

@ -83,17 +83,26 @@
}; };
# ── Les outils ─────────────────────────────────────────────────────────── # ── Les outils ───────────────────────────────────────────────────────────
# python3.12 : la version qu'attend ERPLibre (.python-odoo-version). envfs # Les DEUX Python du dépôt, substitués depuis ses fichiers de version :
# la rend visible en /usr/bin/python3.12, où lib_python_provider.sh la # celui d'Odoo (.python-odoo-version) et celui de l'outillage
# cherche — ni mise ni pyenv n'ont alors à télécharger quoi que ce soit. # (conf/python-erplibre-version), qui ne sont plus la même version. envfs
# les rend visibles en /usr/bin/python3.X, où lib_python_provider.sh les
# cherche — ni mise ni pyenv n'ont alors rien à compiler. Déclarer le seul
# Python d'Odoo laissait pyenv bâtir l'autre, et la compilation de CPython
# s'arrête ici sur « Modules/_cursesmodule.o ».
#
# Le second marqueur est VIDE quand les deux versions coïncident : nommer
# deux fois le même paquet ferait entrer en collision deux chemins
# identiques dans le profil.
# #
# Les sorties « .dev » portent les en-têtes : sans elles, une roue absente # Les sorties « .dev » portent les en-têtes : sans elles, une roue absente
# du dépôt amont devrait se compiler et ne trouverait ni libpq-fe.h ni # du dépôt amont devrait se compiler et ne trouverait ni libpq-fe.h ni
# openssl/ssl.h. Elles ne servent qu'à ce cas, et ne coûtent que du disque. # openssl/ssl.h. Elles ne servent qu'à ce cas, et ne coûtent que du disque.
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
python312 @EL_PY_ODOO_PKG@
python312Packages.pip @EL_PY_ODOO_PKG@Packages.pip
python312Packages.virtualenv @EL_PY_ODOO_PKG@Packages.virtualenv
@EL_PY_TOOLS_PKG@
uv uv
nodejs_22 nodejs_22
postgresql postgresql
@ -336,7 +345,7 @@
# venv, donc avec le python du système. Son échec est silencieux # venv, donc avec le python du système. Son échec est silencieux
# (« 2>/dev/null ») : sans elle, la première page ouverte attendrait le # (« 2>/dev/null ») : sans elle, la première page ouverte attendrait le
# chargement du registre sans que rien ne le dise. # chargement du registre sans que rien ne le dise.
path = with pkgs; [ bash python312 ]; path = with pkgs; [ bash @EL_PY_ODOO_PKG@ ];
# L'unité est déclarée par le module, donc démarrée par la # L'unité est déclarée par le module, donc démarrée par la
# reconstruction — qui a lieu PENDANT « make install_os », alors que la # reconstruction — qui a lieu PENDANT « make install_os », alors que la
# source d'Odoo n'arrive qu'à « make install_odoo_18 ». Sans condition, # source d'Odoo n'arrive qu'à « make install_odoo_18 ». Sans condition,

View file

@ -1 +1 @@
3.12.10 3.14.7

View file

@ -439,6 +439,10 @@ If these last steps to set up your development environment were unsuccessful, fo
## Manual Installation ## Manual Installation
This system interpreter only needs to reach `make` (Python 3.10 or later).
`.venv.erplibre` is built on its own version, `conf/python-erplibre-version`,
through `EL_PYTHON_PROVIDER` (mise or pyenv).
### Install Python 3.10.14 ### Install Python 3.10.14
You can delete the files that are left over in your home directory regarding the python installation when the steps have been completed succesfully. You can delete the files that are left over in your home directory regarding the python installation when the steps have been completed succesfully.
@ -447,6 +451,10 @@ Si ces dernières étapes pour configurer votre environnement de développement
## Installation manuelle ## Installation manuelle
Cet interpréteur système ne sert qu'à atteindre `make` (Python 3.10 ou plus).
`.venv.erplibre` est bâti sur sa propre version, `conf/python-erplibre-version`,
par `EL_PYTHON_PROVIDER` (mise ou pyenv).
### Installer Python 3.10.14 ### Installer Python 3.10.14
Vous pouvez supprimer les fichiers restants dans votre répertoire personnel concernant l'installation de Python une fois les étapes complétées avec succès. Vous pouvez supprimer les fichiers restants dans votre répertoire personnel concernant l'installation de Python une fois les étapes complétées avec succès.

View file

@ -236,6 +236,10 @@ Si ces dernières étapes pour configurer votre environnement de développement
## Installation manuelle ## Installation manuelle
Cet interpréteur système ne sert qu'à atteindre `make` (Python 3.10 ou plus).
`.venv.erplibre` est bâti sur sa propre version, `conf/python-erplibre-version`,
par `EL_PYTHON_PROVIDER` (mise ou pyenv).
### Installer Python 3.10.14 ### Installer Python 3.10.14
Vous pouvez supprimer les fichiers restants dans votre répertoire personnel concernant l'installation de Python une fois les étapes complétées avec succès. Vous pouvez supprimer les fichiers restants dans votre répertoire personnel concernant l'installation de Python une fois les étapes complétées avec succès.

View file

@ -236,6 +236,10 @@ If these last steps to set up your development environment were unsuccessful, fo
## Manual Installation ## Manual Installation
This system interpreter only needs to reach `make` (Python 3.10 or later).
`.venv.erplibre` is built on its own version, `conf/python-erplibre-version`,
through `EL_PYTHON_PROVIDER` (mise or pyenv).
### Install Python 3.10.14 ### Install Python 3.10.14
You can delete the files that are left over in your home directory regarding the python installation when the steps have been completed succesfully. You can delete the files that are left over in your home directory regarding the python installation when the steps have been completed succesfully.

View file

@ -37,8 +37,12 @@ RUN cd $ODOO_PREFIX && \
ls -lha /ERPLibre && \ ls -lha /ERPLibre && \
python -m venv .venv.$ERPLIBRE_VERSION python -m venv .venv.$ERPLIBRE_VERSION
# Le venv des outils prend le python de l'image, celui d'Odoo : le RUN
# s'arrete ici si ce python ne sait pas lire la syntaxe de script/.
RUN cd $ODOO_PREFIX && \ RUN cd $ODOO_PREFIX && \
ls -lha /ERPLibre && \ ls -lha /ERPLibre && \
{ python -m compileall -q script/ || \
{ echo "Le python de l'image ($(python -V 2>&1)) ne lit pas script/ : prendre une version Odoo plus recente." ; exit 1 ; } ; } && \
python -m venv .venv.erplibre && \ python -m venv .venv.erplibre && \
source ./.venv.erplibre/bin/activate && \ source ./.venv.erplibre/bin/activate && \
pip3 install -r requirement/erplibre_require-ments.txt pip3 install -r requirement/erplibre_require-ments.txt

View file

@ -70,11 +70,21 @@ docker build -f Dockerfile.prod.pkg -t technolibre/erplibre:12.0-pkg .
``` ```
<!-- [en] --> <!-- [en] -->
`.venv.erplibre` is built on the image's own Python, Odoo's, without mise or
pyenv. The build stops when that Python cannot parse `script/`, which happens
with a deprecated Odoo version.
### Running ERPLibre using Docker-Compose ### Running ERPLibre using Docker-Compose
Go at the root of this git project. Go at the root of this git project.
<!-- [fr] --> <!-- [fr] -->
`.venv.erplibre` est bâti sur le Python de l'image, celui d'Odoo, sans mise ni
pyenv. La construction s'arrête quand ce Python ne sait pas lire `script/`, ce
qui arrive avec une version d'Odoo dépréciée.
### Exécuter ERPLibre avec Docker-Compose ### Exécuter ERPLibre avec Docker-Compose
Allez à la racine de ce projet git. Allez à la racine de ce projet git.

View file

@ -34,6 +34,11 @@ docker build -f Dockerfile.base -t technolibre/erplibre-base:12.0 .
docker build -f Dockerfile.prod.pkg -t technolibre/erplibre:12.0-pkg . docker build -f Dockerfile.prod.pkg -t technolibre/erplibre:12.0-pkg .
``` ```
`.venv.erplibre` est bâti sur le Python de l'image, celui d'Odoo, sans mise ni
pyenv. La construction s'arrête quand ce Python ne sait pas lire `script/`, ce
qui arrive avec une version d'Odoo dépréciée.
### Exécuter ERPLibre avec Docker-Compose ### Exécuter ERPLibre avec Docker-Compose
Allez à la racine de ce projet git. Allez à la racine de ce projet git.

View file

@ -34,6 +34,11 @@ docker build -f Dockerfile.base -t technolibre/erplibre-base:12.0 .
docker build -f Dockerfile.prod.pkg -t technolibre/erplibre:12.0-pkg . docker build -f Dockerfile.prod.pkg -t technolibre/erplibre:12.0-pkg .
``` ```
`.venv.erplibre` is built on the image's own Python, Odoo's, without mise or
pyenv. The build stops when that Python cannot parse `script/`, which happens
with a deprecated Odoo version.
### Running ERPLibre using Docker-Compose ### Running ERPLibre using Docker-Compose
Go at the root of this git project. Go at the root of this git project.

View file

@ -1,6 +1,58 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# TODO deprecated, this is moved into install_locally.sh to be sure venv is installed and force take specified supported version # © 2021-2026 TechnoLibre (http://www.technolibre.ca)
python3 -m venv .venv.erplibre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
source .venv.erplibre/bin/activate #
pip3 install -r requirement/erplibre_require-ments.txt # Pose le venv d'OUTILLAGE d'ERPLibre, sans qu'une version d'Odoo soit
npm install # choisie : chemin et version se lisent dans conf/, versionné, et non par
# env_var.sh, qui exige .python-odoo-version. Se lance depuis la racine du
# dépôt.
CONF_VENV="conf/python-erplibre-venv"
CONF_PYTHON_VERSION="conf/python-erplibre-version"
for conf_file in "${CONF_VENV}" "${CONF_PYTHON_VERSION}"; do
if [[ ! -f "${conf_file}" ]]; then
echo "Configuration introuvable : ${conf_file}"
echo " Ce script se lance depuis la racine du depot :"
echo " cd <racine du depot> && ./script/install/install_erplibre.sh"
exit 1
fi
done
# « xargs » retire espaces et retour de ligne autour de la valeur.
VENV_ERPLIBRE_PATH=$(xargs < "${CONF_VENV}")
PYTHON_ERPLIBRE_VERSION=$(xargs < "${CONF_PYTHON_VERSION}")
echo -e "Install ${VENV_ERPLIBRE_PATH} with ${PYTHON_ERPLIBRE_VERSION}"
if ! ./script/install/install_venv.sh \
"ERPLibre" "${VENV_ERPLIBRE_PATH}" "${PYTHON_ERPLIBRE_VERSION}"; then
echo "Echec de creation de ${VENV_ERPLIBRE_PATH}, arret."
exit 1
fi
# Un venv rebâti n'a plus bin/repo ; install_git_repo.sh ne le pose que s'il
# manque. TODO s'en passe : seules les synchronisations de dépôts l'exigent,
# donc un échec (hors ligne) est signalé sans arrêter l'installation.
if ! ./script/install/install_git_repo.sh; then
echo "git-repo n'est pas pose dans ${VENV_ERPLIBRE_PATH} : 'repo sync' en a besoin."
echo " Rejouez quand le reseau le permet : ./script/install/install_git_repo.sh"
fi
# el_pip_install vise le venv par son chemin : aucune activation n'est requise.
# shellcheck source=script/install/lib_pip_provider.sh
. ./script/install/lib_pip_provider.sh
if ! el_pip_install "${VENV_ERPLIBRE_PATH}" \
-r requirement/erplibre_require-ments.txt; then
echo "Echec d'installation des dependances Python dans ${VENV_ERPLIBRE_PATH}."
echo " Relancez : ./script/install/install_erplibre.sh"
exit 1
fi
# Prettier ne sert qu'au formatage XML (« make format ») : son échec est
# signalé sans faire échouer une installation Python complète.
if ! npm install; then
echo "npm install a echoue : prettier et son greffon XML ne sont pas poses."
echo " ${VENV_ERPLIBRE_PATH} est utilisable ; seul 'make format' les reclame."
echo " Rejouez quand le reseau le permet : npm install"
fi
exit 0

View file

@ -1,14 +1,44 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VENV_ERPLIBRE_PATH=$(cat "conf/python-erplibre-venv" | xargs) # Se lance depuis la racine du dépôt : ailleurs, le chemin de venv serait vide
# et la cible deviendrait « /bin/repo ».
CONF_VENV="conf/python-erplibre-venv"
if [[ ! -f "${CONF_VENV}" ]]; then
echo "Configuration introuvable : ${CONF_VENV}"
echo " cd <racine du depot> && ./script/install/install_git_repo.sh"
exit 1
fi
VENV_ERPLIBRE_PATH=$(xargs < "${CONF_VENV}")
VENV_REPO_PATH=${VENV_ERPLIBRE_PATH}/bin/repo VENV_REPO_PATH=${VENV_ERPLIBRE_PATH}/bin/repo
# Install git-repo if missing # Install git-repo if missing
if [[ ! -f ${VENV_REPO_PATH} ]]; then if [[ ! -f ${VENV_REPO_PATH} ]]; then
echo "\n---- Install git-repo from Google APIS ----" if [[ ! -d "${VENV_ERPLIBRE_PATH}/bin" ]]; then
curl https://storage.googleapis.com/git-repo-downloads/repo > ${VENV_REPO_PATH} echo "Venv ${VENV_ERPLIBRE_PATH} absent : posez-le avant git-repo."
chmod +x ${VENV_REPO_PATH} echo " ./script/install/install_erplibre.sh"
sed -i 1d ${VENV_REPO_PATH} exit 1
fi
echo -e "\n---- Install git-repo from Google APIS ----"
# Téléchargé à côté, contrôlé, puis déplacé : sans « -f », curl écrit la
# page d'une erreur HTTP et rend 0 ; un corps vide passe le statut.
REPO_TMP="$(mktemp)" || exit 1
if ! curl -fsSL -o "${REPO_TMP}" \
https://storage.googleapis.com/git-repo-downloads/repo \
|| [[ ! -s "${REPO_TMP}" ]]; then
echo "Telechargement de git-repo impossible ou vide (reseau ou cache) :"
echo " ${VENV_REPO_PATH} n'est pas pose."
rm -f "${REPO_TMP}"
exit 1
fi
# Le hashbang du venv remplace celui du python3 du système.
sed -i 1d "${REPO_TMP}"
PYTHON_HASHBANG="#!./${VENV_ERPLIBRE_PATH}/bin/python" PYTHON_HASHBANG="#!./${VENV_ERPLIBRE_PATH}/bin/python"
sed -i "1 i ${PYTHON_HASHBANG}" ${VENV_REPO_PATH} sed -i "1 i ${PYTHON_HASHBANG}" "${REPO_TMP}"
if ! mv "${REPO_TMP}" "${VENV_REPO_PATH}"; then
echo "Mise en place de ${VENV_REPO_PATH} impossible."
rm -f "${REPO_TMP}"
exit 1
fi
# mktemp crée en 0600.
chmod 755 "${VENV_REPO_PATH}"
fi fi

View file

@ -37,6 +37,21 @@ EL_CA_BUNDLE=/var/lib/erplibre/ca-bundle.crt
[ -r "${EL_CA_BUNDLE}" ] || EL_CA_BUNDLE="" [ -r "${EL_CA_BUNDLE}" ] || EL_CA_BUNDLE=""
EL_LOCALE=${EL_LOCALE:-$(lire_seed locale)} EL_LOCALE=${EL_LOCALE:-$(lire_seed locale)}
EL_TZ=${EL_TZ:-$(lire_seed timezone)} EL_TZ=${EL_TZ:-$(lire_seed timezone)}
# « 3.12.10 » donne « python312 », le nom du paquet nixpkgs. Les deux versions
# du dépôt sont déclarées : celle d'Odoo et celle de l'outillage. Le second est
# VIDE quand elles coïncident, un même paquet nommé deux fois entrant en
# collision dans le profil.
el_nix_python_pkg() {
local v
v="$(xargs < "$1" 2> /dev/null)"
[ -n "${v}" ] || return 0
echo "python$(echo "${v}" | cut -d. -f1)$(echo "${v}" | cut -d. -f2)"
}
EL_PY_ODOO_PKG="$(el_nix_python_pkg .python-odoo-version)"
EL_PY_ODOO_PKG="${EL_PY_ODOO_PKG:-python312}"
EL_PY_TOOLS_PKG="$(el_nix_python_pkg conf/python-erplibre-version)"
[ "${EL_PY_TOOLS_PKG}" = "${EL_PY_ODOO_PKG}" ] && EL_PY_TOOLS_PKG=""
MODULE_SRC="conf/nixos/erplibre.nix" MODULE_SRC="conf/nixos/erplibre.nix"
MODULE_DST="/etc/nixos/erplibre.nix" MODULE_DST="/etc/nixos/erplibre.nix"
CONFIG="/etc/nixos/configuration.nix" CONFIG="/etc/nixos/configuration.nix"
@ -57,9 +72,12 @@ echo -e "\n---- Module ERPLibre pour NixOS ----"
sed -e "s/@EL_USER@/${EL_USER}/g" -e "s#@EL_DIR@#${EL_DIR}#g" \ sed -e "s/@EL_USER@/${EL_USER}/g" -e "s#@EL_DIR@#${EL_DIR}#g" \
-e "s/@EL_LOCALE@/${EL_LOCALE}/g" -e "s#@EL_TZ@#${EL_TZ}#g" \ -e "s/@EL_LOCALE@/${EL_LOCALE}/g" -e "s#@EL_TZ@#${EL_TZ}#g" \
-e "s#@EL_CA_BUNDLE@#${EL_CA_BUNDLE}#g" \ -e "s#@EL_CA_BUNDLE@#${EL_CA_BUNDLE}#g" \
-e "s/@EL_PY_ODOO_PKG@/${EL_PY_ODOO_PKG}/g" \
-e "s/@EL_PY_TOOLS_PKG@/${EL_PY_TOOLS_PKG}/g" \
"${MODULE_SRC}" | sudo tee "${MODULE_DST}" > /dev/null "${MODULE_SRC}" | sudo tee "${MODULE_DST}" > /dev/null
echo " posé : ${MODULE_DST} (compte ${EL_USER}, dépôt ${EL_DIR})" echo " posé : ${MODULE_DST} (compte ${EL_USER}, dépôt ${EL_DIR})"
echo " autorité du cache : ${EL_CA_BUNDLE:-aucune}" echo " autorité du cache : ${EL_CA_BUNDLE:-aucune}"
echo " Python : ${EL_PY_ODOO_PKG} (Odoo)${EL_PY_TOOLS_PKG:+, ${EL_PY_TOOLS_PKG} (outillage)}"
echo " régional : locale « ${EL_LOCALE:-non demandée} »," \ echo " régional : locale « ${EL_LOCALE:-non demandée} »," \
"fuseau « ${EL_TZ:-non demandé} »" "fuseau « ${EL_TZ:-non demandé} »"

View file

@ -9,8 +9,13 @@ fi
# Assign arguments to variables # Assign arguments to variables
CONTEXT="$1" CONTEXT="$1"
VENV_PATH="$2" # Sans « / » final : « rm -rf lien/ » viderait la cible d'un lien.
VENV_PATH="${2%"${2##*[!/]}"}"
PYTHON_VERSION="$3" PYTHON_VERSION="$3"
if [ -z "${VENV_PATH}" ]; then
echo "Error: Venv_Path '$2' designates the root."
exit 1
fi
# Display variables (for verification) # Display variables (for verification)
echo "Context: $CONTEXT" echo "Context: $CONTEXT"
@ -18,20 +23,140 @@ echo "Venv Path: $VENV_PATH"
echo "Python Version: $PYTHON_VERSION" echo "Python Version: $PYTHON_VERSION"
# Le CHOIX du fournisseur (mise ou pyenv) vit dans la bibliothèque : ce script # 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 # ne connaît qu'un chemin d'interpréteur.
# troisième sans toucher ici.
# shellcheck source=script/install/lib_python_provider.sh # shellcheck source=script/install/lib_python_provider.sh
. ./script/install/lib_python_provider.sh . ./script/install/lib_python_provider.sh
PYTHON_EXEC="$(el_python_exec "${PYTHON_VERSION}")" # Ce qui borne le PATCH, et pour qui.
#
# Le venv d'Odoo est contraint par son pyproject (« >=3.12.10,<3.13 ») : un
# patch inférieur y serait refusé par Poetry. Rien n'épingle celui de
# l'outillage — exiger le patch y écarte le Python des distributions dès
# qu'il est d'un cran en retard, et force pyenv à compiler CPython pour rien.
EXIGENCE="patch"
[ "${CONTEXT}" = "ERPLibre" ] && EXIGENCE="mineure"
# Version RÉELLE d'un venv : celle que rend son interpréteur, jamais celle
# qu'annonce pyvenv.cfg, figée à la création et que rien ne revalide. Rien
# n'est rendu quand bin/python ne s'exécute plus — un venv bâti sur un CPython
# depuis retiré garde un lien mort, et c'est justement un venv à rebâtir.
el_venv_python_version() {
local py="$1/bin/python"
[ -x "${py}" ] || return 1
"${py}" -c 'import platform;print(platform.python_version())' 2> /dev/null
}
# Le venv est-il HORS SERVICE — non pas d'une autre version, mais inutilisable
# tel quel ? Ce qui suit ne se répare pas : on rebâtit, quel que soit l'appelant.
el_venv_hors_service() {
local py="$1/bin/python"
if [[ ! -f "$1/pyvenv.cfg" ]]; then
echo "aucun pyvenv.cfg"
elif [[ -z "$(el_venv_python_version "$1")" ]]; then
echo "son interpreteur ne s'execute plus"
elif [[ ! -f "$1/bin/activate" ]] || ! "${py}" -m pip --version > /dev/null 2>&1; then
# Ce que laisse un « python -m venv » sans ensurepip.
echo "bin/activate ou pip manquant"
fi
}
# Le venv tourne, mais pas sur la version demandée. La version est celle que
# rend bin/python, pas celle de pyvenv.cfg, figée à la création. Compatible et
# non identique : un python de distribution d'un patch plus récent convient, et
# l'égalité stricte rebâtirait à chaque appel.
el_venv_version_autre() {
if ! el_python_is_compatible \
"$1/bin/python" "${PYTHON_VERSION}" "${EXIGENCE}"; then
echo "bin/python en $(el_venv_python_version "$1"), ${PYTHON_VERSION} exige"
fi
}
# Ce qui oblige à rebâtir, ou rien.
#
# Une version qui diffère ne vaut destruction que pour le venv d'OUTILLAGE :
# le rebâtir coûte une installation pip, quand celui d'Odoo représente une
# installation Poetry entière — des centaines de paquets, dont certains se
# compilent. Un écart de patch ne l'empêche pas de servir ; on le dit, et on le
# laisse. Hors service, en revanche, il ne sert plus personne.
el_venv_a_rebatir() {
local cause
cause="$(el_venv_hors_service "$1")"
if [[ -n "${cause}" ]]; then
echo "${cause}"
return
fi
cause="$(el_venv_version_autre "$1")"
if [[ -n "${cause}" ]] && [[ "${CONTEXT}" == "ERPLibre" ]]; then
echo "${cause}"
fi
}
# Un checkout MONTÉ depuis une autre machine porte le venv de cette machine.
# Son interpréteur ne démarre pas ici — un CPython précompilé cherche sa
# bibliothèque standard sous le préfixe de sa construction —, ce qui le fait
# passer pour défectueux. Le détruire effacerait, à travers le réseau,
# l'installation de la machine à qui il appartient.
# findmnt vient d'util-linux : absent ailleurs, le contrôle s'abstient plutôt
# que de refuser à tort.
el_venv_est_distant() {
local fs
command -v findmnt > /dev/null 2>&1 || return 1
fs="$(findmnt -n -o FSTYPE --target "$1" 2> /dev/null)"
case "${fs}" in
fuse* | sshfs | nfs* | cifs | smb*) return 0 ;;
*) return 1 ;;
esac
}
PYTHON_EXEC="$(el_python_exec "${PYTHON_VERSION}" "${EXIGENCE}")"
if [[ -z "${PYTHON_EXEC}" ]] || [[ ! -x "${PYTHON_EXEC}" ]]; then if [[ -z "${PYTHON_EXEC}" ]] || [[ ! -x "${PYTHON_EXEC}" ]]; then
echo "Aucun interpreteur Python ${PYTHON_VERSION} n'a pu etre obtenu." echo "Aucun interpreteur Python ${PYTHON_VERSION} n'a pu etre obtenu."
echo " Fournisseur demande : ${EL_PYTHON_PROVIDER:-auto}" echo " Fournisseur demande : ${EL_PYTHON_PROVIDER:-auto}"
if command -v mise > /dev/null 2>&1; then
echo " Voir : mise install python@${PYTHON_VERSION} (reseau requis)"
else
echo " Voir 'make install_mise', ou installez pyenv." echo " Voir 'make install_mise', ou installez pyenv."
fi
exit 1 exit 1
fi fi
echo "Interpreteur retenu : ${PYTHON_EXEC}" echo "Interpreteur retenu : ${PYTHON_EXEC}"
# L'interpréteur est obtenu AVANT toute destruction : un provisionnement en
# échec laisse le venv en place.
if [[ -d ${VENV_PATH} ]]; then
REASON="$(el_venv_a_rebatir "${VENV_PATH}")"
CONSERVE="$(el_venv_version_autre "${VENV_PATH}")"
if [[ -z "${REASON}" ]] && [[ -n "${CONSERVE}" ]]; then
echo "Virtual environment ${VENV_PATH} conserve : ${CONSERVE}."
echo " Le rebatir refait toute son installation. Si vous le voulez :"
echo " rm -rf ${VENV_PATH} puis relancez."
fi
# rmdir n'écarte qu'un répertoire vide, ce que laisse une création
# interrompue ; seul un pyvenv.cfg autorise le « rm -rf ».
if [[ -n "${REASON}" ]] && ! rmdir "${VENV_PATH}" 2> /dev/null; then
if [[ ! -f "${VENV_PATH}/pyvenv.cfg" ]]; then
echo "Refus de detruire ${VENV_PATH} : ce n'est pas un venv (${REASON})."
echo " Ecartez-le vous-meme, puis relancez."
exit 1
fi
if el_venv_est_distant "${VENV_PATH}"; then
echo "Refus de detruire ${VENV_PATH} : il est sur un systeme de"
echo " fichiers monte a distance, donc il appartient a une autre"
echo " machine (${REASON} vu d'ici)."
echo " Installez SUR cette machine-la, pas au travers du montage."
exit 1
fi
echo "DESTRUCTION de ${VENV_PATH} : ${REASON}."
echo " Ce qui y a ete pose a la main part avec lui ; il est rebati."
if ! rm -rf "${VENV_PATH}"; then
echo "Destruction de ${VENV_PATH} en echec, probablement faute de droits :"
echo " le venv est peut-etre partiellement detruit. Rien n'est rebati."
echo " Corrigez les droits, puis : rm -rf ${VENV_PATH} et relancez."
exit 1
fi
fi
fi
if [[ ! -d ${VENV_PATH} ]]; then if [[ ! -d ${VENV_PATH} ]]; then
echo -e "\n---- Create Virtual environment Python ----" echo -e "\n---- Create Virtual environment Python ----"
if ! "${PYTHON_EXEC}" -m venv "${VENV_PATH}"; then if ! "${PYTHON_EXEC}" -m venv "${VENV_PATH}"; then
@ -39,3 +164,13 @@ if [[ ! -d ${VENV_PATH} ]]; then
exit 1 exit 1
fi fi
fi fi
# Contrôle final : l'appelant enchaîne sur ce venv, un 0 doit le garantir.
# Hors service seulement : un venv d'une autre version qu'on vient de conserver
# sciemment reste utilisable.
REASON="$(el_venv_hors_service "${VENV_PATH}")"
if [[ ! -d ${VENV_PATH} ]] || [[ -n "${REASON}" ]]; then
echo "Virtual environment ${VENV_PATH} inutilisable : ${REASON:-absent}."
exit 1
fi
echo "Virtual environment ${VENV_PATH} pret."

View file

@ -55,22 +55,29 @@ el_python_is_version() {
[ "${got}" = "${want}" ] [ "${got}" = "${want}" ]
} }
# Vrai si l'exécutable CONVIENT : même majeure.mineure, et patch au moins # Vrai si l'exécutable CONVIENT. Deux exigences, selon ce qui borne l'appelant.
# é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 # patch même majeure.mineure, et patch au moins égal. C'est ce que le
# 3.12.13 : parfaitement utilisable, mais rejeté par l'égalité, ce qui forçait # pyproject d'Odoo demande — « >=3.12.10,<3.13 » —, et Poetry
# pyenv à COMPILER CPython — et gcc 15.2 s'y arrête sur une erreur interne # refuserait un patch inférieur.
# dans Parser/parser.c, un fichier généré de quarante mille lignes. # mineure même majeure.mineure, quel que soit le patch. RIEN n'épingle le
# patch du venv d'OUTILLAGE : l'exiger écarte le Python des
# distributions dès qu'il est d'un patch en retard — NixOS 25.11
# livre 3.14.2 quand conf/ demande 3.14.7 — et force pyenv à
# COMPILER CPython pour une différence qui ne gêne personne.
#
# L'égalité stricte, elle, rejetterait aussi un patch plus RÉCENT : c'est
# el_python_is_version, et elle ne sert qu'aux fournisseurs qui posent une
# version nommée.
el_python_is_compatible() { el_python_is_compatible() {
local exe="$1" want="$2" got local exe="$1" want="$2" exigence="${3:-patch}" got
[ -x "${exe}" ] || return 1 [ -x "${exe}" ] || return 1
got="$("${exe}" -c 'import platform;print(platform.python_version())' \ got="$("${exe}" -c 'import platform;print(platform.python_version())' \
2> /dev/null)" 2> /dev/null)"
[ -n "${got}" ] || return 1 [ -n "${got}" ] || return 1
# Même majeure.mineure : 3.13 ne convient pas à un pyproject borné à <3.13. # Même majeure.mineure : 3.13 ne convient pas à un pyproject borné à <3.13.
[ "${got%.*}" = "${want%.*}" ] || return 1 [ "${got%.*}" = "${want%.*}" ] || return 1
[ "${exigence}" = "mineure" ] && return 0
# Patch au moins égal, comparé en version et non en chaîne (3.12.9 < 3.12.10). # 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}" ] [ "$(printf '%s\n%s\n' "${want}" "${got}" | sort -V | head -1)" = "${want}" ]
} }
@ -81,7 +88,7 @@ el_python_is_compatible() {
# prend des dizaines de minutes quand il aboutit. Le nom suit la convention de # prend des dizaines de minutes quand il aboutit. Le nom suit la convention de
# toutes les distributions, « python3.12 ». # toutes les distributions, « python3.12 ».
el_distro_python_exec() { el_distro_python_exec() {
local want="$1" exe local want="$1" exigence="${2:-patch}" exe
# Des chemins SYSTEME, jamais « command -v » : dans un venv activé celui-ci # 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 # 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. # d'une session interactive n'a rien à faire dans cette décision.
@ -94,7 +101,7 @@ el_distro_python_exec() {
# pas, et la boucle passe a la suite sans rien couter. # pas, et la boucle passe a la suite sans rien couter.
for exe in "/run/current-system/sw/bin/python${want%.*}" \ for exe in "/run/current-system/sw/bin/python${want%.*}" \
"/usr/bin/python${want%.*}" "/usr/local/bin/python${want%.*}"; do "/usr/bin/python${want%.*}" "/usr/local/bin/python${want%.*}"; do
if el_python_is_compatible "${exe}" "${want}"; then if el_python_is_compatible "${exe}" "${want}" "${exigence}"; then
echo "${exe}" echo "${exe}"
return 0 return 0
fi fi
@ -175,10 +182,31 @@ el_pyenv_install() {
echo "${exe}" echo "${exe}"
} }
# Annonce la compilation pyenv AVANT de la subir, et le geste qui l'évite :
# poser mise, ou lire ce que mise reproche quand il est déjà là. Muet si pyenv
# porte déjà la version, puisque rien ne sera compilé.
el_warn_pyenv_fallback() {
local version="$1"
[ -d "$(el_pyenv_root)/versions/${version}" ] && return 0
if command -v mise > /dev/null 2>&1; then
echo "mise n'a pas pu fournir Python ${version} : repli sur pyenv," >&2
echo " qui COMPILE CPython. Pour lire ce que mise reproche :" >&2
echo " mise install python@${version} (reseau requis)" >&2
else
echo "mise est absent : pyenv va etre pose, puis COMPILER CPython" >&2
echo " ${version} -- quelques minutes, bien plus sous emulation, et il" >&2
echo " lui faut une douzaine de -dev (openssl, zlib, readline, sqlite," >&2
echo " bzip2, xz, tk). Pour l'eviter : Ctrl+C, puis" >&2
echo " make install_mise" >&2
echo " qui pose un CPython precompile en quelques secondes, et relancez." >&2
fi
}
# API publique : imprime le chemin absolu d'un interpréteur de cette version, # 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. # ou rien (et rend non nul) si aucun fournisseur n'y parvient.
el_python_exec() { el_python_exec() {
local version="$1" provider="${EL_PYTHON_PROVIDER:-auto}" exe local version="$1" exigence="${2:-patch}"
local provider="${EL_PYTHON_PROVIDER:-auto}" exe
# 1) Déjà présent ? On ne réinstalle rien et on ne touche pas au réseau. # 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 # C'est ce qui rend le changement sans effet pour une installation
@ -205,7 +233,7 @@ el_python_exec() {
# Uniquement en mode « auto » : demander mise ou pyenv explicitement doit # Uniquement en mode « auto » : demander mise ou pyenv explicitement doit
# être respecté, sinon le réglage ne veut plus rien dire. # être respecté, sinon le réglage ne veut plus rien dire.
if [ "${provider}" = "auto" ] \ if [ "${provider}" = "auto" ] \
&& exe="$(el_distro_python_exec "${version}")"; then && exe="$(el_distro_python_exec "${version}" "${exigence}")"; then
echo "Python $("${exe}" -V 2>&1 | awk '{print $2}') de la distribution :" \ echo "Python $("${exe}" -V 2>&1 | awk '{print $2}') de la distribution :" \
"aucune compilation." >&2 "aucune compilation." >&2
echo "${exe}" echo "${exe}"
@ -220,8 +248,7 @@ el_python_exec() {
return 0 return 0
fi fi
[ "${provider}" = "mise" ] && return 1 [ "${provider}" = "mise" ] && return 1
command -v mise > /dev/null 2>&1 \ el_warn_pyenv_fallback "${version}"
&& echo "mise n'a pas pu fournir Python ${version} : repli sur pyenv." >&2
fi fi
el_pyenv_install "${version}" el_pyenv_install "${version}"
} }

View file

@ -1,36 +1,13 @@
#!/bin/bash #!/usr/bin/env bash
DIR_VENV_ERPLIBRE_EXIST=1 # © 2021-2026 TechnoLibre (http://www.technolibre.ca)
DIR_VENV_ERPLIBRE=".venv.erplibre" # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
# If not exist, create it and do installation #
# Can be in conflict with ./script/install_locally.sh # Auto-installation de TODO : install_erplibre.sh pose ou rebâtit le venv
# d'outillage, puis TODO repart sur l'interpréteur de ce venv.
# Se lance depuis la racine du dépôt.
if [[ ! -d "$DIR_VENV_ERPLIBRE" ]]; then if ! ./script/install/install_erplibre.sh; then
DIR_VENV_ERPLIBRE_EXIST=0 echo "Installation d'ERPLibre en echec : TODO ne demarre pas."
echo "$DIR_VENV_ERPLIBRE does not exist." exit 1
# 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
python3 -m venv $DIR_VENV_ERPLIBRE
fi
fi fi
exec "./$(xargs < conf/python-erplibre-venv)/bin/python" ./script/todo/todo.py "$@"
# If exist, source it and start installation
source ./.venv.erplibre/bin/activate
if [[ $DIR_VENV_ERPLIBRE_EXIST -eq 0 ]]; then
pip install -r requirement/erplibre_require-ments.txt
fi
./script/todo/todo.py

View file

@ -153,10 +153,31 @@ class LeModule(unittest.TestCase):
with self.subTest(lib=lib): with self.subTest(lib=lib):
self.assertIn(lib, self.src) self.assertIn(lib, self.src)
def test_the_python_matches_what_the_repository_wants(self): def test_no_python_version_is_written_by_hand(self):
voulu = (RACINE / ".python-odoo-version").read_text().strip() """Une version écrite ici dériverait des fichiers du dépôt au premier
majeur, mineur = voulu.split(".")[:2] changement — et déclarer le seul Python d'Odoo laissait pyenv bâtir
self.assertIn(f"python{majeur}{mineur}", self.src) celui de l'outillage, dont la compilation s'arrête sur
« Modules/_cursesmodule.o »."""
self.assertNotRegex(self.src, r"python3\d\d")
self.assertIn("@EL_PY_ODOO_PKG@", self.src)
self.assertIn("@EL_PY_TOOLS_PKG@", self.src)
def test_both_pythons_of_the_repository_are_declared(self):
"""Le module les reçoit du script, qui les lit dans les deux fichiers
de version."""
script = SCRIPT.read_text(encoding="utf-8")
self.assertIn(".python-odoo-version", script)
self.assertIn("conf/python-erplibre-version", script)
def test_the_second_package_is_empty_when_both_agree(self):
"""Nommer deux fois le même paquet ferait entrer en collision deux
chemins identiques dans le profil."""
script = SCRIPT.read_text(encoding="utf-8")
self.assertRegex(
script,
r'EL_PY_TOOLS_PKG\}"?\s*=\s*"?\$\{EL_PY_ODOO_PKG'
r'|\[ "\$\{EL_PY_TOOLS_PKG\}" = "\$\{EL_PY_ODOO_PKG\}" \]',
)
def test_the_database_role_is_substituted(self): def test_the_database_role_is_substituted(self):
"""Le nom du compte varie d'un déploiement à l'autre ; l'écrire en dur """Le nom du compte varie d'un déploiement à l'autre ; l'écrire en dur
@ -335,7 +356,7 @@ class LeServiceEstDeclare(unittest.TestCase):
"""Une unité ne reçoit pas le PATH d'une session. run.sh lance des """Une unité ne reçoit pas le PATH d'une session. run.sh lance des
scripts dont le shebang est « env bash » : env est dans le PATH par scripts dont le shebang est « env bash » : env est dans le PATH par
défaut, bash non, et run.sh s'arrête avant Odoo.""" défaut, bash non, et run.sh s'arrête avant Odoo."""
self.assertIn("path = with pkgs; [ bash python312 ];", self.src) self.assertIn("path = with pkgs; [ bash @EL_PY_ODOO_PKG@ ];", self.src)
def test_the_repository_is_not_guessed(self): def test_the_repository_is_not_guessed(self):
"""Le service lance le dépôt QUI A POSÉ le module. Écrire """Le service lance le dépôt QUI A POSÉ le module. Écrire

View file

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""install_venv.sh détruit un venv : sous quelles conditions, exactement ?
C'est le seul endroit du dépôt qui efface le travail de quelqu'un. Un venv
porte ce qu'on y a posé à la main — bin/repo, uv, des paquets — et le nom
.venv.erplibre ne dit pas sa version : rien ne distingue de l'extérieur celui
qu'il faut rebâtir de celui qu'il faut garder.
Ces tests pèsent donc les REFUS autant que les destructions :
- un répertoire sans pyvenv.cfg n'est pas un venv et garde son contenu ;
- un venv sur un système de fichiers monté appartient à une autre machine, et
l'effacer le ferait à travers le réseau ;
- un venv utilisable est conservé, sans quoi chaque installation rebâtirait
ce qui marche.
L'interpréteur est un bouchon annoncé par PYENV_ROOT : les tests restent hors
réseau, et rien ne dépend de ce que cette machine a installé.
"""
import os
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
RACINE = Path(__file__).resolve().parents[1]
SCRIPT = RACINE / "script/install/install_venv.sh"
VOULUE = "9.99.0"
AUTRE = "9.98.0"
# Un python qui suffit à ce que le script lui demande : sa version, la création
# d'un venv, et la présence de pip.
BOUCHON = """#!/bin/sh
VERSION=__VERSION__
case "$1" in
-V|--version) echo "Python ${VERSION}" ;;
-c) echo "${VERSION}" ;;
-m)
case "$2" in
venv)
mkdir -p "$3/bin" || exit 1
printf 'version = %s\\n' "${VERSION}" > "$3/pyvenv.cfg"
sed "s/^VERSION=.*/VERSION=${VERSION}/" "$0" > "$3/bin/python"
chmod +x "$3/bin/python"
: > "$3/bin/activate"
;;
pip) echo "pip 99.0 from nowhere" ;;
*) exit 1 ;;
esac
;;
*) exit 1 ;;
esac
exit 0
"""
def pose_bouchon(chemin, version):
chemin.parent.mkdir(parents=True, exist_ok=True)
chemin.write_text(
BOUCHON.replace("__VERSION__", version), encoding="utf-8"
)
chemin.chmod(0o755)
class BancInstallVenv(unittest.TestCase):
"""Un PYENV_ROOT bouchonné, et un répertoire de travail jetable."""
def setUp(self):
self.coin = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.coin, ignore_errors=True)
pose_bouchon(
self.coin / "pyenv" / "versions" / VOULUE / "bin" / "python",
VOULUE,
)
self.bin = self.coin / "bin"
self.bin.mkdir()
def env(self):
return dict(
os.environ,
PYENV_ROOT=str(self.coin / "pyenv"),
PATH=f"{self.bin}:{os.environ['PATH']}",
EL_PYTHON_PROVIDER="pyenv",
)
def lance(self, chemin_venv, contexte="ERPLibre"):
return subprocess.run(
[str(SCRIPT), contexte, str(chemin_venv), VOULUE],
cwd=RACINE,
capture_output=True,
text=True,
env=self.env(),
)
def venv_factice(self, nom, version):
"""Un venv complet, comme « python -m venv » le laisse."""
chemin = self.coin / nom
(chemin / "bin").mkdir(parents=True)
(chemin / "pyvenv.cfg").write_text(f"version = {version}\n")
pose_bouchon(chemin / "bin" / "python", version)
(chemin / "bin" / "activate").touch()
(chemin / "TEMOIN").write_text("pose a la main\n")
return chemin
class TestCeQuIlConserve(BancInstallVenv):
def test_un_venv_a_la_bonne_version_est_garde(self):
venv = self.venv_factice("bon", VOULUE)
fin = self.lance(venv)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertTrue(
(venv / "TEMOIN").exists(), "il a été rebâti pour rien"
)
def test_un_chemin_neuf_est_simplement_cree(self):
venv = self.coin / "neuf"
fin = self.lance(venv)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertTrue((venv / "pyvenv.cfg").exists())
class TestCeQuIlDetruit(BancInstallVenv):
def test_un_venv_d_une_autre_version_est_rebati(self):
venv = self.venv_factice("perime", AUTRE)
fin = self.lance(venv)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertIn("DESTRUCTION", fin.stdout)
self.assertFalse((venv / "TEMOIN").exists())
self.assertIn(VOULUE, (venv / "pyvenv.cfg").read_text())
def test_la_destruction_est_annoncee_avant_d_avoir_lieu(self):
"""Dans un journal de milliers de lignes, l'avis doit précéder."""
venv = self.venv_factice("perime", AUTRE)
fin = self.lance(venv)
self.assertLess(
fin.stdout.index("DESTRUCTION"),
fin.stdout.index("Create Virtual environment"),
)
def test_un_venv_sans_pip_est_rebati(self):
"""Ce que laisse « python -m venv » quand ensurepip manque."""
venv = self.venv_factice("sans_pip", VOULUE)
(venv / "bin" / "activate").unlink()
fin = self.lance(venv)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertFalse((venv / "TEMOIN").exists())
class TestLExigenceDePatch(BancInstallVenv):
"""Le patch ne borne que le venv d'Odoo, contraint par son pyproject.
Sur le venv d'outillage, l'exiger écarterait le Python des distributions
dès qu'il est d'un cran en retard — et ferait compiler CPython pour une
différence que rien ne réclame."""
PATCH_PLUS_BAS = "9.99.0"
DEMANDE = "9.99.4"
def setUp(self):
super().setUp()
pose_bouchon(
self.coin / "pyenv" / "versions" / self.DEMANDE / "bin" / "python",
self.DEMANDE,
)
def joue(self, contexte, venv):
return subprocess.run(
[str(SCRIPT), contexte, str(venv), self.DEMANDE],
cwd=RACINE,
capture_output=True,
text=True,
env=self.env(),
)
def test_l_outillage_garde_un_patch_plus_ancien(self):
venv = self.venv_factice("outils", self.PATCH_PLUS_BAS)
fin = self.joue("ERPLibre", venv)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertTrue((venv / "TEMOIN").exists(), fin.stdout)
def test_odoo_refuse_un_patch_plus_ancien(self):
"""Poetry refuserait un patch inférieur à ce que borne le pyproject."""
venv = self.venv_factice("odoo", self.PATCH_PLUS_BAS)
fin = self.joue("Odoo", venv)
self.assertIn("conserve", fin.stdout + fin.stderr)
def test_une_autre_mineure_ne_passe_jamais(self):
venv = self.venv_factice("mineure", "9.98.0")
fin = self.joue("ERPLibre", venv)
self.assertIn("DESTRUCTION", fin.stdout)
class TestCeQueLeContexteDecide(BancInstallVenv):
"""Rebâtir le venv d'outillage coûte une installation pip ; rebâtir celui
d'Odoo en refait une de Poetry entière. L'écart de version ne vaut donc
destruction que pour le premier."""
def test_le_venv_odoo_d_une_autre_version_est_conserve(self):
venv = self.venv_factice("odoo", AUTRE)
fin = self.lance(venv, contexte="Odoo")
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertIn("conserve", fin.stdout)
self.assertTrue((venv / "TEMOIN").exists())
def test_le_venv_odoo_hors_service_est_rebati_quand_meme(self):
"""Une autre version se tolère ; un interpréteur mort, non."""
venv = self.venv_factice("odoo_casse", VOULUE)
(venv / "bin" / "python").write_text("#!/bin/sh\nexit 1\n")
fin = self.lance(venv, contexte="Odoo")
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertIn("DESTRUCTION", fin.stdout)
self.assertFalse((venv / "TEMOIN").exists())
class TestCeQuIlRefuse(BancInstallVenv):
def test_un_repertoire_sans_pyvenv_cfg_garde_son_contenu(self):
pas_un_venv = self.coin / "donnees"
(pas_un_venv / "sous").mkdir(parents=True)
(pas_un_venv / "sous" / "PRECIEUX").write_text("ne pas effacer\n")
fin = self.lance(pas_un_venv)
self.assertEqual(1, fin.returncode)
self.assertIn("Refus de detruire", fin.stdout)
self.assertTrue((pas_un_venv / "sous" / "PRECIEUX").exists())
def test_un_venv_monte_a_distance_est_epargne(self):
"""Il appartient à une autre machine : l'effacer traverse le réseau."""
faux_findmnt = self.bin / "findmnt"
faux_findmnt.write_text("#!/bin/sh\necho fuse.sshfs\n")
faux_findmnt.chmod(0o755)
venv = self.venv_factice("monte", AUTRE)
fin = self.lance(venv)
self.assertEqual(1, fin.returncode)
self.assertIn("monte a distance", fin.stdout)
self.assertTrue((venv / "TEMOIN").exists())
def test_un_repertoire_vide_ne_demande_pas_d_intervention(self):
"""Rien à perdre : rmdir suffit, et l'installation continue."""
vide = self.coin / "vide"
vide.mkdir()
fin = self.lance(vide)
self.assertEqual(0, fin.returncode, fin.stdout + fin.stderr)
self.assertNotIn("Refus", fin.stdout)
def test_la_racine_est_refusee(self):
fin = subprocess.run(
[str(SCRIPT), "ERPLibre", "/", VOULUE],
cwd=RACINE,
capture_output=True,
text=True,
env=self.env(),
)
self.assertEqual(1, fin.returncode)
self.assertNotIn("DESTRUCTION", fin.stdout)
class TestSansInterpreteur(BancInstallVenv):
def test_aucune_destruction_quand_l_interpreteur_manque(self):
"""L'interpréteur est obtenu AVANT : sinon on reste sans venv."""
venv = self.venv_factice("perime", AUTRE)
env = dict(self.env(), PYENV_ROOT=str(self.coin / "nulle_part"))
fin = subprocess.run(
[str(SCRIPT), "ERPLibre", str(venv), VOULUE],
cwd=RACINE,
capture_output=True,
text=True,
env=env,
)
self.assertEqual(1, fin.returncode)
self.assertTrue((venv / "TEMOIN").exists())
if __name__ == "__main__":
unittest.main()