# calendar_gui.spec # -*- mode: python ; coding: utf-8 -*- import os import sys import collections from pathlib import Path from PyInstaller.utils.hooks import collect_all DEBUG_FILTER = False # Collect reportlab barcode data (same as before) datas = [] binaries = [] hiddenimports = [] tmp_ret = collect_all('reportlab.graphics.barcode') datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] # ---- Analysis ---- a = Analysis( ['calendar_gui.py'], pathex=[os.getcwd()], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=['scipy','numpy'], # keep your existing excludes noarchive=False, optimize=0, ) # Platform-aware essential names & drop patterns IS_WINDOWS = sys.platform.startswith('win') IS_MAC = sys.platform == 'darwin' IS_UNIX = not IS_WINDOWS and not IS_MAC # Lowercase names will be used for case-insensitive matching if IS_WINDOWS: ESSENTIAL_QT_NAMES = [ 'qt5core.dll', 'qt5gui.dll', 'qt5widgets.dll', # the platform plugin folder & plugin name on Windows os.path.join('platforms', 'qwindows.dll'), # Python runtime dlls that are critical f'python{sys.version_info.major}{sys.version_info.minor}.dll', # e.g. python312.dll ] DROP_PATTERNS = [ # Qt optional DLLs you listed (Windows names) 'Qt5Quick.dll', 'qt5qml.dll', 'qt5qmlmodels.dll', 'qt5svg.dll', 'qt5websockets.dll', 'qt5eglfsdeviceintegration.dll', # Pillow / image codec dll patterns (windows) 'libavif', 'libwebp', 'lcms2', 'sharpyuv', 'openjp2', 'webpdemux', 'webpmux', # qml/translations directories ] STRIP_ON_BUILD = False else: # Linux / other UNIX ESSENTIAL_QT_NAMES = [ 'libQt5Core.so', 'libQt5Gui.so', 'libQt5Widgets.so', 'libQt5XcbQpa.so', 'libpython{major}.{minor}.so'.format(major=sys.version_info.major, minor=sys.version_info.minor), 'libstdc++.so.6', 'libc.so.6' ] DROP_PATTERNS = [ # Pillow image codecs & windows variants too 'libavif-', 'libwebp-', 'liblcms2-', 'libsharpyuv', 'libopenjp2', # Qt optional subsystems you listed 'libQt5Quick.so', 'libQt5Qml.so', 'libQt5QmlModels.so', 'libQt5Svg.so', 'libQt5WebSockets.so', 'libQt5EglFSDeviceIntegration.so', ] STRIP_ON_BUILD = True # Normalize patterns to lowercase for case-insensitive comparisons ESSENTIAL_QT_NAMES = [p.lower() for p in ESSENTIAL_QT_NAMES] DROP_PATTERNS = [p.lower() for p in DROP_PATTERNS] # filter block def _iter_strings_in_entry(entry): """ Yield all string-like parts found inside a PyInstaller TOC entry. Handles: - simple strings / Paths - tuples/lists like (src, dest) or (src, dest, type) - objects that have .path or similar (best-effort) """ if entry is None: return # primitive string / Path if isinstance(entry, (str, Path)): yield str(entry) return # sequence-like: iterate if isinstance(entry, collections.abc.Sequence) and not isinstance(entry, (str, bytes, bytearray)): for el in entry: # recurse yield from _iter_strings_in_entry(el) return # fallback: try to cast to string try: s = str(entry) if s: yield s except Exception: return def _entry_has_any(entry, substring): """Return True if any string part of the entry contains substring (case-insensitive).""" sub = substring.lower() for s in _iter_strings_in_entry(entry): if sub in s.lower(): return True return False def entry_basename_set(entry): """ Return set of basenames (lowercased) found in the entry. Useful to match bare filenames like 'Qt5Quick.dll' or 'libQt5Quick.so'. """ names = set() for s in _iter_strings_in_entry(entry): try: p = Path(s) names.add(p.name.lower()) except Exception: names.add(str(s).lower()) return names def should_drop_entry(entry): """ Decide whether to drop this TOC entry. Uses: - ESSENTIAL_QT_NAMES (substrings) to never drop essentials - DROP_PATTERNS (substrings) to drop matches - qml/translations directory substrings Matches case-insensitively against all string parts of the entry. """ # never drop if any essential name substring appears in entry for ess in ESSENTIAL_QT_NAMES: if _entry_has_any(entry, ess): return False # drop if any drop-pattern matches for p in DROP_PATTERNS: if _entry_has_any(entry, p): return True # drop QML or translations paths for s in _iter_strings_in_entry(entry): sl = s.lower().replace('\\', '/') if '/qml/' in sl or '/translations/' in sl: return True # Bonus: drop Pillow codec names if entry mentions 'pillow.libs' AND codec names low_all = " ".join(s.lower() for s in _iter_strings_in_entry(entry)) if 'pillow.libs' in low_all and any(p in low_all for p in DROP_PATTERNS): return True # no reason to drop return False # Now filter a.binaries and a.datas using should_drop_entry filtered_binaries = [] for entry in a.binaries: drop = should_drop_entry(entry) if DEBUG_FILTER: print(("DROP" if drop else "KEEP"), "binary:", list(_iter_strings_in_entry(entry))) if not drop: filtered_binaries.append(entry) a.binaries = filtered_binaries filtered_datas = [] for entry in a.datas: drop = should_drop_entry(entry) if DEBUG_FILTER: print(("DROP" if drop else "KEEP"), "data:", list(_iter_strings_in_entry(entry))) if not drop: filtered_datas.append(entry) a.datas = filtered_datas # ---------------------------------------------------------------- # ---- Build steps: PYZ, EXE, COLLECT ---- pyz = PYZ(a.pure, a.zipped_data, cipher=None) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='azrechner', debug=False, bootloader_ignore_signals=False, strip=STRIP_ON_BUILD, # only strip on unix-like systems upx=True, console=False, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=STRIP_ON_BUILD, # only strip on unix-like systems upx=True, # if you know specific DLLs that break with UPX you can list them here upx_exclude=[], name='azrechner-trimmed-v2', )