# -*- mode: python ; coding: utf-8 -*- """PyInstaller Spec-Datei für SIP Softphone AppImage. Bündelt Python-App + pjsua2 + Qt + alle nativen Abhängigkeiten zu einem komplett unabhängigen Paket. """ import os import subprocess import re # === Pfade === APP_DIR = os.path.dirname(os.path.abspath(SPEC)) PJSUA2_SO = "/tmp/sipwebapp_run/_pjsua2.cpython-314-x86_64-linux-gnu.so" # === pjsua2 native Bibliotheken sammeln === # Alle Shared Libraries die _pjsua2.so braucht (nicht-triviale) def pjsua2_libs_sammeln(): """Alle pjsua2-Abhängigkeiten aus ldd extrahieren.""" ergebnis = subprocess.run( ["ldd", PJSUA2_SO], capture_output=True, text=True) libs = [] # System-Basis-Libs die überall vorhanden sind (nicht bündeln) skip = { "linux-vdso", "ld-linux", "libc.so", "libm.so", "libdl.so", "libpthread.so", "librt.so", } for zeile in ergebnis.stdout.splitlines(): match = re.match(r"\s+(\S+)\s+=>\s+(/\S+)", zeile) if match: name, pfad = match.groups() if not any(s in name for s in skip) and os.path.exists(pfad): libs.append((pfad, ".")) return libs pjsua2_binaries = pjsua2_libs_sammeln() # dbus native Libs dbus_libs = [] for lib_name in ["libdbus-1.so.3", "libglib-2.0.so.0", "libgio-2.0.so.0", "libgobject-2.0.so.0", "libgmodule-2.0.so.0"]: for pfad in [f"/usr/lib/{lib_name}", f"/usr/lib/x86_64-linux-gnu/{lib_name}"]: if os.path.exists(pfad): dbus_libs.append((pfad, ".")) break # === pjsua2 Python-Bindings === pjsua2_daten = [ (PJSUA2_SO, "."), ("/tmp/sipwebapp_run/pjsua2.py", "."), ] # === dbus Python-Bindings === SYS_SITE = "/usr/lib/python3.14/site-packages" dbus_daten = [] if os.path.isdir(f"{SYS_SITE}/dbus"): dbus_daten.append((f"{SYS_SITE}/dbus", "dbus")) for so in ["_dbus_bindings", "_dbus_glib_bindings"]: for f in os.listdir(SYS_SITE): if f.startswith(so) and f.endswith(".so"): dbus_daten.append((f"{SYS_SITE}/{f}", ".")) # === Ressourcen === ressourcen = [ (os.path.join(APP_DIR, "resources"), "resources"), ] # === Hidden Imports (Module die PyInstaller nicht automatisch findet) === hidden_imports = [ "pjsua2", "_pjsua2", "dbus", "dbus.service", "dbus.mainloop.glib", "dbus._dbus", "dbus.bus", "dbus.connection", "dbus.decorators", "dbus.exceptions", "dbus.lowlevel", "dbus.proxies", "dbus.types", "vobject", "vobject.vcard", "vobject.base", ] # === Analysis === a = Analysis( [os.path.join(APP_DIR, "main.py")], pathex=[APP_DIR], binaries=pjsua2_binaries + dbus_libs + pjsua2_daten, datas=ressourcen + dbus_daten, hiddenimports=hidden_imports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[ "tkinter", "unittest", "test", "xmlrpc", "pydoc", "doctest", "lib2to3", ], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name="sipwebapp", debug=False, bootloader_ignore_signals=False, strip=True, upx=False, console=False, # Kein Terminal-Fenster ) coll = COLLECT( exe, a.binaries, a.datas, strip=True, upx=False, name="sipwebapp", )