diff options
| -rw-r--r-- | azrechner-trimmed-v2.spec | 208 | ||||
| -rw-r--r-- | azrechner-trimmed.spec | 109 |
2 files changed, 279 insertions, 38 deletions
diff --git a/azrechner-trimmed-v2.spec b/azrechner-trimmed-v2.spec new file mode 100644 index 0000000..e77e619 --- /dev/null +++ b/azrechner-trimmed-v2.spec @@ -0,0 +1,208 @@ +# 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', +) diff --git a/azrechner-trimmed.spec b/azrechner-trimmed.spec index 51261b5..d42a1b1 100644 --- a/azrechner-trimmed.spec +++ b/azrechner-trimmed.spec @@ -1,9 +1,11 @@ # calendar_gui.spec # -*- mode: python ; coding: utf-8 -*- import os +import sys +from pathlib import Path from PyInstaller.utils.hooks import collect_all -# Collect reportlab barcode data as you already did +# Collect reportlab barcode data (same as before) datas = [] binaries = [] hiddenimports = [] @@ -20,46 +22,79 @@ a = Analysis( hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=['scipy'], # keep your existing excludes + excludes=['scipy','numpy'], # keep your existing excludes noarchive=False, optimize=0, ) -# ---- Helper: drop specific native libs / plugin files we listed ---- -# IMPORTANT: do NOT drop essential Qt runtime libs needed for your platform. -ESSENTIAL_QT_NAMES = [ - 'libQt5Core.so', 'libQt5Gui.so', 'libQt5Widgets.so', 'libQt5XcbQpa.so', - 'libpython3.12.so', 'libstdc++.so.6', 'libc.so.6' -] - -DROP_PATTERNS = [ - # Pillow image codec libs you identified - 'libavif-', 'libwebp-', 'liblcms2-', - 'libsharpyuv', 'libwebpdemux', 'libwebpmux', - - # Qt optional subsystems you listed (Quick, Qml, QmlModels, Svg, WebSockets) - 'libQt5Quick.so', 'libQt5Qml.so', 'libQt5QmlModels.so', - 'libQt5Svg.so', 'libQt5WebSockets.so', 'libQt5EglFSDeviceIntegration.so', - -] - -def should_drop_path(path_str): - s = str(path_str) - # never drop critical essentials +# 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] + + +def should_drop_path(path_obj): + """ + Return True when the path (string or Path) should be dropped from binaries/datas. + Uses case-insensitive substring matching and avoids dropping essential runtime libs. + """ + s = str(path_obj) + s_lower = s.lower().replace('\\', '/') + # never drop critical essentials (match if any essential substring is contained) for ess in ESSENTIAL_QT_NAMES: - if ess in s: + if ess in s_lower: return False # drop if pattern matches for p in DROP_PATTERNS: - if p in s: + if p in s_lower: return True - # also drop QML/translations directories - if '/qml/' in s or '\\qml\\' in s: + # drop QML/translations directories if present + if '/qml/' in s_lower or '/translations/' in s_lower: return True - if '/translations/' in s or '\\translations\\' in s: + # Also drop "pillow.libs" link targets if they match the patterns above + if 'pillow.libs' in s_lower and any(p in s_lower for p in DROP_PATTERNS): return True return False + # ---------- robust filtering for a.binaries / a.datas ---------- def _entry_src(obj): """ @@ -67,18 +102,17 @@ def _entry_src(obj): PyInstaller entries may be (src, dest) or (src, dest, type) etc. """ try: - # sequence-like entries (tuple/list) return obj[0] except Exception: - # fallback: maybe it's already a path-like object return obj + filtered_binaries = [] for entry in a.binaries: src = _entry_src(entry) if should_drop_path(src): - # drop it - # print("Dropping binary:", src) # uncomment for debugging + # drop this binary entry + # print("Dropping binary:", src) continue filtered_binaries.append(entry) a.binaries = filtered_binaries @@ -87,7 +121,7 @@ filtered_datas = [] for entry in a.datas: src = _entry_src(entry) if should_drop_path(src): - # print("Dropping data:", src) # uncomment for debugging + # print("Dropping data:", src) continue filtered_datas.append(entry) a.datas = filtered_datas @@ -105,7 +139,7 @@ exe = EXE( name='azrechner', debug=False, bootloader_ignore_signals=False, - strip=True, # on Linux: strip Python bootstrap (saves some MB) + strip=STRIP_ON_BUILD, # only strip on unix-like systems upx=True, console=False, ) @@ -115,10 +149,9 @@ coll = COLLECT( a.binaries, a.zipfiles, a.datas, - strip=True, # strip shared objects where safe + strip=STRIP_ON_BUILD, # only strip on unix-like systems upx=True, - upx_exclude=[], # leave empty; PyInstaller will avoid known-bad files + # if you know specific DLLs that break with UPX you can list them here + upx_exclude=[], name='azrechner-trimmed', ) - - |
