[IMP] script todo: ne pas retélécharger un IDE déjà installé

Rejouer une installation est le cas normal — une qui est morte, un outil ajouté
après coup — et le téléchargement en est la partie longue : environ cinq
minutes pour PyCharm, autant pour Android Studio, à chaque fois. Les deux
étapes vérifient donc /opt avant de sortir curl.

Mesuré sur erplibre-ubuntu-2604-gnome, IDE déjà posés : les deux étapes passent
de dix minutes à 0,094 s au total. Le reste rejoue quand même — lanceur, alias,
raccourci de bureau — il est idempotent et bon marché. Un test vérifie que la
garde n'avale pas l'échec du cas où il faut bel et bien télécharger.

--- EN ---

Replaying an install is the normal case — one that died, a tool added later —
and the download is the long part: about five minutes for PyCharm, as much for
Android Studio, every time. Both steps now check /opt before reaching for curl.

Measured on erplibre-ubuntu-2604-gnome with both IDEs already in place: the two
steps drop from ten minutes to 0.094 s total. The rest still replays — launcher,
alias, desktop entry — it is idempotent and cheap. A test checks the guard does
not swallow the failure of the case where downloading is actually needed.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-19 07:35:26 -04:00
parent b4310665fc
commit 72b19568af
3 changed files with 98 additions and 2 deletions

View file

@ -5606,6 +5606,15 @@ class TODO:
'if [ -z "$jb" ]; then '
f'echo " {t("no JetBrains build for")} $(uname -m)"; false; '
"else "
# Déjà posé ? On ne retélécharge pas. Rejouer une
# installation est le cas NORMAL — une qui est morte, un outil
# ajouté après coup — et le téléchargement en est la partie
# longue : mesuré, ~5 min pour Android Studio, autant pour
# PyCharm. Le reste de l'étape (lanceur, alias, raccourci)
# rejoue de toute façon, lui est idempotent et bon marché.
"if [ -x /opt/pycharm/bin/pycharm.sh ]; then "
f'echo " {t("already there, download skipped")}"; '
"else "
# /var/tmp et non /tmp : sur Fedora et dérivés /tmp est un tmpfs, en
# RAM — 1,2 Go d'archive y tueraient une VM de 3 Go.
# Le flux dit quelle archive Community prendre pour cette
@ -5635,7 +5644,7 @@ class TODO:
'curl -fsSL "$url" -o "$tmp" && '
"sudo mkdir -p /opt/pycharm && "
'sudo tar -xzf "$tmp" -C /opt/pycharm --strip-components=1; '
'rc=$?; rm -f "$tmp"; [ $rc -eq 0 ]; fi; } && { '
'rc=$?; rm -f "$tmp"; [ $rc -eq 0 ]; fi; fi; } && { '
+ self._qemu_jetbrains_launcher_cmd("/opt/pycharm", "pycharm")
+ self._qemu_desktop_entry_cmd(
"pycharm",
@ -5825,6 +5834,15 @@ class TODO:
f'echo " {t("Android Studio: Google publishes x86_64 only")}"; '
"false; "
"else "
# Déjà posé ? On ne retélécharge pas. Rejouer une
# installation est le cas NORMAL — une qui est morte, un outil
# ajouté après coup — et le téléchargement en est la partie
# longue : mesuré, ~5 min pour Android Studio, autant pour
# PyCharm. Le reste de l'étape (lanceur, alias, raccourci)
# rejoue de toute façon, lui est idempotent et bon marché.
"if [ -x /opt/android-studio/bin/studio ]; then "
f'echo " {t("already there, download skipped")}"; '
"else "
# La page officielle porte l'URL en clair ; le repli garde une
# version connue qui répond, pour le jour où sa forme change.
f"url=$(curl -fsSL --max-time 30 {self._QEMU_ANDROID_PAGE} "
@ -5836,7 +5854,7 @@ class TODO:
"sudo mkdir -p /opt/android-studio && "
'sudo tar -xzf "$tmp" -C /opt/android-studio '
"--strip-components=1; "
'rc=$?; rm -f "$tmp"; [ $rc -eq 0 ]; fi; } && { '
'rc=$?; rm -f "$tmp"; [ $rc -eq 0 ]; fi; fi; } && { '
+ self._qemu_jetbrains_launcher_cmd(
"/opt/android-studio", "studio", alias="android-studio"
)

View file

@ -2260,6 +2260,10 @@ TRANSLATIONS = {
"line": {"fr": "ligne", "en": "line"},
"free space": {"fr": "libre", "en": "free"},
"silent": {"fr": "silence", "en": "silent"},
"already there, download skipped": {
"fr": "déjà présent, téléchargement évité",
"en": "already there, download skipped",
},
"inotify watches raised for the IDE": {
"fr": "watches inotify relevées pour l'IDE",
"en": "inotify watches raised for the IDE",

View file

@ -773,6 +773,80 @@ class TestPycharmFirstOpen(unittest.TestCase):
)
class TestIdeInstallIsReplayable(unittest.TestCase):
"""Rejouer une installation ne doit pas retélécharger 2 Go.
C'est le cas NORMAL : une installation morte qu'on relance, un outil ajouté
après coup. Mesuré sur la VM, les deux étapes passent de ~5 min chacune à
0,094 s au total quand /opt porte déjà l'IDE — le reste (lanceur, alias,
raccourci) rejoue quand même, il est idempotent et bon marché.
"""
def setUp(self):
self.todo = TODO.__new__(TODO)
self.py = self.todo._qemu_pycharm_remote_cmd()
self.st = self.todo._qemu_android_studio_remote_cmd()
def test_pycharm_checks_before_downloading(self):
self.assertIn("[ -x /opt/pycharm/bin/pycharm.sh ]", self.py)
self.assertLess(
self.py.index("/opt/pycharm/bin/pycharm.sh"),
self.py.index("curl"),
)
def test_android_studio_checks_before_downloading(self):
self.assertIn("[ -x /opt/android-studio/bin/studio ]", self.st)
self.assertLess(
self.st.index("/opt/android-studio/bin/studio"),
self.st.index("curl"),
)
def test_the_launcher_still_runs_when_the_download_is_skipped(self):
"""Sauter le téléchargement ne doit pas sauter l'alias : c'est lui qui
rend « pycharm » et « android-studio » appelables."""
# rindex : le chemin du lanceur apparaît aussi dans la garde, tout au
# début. C'est la DERNIÈRE occurrence — l'installation du lanceur — qui
# doit suivre le bloc de téléchargement.
for cmd, marker in (
(self.py, "/usr/local/bin"),
(self.st, "/usr/local/bin"),
):
self.assertGreater(cmd.rindex(marker), cmd.index("curl"), marker)
def test_a_real_download_failure_still_fails(self):
"""La garde ne doit pas avaler l'échec du cas où il faut télécharger.
On force l'absence d'IDE et un curl qui échoue."""
import os
import tempfile
with tempfile.TemporaryDirectory() as tmp:
bin_dir = pathlib.Path(tmp) / "bin"
bin_dir.mkdir()
(bin_dir / "curl").write_text("#!/bin/bash\nexit 22\n")
(bin_dir / "sudo").write_text("#!/bin/bash\nexit 0\n")
(bin_dir / "python3").write_text("#!/bin/bash\ncat >/dev/null\n")
for n in ("curl", "sudo", "python3"):
(bin_dir / n).chmod(0o755)
res = subprocess.run(
["bash", "-c", self.st],
capture_output=True,
text=True,
env=dict(os.environ, PATH=f"{bin_dir}:/usr/bin:/bin"),
timeout=60,
)
out = res.stdout + res.stderr
# Sur cette machine /opt/android-studio n'existe pas : la garde
# laisse donc passer, et l'échec du curl doit se voir.
self.assertIn("⚠", out, out[-300:])
def test_both_steps_are_valid_shell(self):
for cmd in (self.py, self.st):
res = subprocess.run(
["bash", "-n"], input=cmd, capture_output=True, text=True
)
self.assertEqual(0, res.returncode, res.stderr)
class TestPycharmNetIsNarrow(unittest.TestCase):
"""Le filet de fermeture ne doit JAMAIS viser le ssh qui porte l'install.