[FIX] outillage : désimbriquer les f-strings que seul 3.12 sait lire
Cinq fichiers employaient des f-strings PEP 701 — guillemets imbriqués du même type, backslash dans l'expression, expression sur plusieurs lignes — que Python refuse avant 3.12. Or les hooks de script/git/hooks portent « #!/usr/bin/env python3 » et tournent donc sur l'interpréteur du système, qu'une distribution livre encore en 3.10 : la SyntaxError tombe au chargement, sans qu'aucun garde puisse nommer la commande à taper. Les chaînes traduites gardent leur texte à l'octet près, sans quoi leur clé se perdrait. Les commentaires de ces fichiers passent du récit au présent, comme la règle le demande de ce qu'on touche. Vérifié : tout l'arbre Python parse sous 3.10, 3.11, 3.12 et 3.14. --- EN --- Five files used PEP 701 f-strings — quotes of the same kind nested, a backslash in the expression, an expression spanning lines — which Python refuses before 3.12. The hooks in script/git/hooks carry "#!/usr/bin/env python3" and so run on the system interpreter, which a distribution still ships as 3.10: the SyntaxError lands at load, before any guard can name the command to type. Translated strings keep their text byte for byte, else their key would be lost. The comments of those files move from tale to present tense, as the rule asks of what one touches. Checked: the whole Python tree parses under 3.10, 3.11, 3.12 and 3.14. Assisted-by: Claude Opus 5
This commit is contained in:
parent
5dda3de6b8
commit
da2dc63a19
5 changed files with 77 additions and 79 deletions
|
|
@ -502,8 +502,8 @@ def collect(
|
||||||
# et ce qu'il rapporte est fiable. « all » compare toute vue ayant
|
# et ce qu'il rapporte est fiable. « all » compare toute vue ayant
|
||||||
# un arch_fs, ce qui trouve la dérive qu'aucun drapeau ne signale —
|
# un arch_fs, ce qui trouve la dérive qu'aucun drapeau ne signale —
|
||||||
# une vue réécrite en SQL direct — mais au prix d'un plancher de
|
# une vue réécrite en SQL direct — mais au prix d'un plancher de
|
||||||
# bruit MESURÉ : sur une base 18.0 fraîchement installée, 160 des
|
# bruit : sur une base 18.0 fraîchement installée, 160 des 974
|
||||||
# 974 vues à arch_fs diffèrent déjà. read_arch_from_file rend le
|
# vues à arch_fs diffèrent déjà. read_arch_from_file rend le
|
||||||
# XML brut du fichier, alors que la base porte l'arch APRÈS
|
# XML brut du fichier, alors que la base porte l'arch APRÈS
|
||||||
# traitement au chargement : un attribut « groups » est consommé,
|
# traitement au chargement : un attribut « groups » est consommé,
|
||||||
# un <xpath position="attributes"> est appliqué. En « all », un
|
# un <xpath position="attributes"> est appliqué. En « all », un
|
||||||
|
|
@ -707,7 +707,7 @@ def render(data, verbose=False, top=TOP_DEFAULT, category=None, hints=True):
|
||||||
f"🔬 {t('Customised views')} — {data['database']} (Odoo {version}"
|
f"🔬 {t('Customised views')} — {data['database']} (Odoo {version}"
|
||||||
f"{', ' + t('from a backup') if data.get('source') == 'backup' else ''})",
|
f"{', ' + t('from a backup') if data.get('source') == 'backup' else ''})",
|
||||||
"",
|
"",
|
||||||
f" {t("Views"):<38}: {data['n_views']}",
|
f" {t('Views'):<38}: {data['n_views']}",
|
||||||
]
|
]
|
||||||
for name in CATEGORIES:
|
for name in CATEGORIES:
|
||||||
if counts.get(name):
|
if counts.get(name):
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,8 @@ lecture seule, plutôt que de rejouer quoi que ce soit.
|
||||||
Pourquoi pas en démarrant Odoo
|
Pourquoi pas en démarrant Odoo
|
||||||
------------------------------
|
------------------------------
|
||||||
Six démarrages coûteraient une heure, écriraient dans les bases et
|
Six démarrages coûteraient une heure, écriraient dans les bases et
|
||||||
demanderaient de basculer le checkout à chaque palier. Mesuré : la même
|
demanderaient de basculer le checkout à chaque palier. La même inspection
|
||||||
inspection en SQL prend moins d'une demi-seconde par base, et ne touche à
|
en SQL prend moins d'une demi-seconde par base, et ne touche à rien. Ce qu'on y perd — les modèles abstraits, les champs calculés — ne
|
||||||
rien. Ce qu'on y perd — les modèles abstraits, les champs calculés — ne
|
|
||||||
se compare pas d'une version à l'autre de toute façon.
|
se compare pas d'une version à l'autre de toute façon.
|
||||||
|
|
||||||
Ce qui compte le plus
|
Ce qui compte le plus
|
||||||
|
|
@ -184,12 +183,12 @@ def version_of(database, dct):
|
||||||
|
|
||||||
# Ce que la migration écrit dans le journal d'une étape autour d'un test :
|
# Ce que la migration écrit dans le journal d'une étape autour d'un test :
|
||||||
#
|
#
|
||||||
# [2026-08-26 03:19:44.166204] $ .venv…/python3 ./script/…/smoke.py -d …
|
# [<horodatage>] $ .venv…/python3 ./script/…/smoke.py -d …
|
||||||
# [2026-08-26 03:19:59.846406] -> 1
|
# [<horodatage>] -> 1
|
||||||
# [2026-08-26 03:19:59.847489] [test] smoke_public_url -> 1
|
# [<horodatage>] [test] smoke_public_url -> 1
|
||||||
#
|
#
|
||||||
# Entre le « $ » et le « -> », RIEN : mesuré sur trois exécutions du même
|
# Entre le « $ » et le « -> », RIEN : la sortie de l'outil n'est pas
|
||||||
# test, la sortie de l'outil n'est pas capturée. Ce qui explique l'échec
|
# capturée. Ce qui explique l'échec
|
||||||
# est donc AVANT, dans ce qu'Odoo écrivait juste avant qu'on le teste —
|
# est donc AVANT, dans ce qu'Odoo écrivait juste avant qu'on le teste —
|
||||||
# et c'est pour cela que l'extrait remonte, au lieu de descendre.
|
# et c'est pour cela que l'extrait remonte, au lieu de descendre.
|
||||||
MARQUEUR_COMMANDE = "] $ "
|
MARQUEUR_COMMANDE = "] $ "
|
||||||
|
|
@ -451,7 +450,7 @@ def table_counts(database):
|
||||||
|
|
||||||
Construite côté serveur puis exécutée d'un bloc : huit cents requêtes
|
Construite côté serveur puis exécutée d'un bloc : huit cents requêtes
|
||||||
séparées coûteraient huit cents allers-retours, là où celle-ci prend
|
séparées coûteraient huit cents allers-retours, là où celle-ci prend
|
||||||
quatre dixièmes de seconde — mesuré sur une base de 890 tables.
|
quatre dixièmes de seconde sur une base de 890 tables.
|
||||||
"""
|
"""
|
||||||
fabrique = run_psql(
|
fabrique = run_psql(
|
||||||
database,
|
database,
|
||||||
|
|
@ -546,11 +545,11 @@ def inspect(database):
|
||||||
etat["field_module"] = {cle: sorted(v) for cle, v in dct_origine.items()}
|
etat["field_module"] = {cle: sorted(v) for cle, v in dct_origine.items()}
|
||||||
# Lesquels PORTENT une donnée. Un champ `store=false` n'a jamais eu
|
# Lesquels PORTENT une donnée. Un champ `store=false` n'a jamais eu
|
||||||
# de colonne : sa disparition ne perd rien, et il pesait jusqu'à 90 %
|
# de colonne : sa disparition ne perd rien, et il pesait jusqu'à 90 %
|
||||||
# du seau « non déclarés par OpenUpgrade ». Mesuré au palier 16 → 17 :
|
# du seau « non déclarés par OpenUpgrade ». Au palier 16 → 17,
|
||||||
# `__last_update` à lui seul comptait pour 397 des 565.
|
# `__last_update` compte à lui seul pour 397 des 565.
|
||||||
#
|
#
|
||||||
# `store` existe de la 12 à la 18 et n'est jamais NULL — vérifié sur
|
# `store` existe de la 12 à la 18 et n'y est jamais NULL : le
|
||||||
# les sept bases — donc le renseignement est fiable partout.
|
# renseignement est donc fiable partout.
|
||||||
stockes = run_psql(
|
stockes = run_psql(
|
||||||
database,
|
database,
|
||||||
"SELECT model || '.' || name FROM ir_model_fields WHERE store",
|
"SELECT model || '.' || name FROM ir_model_fields WHERE store",
|
||||||
|
|
@ -560,8 +559,8 @@ def inspect(database):
|
||||||
#
|
#
|
||||||
# Une pièce jointe dont `res_field` ne nomme aucun champ vivant est
|
# Une pièce jointe dont `res_field` ne nomme aucun champ vivant est
|
||||||
# DÉJÀ illisible : Odoo lève un KeyError en la contrôlant. Ce ne sont
|
# DÉJÀ illisible : Odoo lève un KeyError en la contrôlant. Ce ne sont
|
||||||
# pas des données, ce sont des débris. Mesuré sur une chaîne 12 → 18 :
|
# pas des données, ce sont des débris. Sur une chaîne 12 → 18, la
|
||||||
# la dette naît aux paliers 13 et 14, reste gelée pendant trois
|
# dette naît aux paliers 13 et 14, reste gelée pendant trois
|
||||||
# paliers, et la 18 ramasse 452 lignes d'un coup — qui se lisent
|
# paliers, et la 18 ramasse 452 lignes d'un coup — qui se lisent
|
||||||
# alors comme 452 pertes.
|
# alors comme 452 pertes.
|
||||||
#
|
#
|
||||||
|
|
@ -700,8 +699,7 @@ def render_detail(diff, categorie, colour=False):
|
||||||
(gagnes, "+", "ok"),
|
(gagnes, "+", "ok"),
|
||||||
):
|
):
|
||||||
lignes.append(
|
lignes.append(
|
||||||
f"── {paint(symbole, teinte, colour)} {len(lst)}"
|
f"── {paint(symbole, teinte, colour)} {len(lst)} {t(categorie)} ──"
|
||||||
f" {t(categorie)} ──"
|
|
||||||
)
|
)
|
||||||
lignes.extend(f" {nom}" for nom in lst)
|
lignes.extend(f" {nom}" for nom in lst)
|
||||||
lignes.append("")
|
lignes.append("")
|
||||||
|
|
@ -724,8 +722,7 @@ def render_missing(etat, colour=False, limit=60):
|
||||||
return f"✅ {t('every attachment file is present')}"
|
return f"✅ {t('every attachment file is present')}"
|
||||||
lignes = [
|
lignes = [
|
||||||
paint(
|
paint(
|
||||||
f"❌ {absents}"
|
f"❌ {absents} {t('attachment files missing from the filestore')}",
|
||||||
f" {t('attachment files missing from the filestore')}",
|
|
||||||
"fail",
|
"fail",
|
||||||
colour,
|
colour,
|
||||||
),
|
),
|
||||||
|
|
@ -1019,8 +1016,8 @@ def overlay_declared(
|
||||||
champs["model_gone"].append(cle)
|
champs["model_gone"].append(cle)
|
||||||
continue
|
continue
|
||||||
# AVANT « non analysé » : « sans donnée propre » est une raison
|
# AVANT « non analysé » : « sans donnée propre » est une raison
|
||||||
# plus forte que « hors du champ d'OpenUpgrade ». Mesuré au
|
# plus forte que « hors du champ d'OpenUpgrade ». Au palier
|
||||||
# palier 16 → 17, le placement avant fait tomber `not_analysed`
|
# 16 → 17, le placement avant fait tomber `not_analysed`
|
||||||
# de 181 à 47 sans changer `undeclared` — le seau résiduel se
|
# de 181 à 47 sans changer `undeclared` — le seau résiduel se
|
||||||
# réduit alors au risque réel : des champs qui AVAIENT des
|
# réduit alors au risque réel : des champs qui AVAIENT des
|
||||||
# données, dans des modules dont OpenUpgrade ne peut rien dire.
|
# données, dans des modules dont OpenUpgrade ne peut rien dire.
|
||||||
|
|
@ -1256,11 +1253,10 @@ RENAME_RATIO = 0.75
|
||||||
def looks_renamed(un, deux):
|
def looks_renamed(un, deux):
|
||||||
"""Les deux noms se ressemblent-ils assez pour être le même sujet ?
|
"""Les deux noms se ressemblent-ils assez pour être le même sujet ?
|
||||||
|
|
||||||
Deux garde-fous ont été essayés et rejetés, mesurés sur une vraie
|
Deux garde-fous plus simples ne tiennent pas. Le seul nombre de lignes
|
||||||
migration. Le seul nombre de lignes accouplait
|
accouple `account_account_tag_account_tax_template_rel` à
|
||||||
`account_account_tag_account_tax_template_rel` à `dms_directory` : les
|
`dms_directory` dès qu'ils en comptent autant. Un mot commun d'au moins
|
||||||
deux comptaient sept lignes. Un mot commun d'au moins cinq lettres
|
cinq lettres accouple `cleanup_purge_wizard_menu` à
|
||||||
accouplait `cleanup_purge_wizard_menu` à
|
|
||||||
`cleanup_create_indexes_line` — « cleanup » ne dit rien.
|
`cleanup_create_indexes_line` — « cleanup » ne dit rien.
|
||||||
|
|
||||||
La ressemblance d'ENSEMBLE tranche : `muk_dms_directory` et
|
La ressemblance d'ENSEMBLE tranche : `muk_dms_directory` et
|
||||||
|
|
@ -1315,9 +1311,9 @@ SANS_DONNEE_PROPRE = ("id",)
|
||||||
# Pourquoi une pièce jointe a disparu — DÉDUIT, jamais déclaré.
|
# Pourquoi une pièce jointe a disparu — DÉDUIT, jamais déclaré.
|
||||||
#
|
#
|
||||||
# SEMANTIC_MAP ne peut pas porter ceci : elle nomme une TABLE, et la
|
# SEMANTIC_MAP ne peut pas porter ceci : elle nomme une TABLE, et la
|
||||||
# cause n'est pas la table, ce sont ces lignes-là. Mesuré sur une chaîne
|
# cause n'est pas la table, ce sont ces lignes-là. Sur une chaîne 12 → 18,
|
||||||
# 12 → 18 : des 516 lignes parties au palier 18, 452 avaient perdu leur
|
# des 516 lignes parties au palier 18, 452 ont perdu leur champ et 63 leur
|
||||||
# champ et 63 leur enregistrement. Une entrée « ir_attachment / pruned »
|
# enregistrement. Une entrée « ir_attachment / pruned »
|
||||||
# aurait rangé les 516 sous « perte attendue » — et la 517e avec.
|
# aurait rangé les 516 sous « perte attendue » — et la 517e avec.
|
||||||
ATTACHMENT_KIND = (
|
ATTACHMENT_KIND = (
|
||||||
("field_debt", "their field was already gone before this step", "dim"),
|
("field_debt", "their field was already gone before this step", "dim"),
|
||||||
|
|
@ -1443,8 +1439,9 @@ def render_text(lst_snapshot, colour=None, limit=8):
|
||||||
f" ⚠️ {etat['database']} : {t('database not found')}"
|
f" ⚠️ {etat['database']} : {t('database not found')}"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
etape = paint(f"{etat['odoo']:<6}", "step", colour)
|
||||||
lignes.append(
|
lignes.append(
|
||||||
f" {paint(f'{etat['odoo']:<6}', 'step', colour)}"
|
f" {etape}"
|
||||||
f" {etat['database']:<34}"
|
f" {etat['database']:<34}"
|
||||||
f" {len(etat['installed']):>4} {t('modules')}"
|
f" {len(etat['installed']):>4} {t('modules')}"
|
||||||
f" · {len(etat['model']):>4} {t('models')}"
|
f" · {len(etat['model']):>4} {t('models')}"
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ from pathlib import Path
|
||||||
#
|
#
|
||||||
# Lancé seul, systemd doit EXÉCUTER le fichier, et l'échoue en « 203/EXEC »
|
# Lancé seul, systemd doit EXÉCUTER le fichier, et l'échoue en « 203/EXEC »
|
||||||
# dans quatre cas au moins : bit x absent, shebang qui ne résout pas, /home
|
# dans quatre cas au moins : bit x absent, shebang qui ne résout pas, /home
|
||||||
# monté noexec, SELinux refusant l'execve. Vécu sur openSUSE s390x — le
|
# monté noexec, SELinux refusant l'execve. L'échec est alors MUET : le
|
||||||
# processus mourait en 3 ms, sans jamais entrer dans le script, ce qui rend le
|
# processus meurt avant d'entrer dans le script, sans une ligne de sortie, et
|
||||||
# diagnostic très pénible : aucune sortie, et un code qui ressemble à une
|
# 203 ressemble à une erreur d'application — le diagnostic part donc dans la
|
||||||
# erreur d'application.
|
# mauvaise direction.
|
||||||
#
|
#
|
||||||
# Passé à bash, run.sh n'est plus qu'une DONNÉE lue : les quatre causes
|
# Passé à bash, run.sh n'est plus qu'une DONNÉE lue : les quatre causes
|
||||||
# disparaissent ensemble, y compris noexec et SELinux, qui ne portent que sur
|
# disparaissent ensemble, y compris noexec et SELinux, qui ne portent que sur
|
||||||
|
|
@ -116,10 +116,9 @@ def main():
|
||||||
args.config_name
|
args.config_name
|
||||||
or f"erplibre_{el_user}_{os.path.basename(os.getcwd())}"
|
or f"erplibre_{el_user}_{os.path.basename(os.getcwd())}"
|
||||||
)
|
)
|
||||||
exec_param = (
|
db_param = w_cmd("-d", args.database or "")
|
||||||
" "
|
port_param = w_cmd("-p", args.port or "")
|
||||||
+ f" {w_cmd("-d", args.database or "")} {w_cmd("-p", args.port or "")}".strip()
|
exec_param = " " + f" {db_param} {port_param}".strip()
|
||||||
)
|
|
||||||
|
|
||||||
# Render the systemd service file content
|
# Render the systemd service file content
|
||||||
unit_content = UNIT_TEMPLATE.format(
|
unit_content = UNIT_TEMPLATE.format(
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ Le dépôt n'a ni pager, ni progression sur place : la sortie s'ajoute ligne à
|
||||||
ligne. Une réponse longue se ferme sur une ligne de pied, jamais sur un
|
ligne. Une réponse longue se ferme sur une ligne de pied, jamais sur un
|
||||||
défilement piloté.
|
défilement piloté.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
@ -386,8 +387,7 @@ class AssistantMenuMixin:
|
||||||
choices = [
|
choices = [
|
||||||
{
|
{
|
||||||
"prompt_description": (
|
"prompt_description": (
|
||||||
f"{t('Here (127.0.0.1)')}"
|
f"{t('Here (127.0.0.1)')} ({t('11 ports, instant')})"
|
||||||
f" ({t('11 ports, instant')})"
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -805,9 +805,8 @@ class AssistantMenuMixin:
|
||||||
f" {t('Look somewhere else')} · {t('Type an address')}"
|
f" {t('Look somewhere else')} · {t('Type an address')}"
|
||||||
f" · {t('Carry on with the OpenAI API (key from the vault)')}"
|
f" · {t('Carry on with the OpenAI API (key from the vault)')}"
|
||||||
)
|
)
|
||||||
print(
|
astuce = t('A local server: "ollama serve" listens on 11434.')
|
||||||
f" 💡 {t('A local server: \"ollama serve\" listens on 11434.')}"
|
print(f" 💡 {astuce}")
|
||||||
)
|
|
||||||
|
|
||||||
def _llm_server_card(self):
|
def _llm_server_card(self):
|
||||||
"""Ce que le serveur en usage annonce savoir faire.
|
"""Ce que le serveur en usage annonce savoir faire.
|
||||||
|
|
@ -1051,8 +1050,7 @@ class AssistantMenuMixin:
|
||||||
if session is None:
|
if session is None:
|
||||||
return
|
return
|
||||||
commande = (
|
commande = (
|
||||||
f"{shlex.quote(chemin)} --resume"
|
f"{shlex.quote(chemin)} --resume {shlex.quote(session.session_id)}"
|
||||||
f" {shlex.quote(session.session_id)}"
|
|
||||||
)
|
)
|
||||||
if not getattr(self.execute, "cmd_source_default", ""):
|
if not getattr(self.execute, "cmd_source_default", ""):
|
||||||
print(t("No terminal can be opened here. Paste this command:"))
|
print(t("No terminal can be opened here. Paste this command:"))
|
||||||
|
|
@ -1097,8 +1095,7 @@ class AssistantMenuMixin:
|
||||||
1 for _, verdict, _ in self._llm_apparier(gpts) if verdict == "ok"
|
1 for _, verdict, _ in self._llm_apparier(gpts) if verdict == "ok"
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
f"{t('gpt tools')} ({len(gpts)},"
|
f"{t('gpt tools')} ({len(gpts)}, {compatibles} {t('compatible')})"
|
||||||
f" {compatibles} {t('compatible')})"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _llm_apparier(self, gpts):
|
def _llm_apparier(self, gpts):
|
||||||
|
|
|
||||||
|
|
@ -506,10 +506,12 @@ class QemuInstallMixin:
|
||||||
# ERREUR, alors que son étape finale écrit encore l'autorité, les
|
# ERREUR, alors que son étape finale écrit encore l'autorité, les
|
||||||
# variables et le sudoers : attendre l'unité, faute de quoi elles
|
# variables et le sudoers : attendre l'unité, faute de quoi elles
|
||||||
# arrivent après cette session, qui vivra sans elles.
|
# arrivent après cette session, qui vivra sans elles.
|
||||||
+ attente_cloud_final() + "; "
|
+ attente_cloud_final()
|
||||||
|
+ "; "
|
||||||
# Les variables du cache sont écrites par cloud-init PENDANT
|
# Les variables du cache sont écrites par cloud-init PENDANT
|
||||||
# l'attente : cette session, ouverte avant, ne les a pas reçues.
|
# l'attente : cette session, ouverte avant, ne les a pas reçues.
|
||||||
+ cache_env_reload() + "; "
|
+ cache_env_reload()
|
||||||
|
+ "; "
|
||||||
# ICI, et nulle part avant. Le faisceau que ces exports désignent
|
# ICI, et nulle part avant. Le faisceau que ces exports désignent
|
||||||
# est écrit par cloud-init lui aussi : mesuré sur une VM, la
|
# est écrit par cloud-init lui aussi : mesuré sur une VM, la
|
||||||
# session ssh est acceptée une seconde avant qu'il existe, donc
|
# session ssh est acceptée une seconde avant qu'il existe, donc
|
||||||
|
|
@ -599,31 +601,28 @@ class QemuInstallMixin:
|
||||||
|
|
||||||
# Miroirs openSUSE préférés, du plus proche au dernier recours. Le
|
# Miroirs openSUSE préférés, du plus proche au dernier recours. Le
|
||||||
# redirecteur officiel n'est PAS géographique pour cette distribution :
|
# redirecteur officiel n'est PAS géographique pour cette distribution :
|
||||||
# mesuré depuis Montréal sur les métadonnées oss s390x (15 Mo),
|
# download.opensuse.org sert depuis l'Europe, là où un miroir
|
||||||
# download.opensuse.org met 23,8 s — il sert depuis l'Europe — contre
|
# nord-américain répond en une fraction du temps sur les mêmes
|
||||||
# 2,7 s pour mirrors.rit.edu. Les trois familles dnf, elles, choisissent
|
# métadonnées. Les trois familles dnf, elles, choisissent déjà un miroir
|
||||||
# déjà un miroir canadien toutes seules ; rien à faire de ce côté.
|
# proche toutes seules ; rien à faire de ce côté.
|
||||||
#
|
#
|
||||||
# Chaque miroir est SONDÉ sur le chemin de l'architecture ET du produit
|
# Chaque miroir est SONDÉ sur le chemin de l'architecture ET du produit
|
||||||
# courants, puis le premier qui répond gagne. C'est nécessaire : aucun ne
|
# courants, puis le premier qui répond gagne. C'est nécessaire : aucun ne
|
||||||
# réplique tout. Relevé le 2026-08-12 —
|
# réplique tout — l'un sert Leap mais pas Tumbleweed, l'autre les ports
|
||||||
# csclub Leap oui, Tumbleweed non (404)
|
# zsystems mais pas x86_64. D'où plusieurs entrées plutôt qu'une : avec un
|
||||||
# rit.edu zsystems oui ; injoignable ce jour-là (curl 7)
|
# seul miroir, sa panne renvoie tout le monde sur le redirecteur, servi
|
||||||
# leaseweb Tumbleweed x86_64 et Leap oui, ports zsystems non
|
# d'Europe. Aucun sondage concluant : on garde les dépôts de l'image,
|
||||||
# D'où plusieurs entrées plutôt qu'une : avec la seule rit.edu, sa panne
|
# donc le comportement d'avant.
|
||||||
# renvoyait tout le monde sur download.opensuse.org, servi d'Europe.
|
|
||||||
# Ordonnées par proximité de Montréal. Aucun sondage concluant : on garde
|
|
||||||
# les dépôts de l'image, donc le comportement d'avant.
|
|
||||||
_QEMU_ZYPPER_MIRRORS = (
|
_QEMU_ZYPPER_MIRRORS = (
|
||||||
"https://mirror.csclub.uwaterloo.ca/opensuse",
|
"https://mirror.csclub.uwaterloo.ca/opensuse",
|
||||||
"https://mirrors.rit.edu/opensuse",
|
"https://mirrors.rit.edu/opensuse",
|
||||||
"https://mirror.us.leaseweb.net/opensuse",
|
"https://mirror.us.leaseweb.net/opensuse",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Miroirs Arch canadiens, du plus rapide au suivant. Mesuré depuis
|
# Miroirs Arch canadiens, du plus rapide au suivant. Le miroir
|
||||||
# Montréal sur extra.db : quantum5 2,0 s, xenyth 7,1 s, contre 8,0 s pour
|
# « géographique » officiel, geo.mirror.pkgbuild.com, n'est pas le plus
|
||||||
# geo.mirror.pkgbuild.com — le miroir « géographique » officiel n'est donc
|
# rapide depuis l'Amérique du Nord : d'où une liste explicite plutôt que
|
||||||
# pas le meilleur ici. Arch n'est proposé qu'en amd64 dans le catalogue,
|
# lui. Arch n'est proposé qu'en amd64 dans le catalogue,
|
||||||
# et ces deux-là ne servent que x86_64 (Arch Linux ARM a ses propres
|
# et ces deux-là ne servent que x86_64 (Arch Linux ARM a ses propres
|
||||||
# miroirs) : la garde d'architecture le dit quand même.
|
# miroirs) : la garde d'architecture le dit quand même.
|
||||||
_QEMU_PACMAN_MIRRORS = (
|
_QEMU_PACMAN_MIRRORS = (
|
||||||
|
|
@ -1219,13 +1218,13 @@ class QemuInstallMixin:
|
||||||
# couvre les quatre gestionnaires (Arch l'a dans extra, Debian et Ubuntu ne
|
# couvre les quatre gestionnaires (Arch l'a dans extra, Debian et Ubuntu ne
|
||||||
# l'ont qu'en snap — coupé ici —, Fedora et openSUSE pas du tout).
|
# l'ont qu'en snap — coupé ici —, Fedora et openSUSE pas du tout).
|
||||||
#
|
#
|
||||||
# La ligne COMMUNITY, et non le produit unifié. Mesuré dans une VM :
|
# La ligne COMMUNITY, et non le produit unifié.
|
||||||
# « code=PCC&latest » sert maintenant pycharm-2025.3, le build unifié, qui
|
# « code=PCC&latest » sert le build unifié, qui
|
||||||
# s'arrête sur sa licence — son journal dit « NoValidIdeLicense » puis
|
# s'arrête sur sa licence — son journal dit « NoValidIdeLicense » puis
|
||||||
# « Get licenses: request requires authentication », et le projet ne
|
# « Get licenses: request requires authentication », et le projet ne
|
||||||
# s'ouvre jamais. Aucune ouverture, donc aucun .idea, donc rien à
|
# s'ouvre jamais. Aucune ouverture, donc aucun .idea, donc rien à
|
||||||
# configurer ensuite. Community ne demande aucun compte, et elle est
|
# configurer ensuite. Community ne demande aucun compte, et elle reste
|
||||||
# toujours publiée et corrigée : 2025.2.6.2 date du 2026-07-29.
|
# publiée et corrigée.
|
||||||
#
|
#
|
||||||
# Aucun numéro figé ici : on prend la plus récente archive
|
# Aucun numéro figé ici : on prend la plus récente archive
|
||||||
# « pycharm-community- » du flux officiel des versions, pour
|
# « pycharm-community- » du flux officiel des versions, pour
|
||||||
|
|
@ -1248,7 +1247,7 @@ class QemuInstallMixin:
|
||||||
# INDÉPENDANTS, l'un ne se déduit pas de l'autre, et le flux updates.xml de
|
# INDÉPENDANTS, l'un ne se déduit pas de l'autre, et le flux updates.xml de
|
||||||
# Google ne publie ni l'un ni l'autre. On lit donc l'URL sur la page
|
# Google ne publie ni l'un ni l'autre. On lit donc l'URL sur la page
|
||||||
# officielle, qui la porte en clair, et on retombe sur celle-ci si la page
|
# officielle, qui la porte en clair, et on retombe sur celle-ci si la page
|
||||||
# change de forme. Relevée et vérifiée (HTTP 200) le 2026-08-17.
|
# change de forme. Ce repli est une URL figée : elle vieillit.
|
||||||
_QEMU_ANDROID_URL = (
|
_QEMU_ANDROID_URL = (
|
||||||
"https://dl.google.com/dl/android/studio/ide-zips/2026.1.3.8/"
|
"https://dl.google.com/dl/android/studio/ide-zips/2026.1.3.8/"
|
||||||
"android-studio-quail3-patch1-linux.tar.gz"
|
"android-studio-quail3-patch1-linux.tar.gz"
|
||||||
|
|
@ -1310,6 +1309,12 @@ class QemuInstallMixin:
|
||||||
Tout le bloc est gardé : un IDE qui ne s'installe pas ne doit pas faire
|
Tout le bloc est gardé : un IDE qui ne s'installe pas ne doit pas faire
|
||||||
échouer l'installation d'ERPLibre, qui elle a duré une heure."""
|
échouer l'installation d'ERPLibre, qui elle a duré une heure."""
|
||||||
el_dir = self._qemu_install_dir(prod)
|
el_dir = self._qemu_install_dir(prod)
|
||||||
|
# Hors de la f-string : une expression sur deux lignes dans ses
|
||||||
|
# accolades ne parse qu'à partir de 3.12 (PEP 701).
|
||||||
|
idea_note = t(
|
||||||
|
"open the project once and close PyCharm; the .idea "
|
||||||
|
"it writes is what the install configures"
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
f'echo "== {t("Installing PyCharm (long)")} =="; '
|
f'echo "== {t("Installing PyCharm (long)")} =="; '
|
||||||
"{ "
|
"{ "
|
||||||
|
|
@ -1378,8 +1383,7 @@ class QemuInstallMixin:
|
||||||
# PyCharm n'a évidemment jamais ouvert le dépôt.
|
# PyCharm n'a évidemment jamais ouvert le dépôt.
|
||||||
+ f'echo " {t("PyCharm installed:")} /opt/pycharm '
|
+ f'echo " {t("PyCharm installed:")} /opt/pycharm '
|
||||||
f'({t("command")} pycharm, {t("project")} {el_dir})"; '
|
f'({t("command")} pycharm, {t("project")} {el_dir})"; '
|
||||||
f'echo " {t("open the project once and close PyCharm; the .idea "
|
f'echo " {idea_note}"; '
|
||||||
"it writes is what the install configures")}"; '
|
|
||||||
f'}} || echo " ⚠ {t("PyCharm not installed (see above)")}"; '
|
f'}} || echo " ⚠ {t("PyCharm not installed (see above)")}"; '
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1676,8 +1680,9 @@ class QemuInstallMixin:
|
||||||
# Aucune n'est empaquetée par une distribution : on passe donc par le site.
|
# Aucune n'est empaquetée par une distribution : on passe donc par le site.
|
||||||
#
|
#
|
||||||
# L'archive dépend de la version de GNOME Shell, et ce n'est pas une
|
# L'archive dépend de la version de GNOME Shell, et ce n'est pas une
|
||||||
# précaution de principe : mesuré le 2026-08-17, le même point d'entrée
|
# précaution de principe : le même point d'entrée sert une archive
|
||||||
# sert gTile v59 pour GNOME 46, v62 pour GNOME 48 et v52 pour GNOME 3.38.
|
# différente selon la version — gTile v59 pour GNOME 46, v62 pour
|
||||||
|
# GNOME 48, v52 pour GNOME 3.38.
|
||||||
# Une URL figée poserait donc, tôt ou tard, une archive faite pour une
|
# Une URL figée poserait donc, tôt ou tard, une archive faite pour une
|
||||||
# autre version.
|
# autre version.
|
||||||
#
|
#
|
||||||
|
|
@ -2097,8 +2102,8 @@ class QemuInstallMixin:
|
||||||
# peut rendre 0 sans avoir rien produit.
|
# peut rendre 0 sans avoir rien produit.
|
||||||
# DEUX emplacements, et il faut les deux. Avec une ABI injectée,
|
# DEUX emplacements, et il faut les deux. Avec une ABI injectée,
|
||||||
# AGP écrit dans « intermediates/apk/debug » et non dans
|
# AGP écrit dans « intermediates/apk/debug » et non dans
|
||||||
# « outputs/apk/debug » : mesuré, une compilation RÉUSSIE était
|
# « outputs/apk/debug » : une compilation RÉUSSIE se rapporte
|
||||||
# rapportée « aucun APK produit » parce que je ne regardais que le
|
# « aucun APK produit » dès que le contrôle ne regarde que le
|
||||||
# second. Un contrôle qui cherche au mauvais endroit ne vaut pas
|
# second. Un contrôle qui cherche au mauvais endroit ne vaut pas
|
||||||
# mieux que pas de contrôle.
|
# mieux que pas de contrôle.
|
||||||
f"apk=$(ls {el_dir}/mobile/erplibre_home_mobile/android/app/build"
|
f"apk=$(ls {el_dir}/mobile/erplibre_home_mobile/android/app/build"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue