2024-08-26 14:55:36 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
2024-08-26 14:55:36 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
2020-07-24 02:20:30 -04:00
|
|
|
import argparse
|
2020-08-06 05:36:48 -04:00
|
|
|
import ast
|
2022-01-24 01:39:49 -05:00
|
|
|
import logging
|
|
|
|
|
import os
|
2025-07-14 03:44:48 -04:00
|
|
|
import re
|
2025-07-19 05:34:25 -04:00
|
|
|
import shutil
|
[FIX] poetry update: run poetry in the Odoo venv, not the active one
Poetry works in the active virtualenv when VIRTUAL_ENV is set. Launched
from a shell under .venv.erplibre, poetry add and poetry lock inherited
its Python, outside the pyproject bounds, and failed with
InvalidCurrentPythonVersionError. Both calls now activate the Odoo venv
of the ERPLibre version, as install_locally.sh already does.
Checked: poetry env info -p reports .venv.erplibre before, the Odoo
venv after, for the shell script and for the Python call alike.
--- FR ---
[FIX] poetry update : poetry dans le venv Odoo, pas dans le venv actif
Poetry travaille dans le virtualenv actif quand VIRTUAL_ENV est posé.
Lancés d'un shell sous .venv.erplibre, poetry add et poetry lock en
héritaient le Python, hors des bornes du pyproject, et échouaient sur
InvalidCurrentPythonVersionError. Les deux appels activent désormais le
venv Odoo de la version ERPLibre, comme le fait déjà install_locally.sh.
Vérifié : poetry env info -p donne .venv.erplibre avant, le venv Odoo
après, pour le script shell comme pour l'appel Python.
Assisted-by: Claude Opus 5.5
2026-09-25 02:54:44 -04:00
|
|
|
import subprocess
|
[FIX] poetry update: stop on a missing pyproject.toml, offer the switch
The root pyproject.toml and poetry.lock are unversioned symlinks to the
active version's files in requirement/, created by the version switch.
Without them the script died on a FileNotFoundError traceback. It now
names the fix, make switch_odoo_XX for the active Odoo version; on a
terminal it offers to run it and restarts with the same arguments.
Checked: an existing file passes, a missing one exits 1 off a terminal
and on a declined prompt. The accepted path, which runs the switch, is
not tested.
--- FR ---
[FIX] poetry update : arrêt sur pyproject.toml absent, switch proposé
pyproject.toml et poetry.lock à la racine sont des liens non versionnés
vers les fichiers de la version active dans requirement/, créés par le
switch de version. Sans eux, le script mourait sur un FileNotFoundError.
Il nomme désormais le remède, make switch_odoo_XX pour la version Odoo
active ; dans un terminal, il propose de le lancer et se relance avec
les mêmes arguments. Vérifié : un fichier présent passe, un absent sort
en 1 hors terminal et sur un refus. L'acceptation, qui lance le switch,
n'est pas testée.
Assisted-by: Claude Opus 5.5
2026-09-25 02:54:33 -04:00
|
|
|
import sys
|
2020-07-24 02:20:30 -04:00
|
|
|
from collections import OrderedDict, defaultdict
|
|
|
|
|
from pathlib import Path
|
2022-01-24 01:39:49 -05:00
|
|
|
|
2020-07-24 02:20:30 -04:00
|
|
|
import iscompatible
|
2022-01-24 01:39:49 -05:00
|
|
|
import toml
|
2025-04-13 12:58:46 -04:00
|
|
|
from colorama import Fore, Style
|
2025-07-14 03:44:48 -04:00
|
|
|
from packaging.markers import default_environment
|
|
|
|
|
from packaging.requirements import Requirement
|
|
|
|
|
from packaging.version import Version
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
2025-07-14 03:44:48 -04:00
|
|
|
_logger.setLevel(logging.INFO)
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_config():
|
|
|
|
|
"""Parse command line arguments, extracting the config file name,
|
|
|
|
|
returning the union of config file and command line arguments
|
|
|
|
|
|
|
|
|
|
:return: dict of config file settings and command line arguments
|
|
|
|
|
"""
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
2021-06-27 00:49:00 -04:00
|
|
|
description="""\
|
2020-08-06 03:09:49 -04:00
|
|
|
Update pip dependency in Poetry, clear pyproject.toml, search all dependencies
|
2020-07-24 02:20:30 -04:00
|
|
|
from requirements.txt, search conflict version and generate a new list.
|
|
|
|
|
Launch Poetry installation.
|
2021-06-27 00:49:00 -04:00
|
|
|
""",
|
|
|
|
|
epilog="""\
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-d",
|
|
|
|
|
"--dir",
|
|
|
|
|
dest="dir",
|
|
|
|
|
default="./",
|
|
|
|
|
help="Path of repo to change remote, including submodule.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-f",
|
|
|
|
|
"--force",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Force create new list of dependency, ignore poetry.lock.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-v",
|
|
|
|
|
"--verbose",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="More information in execution, show stats.",
|
2020-07-24 02:20:30 -04:00
|
|
|
)
|
2021-09-28 00:44:05 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--dry",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Don't apply change, only show warning.",
|
|
|
|
|
)
|
2025-07-14 03:44:48 -04:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--set_version_poetry",
|
|
|
|
|
help="Force to change poetry version, default is from file './.poetry-version'.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--set_version_odoo",
|
|
|
|
|
help="Force to change odoo version, default is from file './.odoo-version'.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--set_version_python",
|
2025-07-19 05:34:25 -04:00
|
|
|
help="Force to change python version, default is from file './.python-odoo-version'.",
|
2025-07-14 03:44:48 -04:00
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--set_version_erplibre",
|
|
|
|
|
help="Force to change erplibre version, default is from file './.erplibre-version'.",
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
args = parser.parse_args()
|
2025-07-14 03:44:48 -04:00
|
|
|
if not args.set_version_poetry:
|
|
|
|
|
with open("./.poetry-version", "r", encoding="utf-8") as fichier:
|
|
|
|
|
args.set_version_poetry = fichier.read().strip()
|
|
|
|
|
if not args.set_version_odoo:
|
|
|
|
|
with open("./.odoo-version", "r", encoding="utf-8") as fichier:
|
|
|
|
|
args.set_version_odoo = fichier.read().strip()
|
|
|
|
|
if not args.set_version_python:
|
2025-07-19 05:34:25 -04:00
|
|
|
with open("./.python-odoo-version", "r", encoding="utf-8") as fichier:
|
2025-07-14 03:44:48 -04:00
|
|
|
args.set_version_python = fichier.read().strip()
|
|
|
|
|
if not args.set_version_erplibre:
|
|
|
|
|
with open("./.erplibre-version", "r", encoding="utf-8") as fichier:
|
|
|
|
|
args.set_version_erplibre = fichier.read().strip()
|
2020-07-24 02:20:30 -04:00
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
def get_lst_requirements_txt(
|
|
|
|
|
config, ignore_dir_startswith: list = None, force_add_item: list = None
|
|
|
|
|
):
|
|
|
|
|
return get_file_from_glob(
|
|
|
|
|
config,
|
|
|
|
|
"requirements.[tT][xX][tT]",
|
|
|
|
|
ignore_dir_startswith=ignore_dir_startswith,
|
|
|
|
|
force_add_item=force_add_item,
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
def get_lst_manifest_py(config, ignore_dir_startswith: list = None):
|
|
|
|
|
return get_file_from_glob(
|
|
|
|
|
config, "__manifest__.py", ignore_dir_startswith=ignore_dir_startswith
|
|
|
|
|
)
|
2024-11-02 02:09:09 -04:00
|
|
|
|
2024-11-24 22:15:19 -05:00
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
def get_file_from_glob(
|
|
|
|
|
config,
|
|
|
|
|
glob_txt,
|
|
|
|
|
ignore_dir_startswith: list = None,
|
|
|
|
|
force_add_item: list = None,
|
|
|
|
|
):
|
2024-11-02 02:09:09 -04:00
|
|
|
lst_v = []
|
2025-07-19 05:34:25 -04:00
|
|
|
# TODO take all groups odoo##.# from manifest, will create a dependency
|
|
|
|
|
# Hardcode logic from manifest
|
|
|
|
|
lst_path = [
|
2025-08-07 06:28:34 -04:00
|
|
|
Path(f"./odoo{config.set_version_odoo}/addons").rglob(glob_txt),
|
2025-07-19 05:34:25 -04:00
|
|
|
Path(f"./odoo{config.set_version_odoo}/odoo").rglob(glob_txt),
|
|
|
|
|
]
|
|
|
|
|
for gen_path in lst_path:
|
|
|
|
|
for a in gen_path:
|
|
|
|
|
a_dirname = os.path.dirname(a)
|
|
|
|
|
if a_dirname.startswith(".repo/") or a_dirname.startswith(".venv"):
|
2025-07-14 03:44:48 -04:00
|
|
|
continue
|
2025-07-19 05:34:25 -04:00
|
|
|
if ignore_dir_startswith:
|
|
|
|
|
ignore_it = False
|
|
|
|
|
for item_ignore_dir in ignore_dir_startswith:
|
|
|
|
|
if a_dirname.startswith(item_ignore_dir):
|
|
|
|
|
ignore_it = True
|
|
|
|
|
break
|
|
|
|
|
if ignore_it:
|
|
|
|
|
continue
|
|
|
|
|
lst_v.append(a)
|
2025-07-14 03:44:48 -04:00
|
|
|
if force_add_item:
|
|
|
|
|
for item in force_add_item:
|
|
|
|
|
lst_v.append(Path(item))
|
2024-11-02 02:09:09 -04:00
|
|
|
return lst_v
|
2020-08-06 05:36:48 -04:00
|
|
|
|
|
|
|
|
|
2020-07-24 02:20:30 -04:00
|
|
|
def combine_requirements(config):
|
|
|
|
|
"""
|
|
|
|
|
Search all module and version in all requirements.txt file in this project.
|
|
|
|
|
For each version, check compatibility and show warning with provenance file
|
2020-08-06 03:09:49 -04:00
|
|
|
Generate requirements.txt to "./.venv/build_dependency.txt"
|
2020-07-24 02:20:30 -04:00
|
|
|
:param config:
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
2025-07-14 03:44:48 -04:00
|
|
|
priority_filename_requirement = (
|
|
|
|
|
f"requirement/requirements.{config.set_version_erplibre}.txt"
|
|
|
|
|
)
|
|
|
|
|
second_priority_filename_requirement = (
|
|
|
|
|
f"odoo{config.set_version_odoo}/odoo/requirements.txt"
|
|
|
|
|
)
|
2023-02-14 17:32:25 -05:00
|
|
|
except_sign = ";"
|
|
|
|
|
lst_sign = ("==", "!=", ">=", "<=", "<", ">", except_sign, "~=")
|
2020-07-24 02:20:30 -04:00
|
|
|
lst_requirements = []
|
2025-07-14 03:44:48 -04:00
|
|
|
lst_replace_special_sign = {}
|
2020-07-24 02:20:30 -04:00
|
|
|
ignore_requirements = ["sys_platform == 'win32'", "python_version < '3.7'"]
|
|
|
|
|
dct_requirements = defaultdict(set)
|
2025-07-14 03:44:48 -04:00
|
|
|
lst_requirements_file = get_lst_requirements_txt(
|
|
|
|
|
config,
|
|
|
|
|
ignore_dir_startswith=["script/"],
|
|
|
|
|
force_add_item=[priority_filename_requirement],
|
|
|
|
|
)
|
2024-08-25 01:34:06 -04:00
|
|
|
# lst_requirements_with_condition = set()
|
|
|
|
|
# dct_special_condition = defaultdict(list)
|
2025-03-22 02:41:07 -04:00
|
|
|
|
|
|
|
|
if not os.path.isfile(priority_filename_requirement):
|
|
|
|
|
_logger.error(f"File {priority_filename_requirement} not found.")
|
|
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
print(f"Find {len(lst_requirements_file)} requirements.txt files.")
|
|
|
|
|
|
2020-07-24 02:20:30 -04:00
|
|
|
dct_requirements_module_filename = defaultdict(list)
|
|
|
|
|
for requirements_filename in lst_requirements_file:
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(requirements_filename, "r") as f:
|
2020-07-24 02:20:30 -04:00
|
|
|
for a in f.readlines():
|
|
|
|
|
b = a.strip()
|
|
|
|
|
if not b or b[0] == "#":
|
|
|
|
|
continue
|
2024-08-25 01:34:06 -04:00
|
|
|
if " @ " in b:
|
|
|
|
|
# Support when requirement line is like "package @ git+https://URL"
|
|
|
|
|
b = b.split("@ ")[1]
|
|
|
|
|
if "#" in b:
|
|
|
|
|
# remove comments at the end of module
|
|
|
|
|
b = b[: b.index("#")].strip()
|
|
|
|
|
comment_depend = ""
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
# Défini pour TOUTE ligne : il est relu plus bas, y compris
|
|
|
|
|
# pour les lignes sans marqueur.
|
|
|
|
|
keep_marker = ""
|
2024-08-25 01:34:06 -04:00
|
|
|
if except_sign in b:
|
2025-07-14 03:44:48 -04:00
|
|
|
# current_python_version = platform.python_version()
|
|
|
|
|
# current_platform = sys.platform
|
|
|
|
|
current_platform_2 = default_environment()
|
|
|
|
|
current_platform_2["python_version"] = ".".join(
|
|
|
|
|
config.set_version_python.split(".")[:2]
|
|
|
|
|
)
|
|
|
|
|
current_platform_2["python_full_version"] = (
|
|
|
|
|
config.set_version_python
|
|
|
|
|
)
|
|
|
|
|
current_platform_2["implementation_version"] = (
|
|
|
|
|
config.set_version_python
|
|
|
|
|
)
|
|
|
|
|
req = Requirement(b)
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
# Le nom de l'artefact généré — pyproject.odooXX_pythonYY —
|
|
|
|
|
# fige la version d'Odoo et celle de Python, PAS
|
|
|
|
|
# l'architecture. Évaluer « python_version » ici est donc
|
|
|
|
|
# juste ; évaluer « platform_machine » y graverait
|
|
|
|
|
# silencieusement l'architecture de la machine qui génère,
|
|
|
|
|
# et le lock produit sur x86 devient faux sur s390x.
|
|
|
|
|
# Un tel marqueur est donc CONSERVÉ et transmis à Poetry,
|
|
|
|
|
# qui sait résoudre les deux branches dans un seul lock.
|
|
|
|
|
if "platform_machine" in str(req.marker):
|
|
|
|
|
keep_marker = str(req.marker)
|
|
|
|
|
else:
|
|
|
|
|
is_compatible = req.marker.evaluate(current_platform_2)
|
|
|
|
|
if not is_compatible:
|
|
|
|
|
continue
|
2025-07-14 03:44:48 -04:00
|
|
|
# this support python_version into comment_depend, check odoo/requirements.txt
|
|
|
|
|
# b, comment_depend = b.split(except_sign)
|
|
|
|
|
# b = b.strip()
|
|
|
|
|
# comment_depend = comment_depend.strip()
|
2024-08-25 01:34:06 -04:00
|
|
|
# print(comment_depend)
|
2025-07-14 03:44:48 -04:00
|
|
|
# Rewrite dependancy : module==version
|
|
|
|
|
b = req.name + str(req.specifier)
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
if keep_marker:
|
|
|
|
|
b += f" ; {keep_marker}"
|
|
|
|
|
|
|
|
|
|
# Deux lignes du même module portant des marqueurs DIFFÉRENTS
|
|
|
|
|
# ne sont pas un conflit : elles s'excluent mutuellement. La
|
|
|
|
|
# clé inclut donc le marqueur, sans quoi la détection de
|
|
|
|
|
# versions divergentes les opposerait et en éliminerait une.
|
|
|
|
|
#
|
|
|
|
|
# Le nom vient de PEP 508 et non d'un découpage : avec un
|
|
|
|
|
# marqueur, le premier opérateur de la ligne est souvent celui
|
|
|
|
|
# du marqueur (« != »), et le découpage rendrait
|
|
|
|
|
# « factur-x>=4.2 » comme nom de module — que la liste des
|
|
|
|
|
# ignorés ne retrouverait plus.
|
|
|
|
|
if keep_marker:
|
|
|
|
|
module_name = f"{req.name} ; {keep_marker}"
|
|
|
|
|
dct_requirements[module_name].add(b)
|
|
|
|
|
dct_requirements_module_filename[b].append(
|
|
|
|
|
str(requirements_filename)
|
|
|
|
|
)
|
|
|
|
|
continue
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
# Regroup requirement
|
|
|
|
|
for sign in lst_sign:
|
|
|
|
|
if sign in b:
|
|
|
|
|
# Exception, some time the sign ; can be first, just check
|
2021-06-27 00:49:00 -04:00
|
|
|
if (
|
2023-02-14 17:32:25 -05:00
|
|
|
sign != except_sign
|
|
|
|
|
and except_sign in b
|
|
|
|
|
and b.find(sign) > b.find(except_sign)
|
2021-06-27 00:49:00 -04:00
|
|
|
):
|
2023-02-14 17:32:25 -05:00
|
|
|
module_name = b[: b.find(except_sign)]
|
2020-07-24 02:20:30 -04:00
|
|
|
else:
|
2021-06-27 00:49:00 -04:00
|
|
|
module_name = b[: b.find(sign)]
|
2020-07-24 02:20:30 -04:00
|
|
|
module_name = module_name.strip()
|
|
|
|
|
# Special condition for ";", ignore it
|
2024-08-25 01:34:06 -04:00
|
|
|
# if except_sign in b:
|
|
|
|
|
# for ignore_string in ignore_requirements:
|
|
|
|
|
# if ignore_string in b:
|
|
|
|
|
# break
|
|
|
|
|
# if ignore_string in b:
|
|
|
|
|
# break
|
|
|
|
|
# # lst_requirements_with_condition.add(module_name)
|
|
|
|
|
# value = b[: b.find(except_sign)].strip()
|
|
|
|
|
# # dct_special_condition[module_name].append(b)
|
|
|
|
|
# else:
|
|
|
|
|
# value = b
|
|
|
|
|
value = b
|
2020-07-24 02:20:30 -04:00
|
|
|
dct_requirements[module_name].add(value)
|
|
|
|
|
filename = str(requirements_filename)
|
2021-06-27 00:49:00 -04:00
|
|
|
dct_requirements_module_filename[value].append(
|
|
|
|
|
filename
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
dct_requirements[b].add(b)
|
|
|
|
|
filename = str(requirements_filename)
|
|
|
|
|
dct_requirements_module_filename[b].append(filename)
|
|
|
|
|
lst_requirements.append(b)
|
|
|
|
|
|
2024-08-25 01:34:06 -04:00
|
|
|
dct_requirements = get_manifest_external_dependencies(
|
2025-07-14 03:44:48 -04:00
|
|
|
config, dct_requirements, lst_sign
|
2024-08-25 01:34:06 -04:00
|
|
|
)
|
2020-08-06 05:36:48 -04:00
|
|
|
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
# Un module dont les requirements donnent des variantes par marqueur ne doit
|
|
|
|
|
# pas recevoir en plus une entrée NUE. Les manifest.py des addons déclarent
|
|
|
|
|
# « factur-x » sans version ni marqueur : cette entrée s'ajouterait aux deux
|
|
|
|
|
# branches et poetry recevrait trois contraintes pour le même paquet, dont
|
|
|
|
|
# une sans condition — ce qui annulerait la distinction par architecture.
|
|
|
|
|
marked_names = {
|
|
|
|
|
key.split(";")[0].strip() for key in dct_requirements if ";" in key
|
|
|
|
|
}
|
|
|
|
|
for name in marked_names:
|
|
|
|
|
if name in dct_requirements:
|
|
|
|
|
del dct_requirements[name]
|
|
|
|
|
print(
|
|
|
|
|
f"{Fore.YELLOW}WARNING{Style.RESET_ALL} - '{name}' est déjà"
|
|
|
|
|
" décliné par marqueur : l'entrée sans condition est ignorée."
|
|
|
|
|
)
|
|
|
|
|
|
2022-02-15 13:34:01 -05:00
|
|
|
# Merge all requirement by insensitive
|
|
|
|
|
dct_requirement_insensitive = {}
|
|
|
|
|
copy_dct_requirements = dct_requirements.copy()
|
|
|
|
|
dct_requirements = defaultdict(set)
|
|
|
|
|
for req_name, set_info in copy_dct_requirements.items():
|
|
|
|
|
lower_req_name = req_name.lower()
|
|
|
|
|
associate_req_name = dct_requirement_insensitive.get(lower_req_name)
|
|
|
|
|
if not associate_req_name:
|
|
|
|
|
dct_requirement_insensitive[lower_req_name] = req_name
|
|
|
|
|
dct_requirements[req_name] = set_info
|
|
|
|
|
else:
|
|
|
|
|
dct_requirements[associate_req_name] = set.union(
|
|
|
|
|
dct_requirements[associate_req_name], set_info
|
|
|
|
|
)
|
|
|
|
|
|
2021-06-27 00:49:00 -04:00
|
|
|
dct_requirements_diff_version = {
|
|
|
|
|
k: v for k, v in dct_requirements.items() if len(v) > 1
|
|
|
|
|
}
|
2020-07-24 02:20:30 -04:00
|
|
|
# dct_requirements_same_version = {k: v for k, v in dct_requirements.items() if
|
|
|
|
|
# len(v) == 1}
|
|
|
|
|
if config.verbose:
|
|
|
|
|
# TODO show total repo to compare lst requirements
|
2021-06-27 00:49:00 -04:00
|
|
|
print(
|
|
|
|
|
f"Total requirements.txt {len(lst_requirements_file)}, total"
|
|
|
|
|
f" module {len(lst_requirements)}, unique module"
|
|
|
|
|
f" {len(dct_requirements)}, module with different version"
|
|
|
|
|
f" {len(dct_requirements_diff_version)}."
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
if dct_requirements_diff_version:
|
|
|
|
|
# Validate compatibility
|
2020-09-03 04:28:43 -04:00
|
|
|
for key, lst_requirement in dct_requirements_diff_version.items():
|
2020-07-24 02:20:30 -04:00
|
|
|
result = None
|
|
|
|
|
|
2020-09-03 04:28:43 -04:00
|
|
|
lst_version_requirement = []
|
|
|
|
|
for requirement in lst_requirement:
|
2025-07-19 05:34:25 -04:00
|
|
|
# if ".*" in requirement:
|
|
|
|
|
# requirement = requirement.replace(".*", "")
|
2023-02-14 17:32:25 -05:00
|
|
|
if "~=" in requirement:
|
|
|
|
|
old_requirement = requirement
|
|
|
|
|
requirement = requirement.replace("~=", "==")
|
|
|
|
|
lst_replace_special_sign[requirement] = old_requirement
|
2025-07-14 03:44:48 -04:00
|
|
|
|
2025-07-19 05:34:25 -04:00
|
|
|
requirement_begin = requirement.split(except_sign)[0]
|
|
|
|
|
match = re.search(r"[=<>~!]{1,2}(.*)", requirement_begin)
|
2025-07-14 03:44:48 -04:00
|
|
|
if not match:
|
2020-07-24 02:20:30 -04:00
|
|
|
# Ignore empty version
|
|
|
|
|
continue
|
2025-07-14 03:44:48 -04:00
|
|
|
match_version = match.group(1).strip()
|
2025-07-19 05:34:25 -04:00
|
|
|
if ".*" in match_version:
|
|
|
|
|
match_version = match_version.replace(".*", "")
|
2025-07-14 03:44:48 -04:00
|
|
|
match_sign = match.group(0).strip()[: -len(match_version)]
|
|
|
|
|
match_app_name = requirement[
|
|
|
|
|
: -(len(match_sign) + len(match_version))
|
|
|
|
|
]
|
2025-07-19 05:34:25 -04:00
|
|
|
match_version_format = match_version.replace("'", "").replace(
|
|
|
|
|
'"', ""
|
|
|
|
|
)
|
|
|
|
|
result_number = [(match_sign, Version(match_version_format))]
|
2025-07-14 03:44:48 -04:00
|
|
|
# result_number = iscompatible.parse_requirements(requirement)
|
|
|
|
|
if not result_number:
|
|
|
|
|
continue
|
2020-09-03 04:28:43 -04:00
|
|
|
lst_version_requirement.append((requirement, result_number))
|
2020-07-24 02:20:30 -04:00
|
|
|
# Check compatibility with all possibility
|
|
|
|
|
is_compatible = True
|
2020-09-03 04:28:43 -04:00
|
|
|
if len(lst_version_requirement) > 1:
|
2021-06-27 00:49:00 -04:00
|
|
|
highest_value = sorted(
|
2025-07-14 03:44:48 -04:00
|
|
|
lst_version_requirement, key=lambda tup: tup[1][0][1]
|
2021-06-27 00:49:00 -04:00
|
|
|
)[-1]
|
2020-09-03 04:28:43 -04:00
|
|
|
for version_requirement in lst_version_requirement:
|
2022-02-15 13:34:01 -05:00
|
|
|
# TODO support me in iscompatible lib
|
|
|
|
|
# check after ., because b can appear in number
|
2025-07-14 03:44:48 -04:00
|
|
|
# v_r_split = version_requirement[0].split(".", 1)
|
|
|
|
|
# if len(v_r_split) > 1 and "b" in v_r_split[1]:
|
|
|
|
|
# version_requirement_upd = version_requirement[0][
|
|
|
|
|
# : version_requirement[0].rindex("b")
|
|
|
|
|
# ]
|
|
|
|
|
# else:
|
|
|
|
|
# version_requirement_upd = version_requirement[0]
|
|
|
|
|
version_requirement_upd = version_requirement[0]
|
2025-07-19 05:34:25 -04:00
|
|
|
if ".*" in version_requirement_upd:
|
|
|
|
|
version_requirement_upd = (
|
|
|
|
|
version_requirement_upd.replace(".*", "")
|
|
|
|
|
)
|
2022-02-15 13:34:01 -05:00
|
|
|
try:
|
|
|
|
|
is_compatible &= iscompatible.iscompatible(
|
2025-07-14 03:44:48 -04:00
|
|
|
version_requirement_upd, highest_value[1][0][1]
|
2022-02-15 13:34:01 -05:00
|
|
|
)
|
|
|
|
|
except Exception as e:
|
2025-07-14 03:44:48 -04:00
|
|
|
_logger.error(e)
|
|
|
|
|
is_compatible &= iscompatible.iscompatible(
|
|
|
|
|
version_requirement_upd, highest_value[1][0][1]
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
if is_compatible:
|
|
|
|
|
result = highest_value[0]
|
|
|
|
|
else:
|
|
|
|
|
# Find the requirements file and print the conflict
|
|
|
|
|
# Take the version from Odoo by default, else take the more recent
|
|
|
|
|
odoo_value = None
|
2021-06-27 00:22:51 -04:00
|
|
|
erplibre_value = None
|
2020-09-03 04:28:43 -04:00
|
|
|
for version_requirement in lst_version_requirement:
|
2023-02-14 17:32:25 -05:00
|
|
|
key_require = version_requirement[0]
|
|
|
|
|
if key_require in lst_replace_special_sign.keys():
|
|
|
|
|
key_require = lst_replace_special_sign.get(
|
|
|
|
|
key_require
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
filename_1 = dct_requirements_module_filename.get(
|
2023-02-14 17:32:25 -05:00
|
|
|
key_require
|
2021-06-27 00:49:00 -04:00
|
|
|
)
|
2025-07-19 05:34:25 -04:00
|
|
|
if not filename_1:
|
|
|
|
|
_logger.error(
|
|
|
|
|
f"Cannot find key '{key_require}' into list of requirements."
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
if priority_filename_requirement in filename_1:
|
|
|
|
|
erplibre_value = version_requirement[0]
|
|
|
|
|
elif (
|
|
|
|
|
second_priority_filename_requirement
|
|
|
|
|
in filename_1
|
|
|
|
|
):
|
|
|
|
|
odoo_value = version_requirement[0]
|
2020-07-24 02:20:30 -04:00
|
|
|
|
2021-06-27 00:22:51 -04:00
|
|
|
if erplibre_value:
|
2021-06-27 00:49:00 -04:00
|
|
|
str_result_choose = (
|
|
|
|
|
f"Select {erplibre_value} because from ERPLibre"
|
|
|
|
|
)
|
2021-06-27 00:22:51 -04:00
|
|
|
result = erplibre_value
|
|
|
|
|
elif odoo_value:
|
2021-06-27 00:49:00 -04:00
|
|
|
str_result_choose = (
|
|
|
|
|
f"Select {odoo_value} because from Odoo"
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
result = odoo_value
|
|
|
|
|
else:
|
|
|
|
|
result = highest_value[0]
|
|
|
|
|
str_result_choose = f"Select highest value {result}"
|
|
|
|
|
str_versions = " VS ".join(
|
2021-06-27 00:49:00 -04:00
|
|
|
[
|
|
|
|
|
f"{a[0]} from"
|
|
|
|
|
f" {dct_requirements_module_filename.get(a[0])}"
|
|
|
|
|
for a in lst_version_requirement
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
print(
|
2024-02-23 13:01:10 -05:00
|
|
|
f"{Fore.YELLOW}WARNING{Style.RESET_ALL} - Not"
|
|
|
|
|
f" compatible {str_versions} - {str_result_choose}."
|
2021-06-27 00:49:00 -04:00
|
|
|
)
|
2020-09-03 04:28:43 -04:00
|
|
|
elif len(lst_version_requirement) == 1:
|
|
|
|
|
result = lst_version_requirement[0][0]
|
2020-07-24 02:20:30 -04:00
|
|
|
else:
|
|
|
|
|
result = key
|
|
|
|
|
|
|
|
|
|
if result:
|
2023-02-14 17:32:25 -05:00
|
|
|
dct_requirements[key] = {result}
|
2020-07-24 02:20:30 -04:00
|
|
|
else:
|
2020-09-03 04:28:43 -04:00
|
|
|
print(f"Internal error, missing result for {lst_requirement}.")
|
2020-07-24 02:20:30 -04:00
|
|
|
|
2020-08-06 03:09:49 -04:00
|
|
|
# Support ignored requirements
|
2025-11-25 23:39:04 -05:00
|
|
|
lst_ignore, ignore_requirement_file = get_list_ignored()
|
2020-08-06 03:09:49 -04:00
|
|
|
lst_ignored_key = []
|
|
|
|
|
for key in dct_requirements.keys():
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
# La clé n'est pas toujours un nom nu : selon la branche qui l'a créée
|
|
|
|
|
# elle porte un marqueur (« paquet ; platform_machine … ») ou une
|
|
|
|
|
# version (« pymssql<=2.2.5 »). La liste d'ignorés ne nomme que le
|
|
|
|
|
# module, on ramène donc la clé à son nom avant de comparer.
|
|
|
|
|
bare_key = re.split(r"[\[<>=!~;]", key)[0].strip()
|
2020-08-06 03:09:49 -04:00
|
|
|
for ignored in lst_ignore:
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
if ignored == bare_key:
|
2020-08-06 03:09:49 -04:00
|
|
|
lst_ignored_key.append(key)
|
|
|
|
|
for key in lst_ignored_key:
|
|
|
|
|
del dct_requirements[key]
|
2025-11-25 23:39:04 -05:00
|
|
|
print(
|
|
|
|
|
f"{Fore.YELLOW}WARNING{Style.RESET_ALL} - Ignore '{key}' from '{ignore_requirement_file}'"
|
|
|
|
|
)
|
2020-08-06 03:09:49 -04:00
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
venv_dir = f".venv.{config.set_version_erplibre}"
|
|
|
|
|
with open(f"./{venv_dir}/build_dependency.txt", "w") as f:
|
2024-08-25 01:34:06 -04:00
|
|
|
# TODO remove all comment
|
2020-07-24 02:20:30 -04:00
|
|
|
f.writelines([f"{list(a)[0]}\n" for a in dct_requirements.values()])
|
|
|
|
|
|
|
|
|
|
|
2020-08-06 03:09:49 -04:00
|
|
|
def sorted_dependency_poetry(pyproject_filename):
|
2020-07-24 02:20:30 -04:00
|
|
|
# Open pyproject.toml
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(pyproject_filename, "r") as f:
|
2020-07-24 02:20:30 -04:00
|
|
|
dct_pyproject = toml.load(f)
|
|
|
|
|
|
|
|
|
|
# Get dependencies and update list, sorted
|
|
|
|
|
# [tool.poetry.dependencies]
|
|
|
|
|
tool = dct_pyproject.get("tool")
|
|
|
|
|
if tool:
|
|
|
|
|
poetry = tool.get("poetry")
|
|
|
|
|
if poetry:
|
|
|
|
|
dependencies = poetry.get("dependencies")
|
2021-06-27 00:49:00 -04:00
|
|
|
python_dependency = ("python", dependencies.get("python", ""))
|
|
|
|
|
lst_dependency = [
|
|
|
|
|
(k, v) for k, v in dependencies.items() if k != "python"
|
|
|
|
|
]
|
2020-07-24 02:20:30 -04:00
|
|
|
lst_dependency = sorted(lst_dependency, key=lambda tup: tup[0])
|
2021-06-27 00:49:00 -04:00
|
|
|
poetry["dependencies"] = OrderedDict(
|
|
|
|
|
[python_dependency] + lst_dependency
|
|
|
|
|
)
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
# Rewrite pyproject.toml
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(pyproject_filename, "w") as f:
|
2020-07-24 02:20:30 -04:00
|
|
|
toml.dump(dct_pyproject, f)
|
|
|
|
|
|
|
|
|
|
|
2020-08-06 03:09:49 -04:00
|
|
|
def delete_dependency_poetry(pyproject_filename):
|
2020-07-24 02:20:30 -04:00
|
|
|
# Open pyproject.toml
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(pyproject_filename, "r") as f:
|
2020-07-24 02:20:30 -04:00
|
|
|
dct_pyproject = toml.load(f)
|
|
|
|
|
|
|
|
|
|
# Get dependencies and update list, sorted
|
|
|
|
|
# [tool.poetry.dependencies]
|
|
|
|
|
tool = dct_pyproject.get("tool")
|
|
|
|
|
if tool:
|
|
|
|
|
poetry = tool.get("poetry")
|
|
|
|
|
if poetry:
|
|
|
|
|
dependencies = poetry.get("dependencies")
|
2021-06-27 00:49:00 -04:00
|
|
|
python_dependency = ("python", dependencies.get("python", ""))
|
2020-08-06 05:36:48 -04:00
|
|
|
poetry["dependencies"] = OrderedDict([python_dependency])
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
# Rewrite pyproject.toml
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(pyproject_filename, "w") as f:
|
2020-07-24 02:20:30 -04:00
|
|
|
toml.dump(dct_pyproject, f)
|
|
|
|
|
|
|
|
|
|
|
2025-07-14 03:44:48 -04:00
|
|
|
def get_manifest_external_dependencies(config, dct_requirements, lst_sign):
|
|
|
|
|
lst_manifest_file = get_lst_manifest_py(
|
|
|
|
|
config, ignore_dir_startswith=["script/"]
|
|
|
|
|
)
|
|
|
|
|
print(f"Find {len(lst_manifest_file)} manifest.py files.")
|
2020-08-06 05:36:48 -04:00
|
|
|
lst_dct_ext_depend = []
|
|
|
|
|
for manifest_file in lst_manifest_file:
|
2021-06-27 00:49:00 -04:00
|
|
|
with open(manifest_file, "r") as f:
|
2020-08-06 05:36:48 -04:00
|
|
|
contents = f.read()
|
|
|
|
|
try:
|
|
|
|
|
dct = ast.literal_eval(contents)
|
|
|
|
|
except:
|
|
|
|
|
_logger.error(f"File {manifest_file} contains error, skip.")
|
|
|
|
|
continue
|
|
|
|
|
ext_depend = dct.get("external_dependencies")
|
|
|
|
|
if not ext_depend:
|
|
|
|
|
continue
|
|
|
|
|
python = ext_depend.get("python")
|
|
|
|
|
if not python:
|
|
|
|
|
continue
|
|
|
|
|
for depend in python:
|
2024-08-25 01:34:06 -04:00
|
|
|
# TODO duplicate code, check combine_requirements()
|
|
|
|
|
module_name = depend
|
|
|
|
|
for sign in lst_sign:
|
|
|
|
|
if sign in depend:
|
|
|
|
|
module_name = depend[: depend.find(sign)].strip()
|
|
|
|
|
requirement = dct_requirements.get(module_name)
|
2020-08-06 05:36:48 -04:00
|
|
|
if requirement:
|
|
|
|
|
requirement.add(depend)
|
|
|
|
|
else:
|
2024-08-25 01:34:06 -04:00
|
|
|
dct_requirements[module_name] = set([depend])
|
2020-08-06 05:36:48 -04:00
|
|
|
|
|
|
|
|
return dct_requirements
|
|
|
|
|
|
|
|
|
|
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
def apply_marker_variants(pyproject_filename, build_dependency_filename):
|
|
|
|
|
"""Rétablit les paquets déclinés en plusieurs branches par marqueur.
|
|
|
|
|
|
|
|
|
|
« poetry add » déduplique par nom : des deux branches de factur-x, seule la
|
|
|
|
|
dernière survit, et l'architecture qui n'est pas la sienne hérite de la
|
|
|
|
|
mauvaise version. Poetry sait pourtant exprimer le cas — mais uniquement
|
|
|
|
|
dans le pyproject, en liste de contraintes, hors de portée du CLI. On la
|
|
|
|
|
reconstruit depuis build_dependency.txt, qui porte toutes les branches.
|
|
|
|
|
|
|
|
|
|
:return: True si le pyproject a été réécrit, donc si le lock est à refaire
|
|
|
|
|
"""
|
|
|
|
|
dct_variant = defaultdict(list)
|
|
|
|
|
with open(build_dependency_filename, "r") as f:
|
|
|
|
|
for line in f:
|
|
|
|
|
line = line.strip()
|
|
|
|
|
if not line or ";" not in line:
|
|
|
|
|
continue
|
|
|
|
|
req = Requirement(line)
|
|
|
|
|
dct_variant[req.name].append(
|
|
|
|
|
{"version": str(req.specifier), "markers": str(req.marker)}
|
|
|
|
|
)
|
|
|
|
|
dct_variant = {k: v for k, v in dct_variant.items() if len(v) > 1}
|
|
|
|
|
if not dct_variant:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
with open(pyproject_filename, "r") as f:
|
|
|
|
|
dct_pyproject = toml.load(f)
|
|
|
|
|
dependencies = (
|
|
|
|
|
dct_pyproject.get("tool", {}).get("poetry", {}).get("dependencies")
|
|
|
|
|
)
|
|
|
|
|
if not dependencies:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
has_changed = False
|
|
|
|
|
for name, lst_branch in dct_variant.items():
|
|
|
|
|
key = next(
|
|
|
|
|
(k for k in dependencies if k.lower() == name.lower()), None
|
|
|
|
|
)
|
|
|
|
|
if not key:
|
|
|
|
|
continue
|
|
|
|
|
dependencies[key] = lst_branch
|
|
|
|
|
has_changed = True
|
|
|
|
|
str_branch = ", ".join(
|
|
|
|
|
f"{d['version']} si {d['markers']}" for d in lst_branch
|
|
|
|
|
)
|
|
|
|
|
print(f"Décline '{key}' par marqueur : {str_branch}.")
|
|
|
|
|
|
|
|
|
|
if has_changed:
|
|
|
|
|
with open(pyproject_filename, "w") as f:
|
|
|
|
|
toml.dump(dct_pyproject, f)
|
|
|
|
|
return has_changed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_poetry_lock(config):
|
|
|
|
|
"""
|
|
|
|
|
:return: True if success
|
|
|
|
|
"""
|
[FIX] poetry update: run poetry in the Odoo venv, not the active one
Poetry works in the active virtualenv when VIRTUAL_ENV is set. Launched
from a shell under .venv.erplibre, poetry add and poetry lock inherited
its Python, outside the pyproject bounds, and failed with
InvalidCurrentPythonVersionError. Both calls now activate the Odoo venv
of the ERPLibre version, as install_locally.sh already does.
Checked: poetry env info -p reports .venv.erplibre before, the Odoo
venv after, for the shell script and for the Python call alike.
--- FR ---
[FIX] poetry update : poetry dans le venv Odoo, pas dans le venv actif
Poetry travaille dans le virtualenv actif quand VIRTUAL_ENV est posé.
Lancés d'un shell sous .venv.erplibre, poetry add et poetry lock en
héritaient le Python, hors des bornes du pyproject, et échouaient sur
InvalidCurrentPythonVersionError. Les deux appels activent désormais le
venv Odoo de la version ERPLibre, comme le fait déjà install_locally.sh.
Vérifié : poetry env info -p donne .venv.erplibre avant, le venv Odoo
après, pour le script shell comme pour l'appel Python.
Assisted-by: Claude Opus 5.5
2026-09-25 02:54:44 -04:00
|
|
|
venv_dir = os.path.abspath(f".venv.{config.set_version_erplibre}")
|
|
|
|
|
# poetry works in the active venv when there is one: the caller's
|
|
|
|
|
# VIRTUAL_ENV (often .venv.erplibre) would hand it a Python outside the
|
|
|
|
|
# pyproject bounds, so the Odoo venv is activated for this call.
|
|
|
|
|
env = dict(os.environ, VIRTUAL_ENV=venv_dir)
|
|
|
|
|
env["PATH"] = os.pathsep.join([f"{venv_dir}/bin", env.get("PATH", "")])
|
|
|
|
|
status = subprocess.run([f"{venv_dir}/bin/poetry", "lock"], env=env)
|
|
|
|
|
return status.returncode == 0
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
|
|
|
|
|
|
2020-08-06 03:09:49 -04:00
|
|
|
def call_poetry_add_build_dependency():
|
2021-06-27 00:22:51 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
:return: True if success
|
|
|
|
|
"""
|
2023-01-02 20:33:57 -05:00
|
|
|
status = os.system("./script/poetry/poetry_add_build_dependency.sh")
|
2021-06-27 00:22:51 -04:00
|
|
|
return status == 0
|
2020-08-06 03:09:49 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_list_ignored():
|
2025-11-25 23:39:04 -05:00
|
|
|
odoo_erplibre_version = ""
|
|
|
|
|
with open("./.erplibre-version", "r", encoding="utf-8") as fichier:
|
|
|
|
|
odoo_erplibre_version = fichier.read().strip()
|
|
|
|
|
if not odoo_erplibre_version:
|
|
|
|
|
_logger.warning(
|
|
|
|
|
"Missing ./.erplibre-version file to determine which requirement/ignore_requirement to use."
|
|
|
|
|
)
|
|
|
|
|
return []
|
|
|
|
|
ignore_requirement_file = (
|
|
|
|
|
f"./requirement/ignore_requirements.{odoo_erplibre_version}.txt"
|
|
|
|
|
)
|
|
|
|
|
with open(ignore_requirement_file, "r") as f:
|
|
|
|
|
lst_ignore_requirements = [
|
|
|
|
|
a.strip() for a in f.readlines() if a.strip()
|
|
|
|
|
]
|
|
|
|
|
return lst_ignore_requirements, ignore_requirement_file
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
|
[FIX] poetry update: stop on a missing pyproject.toml, offer the switch
The root pyproject.toml and poetry.lock are unversioned symlinks to the
active version's files in requirement/, created by the version switch.
Without them the script died on a FileNotFoundError traceback. It now
names the fix, make switch_odoo_XX for the active Odoo version; on a
terminal it offers to run it and restarts with the same arguments.
Checked: an existing file passes, a missing one exits 1 off a terminal
and on a declined prompt. The accepted path, which runs the switch, is
not tested.
--- FR ---
[FIX] poetry update : arrêt sur pyproject.toml absent, switch proposé
pyproject.toml et poetry.lock à la racine sont des liens non versionnés
vers les fichiers de la version active dans requirement/, créés par le
switch de version. Sans eux, le script mourait sur un FileNotFoundError.
Il nomme désormais le remède, make switch_odoo_XX pour la version Odoo
active ; dans un terminal, il propose de le lancer et se relance avec
les mêmes arguments. Vérifié : un fichier présent passe, un absent sort
en 1 hors terminal et sur un refus. L'acceptation, qui lance le switch,
n'est pas testée.
Assisted-by: Claude Opus 5.5
2026-09-25 02:54:33 -04:00
|
|
|
def ensure_pyproject_link(config, pyproject_filename):
|
|
|
|
|
"""Stop cleanly when the root pyproject.toml is missing.
|
|
|
|
|
|
|
|
|
|
pyproject.toml and poetry.lock at the root are symlinks to the active
|
|
|
|
|
version's files in requirement/, created by the version switch; they are
|
|
|
|
|
not versioned. When absent, or dangling, the fix is `make switch_odoo_XX`.
|
|
|
|
|
On a terminal, offer to run it and restart this script with the same
|
|
|
|
|
arguments; otherwise print the command and exit with code 1.
|
|
|
|
|
|
|
|
|
|
:param config: parsed arguments, set_version_odoo gives the make target
|
|
|
|
|
:param pyproject_filename: path of the pyproject.toml to open
|
|
|
|
|
:return: None when the file exists; never returns otherwise
|
|
|
|
|
"""
|
|
|
|
|
if os.path.isfile(pyproject_filename):
|
|
|
|
|
return
|
|
|
|
|
odoo_major = config.set_version_odoo.split(".")[0]
|
|
|
|
|
make_cmd = f"make switch_odoo_{odoo_major}"
|
|
|
|
|
print(
|
|
|
|
|
f"{Fore.RED}ERROR{Style.RESET_ALL} - Missing '{pyproject_filename}':"
|
|
|
|
|
" the Poetry links of the active version are not created."
|
|
|
|
|
)
|
|
|
|
|
print(f"Fix: run '{make_cmd}', then relaunch this script.")
|
|
|
|
|
if not sys.stdin.isatty():
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
answer = input(f"Run '{make_cmd}' and relaunch now? [y/N] ")
|
|
|
|
|
if answer.strip().lower() not in ("y", "yes", "o", "oui"):
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
if os.system(make_cmd) != 0 or not os.path.isfile(pyproject_filename):
|
|
|
|
|
print(f"{Fore.RED}ERROR{Style.RESET_ALL} - '{make_cmd}' failed.")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
|
|
|
|
|
|
|
|
|
2020-07-24 02:20:30 -04:00
|
|
|
def main():
|
|
|
|
|
# repo = Repo(root_path)
|
|
|
|
|
# lst_repo = get_all_repo()
|
|
|
|
|
config = get_config()
|
|
|
|
|
|
2025-07-19 05:34:25 -04:00
|
|
|
# print(
|
|
|
|
|
# f"Version: ERPLibre '{config.set_version_erplibre}' : Python ERPLibre '{config.set_version_python_erplibre}' : Poetry '{config.set_version_poetry}' : Odoo '{config.set_version_odoo}' : Python Odoo '{config.set_version_python_odoo}'"
|
|
|
|
|
# )
|
2025-07-14 03:44:48 -04:00
|
|
|
print(
|
|
|
|
|
f"Version: Poetry '{config.set_version_poetry}' : Odoo '{config.set_version_odoo}' : Python '{config.set_version_python}' : ERPLibre '{config.set_version_erplibre}'"
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-19 05:34:25 -04:00
|
|
|
poetry_default_lock_path = "./poetry.lock"
|
|
|
|
|
pyproject_toml_filename = ""
|
|
|
|
|
poetry_target_lock_path = (
|
|
|
|
|
f"./requirement/poetry.{config.set_version_erplibre}.lock"
|
|
|
|
|
)
|
|
|
|
|
|
2021-09-28 00:44:05 -04:00
|
|
|
if not config.dry:
|
|
|
|
|
pyproject_toml_filename = f"{config.dir}pyproject.toml"
|
[FIX] poetry update: stop on a missing pyproject.toml, offer the switch
The root pyproject.toml and poetry.lock are unversioned symlinks to the
active version's files in requirement/, created by the version switch.
Without them the script died on a FileNotFoundError traceback. It now
names the fix, make switch_odoo_XX for the active Odoo version; on a
terminal it offers to run it and restarts with the same arguments.
Checked: an existing file passes, a missing one exits 1 off a terminal
and on a declined prompt. The accepted path, which runs the switch, is
not tested.
--- FR ---
[FIX] poetry update : arrêt sur pyproject.toml absent, switch proposé
pyproject.toml et poetry.lock à la racine sont des liens non versionnés
vers les fichiers de la version active dans requirement/, créés par le
switch de version. Sans eux, le script mourait sur un FileNotFoundError.
Il nomme désormais le remède, make switch_odoo_XX pour la version Odoo
active ; dans un terminal, il propose de le lancer et se relance avec
les mêmes arguments. Vérifié : un fichier présent passe, un absent sort
en 1 hors terminal et sur un refus. L'acceptation, qui lance le switch,
n'est pas testée.
Assisted-by: Claude Opus 5.5
2026-09-25 02:54:33 -04:00
|
|
|
ensure_pyproject_link(config, pyproject_toml_filename)
|
2021-09-28 00:44:05 -04:00
|
|
|
delete_dependency_poetry(pyproject_toml_filename)
|
2020-07-24 02:20:30 -04:00
|
|
|
combine_requirements(config)
|
2021-09-28 00:44:05 -04:00
|
|
|
if not config.dry:
|
2025-07-19 05:34:25 -04:00
|
|
|
if config.force and os.path.isfile(poetry_default_lock_path):
|
|
|
|
|
os.remove(poetry_default_lock_path)
|
2021-09-28 00:44:05 -04:00
|
|
|
status = call_poetry_add_build_dependency()
|
2025-07-19 05:34:25 -04:00
|
|
|
if status and pyproject_toml_filename:
|
2021-09-28 00:44:05 -04:00
|
|
|
sorted_dependency_poetry(pyproject_toml_filename)
|
[ADD] poetry: pin a dependency per architecture
factur-x >= 4.0 needs saxonche, which Saxonica ships no s390x wheel for.
Pin 3.x there, 4.2+ everywhere else.
PEP 508 markers now survive the whole chain: kept instead of evaluated,
and each line reaches "poetry add" as a single argument -- the
"grep -v ';'" silently dropped them. Since "poetry add" dedupes by name,
the multi-constraint form, which the CLI cannot express, is written back
into the pyproject and the lock regenerated.
PyMuPDF is set aside on s390x for the same reason: MuPDF does not build
there. Along the way, the ignore list missed pymssql, whose key carried
its version, and two pre-existing solver deadlocks, lxml-html-clean and
s3fs, blocked any regeneration.
--- FR ---
factur-x >= 4.0 dépend de saxonche, sans roue s390x chez Saxonica. On
épingle la 3.x là-bas, la 4.2+ ailleurs.
Le marqueur PEP 508 traverse maintenant la chaîne : il est conservé au
lieu d'être évalué, et chaque ligne devient un seul argument de « poetry
add » — le « grep -v ';' » écartait ces lignes en silence. « poetry add »
dédupliquant par nom, la forme multi-contraintes, hors de portée du CLI,
est réécrite dans le pyproject, puis le lock refait.
PyMuPDF est écarté sur s390x pour la même raison : MuPDF ne s'y construit
pas. Au passage, la liste d'ignorés ratait pymssql, dont la clé portait sa
version, et deux blocages préexistants du solveur, lxml-html-clean et
s3fs, interdisaient toute régénération.
Assisted-by: Claude Opus 5
2026-08-11 05:02:11 -04:00
|
|
|
build_dependency_filename = (
|
|
|
|
|
f"./.venv.{config.set_version_erplibre}/build_dependency.txt"
|
|
|
|
|
)
|
|
|
|
|
if apply_marker_variants(
|
|
|
|
|
pyproject_toml_filename, build_dependency_filename
|
|
|
|
|
):
|
|
|
|
|
# Le lock écrit par « poetry add » ignore les branches qu'on
|
|
|
|
|
# vient de rétablir : il ne décrit plus le pyproject.
|
|
|
|
|
status = call_poetry_lock(config)
|
2025-07-19 05:34:25 -04:00
|
|
|
if (
|
|
|
|
|
config.force
|
|
|
|
|
and not os.path.islink(poetry_default_lock_path)
|
|
|
|
|
and os.path.isfile(poetry_default_lock_path)
|
|
|
|
|
and os.path.isfile(poetry_target_lock_path)
|
|
|
|
|
):
|
|
|
|
|
# If "./poetry.lock" is not symbolic link, force replace original
|
|
|
|
|
os.remove(poetry_target_lock_path)
|
|
|
|
|
shutil.move(poetry_default_lock_path, poetry_target_lock_path)
|
|
|
|
|
os.symlink(poetry_target_lock_path, poetry_default_lock_path)
|
2020-07-24 02:20:30 -04:00
|
|
|
|
|
|
|
|
|
2021-06-27 00:49:00 -04:00
|
|
|
if __name__ == "__main__":
|
2020-07-24 02:20:30 -04:00
|
|
|
main()
|