1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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',
)
|