fix: name the python install command per system

Without Python the bootstrap told every system to run sudo apt install python3. It now reads Termux, macOS and /etc/os-release to name pkg, xcode-select, apt, dnf, pacman, zypper or apk, and in Termux offers to run pkg install python, which needs no root.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:04:54 +02:00
1 parent 7cebbfe2ec
commit c29c456206
3 files changed
+99 -10

No files matched your search

+56 -8
View File
@@ -118,17 +118,65 @@ if [ -z "$INSTALLER" ] || [ ! -f "$INSTALLER" ]; then
INSTALLER="$TEMP_INSTALLER"
fi
PYTHON=""
for command_name in python3 python; do
if command -v "$command_name" >/dev/null 2>&1 \
&& "$command_name" -c 'import sys; raise SystemExit(sys.version_info < (3, 8))' 2>/dev/null; then
PYTHON=$command_name
break
find_python() {
PYTHON=""
for command_name in python3 python; do
if command -v "$command_name" >/dev/null 2>&1 \
&& "$command_name" -c 'import sys; raise SystemExit(sys.version_info < (3, 8))' 2>/dev/null; then
PYTHON=$command_name
return 0
fi
done
return 1
}
# The command that installs Python here, named for this system: Termux has
# no sudo and installs with pkg, macOS has no apt.
python_install_command() {
case "${TERMUX_VERSION:-}:${PREFIX:-}" in
?*:*|*com.termux*) echo "pkg install python"; return ;;
esac
if [ "$(uname -s 2>/dev/null)" = "Darwin" ]; then
echo "xcode-select --install"
return
fi
done
os_ids=""
if [ -r /etc/os-release ]; then
os_ids=$(sed -n 's/^ID\(_LIKE\)\{0,1\}=//p' /etc/os-release | tr -d '"' | tr '\n' ' ')
fi
for os_id in $os_ids; do
case "$os_id" in
debian|ubuntu) echo "sudo apt install python3"; return ;;
fedora|rhel|centos) echo "sudo dnf install python3"; return ;;
arch) echo "sudo pacman -S python"; return ;;
opensuse*|suse) echo "sudo zypper install python3"; return ;;
alpine) echo "apk add python3"; return ;;
esac
done
echo ""
}
if ! find_python; then
install_command=$(python_install_command)
# Termux installs without root: at a terminal, the bootstrap offers to run it.
if [ "$install_command" = "pkg install python" ] && (: </dev/tty) 2>/dev/null; then
printf 'Python 3.8 or newer is required. Install it now with "%s"? [Y/n] ' \
"$install_command" >/dev/tty
read -r answer </dev/tty || answer="n"
case "$answer" in
[Nn]*) ;;
*) pkg install -y python </dev/tty && find_python || true ;;
esac
fi
fi
if [ -z "$PYTHON" ]; then
echo "Error: Python 3.8 or newer is required." >&2
echo " Install it, then run this command again. On Debian or Ubuntu: sudo apt install python3" >&2
if [ -n "$install_command" ]; then
echo " Install it with: $install_command" >&2
echo " Then run this command again." >&2
else
echo " Install it with this system's package manager, then run this command again." >&2
fi
exit 1
fi
+37
View File
@@ -2378,6 +2378,43 @@ class RetroArchWhereItReallyLives(unittest.TestCase):
self.assertIn(("retroarch", support / "system"), found)
class NoPythonNamesThisSystemsCommand(unittest.TestCase):
"""Without Python the bootstrap said `sudo apt install python3` on every
system: Termux has no sudo and installs with `pkg install python`, and
macOS has no apt."""
def _run(self, extra_env: dict, uname: str = "Linux") -> subprocess.CompletedProcess:
root = Path(_tempdir(self))
fake_bin = root / "bin"
fake_bin.mkdir()
for tool in ("sed", "tr", "dirname", "cat"):
found = shutil.which(tool)
if found:
(fake_bin / tool).symlink_to(found)
(fake_bin / "uname").write_text(f"#!/bin/sh\necho {uname}\n", encoding="utf-8")
(fake_bin / "uname").chmod(0o755)
# No terminal: the Termux offer is a question asked on /dev/tty.
return subprocess.run(
["/bin/sh", str(REPO_ROOT / "install.sh")], cwd=root,
env={"PATH": str(fake_bin), "HOME": str(root), **extra_env},
capture_output=True, text=True, timeout=30, check=False,
start_new_session=True,
)
def test_termux_is_told_pkg(self):
proc = self._run({"TERMUX_VERSION": "0.118",
"PREFIX": "/data/data/com.termux/files/usr"})
self.assertEqual(proc.returncode, 1)
self.assertIn("Install it with: pkg install python", proc.stderr)
self.assertNotIn("sudo", proc.stderr)
def test_macos_is_told_xcode_select(self):
proc = self._run({}, uname="Darwin")
self.assertEqual(proc.returncode, 1)
self.assertIn("Install it with: xcode-select --install", proc.stderr)
self.assertNotIn("apt", proc.stderr)
class ThePowerShellWrapperPassesTheStatusOn(unittest.TestCase):
"""Every non-zero exit became a red "installer failed" exception and
code 1: a --check that found work read as a crash, 2 and 130 were lost."""
+6 -2
View File
@@ -154,8 +154,12 @@ its BIOS folder, see [Getting started](getting-started.md).
### Error: Python 3.8 or newer is required.
No Python 3.8 or newer was found: Python is missing, or older. The message
names the command that installs it, `sudo apt install python3` on Debian and
Ubuntu. On a handheld running Batocera or Recalbox it is already there, so
names the command that installs it on the system it runs on: `sudo apt install
python3` on Debian and Ubuntu, `sudo dnf install python3` on Fedora, `sudo
pacman -S python` on Arch, `xcode-select --install` on macOS and `pkg install
python` in Termux, where it offers to run that command itself (Termux installs
without root). On Windows, `install.ps1` offers to run `winget install
Python.Python.3.12`. On a handheld running Batocera or Recalbox it is already there, so
this message usually means the command ran on the computer rather than on the
console.