Linux AppImage fails with "No module named installer.gui" when the PyPI

This commit is contained in:
jay23606 committed 2026-11-08 11:45:21 -05:00
1 parent ae30a61b7b
commit 9d07157b3e
2 files changed
+59

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
"""The AppImage entry must ignore the user's site-packages (issue 985).
AppRun starts the bundled Python with `-m installer.gui`. Without PYTHONNOUSERSITE, a PyPI package named `installer`
in the user site shadows the project's own installer package and the window fails with "No module named installer.gui".
The test reads the export lines from AppRun, then runs Python with that environment against a fake user site holding
a shadowing `installer` package (no `gui`) and a fake bundle holding the real layout. The bundle is added the way
totk-release.pth adds it, after the user site, so the user site wins unless AppRun turns it off.
"""
import os
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / 'tools'))
import make_release_linux # noqa: E402
line = 'export PYTHONNOUSERSITE=1'
lines = make_release_linux.APPRUN.splitlines()
# Environment from AppRun's export lines. Values that refer to $HERE point into the mount and are not needed here.
env = dict(os.environ)
env.pop('PYTHONNOUSERSITE', None)
for text in lines:
if text.startswith('export ') and '=' in text and '$' not in text:
name, value = text[len('export '):].split('=', 1)
env[name] = value
env.pop('PYTHONPATH', None)
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
user_base = tmp / 'userbase'
env['PYTHONUSERBASE'] = str(user_base)
user_site = Path(subprocess.run(
[sys.executable, '-c', 'import site; print(site.getusersitepackages())'],
env=env, capture_output=True, text=True, check=True, cwd=tmp).stdout.strip())
shadow = user_site / 'installer'
shadow.mkdir(parents=True)
(shadow / '__init__.py').write_text('# stands in for the PyPI "installer" package: no gui submodule\n')
bundle = tmp / 'bundle'
(bundle / 'installer').mkdir(parents=True)
(bundle / 'installer' / '__init__.py').write_text('')
(bundle / 'installer' / 'gui.py').write_text('print("gui imported")\n')
code = 'import site, sys; site.addsitedir(sys.argv[1]); import installer.gui'
result = subprocess.run([sys.executable, '-c', code, str(bundle)],
env=env, capture_output=True, text=True, cwd=tmp)
assert result.returncode == 0, f'installer.gui not importable with AppRun env: {result.stderr.strip()}'
# Checked last so the import behaviour is what fails on the base commit; this line guards the mechanism.
assert line in lines, f'AppRun does not set {line!r}; AppRun lines: {lines}'
print('AppRun sets PYTHONNOUSERSITE=1 and bundled installer.gui imports past a user-site shadow')
+2
View File
@@ -426,6 +426,8 @@ APPRUN = '''#!/bin/sh
# The AppImage entry: the setup window, run by the bundled Python from the read-only mount (no bytecode written there).
HERE="$(dirname "$(readlink -f "$0")")"
export PYTHONDONTWRITEBYTECODE=1
# Ignore ~/.local/lib/pythonX.Y/site-packages: a user-installed package such as PyPI's `installer` would shadow ours
export PYTHONNOUSERSITE=1
export TCL_LIBRARY="$HERE/python/lib/tcl9.0"
export TK_LIBRARY="$HERE/python/lib/tk9.0"
exec "$HERE/python/bin/python3" -m installer.gui "$@"