-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFormTransPDF.spec
More file actions
182 lines (163 loc) · 6.57 KB
/
Copy pathFormTransPDF.spec
File metadata and controls
182 lines (163 loc) · 6.57 KB
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
# -*- mode: python ; coding: utf-8 -*-
"""
FormTransPDF — PyInstaller 打包配置
打包命令:
pyinstaller FormTransPDF.spec
# 打包为单文件
# → dist/FormTransPDF/FormTransPDF.exe
# 打包带有命令行用于调试
pyinstaller FormTransPDF.spec --console
策略: --onedir 模式,最大化兼容性,不压缩体积。
通过 collect-all 确保所有子模块、数据文件、插件均被包含。
"""
import sys
import os
import glob
from pathlib import Path
# ── 项目根目录 ──────────────────────────────────────────
_PROJECT_ROOT = Path(SPECPATH).resolve()
# ── 需要完整收集的包 ─────────────────────────────────────
_COLLECT_ALL_PACKAGES = [
"pdf2zh_next",
"babeldoc",
"PySide6",
"bitstring",
"hyperscan",
"tiktoken", # pkgutil.iter_modules 动态发现 tiktoken_ext 命名空间插件
"tiktoken_ext", # tiktoken 的编码插件命名空间包(独立于 tiktoken)
]
# ── 基础隐藏导入 ────────────────────────────────────────
_HIDDEN_IMPORTS = [
# multiprocessing(pdf2zh-next 子进程翻译)
"multiprocessing",
"multiprocessing.pool",
"multiprocessing.popen_spawn_win32",
# PySide6 QtPdf(QtPdf 插件)
"PySide6.QtPdf",
"PySide6.QtPdfWidgets",
]
# ── 应用自身数据文件 ────────────────────────────────────
_DATAS = [
# 打包整个图标目录(SVG 为运行时图标资源,app.ico 为窗口图标)
(str(_PROJECT_ROOT / "src" / "resources" / "icons"), "resources/icons"),
]
# ═══════════════════════════════════════════════════════════
# ① 先执行 collect-all,将所有 hidden import / data 收集齐全
# (必须在 Analysis() 之前,否则 a.pure 已冻结,新模块不进入 PYZ)
# ═══════════════════════════════════════════════════════════
_all_hidden = list(_HIDDEN_IMPORTS)
_all_datas = list(_DATAS)
_all_binaries = []
for pkg in _COLLECT_ALL_PACKAGES:
try:
from PyInstaller.utils.hooks import collect_all as _collect_all
datas, binaries, hiddenimports = _collect_all(pkg)
_all_datas.extend(datas)
_all_binaries.extend(binaries)
_all_hidden.extend(hiddenimports)
print(f" [OK] collect-all: {pkg} "
f"({len(datas)} data, {len(binaries)} bin, "
f"{len(hiddenimports)} imports)")
except Exception as exc:
print(f" [WARN] collect-all failed for {pkg}: {exc}")
# ── 手动收集 delvewheel .libs 下的 DLL ────────────────────
# PyInstaller 对 numpy/pandas/scipy 有内置 hook,但 hyperscan 没有。
# 用 sys.prefix 而非 site.getsitepackages()(后者在 exec() 中不可靠)。
_site_packages = os.path.join(sys.prefix, 'Lib', 'site-packages')
for _libs_name in os.listdir(_site_packages):
if not _libs_name.endswith('.libs'):
continue
_libs_path = os.path.join(_site_packages, _libs_name)
if not os.path.isdir(_libs_path):
continue
for _dll in glob.glob(os.path.join(_libs_path, '*.dll')):
_all_binaries.append((_dll, _libs_name))
print(f" [OK] libs DLL: {os.path.basename(_dll)} -> {_libs_name}/")
# 去重
_all_hidden = list(set(_all_hidden))
# ═══════════════════════════════════════════════════════════
# ② Analysis(此时 hiddenimports 已完整)
# ═══════════════════════════════════════════════════════════
a = Analysis(
[str(_PROJECT_ROOT / "src" / "main.py")],
pathex=[str(_PROJECT_ROOT)],
binaries=_all_binaries,
datas=_all_datas,
hiddenimports=_all_hidden,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[
"PyQt6",
# PySide6 子模块(不需要的排除以减小体积、避免 QML 等 hook 错误)
"PySide6.QtQml",
"PySide6.QtQuick",
"PySide6.QtQuickWidgets",
# 注意:QtSvg 必须保留(icon_factory 用 QSvgRenderer 渲染 SVG 图标)
"PySide6.QtCharts",
"PySide6.QtDataVisualization",
"PySide6.QtSensors",
"PySide6.QtMultimedia",
"PySide6.QtMultimediaWidgets",
"PySide6.QtWebEngineCore",
"PySide6.QtWebEngineWidgets",
"PySide6.QtWebChannel",
"PySide6.QtPositioning",
"PySide6.QtRemoteObjects",
"PySide6.QtSerialPort",
"PySide6.QtSerialBus",
"PySide6.QtTextToSpeech",
"PySide6.QtAxContainer",
"PySide6.QtConcurrent",
"PySide6.QtStateMachine",
"PySide6.Qt3DCore",
"PySide6.Qt3DRender",
"PySide6.Qt3DInput",
"PySide6.Qt3DAnimation",
"PySide6.Qt3DExtras",
"PySide6.QtBluetooth",
"PySide6.QtNfc",
"PySide6.QtHelp",
"PySide6.QtSql",
"PySide6.QtTest",
"PySide6.QtDesigner",
"PySide6.QtUiTools",
"PySide6.QtXml",
"PySide6.QtDBus",
"PySide6.scripts",
],
noarchive=False,
optimize=0,
)
# ═══════════════════════════════════════════════════════════
# ③ PYZ / EXE / COLLECT
# ═══════════════════════════════════════════════════════════
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name="FormTransPDF",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(_PROJECT_ROOT / "src" / "resources" / "icons" / "app.ico"),
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="FormTransPDF",
)