diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad91e01..5c0f03c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -180,6 +180,11 @@ jobs: ls -la dist/chimpstackr/ echo "=== Testing CLI ===" dist/chimpstackr/chimpstackr-cli --help || true + # Host GLib/GTK/libstdc++ must come from the user's system (issue #179) + if ls dist/chimpstackr/_internal | grep -E '^lib(glib-2\.0|gio-2\.0|gtk-3|stdc\+\+)\.so'; then + echo "::error::Build host system libs were bundled, see chimpstackr.spec" + exit 1 + fi - name: Create AppImage run: | diff --git a/chimpstackr.spec b/chimpstackr.spec index 3f99d94..5f63b8e 100644 --- a/chimpstackr.spec +++ b/chimpstackr.spec @@ -113,6 +113,43 @@ a_cli = Analysis( noarchive=False, ) +# ── Linux: use the user's desktop stack, don't bundle the build host's ── +# PyInstaller picks these up from the build machine (ubuntu-24.04). At runtime the +# user's own GIO/GVfs modules, GTK input methods, pixbuf loaders and Mesa drivers +# get loaded into the app and need the user's matching versions of these libs. +# Bundling older copies shadows them and crashes newer distros (issue #179: +# Ubuntu 26.04 GVfs needs g_variant_builder_init_static from GLib 2.84+). +# Wheel-vendored libs have hashed names (e.g. libxcb-ad31f5a3.so) and are kept. +if sys.platform.startswith('linux'): + import re + _system_libs = re.compile(r'^lib(' + '|'.join([ + # GLib / GTK3 stack (loaded by Qt's gtk3 platform theme). libgthread + # stays bundled: Qt needs it, it's an empty stub, and openSUSE omits it. + # libbz2 too: Fedora names it libbz2.so.1, Python's _bz2 wants .so.1.0. + 'glib-2.0', 'gio-2.0', 'gobject-2.0', 'gmodule-2.0', + 'gtk-3', 'gdk-3', 'gdk_pixbuf-2.0', 'atk-1.0', 'atk-bridge-2.0', 'atspi', + 'pango-1.0', 'pangocairo-1.0', 'pangoft2-1.0', 'cairo', 'cairo-gobject', + 'harfbuzz', 'fribidi', 'thai', 'datrie', 'graphite2', 'pixman-1', 'epoxy', + 'fontconfig', 'freetype', 'png16', 'brotlicommon', 'brotlidec', 'expat', + 'ffi', 'pcre2-8', 'mount', 'blkid', 'selinux', 'z', 'zstd', + # X11 / xcb (shared with the system's Mesa libGL) + 'X11', 'X11-xcb', 'Xau', 'Xdmcp', 'Xext', 'Xfixes', 'Xi', 'Xinerama', + 'Xrandr', 'Xrender', 'Xcursor', 'Xcomposite', 'Xdamage', 'xkbcommon', + 'xcb-glx', 'xcb-randr', 'xcb-render', 'xcb-shm', 'xcb-sync', 'xcb-xfixes', + # D-Bus, systemd, crypto and Kerberos (deps of the system's GVfs modules) + 'dbus-1', 'systemd', 'gcrypt', 'gpg-error', 'bsd', 'md', 'ssl', 'crypto', + 'gssapi_krb5', 'krb5', 'krb5support', 'k5crypto', 'com_err', 'keyutils', + 'cap', 'lz4', 'lzma', 'uuid', + # Toolchain runtime (Mesa's LLVM needs the system's newer libstdc++) + 'stdc\\+\\+', 'gcc_s', + ]) + r')\.so(\.|$)') + + def _drop_system_libs(toc): + return [e for e in toc if not _system_libs.match(os.path.basename(e[0]))] + + a.binaries = _drop_system_libs(a.binaries) + a_cli.binaries = _drop_system_libs(a_cli.binaries) + # Merge analyses so shared libs are only collected once MERGE( (a, 'chimpstackr', 'chimpstackr'), diff --git a/src/cli.py b/src/cli.py index d973e5c..2022694 100644 --- a/src/cli.py +++ b/src/cli.py @@ -7,6 +7,13 @@ python -m src.cli --input img1.jpg img2.jpg img3.jpg --output stacked.png --align """ import os +import multiprocessing + +# Frozen builds: let multiprocessing helpers that re-run this executable exit +# instead of running the CLI (see src/run.py) +if __name__ == "__main__": + multiprocessing.freeze_support() + os.environ.setdefault('OPENCV_IO_ENABLE_OPENEXR', '1') import argparse diff --git a/src/run.py b/src/run.py index f6e8c02..07aa00a 100644 --- a/src/run.py +++ b/src/run.py @@ -1,4 +1,10 @@ -import os, sys, tempfile, logging +import os, sys, tempfile, logging, multiprocessing + +# Frozen builds: multiprocessing helpers (e.g. the resource tracker Python 3.14 +# starts for numba's parallel lock) re-run this executable. Let them do their +# job and exit instead of opening another copy of the app (issue #179). +if __name__ == "__main__": + multiprocessing.freeze_support() # Enable OpenEXR support in OpenCV (must be set before cv2 import) os.environ.setdefault('OPENCV_IO_ENABLE_OPENEXR', '1')