fix: pick a Python that can do HTTPS

This commit is contained in:
Abdessamad Derraz committed 2026-11-05 11:53:49 +01:00
1 parent 4d46587693
commit bcedd22a9e
7 files changed
+118 -13

No files matched your search

+1 -1
View File
@@ -84,7 +84,7 @@
param($candidate)
if (-not $candidate) { return $false }
$arguments = $candidate[1]
& $candidate[0].Source @arguments -c "import sys; raise SystemExit(sys.version_info < (3, 8))" | Out-Null
& $candidate[0].Source @arguments -c "import sys, ssl, unicodedata, zlib; raise SystemExit(sys.version_info < (3, 8))" 2>$null | Out-Null
return ($LASTEXITCODE -eq 0)
}
$needPython = "Python 3.8 or newer is required. Install it with " +
+14 -2
View File
@@ -121,14 +121,21 @@ if [ -z "$INSTALLER" ] || [ ! -f "$INSTALLER" ]; then
INSTALLER="$TEMP_INSTALLER"
fi
# A Python can be built without ssl, unicodedata or zlib, and the installer
# needs all three: a python3 lacking ssl was chosen over a full python and
# every download then failed as if the network were down.
find_python() {
PYTHON=""
PYTHON_LACKING=""
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
command -v "$command_name" >/dev/null 2>&1 || continue
"$command_name" -c 'import sys; raise SystemExit(sys.version_info < (3, 8))' \
2>/dev/null || continue
if "$command_name" -c 'import ssl, unicodedata, zlib' 2>/dev/null; then
PYTHON=$command_name
return 0
fi
PYTHON_LACKING=${PYTHON_LACKING:-$command_name}
done
return 1
}
@@ -172,6 +179,11 @@ if ! find_python; then
esac
fi
fi
if [ -z "$PYTHON" ] && [ -n "$PYTHON_LACKING" ]; then
echo "Error: $PYTHON_LACKING is built without a module the installer needs" >&2
echo " (ssl, unicodedata or zlib). Run it with a full Python 3.8 or newer." >&2
exit 1
fi
if [ -z "$PYTHON" ]; then
echo "Error: Python 3.8 or newer is required." >&2
if [ -n "$install_command" ]; then
+8 -8
View File
@@ -62,8 +62,8 @@
{
"dest": "roms/ports/retrobios/tui.txt",
"repo_path": "ports/shared/tui.txt",
"size": 35984,
"sha256": "7291b11eef5b5c2df8d2befab10614a2d515bffdf96dc208529b5dee9e6df0f1",
"size": 36434,
"sha256": "fa05432a9a4325f3fd3220eec0548ad932dffc1888b93a08af2047cc78aa4d0c",
"executable": false
}
]
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1134145,
"sha256": "9a837360311366ecb4a65c4c4eff5388319ff0ade4f6acad3709cd4e63249a59"
"size": 1134595,
"sha256": "72ef1c1054aaff03491127694455e27d25edcc5565ace9e490bb5623c2a62840"
},
"members": [
{
@@ -141,8 +141,8 @@
{
"name": "RetroBIOS/tui.txt",
"repo_path": "ports/shared/tui.txt",
"size": 35984,
"sha256": "7291b11eef5b5c2df8d2befab10614a2d515bffdf96dc208529b5dee9e6df0f1",
"size": 36434,
"sha256": "fa05432a9a4325f3fd3220eec0548ad932dffc1888b93a08af2047cc78aa4d0c",
"executable": false
}
]
@@ -208,8 +208,8 @@
{
"dest": ".config/modules/retrobios/tui.txt",
"repo_path": "ports/shared/tui.txt",
"size": 35984,
"sha256": "7291b11eef5b5c2df8d2befab10614a2d515bffdf96dc208529b5dee9e6df0f1",
"size": 36434,
"sha256": "fa05432a9a4325f3fd3220eec0548ad932dffc1888b93a08af2047cc78aa4d0c",
"executable": false
}
]
+1 -1
View File
@@ -43,7 +43,7 @@ rb_fetch_runtime() {
fi
PortMasterDialogInit "no-check"
PortMasterDialog "messages_begin"
PortMasterDialog "message" "This system's Python is older than 3.8. Downloading PortMaster's $RB_RUNTIME runtime."
PortMasterDialog "message" "This system's Python is older than 3.8 or lacks a module install.py needs. Downloading PortMaster's $RB_RUNTIME runtime."
answer=$(rb_answer "check_runtime" "$RB_RUNTIME.squashfs")
PortMasterDialog "messages_end"
PortMasterDialogExit
+11 -1
View File
@@ -193,10 +193,17 @@ rb_open_log() {
# --- Python ---------------------------------------------------------------
rb_python_ok() {
# The modules install.py needs that a Python can be built without: a python3
# lacking ssl was chosen over PortMaster's runtime, and every download failed
# as if the network were down.
rb_python_new() {
"$@" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)' >/dev/null 2>&1
}
rb_python_ok() {
rb_python_new "$@" && "$@" -c 'import ssl, unicodedata, zlib' >/dev/null 2>&1
}
# The OS python3 when it is 3.8 or newer, else PortMaster's python_3.11
# runtime on aarch64, mounted as PortMaster's own Python ports do. A launcher
# that can download the runtime defines rb_fetch_runtime. Sets
@@ -252,6 +259,9 @@ rb_release_python() {
rb_python_text() {
local found
found=$(python3 --version 2>&1 | awk '{print $2}')
if command -v python3 >/dev/null 2>&1 && rb_python_new python3; then
found="$found, built without a module install.py needs (ssl, unicodedata or zlib)"
fi
case "$RB_PYTHON_PROBLEM" in
old) printf '%s\n' "Python 3.8 or newer is needed. This system has ${found:-none}, and PortMaster's Python runtime exists for aarch64 only, and this device is $(rb_arch). $RB_PC_ROUTE" ;;
runtime_offline) printf '%s\n' "Python 3.8 or newer is needed. This system has ${found:-none}, and PortMaster's $RB_RUNTIME runtime needs a connection to download. $RB_PC_ROUTE" ;;
+38
View File
@@ -1080,6 +1080,44 @@ class ThePortMasterPort(unittest.TestCase):
self.assertIn("--dest", screen)
self.assertEqual(device.calls(), [])
def _python_without_ssl(self, device) -> None:
"""A python3 3.8 or newer whose ssl import fails, as on a CFW image
built without OpenSSL."""
real = shutil.which("python3.8") or sys.executable
shadow = device.root / "no-ssl"
shadow.mkdir()
(shadow / "ssl.py").write_text('raise ImportError("built without ssl")\n')
device.tool("python3", f'#!/bin/sh\nPYTHONPATH="{shadow}" exec {real} "$@"\n')
def test_a_python_without_ssl_is_told_on_the_console(self):
"""Only the version was probed: a python3 without ssl was chosen and
every download failed as if the network were down."""
device = self.device()
self._python_without_ssl(device)
device.tool("uname", "#!/bin/sh\necho armv7l\n")
proc = device.run(["bash", self.script])
self.assertEqual(proc.returncode, 1)
screen = " ".join(device.screen.split())
self.assertIn("built without a module install.py needs", screen)
self.assertEqual(device.calls(), [])
def test_a_python_without_ssl_gives_way_to_the_runtime(self):
device = self.device()
self._python_without_ssl(device)
real = shutil.which("python3.8") or sys.executable
device.tool("uname", "#!/bin/sh\necho aarch64\n")
device.tool("mount", '#!/bin/sh\nprintf "mount %s\\n" "$*" >> "$FAKE_STATE/tools.log"\n'
'mkdir -p "$2/bin"\n'
f'printf \'#!/bin/sh\\nunset PYTHONHOME\\nexec {real} "$@"\\n\' > "$2/bin/python3"\n'
'chmod +x "$2/bin/python3"\n')
device.tool("umount", FAKE_RECORDER)
(self.control / "libs").mkdir()
(self.control / "libs" / "python_3.11.squashfs").write_bytes(b"")
proc = device.run(["bash", self.script])
self.assertEqual(proc.returncode, 0, proc.stderr.decode())
[call] = device.tui_calls()
self.assertTrue(any(t.startswith("mount") for t in device.tools()))
def test_the_runtime_is_mounted_then_unmounted(self):
"""ArkOS's Python 3.7: PortMaster's python_3.11 runtime, mounted for
the run and unmounted after it."""
+45
View File
@@ -2575,6 +2575,51 @@ class NoPythonNamesThisSystemsCommand(unittest.TestCase):
self.assertNotIn("apt", proc.stderr)
class ABootstrapPythonCanDoHttps(unittest.TestCase):
"""find_python checked the version only: a python3 built without ssl
was chosen over a full python, and every download then failed."""
def _bin(self) -> tuple[Path, Path]:
root = Path(_tempdir(self))
fake_bin = root / "bin"
fake_bin.mkdir()
for tool in ("sed", "tr", "dirname", "cat", "awk", "uname"):
found = shutil.which(tool)
if found:
(fake_bin / tool).symlink_to(found)
shadow = root / "no-ssl"
shadow.mkdir()
(shadow / "ssl.py").write_text('raise ImportError("built without ssl")\n')
(fake_bin / "python3").write_text(
f'#!/bin/sh\nPYTHONPATH="{shadow}" exec {shlex.quote(sys.executable)} "$@"\n')
(fake_bin / "python3").chmod(0o755)
(root / "install.py").write_text("import ssl; print('installer ran')\n")
shutil.copy(REPO_ROOT / "install.sh", root / "install.sh")
return root, fake_bin
def _run(self, root: Path, fake_bin: Path) -> subprocess.CompletedProcess:
return subprocess.run(
["/bin/sh", str(root / "install.sh")], cwd=root,
env={"PATH": str(fake_bin), "HOME": str(root)},
capture_output=True, text=True, timeout=30, check=False,
stdin=subprocess.DEVNULL, start_new_session=True,
)
def test_a_full_python_is_preferred(self):
root, fake_bin = self._bin()
(fake_bin / "python").symlink_to(sys.executable)
proc = self._run(root, fake_bin)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("installer ran", proc.stdout)
def test_the_missing_module_is_named(self):
root, fake_bin = self._bin()
proc = self._run(root, fake_bin)
self.assertEqual(proc.returncode, 1)
self.assertNotIn("installer ran", proc.stdout)
self.assertIn("python3 is built without a module the installer needs", proc.stderr)
@unittest.skipUnless(shutil.which("pwsh") and shutil.which("script"), "needs PowerShell and a terminal")
class WindowsWithoutPythonIsOfferedWinget(unittest.TestCase):
"""A Windows machine without Python was told to install it, open a new