Noble's apt-shipped setuptools (68.1.2) predates PEP 639 SPDX string
license support, added in setuptools 77.0.0. Since pyproject.toml
declares `license = "GPL-3.0-or-later"` in that form, `setup.py
install` now aborts with
configuration error: `project.license` must be valid exactly by
one definition (2 matches found)
on every AppImage build.
Add three things to the existing pycairo pip install:
* `setuptools>=77` — the actual PEP 639 fix.
* `packaging>=24.2` — setuptools 77+ delegates SPDX normalization to
`packaging.licenses`, which landed in packaging 24.2; Noble ships
24.0, so the setuptools upgrade alone raises
`ImportError: Cannot import packaging.licenses`.
* `--ignore-installed packaging` — Noble's `python3-packaging` is
apt-managed with no RECORD file, so pip refuses to upgrade it in
place ("Cannot uninstall packaging 24.0"). Ignoring the installed
copy lets the newer packaging land alongside and shadow it on
sys.path, without disturbing dpkg's view of the system.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gtk 3's Adwaita CSS (compiled into libgtk-3 as a GResource) draws
GtkSpinner via -gtk-icontheme("process-working-symbolic"). Recent
adwaita-icon-theme releases (Fedora 43 ships 49) dropped that icon,
and linuxdeploy-plugin-gtk doesn't bundle icon themes — it only
extends XDG_DATA_DIRS to /usr/share so lookups fall through to the
host. On Fedora hosts the lookup fails: the spinner widget occupies
its CSS min-size but shows no visible content. Sidebar service
reloads and Preferences dialogs both looked frozen even though the
underlying operation was progressing.
Add adwaita-icon-theme to apt and copy /usr/share/icons/Adwaita into
$APPDIR/usr/share/icons/Adwaita. Since AppRun places $APPDIR/usr/share
before /usr/share in XDG_DATA_DIRS, GTK finds the bundled Noble copy
(which still has the icon) before the host's newer, trimmed one.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Jammy's WebKit2GTK 4.1 point release (2.42) has a bug that kills the
WebKitWebProcess after one input event on the ZOOM Platform login
page; once the renderer dies the dialog freezes, stops repainting,
and accepts no further input. The dialog frame itself stays alive
so it wasn't obvious from the outside — the page just seemed to
hang. Non-AppImage runs weren't affected because they load the
host's newer WebKit.
Noble's 2.44+ point release fixes it. Bump the base image, update
apt package names where they've been renamed in the t64 transition
(libfuse2 → libfuse2t64, libgnome-desktop-3-19 →
libgnome-desktop-3-20t64), switch to apt's meson (Noble's is
current enough for meson-python), and pass --break-system-packages
to pip since Noble's system Python is PEP 668-managed.
Trade-off: glibc floor moves from 2.35 to 2.39, so the AppImage
stops running on hosts older than roughly 2024 distros (Ubuntu 22.04
and Debian 12 are too old). Documented in the Dockerfile header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three related bugs prevented the AppImage's WebKit-based service
login from working on Fedora / Arch / openSUSE — invisible on Ubuntu
because the host happens to satisfy the AppDir's missing pieces.
1. TLS backend not bundled. linuxdeploy-plugin-gtk copies libgio but
not the GIO modules directory that holds glib-networking's
libgiognutls.so. Without it GIO falls back to a compile-time libdir
that doesn't exist off-Debian, and WebKit reports "TLS is not
available" on the first HTTPS request. Copy the modules directory
into $APPDIR/usr/lib/gio/modules with an $ORIGIN/../.. RPATH, add
glib-networking to apt explicitly (it was pulled transitively but
its modules weren't being copied), and export GIO_MODULE_DIR in
AppRun.
2. WebKit shared libraries not bundled. libwebkit2gtk-4.1 and
libjavascriptcoregtk-4.1 are loaded lazily via gi.repository when
the user clicks a login button, so they never appear in any
binary's NEEDED chain — linuxdeploy has no reason to bundle them.
On non-Ubuntu hosts dlopen() falls through to the host copies with
different symbols, e.g. `undefined symbol: webkit_web_context_new`.
Pass both sonames to linuxdeploy explicitly.
3. WebKit helper processes not bundled and hardcoded path unreachable.
WebKit2 4.1 forks WebKitNetworkProcess / WebKitWebProcess from a
compile-time path baked into libwebkit2gtk-4.1.so.0
(/usr/lib/x86_64-linux-gnu/webkit2gtk-4.1/). linuxdeploy bundles
shared libraries, not auxiliary executables, and WEBKIT_EXEC_PATH
was removed upstream — the path is only overridable by byte-patching
the .so. Copy the webkit2gtk-4.1/ directory into the AppDir with
$ORIGIN/.. RPATH on each helper, and byte-patch the hardcoded
compile-time path inside libwebkit2gtk-4.1.so.0 to a writable
/tmp/.lutris-appimage.dir/webkit2gtk-4.1 (same byte length). AppRun
creates the symlink there before launch.
Also disable WebKit's bubblewrap sandbox
(WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1): bwrap opens a fresh
mount namespace that can't see the AppImage's FUSE mount at
/tmp/.mount_LutrisXXX, so any helper spawned inside it fails to reach
either the symlink target or the bundled libs. Lutris already runs
unsandboxed and spawns Wine, so an additional WebKit sandbox buys
negligible isolation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
linuxdeploy-plugin-gtk's generated AppRun hook unconditionally sets
GTK_THEME to "Adwaita:light/dark" with a comment that "custom themes
are broken." The "broken" claim is from 2018 and is disputed in
upstream #39 (open since 2023-02 with no maintainer response); the
practical effect is that Lutris's AppImage looks Adwaita-ish on every
host regardless of the user's actual GTK theme — visibly out of place
on KDE, breaks any custom theming, and undoes whatever care the user
took with their desktop.
Patch the vendored hook generator so GTK_THEME is exported only when
the caller explicitly opted in via APPIMAGE_GTK_THEME. The default is
no override at all, letting the host's GTK_THEME (or its absence)
take effect, which is what every other GTK app on the system does.
The colour-scheme detection block above the override is now dead
weight — it existed solely to choose between Adwaita:dark and
Adwaita:light — so drop it.
Each modification is annotated "LUTRIS-MOD" in the vendored script,
matching the convention promised in its file header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The plugin's upstream (linuxdeploy/linuxdeploy-plugin-gtk) has had no
commits since 2023-10-01 and feedback on #6726 flagged its dormancy
as a concern. Fetching the script from `master` on every build also
means the AppImage we ship is implicitly tied to whatever upstream
serves at build time, which is not reproducible and not auditable.
Vendor the script at commit 3b67a1d1 alongside its MIT licence. The
Dockerfile now COPYs the local copy into the build image instead of
curl-ing it from raw.githubusercontent.com, so the AppImage build is
reproducible from the repo alone and we have a place to apply local
patches if upstream stays dead (a separate follow-up commit drops the
plugin's hardcoded GTK theme override, which is the first such patch).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous AppRun exported PYTHONPATH pointing at the bundled stdlib and
prepended $APPDIR/usr/bin to PATH. Both leaked into every host
subprocess Lutris launches: a host umu-launcher invoked via its
`#!/usr/bin/env python3` shebang picked up the bundled Python 3.10
instead of the host's (no Xlib, no requests, no umu deps), and any
host Python that did get used inherited a PYTHONPATH pointing at our
3.10 stdlib (MAGIC mismatch in _sre on Fedora's 3.14).
Both exports were structurally unnecessary: AppRun already execs the
bundled interpreter by full path, so PATH does not need our bin
directory at the front, and switching setup.py to --install-layout=deb
makes Lutris findable via the bundled Python's default sys.path search
(dist-packages, what Debian-patched Python uses) without an env-var
hint.
Net effect: host umu-launcher, wine, xrandr, etc. now inherit a clean
PATH and no PYTHONPATH from the AppImage. The AppImage still requires
host Python 3.10+ for runners that themselves call Python (UMU is the
practical case); modern distros satisfy this by default.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the manual `cp -a /usr/lib/python3/dist-packages/{gi,cairo,
dbus,_dbus_*}` block with `pip install --target` on PyGObject<3.50,
dbus-python, and pycairo, so every runtime Python dep in the AppImage
comes from a single source we can version-pin instead of being
silently tied to whatever Ubuntu 22.04's apt happens to ship.
This requires the build image to be able to compile both packages
from source — they ship as sdist only and have moved to meson-python
as their build backend — so the Dockerfile gains:
* meson via pip (>=0.63.3; apt's 0.61 is too old for meson-python)
* pycairo via pip in the build image, so PyGObject's meson can find
its C headers (apt's python3-cairo ships no headers; the python3-
cairo and python3-gi-cairo apt packages are no longer needed and
have been dropped)
* ninja-build, libdbus-glib-1-dev — meson's runner and dbus-python's
GLib mainloop bindings respectively
PyGObject is pinned <3.50 because 3.50 switched to the girepository-
2.0 ABI introduced in GLib 2.80, which Ubuntu 22.04 (GLib 2.72)
doesn't have. Apt's python3-gi 3.42 stays installed for build-time
use — setup.py imports lutris/__init__.py which calls
gi.require_version() at import time.
The patchelf RPATH step from the previous commit still applies: pip
installs land in dist-packages with no RPATH set, and linuxdeploy
only patches the copies it makes (in usr/lib), not the originals
where Python actually loads them from.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The usr/lib/python3.10/config-3.10-x86_64-linux-gnu/ directory holds
link-time-only artifacts (Makefile, libpython symlinks, and a
relocatable object file `python.o` that wraps Py_Main for embedding
Python in a custom executable). None of it is referenced when running
Python code, but the relocatable object made linuxdeploy emit a
spurious "patchelf: wrong ELF type" error on every build — patchelf
refuses to touch ET_REL files, linuxdeploy logs the failure and moves
on. Strip the directory along with the other developer-only chunks of
the stdlib already removed; saves a few MB and leaves the build log
free of distracting errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously `make appimage` produced Lutris-dev-x86_64.AppImage unless the
caller remembered to set LUTRIS_VERSION. On a tagged release commit that
yielded a misleadingly-named artifact. Have build.sh awk the version out
of lutris/__init__.py when no explicit value is provided; an env override
(e.g. LUTRIS_VERSION=0.5.23-rc1 for a release candidate) still wins.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Flathub's recent ban on AI-using projects (see #6724) puts Lutris's
Flatpak channel at risk, and Flatpak has always been an awkward fit
anyway: Lutris is fundamentally an unsandboxed game manager that needs
to call host xrandr, 7z, fuser, wine, flatpak, mount drives, and reach
the user's whole $HOME. AppImage gives us a single-file distribution
that does none of that sandboxing.
This adds `make appimage`, which builds a self-contained AppImage in
a Docker/Podman container based on Ubuntu 22.04 (glibc 2.35, Python
3.10, GTK3, WebKit2GTK 4.1). The pipeline:
utils/appimage/build.sh — host wrapper (docker or podman)
utils/appimage/Dockerfile — build env with all GI typelibs
utils/appimage/build-in-container.sh
— assembles AppDir via linuxdeploy
+ linuxdeploy-plugin-gtk, runs
appimagetool
utils/appimage/AppRun — launcher; sources plugin-gtk hook,
sets PYTHONPATH, execs bundled
python3 with bin/lutris
Design notes worth flagging for review:
* The bundled Python is the build image's /usr/bin/python3.10 plus its
stdlib, with tkinter/test/idle stripped.
* PyGObject and dbus-python are taken from apt (python3-gi, python3-dbus)
rather than pip — both have switched to meson-python build backends
that drag in a sizeable toolchain, and the prebuilt packages link
against exactly the libgirepository/libdbus we already bundle from
the same distro.
* AppRun deliberately does NOT export LD_LIBRARY_PATH. linuxdeploy
already sets $ORIGIN-relative RPATH on every binary it deploys, and
exporting LD_LIBRARY_PATH would leak into every host subprocess —
which surfaced immediately when flatpak crashed loading our bundled
libssl 3.0 instead of the host's 3.4. The build script patchelf's
the RPATH on the manually-copied _gi.so / _dbus*.so so they still
find the bundled libgirepository without the env override.
* PATH is left intact apart from prepending $APPDIR/usr/bin, so host
tools (xrandr, 7z, wine, flatpak) still win — that's the whole
point of choosing AppImage over Flatpak here.
Output is dist/Lutris-<version>-x86_64.AppImage at ~83 MB. Smoke-tested
end-to-end on Fedora 43 Nobara: boots GUI, GPU detection, DB load,
service auth, GTK theme, and host subprocess invocations all work.
Refs #6724.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`"Foo" | None` fails at runtime on Python 3.10–3.13 because str doesn't
implement __or__. This only works on 3.14+ where PEP 749 defers annotation
evaluation. Add a check for this pattern alongside the existing conditional
import check so we catch it before it reaches users.
Ref: https://github.com/lutris/lutris/pull/6595
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Broaden the checker to flag unquoted annotations referencing any import
under an `if` or `try` block, not only `TYPE_CHECKING` guards. This
catches cases like try/except optional imports (e.g. GnomeDesktop) that
can be None at runtime and crash on dotted attribute access in eager
annotations.
Also fix dotted access check to cover `from X import Y` names used as
`Y.Attr` in annotations, not just `import X` modules.
Motivated by #6552 (fixed in 7019866).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python 3.14 evaluates annotations lazily (PEP 649), so unquoted
annotations like `threading.Event` work even when `threading` is only
imported under TYPE_CHECKING. On Python 3.10, these annotations are
evaluated eagerly and raise NameError at import time.
Add utils/check_annotations.py, an AST-based checker that detects
unquoted annotations referencing TYPE_CHECKING-only imports, covering
both bare names (`HTTPResponse`) and dotted access (`threading.Event`)
— the latter being a gap in ruff's FA102 rule. Wire it into `make
annotation-compat`, `make check`, and a new CI job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
./utils contains stand alone utility scripts; utility routines used
by wine seem to be in lutris/util/wine, so lets move this there.
I am hoping this will address issue #4529
- Updated min version check in setup.py to Python 3.6
- Updated isort config file and calls to align with v5.x
- Added init-hook for gi imports in .pylintrc to avoid invalid no-member issues
- Makefile: added lock, show-tree, bandit, black, mypy; updated test, cover, dev, isort, autopep8, check, isort-check, flake8, pylint; removed req, requirements;
- Updated .travis.yml to use poetry and make
- Added my email in AUTHORS
- Updated CONTRIBUTING.md
- Updated lint_python.yml to use poetry and make, reorganized instructions to have all install related steps first
- sorted imports: lutris, lutris-wrapper, cleanup_prefix.py and multiple files in tests dir