[ADD] tui qemu: choose mise or pyenv at deploy time
The interpreter provider is now chosen when creating a VM, in both interfaces. "mise" installs it in the VM and lays down a precompiled CPython; "pyenv" keeps today's build-from-source. The question is only asked where it means something: mise publishes binaries for amd64 and arm64 only. An all-s390x fleet never sees it, a mixed fleet sees it along with the architectures that will fall back to pyenv -- said before deploying, not discovered in a log an hour later. mise goes into /usr/local/bin rather than ~/.local/bin: the remote command runs through "ssh host 'command'", where neither ~/.profile nor ~/.bashrc is read. Choosing "mise" exports "auto", not "mise", so a failed install can still fall back to pyenv. The unavailability notice was translated as "pyenv required", which said the opposite of what happens: pyenv is the fallback, not a demand. --- FR --- Le fournisseur d'interpréteur se choisit désormais à la création d'une VM, dans les deux interfaces. « mise » l'installe dans la VM et pose un CPython précompilé ; « pyenv » garde la compilation d'aujourd'hui. La question n'est posée que là où elle a un sens : mise ne publie de binaires que pour amd64 et arm64. Un parc tout s390x ne la voit jamais, un parc mixte la voit avec les architectures qui retomberont sur pyenv — dit avant de déployer, pas découvert dans un log une heure plus tard. mise va dans /usr/local/bin plutôt que ~/.local/bin : la commande distante passe par « ssh hôte 'commande' », où ni ~/.profile ni ~/.bashrc ne sont lus. Choisir « mise » exporte « auto », pas « mise », pour qu'une installation ratée puisse encore retomber sur pyenv. L'avis d'indisponibilité était traduit par « pyenv exigé », qui disait l'inverse de ce qui se passe : pyenv est le repli, pas une exigence. Assisted-by: Claude Opus 5
This commit is contained in:
parent
70d9ad456c
commit
7f6c18037d
3 changed files with 171 additions and 4 deletions
|
|
@ -178,6 +178,7 @@ def build_spec(vms, domains, form):
|
||||||
"ssh_key": form["ssh_key"],
|
"ssh_key": form["ssh_key"],
|
||||||
"timezone": form.get("timezone", ""),
|
"timezone": form.get("timezone", ""),
|
||||||
"desktop": form.get("desktop", ""),
|
"desktop": form.get("desktop", ""),
|
||||||
|
"python_provider": form.get("python_provider", ""),
|
||||||
"install": form["install"],
|
"install": form["install"],
|
||||||
"add_ssh_config": form["add_ssh_config"],
|
"add_ssh_config": form["add_ssh_config"],
|
||||||
"parallelism": form["parallelism"],
|
"parallelism": form["parallelism"],
|
||||||
|
|
@ -245,6 +246,8 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
desktop_disk = ctx.get("desktop_disk_gb") or 0
|
desktop_disk = ctx.get("desktop_disk_gb") or 0
|
||||||
# [(clé, libellé)] — la liste vient de todo.py, source unique.
|
# [(clé, libellé)] — la liste vient de todo.py, source unique.
|
||||||
desktops = list(ctx.get("desktops") or [])
|
desktops = list(ctx.get("desktops") or [])
|
||||||
|
# Architectures pour lesquelles mise publie un binaire.
|
||||||
|
mise_arches = set(ctx.get("mise_arches") or ())
|
||||||
defaults = ctx.get("defaults") or {}
|
defaults = ctx.get("defaults") or {}
|
||||||
result = {"spec": None}
|
result = {"spec": None}
|
||||||
|
|
||||||
|
|
@ -508,6 +511,18 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
value=defaults.get("add_ssh_config", True),
|
value=defaults.get("add_ssh_config", True),
|
||||||
id="f_sshcfg",
|
id="f_sshcfg",
|
||||||
)
|
)
|
||||||
|
# mise pose un CPython précompilé, pyenv le compile.
|
||||||
|
# Grisé quand AUCUNE des VM retenues n'est sur une
|
||||||
|
# architecture que mise sert.
|
||||||
|
yield Static(
|
||||||
|
t("Python interpreter:"), classes="grouptitle"
|
||||||
|
)
|
||||||
|
with RadioSet(id="f_python"):
|
||||||
|
yield RadioButton(
|
||||||
|
t("mise (precompiled, faster)"), value=True
|
||||||
|
)
|
||||||
|
yield RadioButton(t("pyenv (compiles from source)"))
|
||||||
|
yield Static("", id="miswarn")
|
||||||
yield Static(t("Parallelism"), classes="grouptitle")
|
yield Static(t("Parallelism"), classes="grouptitle")
|
||||||
# Cochée, la case donne une exécution PAR installation :
|
# Cochée, la case donne une exécution PAR installation :
|
||||||
# le plafond du nombre de CPU ne s'applique plus. Décochée,
|
# le plafond du nombre de CPU ne s'applique plus. Décochée,
|
||||||
|
|
@ -600,6 +615,44 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
grow += desktop_disk
|
grow += desktop_disk
|
||||||
self.rows = plan_rows(self.vms, domains, grow)
|
self.rows = plan_rows(self.vms, domains, grow)
|
||||||
self._render_plan()
|
self._render_plan()
|
||||||
|
self._render_mise()
|
||||||
|
|
||||||
|
def _render_mise(self):
|
||||||
|
"""Grise le choix quand aucune VM retenue n'est servie par mise,
|
||||||
|
et nomme les architectures qui retomberont sur pyenv."""
|
||||||
|
usable = self._mise_usable()
|
||||||
|
self.query_one("#f_python", RadioSet).disabled = not usable
|
||||||
|
skipped = sorted(
|
||||||
|
{
|
||||||
|
vm["arch"]
|
||||||
|
for vm in self.vms
|
||||||
|
if vm["arch"] not in mise_arches
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = ""
|
||||||
|
if skipped:
|
||||||
|
msg = (
|
||||||
|
f" ⚠ {t('mise has no binary for:')} "
|
||||||
|
f"{', '.join(skipped)} — {t('those VMs use pyenv')}"
|
||||||
|
)
|
||||||
|
self.query_one("#miswarn", Static).update(msg)
|
||||||
|
|
||||||
|
def _python_provider(self):
|
||||||
|
"""« mise », « pyenv », ou rien — c'est-à-dire « automatique ».
|
||||||
|
|
||||||
|
Rien, et surtout pas « pyenv », quand mise n'est servi par aucune
|
||||||
|
architecture retenue. « mise est indisponible » ne veut pas dire
|
||||||
|
« l'utilisateur exige pyenv » : la nuance décide de tout, puisqu'un
|
||||||
|
choix EXPLICITE écarte le Python de la distribution. Sur s390x,
|
||||||
|
renvoyer « pyenv » forçait la compilation de CPython — celle dont
|
||||||
|
gcc 15.2 ne revient pas."""
|
||||||
|
if not self._mise_usable():
|
||||||
|
return ""
|
||||||
|
index = self.query_one("#f_python", RadioSet).pressed_index
|
||||||
|
return "pyenv" if index == 1 else "mise"
|
||||||
|
|
||||||
|
def _mise_usable(self):
|
||||||
|
return any(vm["arch"] in mise_arches for vm in self.vms)
|
||||||
|
|
||||||
def _desktop(self):
|
def _desktop(self):
|
||||||
"""« » pour un serveur, sinon la clé de la saveur choisie."""
|
"""« » pour un serveur, sinon la clé de la saveur choisie."""
|
||||||
|
|
@ -783,6 +836,7 @@ def run_deploy_form(ctx, run_app: bool = True):
|
||||||
or ctx.get("timezone")
|
or ctx.get("timezone")
|
||||||
or "",
|
or "",
|
||||||
"desktop": self._desktop(),
|
"desktop": self._desktop(),
|
||||||
|
"python_provider": self._python_provider(),
|
||||||
"install": install,
|
"install": install,
|
||||||
"add_ssh_config": self.query_one("#f_sshcfg", Checkbox).value,
|
"add_ssh_config": self.query_one("#f_sshcfg", Checkbox).value,
|
||||||
# Une exécution par installation : le nombre de VM retenues
|
# Une exécution par installation : le nombre de VM retenues
|
||||||
|
|
|
||||||
|
|
@ -4512,8 +4512,51 @@ class TODO:
|
||||||
'(vncpasswd puis vncserver :1)"; fi; '
|
'(vncpasswd puis vncserver :1)"; fi; '
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# mise ne publie de binaire que pour ces architectures : 46 assets à la
|
||||||
|
# v2026.8.4, aucun s390x — son propre script d'installation refuse cette
|
||||||
|
# plateforme. Ailleurs, le choix « mise » est sans objet et on reste sur
|
||||||
|
# pyenv, ce que le formulaire et l'invite disent avant de déployer.
|
||||||
|
QEMU_MISE_ARCHES = ("amd64", "arm64")
|
||||||
|
|
||||||
|
def _qemu_mise_remote_cmd(self, python_provider):
|
||||||
|
"""Pose mise DANS la VM et fixe EL_PYTHON_PROVIDER pour l'installation.
|
||||||
|
|
||||||
|
mise s'installe par défaut dans ~/.local/bin, qui n'est PAS dans le
|
||||||
|
PATH d'un « ssh hôte 'commande' » : ni ~/.profile ni ~/.bashrc n'y sont
|
||||||
|
lus. On le pose donc dans /usr/local/bin, présent dans le PATH par
|
||||||
|
défaut — même raison que pour cargo et rustc.
|
||||||
|
|
||||||
|
Sans mise utilisable, rien n'est écrit : lib_python_provider.sh
|
||||||
|
retombe alors sur pyenv toute seule."""
|
||||||
|
if python_provider == "pyenv":
|
||||||
|
# Explicite : même si mise se trouvait déjà dans l'image, on ne
|
||||||
|
# l'utilise pas. Sans cela le mode « auto » du dépôt le prendrait.
|
||||||
|
return "export EL_PYTHON_PROVIDER=pyenv; "
|
||||||
|
if python_provider != "mise":
|
||||||
|
return ""
|
||||||
|
return (
|
||||||
|
f'echo "== {t("Installing mise (precompiled Python)")} =="; '
|
||||||
|
"if command -v mise >/dev/null 2>&1; then "
|
||||||
|
'echo " mise: $(mise --version)"; '
|
||||||
|
"else "
|
||||||
|
# La variable est passée À sudo, pas exportée avant : « sudo -E »
|
||||||
|
# dépend de env_reset dans sudoers et n'est pas garanti.
|
||||||
|
"curl -fsSL https://mise.run "
|
||||||
|
"| sudo MISE_INSTALL_PATH=/usr/local/bin/mise sh "
|
||||||
|
'|| echo " mise indisponible ici : pyenv prendra le relais"; '
|
||||||
|
"fi; "
|
||||||
|
# « auto », et non « mise » : si l'installation ci-dessus a échoué,
|
||||||
|
# lib_python_provider.sh doit pouvoir retomber sur pyenv.
|
||||||
|
"export EL_PYTHON_PROVIDER=auto; "
|
||||||
|
)
|
||||||
|
|
||||||
def _qemu_erplibre_remote_cmd(
|
def _qemu_erplibre_remote_cmd(
|
||||||
self, branch, final_cmd=None, prod=False, desktop=False
|
self,
|
||||||
|
branch,
|
||||||
|
final_cmd=None,
|
||||||
|
prod=False,
|
||||||
|
desktop=False,
|
||||||
|
python_provider="",
|
||||||
):
|
):
|
||||||
"""Script exécuté DANS la VM. `branch` à None n'installe QUE le bureau
|
"""Script exécuté DANS la VM. `branch` à None n'installe QUE le bureau
|
||||||
— le choix graphique ne dépend pas d'ERPLibre, et une VM peut être
|
— le choix graphique ne dépend pas d'ERPLibre, et une VM peut être
|
||||||
|
|
@ -4521,7 +4564,9 @@ class TODO:
|
||||||
|
|
||||||
`final_cmd` par défaut : install_os + install_odoo_18. `prod` :
|
`final_cmd` par défaut : install_os + install_odoo_18. `prod` :
|
||||||
installe dans /opt/erplibre (au lieu de ~/git/erplibre) + service
|
installe dans /opt/erplibre (au lieu de ~/git/erplibre) + service
|
||||||
SELinux confiné. `desktop` : ajoute GNOME et son accès distant."""
|
SELinux confiné. `desktop` : ajoute GNOME et son accès distant.
|
||||||
|
`python_provider` : « mise » pour un CPython précompilé, sinon le
|
||||||
|
comportement par défaut du dépôt (pyenv, qui compile)."""
|
||||||
if not branch:
|
if not branch:
|
||||||
# Bureau seul : ni clone ni make, mais on garde le prologue —
|
# Bureau seul : ni clone ni make, mais on garde le prologue —
|
||||||
# attente de cloud-init et coupure des mises à jour automatiques,
|
# attente de cloud-init et coupure des mises à jour automatiques,
|
||||||
|
|
@ -4635,6 +4680,7 @@ class TODO:
|
||||||
"for t in curl git make; do command -v $t >/dev/null 2>&1 || "
|
"for t in curl git make; do command -v $t >/dev/null 2>&1 || "
|
||||||
'{ echo "Outil manquant apres installation: $t '
|
'{ echo "Outil manquant apres installation: $t '
|
||||||
'(reseau de la VM ?)"; exit 1; }; done; '
|
'(reseau de la VM ?)"; exit 1; }; done; '
|
||||||
|
+ self._qemu_mise_remote_cmd(python_provider)
|
||||||
# Clone : /opt/erplibre en PROD (racine, puis chown à l'utilisateur
|
# Clone : /opt/erplibre en PROD (racine, puis chown à l'utilisateur
|
||||||
# pour que make/venv s'exécutent sans sudo), ~/git/erplibre en dev.
|
# pour que make/venv s'exécutent sans sudo), ~/git/erplibre en dev.
|
||||||
+ (
|
+ (
|
||||||
|
|
@ -4665,6 +4711,7 @@ class TODO:
|
||||||
final_cmd=None,
|
final_cmd=None,
|
||||||
prod=False,
|
prod=False,
|
||||||
desktop=False,
|
desktop=False,
|
||||||
|
python_provider="",
|
||||||
):
|
):
|
||||||
"""Lance l'install ERPLibre en parallèle DÉTACHÉE sur les VM et ouvre
|
"""Lance l'install ERPLibre en parallèle DÉTACHÉE sur les VM et ouvre
|
||||||
le dashboard Textual. Quitter le dashboard n'arrête pas les installs.
|
le dashboard Textual. Quitter le dashboard n'arrête pas les installs.
|
||||||
|
|
@ -4677,7 +4724,7 @@ class TODO:
|
||||||
)
|
)
|
||||||
|
|
||||||
remote = self._qemu_erplibre_remote_cmd(
|
remote = self._qemu_erplibre_remote_cmd(
|
||||||
branch, final_cmd, prod, desktop
|
branch, final_cmd, prod, desktop, python_provider
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
mod = self._qemu_import_module()
|
mod = self._qemu_import_module()
|
||||||
|
|
@ -4742,6 +4789,7 @@ class TODO:
|
||||||
final_cmd=None,
|
final_cmd=None,
|
||||||
prod=False,
|
prod=False,
|
||||||
desktop=False,
|
desktop=False,
|
||||||
|
python_provider="",
|
||||||
):
|
):
|
||||||
"""Clone ERPLibre (branche donnée) dans la VM puis exécute la commande
|
"""Clone ERPLibre (branche donnée) dans la VM puis exécute la commande
|
||||||
d'install du profil choisi (streamé). `ip` : IP déjà résolue ;
|
d'install du profil choisi (streamé). `ip` : IP déjà résolue ;
|
||||||
|
|
@ -4763,7 +4811,7 @@ class TODO:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
remote = self._qemu_erplibre_remote_cmd(
|
remote = self._qemu_erplibre_remote_cmd(
|
||||||
branch, final_cmd, prod, desktop
|
branch, final_cmd, prod, desktop, python_provider
|
||||||
)
|
)
|
||||||
ssh_opts = (
|
ssh_opts = (
|
||||||
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
||||||
|
|
@ -5145,6 +5193,9 @@ class TODO:
|
||||||
print(
|
print(
|
||||||
f" {t('VM type:')} {t('Graphical (server + desktop):')} {label}"
|
f" {t('VM type:')} {t('Graphical (server + desktop):')} {label}"
|
||||||
)
|
)
|
||||||
|
prov = spec.get("python_provider")
|
||||||
|
if prov:
|
||||||
|
print(f" {t('Python interpreter:')} {prov}")
|
||||||
print(f" {t('SSH key:')} {spec.get('ssh_key') or t('none')}")
|
print(f" {t('SSH key:')} {spec.get('ssh_key') or t('none')}")
|
||||||
cfg = (
|
cfg = (
|
||||||
t("one entry per VM") if spec["add_ssh_config"] else t("untouched")
|
t("one entry per VM") if spec["add_ssh_config"] else t("untouched")
|
||||||
|
|
@ -5472,6 +5523,7 @@ class TODO:
|
||||||
"disk_presets": self._QEMU_DISK_PRESETS,
|
"disk_presets": self._QEMU_DISK_PRESETS,
|
||||||
"extra_disk_gb": self.ERPLIBRE_EXTRA_DISK_GB,
|
"extra_disk_gb": self.ERPLIBRE_EXTRA_DISK_GB,
|
||||||
"desktop_disk_gb": self.QEMU_DESKTOP_EXTRA_DISK_GB,
|
"desktop_disk_gb": self.QEMU_DESKTOP_EXTRA_DISK_GB,
|
||||||
|
"mise_arches": self.QEMU_MISE_ARCHES,
|
||||||
"desktops": [
|
"desktops": [
|
||||||
(k, v["label"]) for k, v in self._QEMU_DESKTOP.items()
|
(k, v["label"]) for k, v in self._QEMU_DESKTOP.items()
|
||||||
],
|
],
|
||||||
|
|
@ -5762,6 +5814,32 @@ class TODO:
|
||||||
return ""
|
return ""
|
||||||
return flavours[index] if 0 <= index < len(flavours) else ""
|
return flavours[index] if 0 <= index < len(flavours) else ""
|
||||||
|
|
||||||
|
def _qemu_ask_python_provider(self, arches):
|
||||||
|
"""mise (CPython précompilé) ou pyenv (compilation).
|
||||||
|
|
||||||
|
`arches` : les architectures du parc à déployer. mise ne publie pas de
|
||||||
|
binaire pour toutes — hors de QEMU_MISE_ARCHES la question n'a pas de
|
||||||
|
sens et on ne la pose pas."""
|
||||||
|
usable = [a for a in arches if a in self.QEMU_MISE_ARCHES]
|
||||||
|
if not usable:
|
||||||
|
# Rien, pas « pyenv » : le mode automatique doit rester libre de
|
||||||
|
# préférer un Python de la distribution. Voir _python_provider()
|
||||||
|
# dans le formulaire, même raisonnement.
|
||||||
|
return ""
|
||||||
|
print(f"\n{t('Python interpreter:')}")
|
||||||
|
print(f" [1] {t('mise (precompiled, faster)')} *")
|
||||||
|
print(f" [2] {t('pyenv (compiles from source)')}")
|
||||||
|
skipped = [a for a in arches if a not in self.QEMU_MISE_ARCHES]
|
||||||
|
if skipped:
|
||||||
|
# Dit AVANT le déploiement plutôt que découvert dans un log.
|
||||||
|
print(
|
||||||
|
f" ⚠ {t('mise has no binary for:')} "
|
||||||
|
f"{', '.join(sorted(set(skipped)))} — "
|
||||||
|
f"{t('those VMs use pyenv')}"
|
||||||
|
)
|
||||||
|
sel = input(t("Choice (number, blank = mise): ")).strip()
|
||||||
|
return "pyenv" if sel == "2" else "mise"
|
||||||
|
|
||||||
def _qemu_host_timezone(self):
|
def _qemu_host_timezone(self):
|
||||||
"""Fuseau de l'hôte. Défini une seule fois, dans deploy_qemu.py, qui
|
"""Fuseau de l'hôte. Défini une seule fois, dans deploy_qemu.py, qui
|
||||||
est aussi ce qui l'écrit dans le cloud-config : l'invite ne peut donc
|
est aussi ce qui l'écrit dans le cloud-config : l'invite ne peut donc
|
||||||
|
|
@ -5821,6 +5899,9 @@ class TODO:
|
||||||
timezone = self._qemu_ask_timezone()
|
timezone = self._qemu_ask_timezone()
|
||||||
locale = self._qemu_ask_locale()
|
locale = self._qemu_ask_locale()
|
||||||
desktop = self._qemu_ask_desktop()
|
desktop = self._qemu_ask_desktop()
|
||||||
|
python_provider = self._qemu_ask_python_provider(
|
||||||
|
[vm["arch"] for vm in vms]
|
||||||
|
)
|
||||||
|
|
||||||
# 4) Option : installer ERPLibre dans ~/git/erplibre de chaque VM.
|
# 4) Option : installer ERPLibre dans ~/git/erplibre de chaque VM.
|
||||||
install = None
|
install = None
|
||||||
|
|
@ -5898,6 +5979,7 @@ class TODO:
|
||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
"locale": locale,
|
"locale": locale,
|
||||||
"desktop": desktop,
|
"desktop": desktop,
|
||||||
|
"python_provider": python_provider,
|
||||||
"install": install,
|
"install": install,
|
||||||
"add_ssh_config": add_ssh_config,
|
"add_ssh_config": add_ssh_config,
|
||||||
"parallelism": parallelism,
|
"parallelism": parallelism,
|
||||||
|
|
@ -5966,6 +6048,7 @@ class TODO:
|
||||||
install = spec.get("install")
|
install = spec.get("install")
|
||||||
install_branch = install["branch"] if install else None
|
install_branch = install["branch"] if install else None
|
||||||
desktop = spec.get("desktop") or ""
|
desktop = spec.get("desktop") or ""
|
||||||
|
python_provider = spec.get("python_provider") or ""
|
||||||
ssh_key = spec.get("ssh_key")
|
ssh_key = spec.get("ssh_key")
|
||||||
add_ssh_config = spec["add_ssh_config"]
|
add_ssh_config = spec["add_ssh_config"]
|
||||||
parallelism = spec["parallelism"]
|
parallelism = spec["parallelism"]
|
||||||
|
|
@ -6040,6 +6123,7 @@ class TODO:
|
||||||
install["cmd"] if install else None,
|
install["cmd"] if install else None,
|
||||||
install["prod"] if install else False,
|
install["prod"] if install else False,
|
||||||
desktop=desktop,
|
desktop=desktop,
|
||||||
|
python_provider=python_provider,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
|
|
@ -6055,6 +6139,7 @@ class TODO:
|
||||||
install["cmd"],
|
install["cmd"],
|
||||||
install["prod"],
|
install["prod"],
|
||||||
desktop=desktop,
|
desktop=desktop,
|
||||||
|
python_provider=python_provider,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sommaire TOTAL (déploiement + résolution IP + ssh_config + install
|
# Sommaire TOTAL (déploiement + résolution IP + ssh_config + install
|
||||||
|
|
|
||||||
|
|
@ -2023,6 +2023,34 @@ TRANSLATIONS = {
|
||||||
"fr": "Choix (numéro, vide = rouvrir) : ",
|
"fr": "Choix (numéro, vide = rouvrir) : ",
|
||||||
"en": "Choice (number, blank = reopen): ",
|
"en": "Choice (number, blank = reopen): ",
|
||||||
},
|
},
|
||||||
|
"Python interpreter:": {
|
||||||
|
"fr": "Interpreteur Python :",
|
||||||
|
"en": "Python interpreter:",
|
||||||
|
},
|
||||||
|
"mise (precompiled, faster)": {
|
||||||
|
"fr": "mise (precompile, plus rapide)",
|
||||||
|
"en": "mise (precompiled, faster)",
|
||||||
|
},
|
||||||
|
"pyenv (compiles from source)": {
|
||||||
|
"fr": "pyenv (compile depuis les sources)",
|
||||||
|
"en": "pyenv (compiles from source)",
|
||||||
|
},
|
||||||
|
"Choice (number, blank = mise): ": {
|
||||||
|
"fr": "Choix (numero, vide = mise) : ",
|
||||||
|
"en": "Choice (number, blank = mise): ",
|
||||||
|
},
|
||||||
|
"mise has no binary for:": {
|
||||||
|
"fr": "mise ne publie pas de binaire pour :",
|
||||||
|
"en": "mise has no binary for:",
|
||||||
|
},
|
||||||
|
"those VMs use pyenv": {
|
||||||
|
"fr": "ces VM utiliseront pyenv",
|
||||||
|
"en": "those VMs use pyenv",
|
||||||
|
},
|
||||||
|
"Installing mise (precompiled Python)": {
|
||||||
|
"fr": "Installation de mise (Python precompile)",
|
||||||
|
"en": "Installing mise (precompiled Python)",
|
||||||
|
},
|
||||||
"VM type:": {
|
"VM type:": {
|
||||||
"fr": "Type de VM :",
|
"fr": "Type de VM :",
|
||||||
"en": "VM type:",
|
"en": "VM type:",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue