Files
chongming-license-gui/packaging
2026-09-22 16:08:12 +08:00
..
2026-09-22 16:08:12 +08:00
2026-09-22 16:08:12 +08:00
2026-09-22 16:08:12 +08:00
2026-09-22 16:08:12 +08:00
2026-09-22 16:08:12 +08:00

打包分发

把 GUI 打成免安装 exe,发给厂商内部同事(商务/售后)。两种方式各一个脚本, 都自带"私钥守卫 + 产物自检"。

uv sync --group dev                                  # 装打包工具(pyinstaller / nuitka)

uv run python packaging/build_pyinstaller.py          # 方式一:PyInstaller(快,默认单文件)
uv run python packaging/build_nuitka.py               # 方式二:Nuitka(真编译,慢但更小更快)

# 产物
dist/pyinstaller/chongming-license-gui.exe
dist/nuitka/chongming-license-gui.exe

该选哪个

下表为本机实测值(Windows 11 / Python 3.14 / Qt 6.11.2 / MSVC 14.5,单文件模式):

PyInstaller Nuitka
构建耗时 约 2.5 分钟(实测 149s) 约 10 分钟(首次实测 1140s,含一次编译器重试)
构建前提 无 需要 C 编译器(MSVC 或 Nuitka 支持的 mingw64)
单文件体积 54.8 MB 38.6 MB
启动 + 跑完自检 2.7s 1.8s
反编译难度 打包的 .pyc 可被还原 编译成 C,难得多
稳定性 生态成熟、踩坑资料多 需要针对 Python/Qt 版本调参(见下)

建议:日常发给同事用 Nuitka 单文件(更小、启动更快、更难反编译); 需要快速出包/临时验证用 PyInstaller(两分半就好,且不需要编译器)。

⚠️ 要 PostgreSQL 支持就用 PyInstaller。 Nuitka 在 Python 3.13+ 上只能用 MSVC (--mingw64 被 Nuitka 明确拒绝),而 MSVC 编译 psycopg 的生成代码会 fatal error C1002(编译器堆空间不足)—— 这是编译器上限,不是代码问题。 Nuitka 脚本检测到这一点会自动降级为"不带 PG"的构建并告诉你; 也可以显式 --no-postgres。不带 PG 的包里,界面会如实提示 「未安装 PostgreSQL 驱动(可选功能)」,本地 SQLite 台账与签发校验全部照常。

常用参数

两个脚本参数一致:

参数 作用
--onedir / --standalone 打成目录而不是单文件。启动更快,适合放在固定办公机上;分发时整个目录一起拷
--console 保留控制台窗口。默认是无控制台(纯 GUI);排查"双击没反应"时用得上
--clean 构建前清理旧的 dist/(Nuitka 还会顺手删中间产物)
--lto=no(仅 Nuitka) 关掉链接期优化 —— 显著加快构建(默认开着,链接阶段实测要 6 分钟以上)
--no-low-memory(仅 Nuitka) 关掉低内存模式。默认开启,见下方 C1002 说明
--no-selftest 跳过构建后的产物自检(不建议)
--allow-sensitive-files 源码树里有私钥/.lic/台账库时仍继续构建(默认中止,见下)
--msvc / --mingw64(仅 Nuitka) 指定编译器;默认自动挑选

两道安全闸门(重要)

第一道:构建前扫源码树。 命中 license_private*.pem、*_private.pem、 *_private.key、*.lic、licenses.db 一律中止构建并列出文件:

构建中止:源码树里存在私钥/授权文件/台账库,它们绝不能进入分发包。
  - license_L20260921-A36014.lic

原因很直接:这个工具含签发能力。私钥一旦随包发出去,拿到的人就能自己签发有效授权; .lic 与台账库则含客户名与机器指纹。确实只是临时放置时,可加 --allow-sensitive-files 跳过(会打印警告并列出文件)。

第二道:构建后再查产物目录,避免 onedir 模式把工作目录里的散落文件一起拷进去。

构建后自检(为什么必须有)

冻结失败往往是静默的 —— 界面能开,但图标全空白、复选框点不动,或者签发时才抛异常。 所以构建脚本会自动调用 <exe> --selftest 跑一遍真依赖,而且跑两轮:

轮次 QT_QPA_PLATFORM 验的是什么
1 offscreen 无头环境也能验;CI 里可跑
2 系统原生(Windows 上是 windows) Qt 的 platforms 插件真的打进产物了 —— 少了它,开发机上一切正常,同事双击闪退

自检覆盖的正是打包最容易漏的东西:

  • QtSvg 模块:少了它所有图标变空白(QIcon 是空的,按钮上什么都不画);
  • svg 图片格式插件:少了它样式表里的复选框/单选框指示器加载失败 → 勾选框变空白;
  • cryptography 的 Ed25519 后端:签发与验签全链路(含"异机指纹被拒绝");
  • orjson(编译扩展):申请码编解码往返;
  • sqlite3 + 台账:入库/检索/统计;
  • PostgreSQL 驱动(可选功能):构建输出里会打印一行 PostgreSQL 驱动:可用/不可用; 另有一个显式的 CHONGMING_LICENSE_PG 开关,给了才会真连一次库并跑一遍台账 适配层(在事务里,跑完回滚,不留痕迹);
  • tomllib + 路径处理:插件自动发现;
  • 主窗口装配:四个页签、表格 9 列、结论横幅、整窗渲染出非空位图。

自检不碰真实数据:QSettings 被重定向到临时 INI、台账库指向临时文件 (否则 RegistryTab 初始化会去动你真实的 ~/.chongming/licenses.db)。 它也不会弹窗 —— 全程不调用 show(),只在内存里渲染。

手动跑:

dist/pyinstaller/chongming-license-gui.exe --selftest --selftest-report 报告.txt
CHONGMING_LICENSE_PG="postgresql://user:pw@host:5432/db" \
    dist/pyinstaller/chongming-license-gui.exe --selftest   # 额外验一次真库

踩过的坑:PATH 上的同名 DLL 会污染产物

症状:exe 能启动、界面正常,但 PostgreSQL 功能报 ImportError: no pq wrapper available,往深看是 DLL load failed while importing _ssl: 找不到指定的程序。

原因:Windows 上 libssl-3-x64.dll / libcrypto-3-x64.dll 这类通用名 DLL 到处都是 —— Git for Windows、msys64、各种 SDK、厂商工具目录。它们都在 PATH 上。 PyInstaller 解析二进制依赖时会搜 PATH,搜到哪份就用哪份,于是把 _ssl.pyd 需要的 OpenSSL 换成了别的构建:

来源 大小
Python 自带(正确) 1,579,008 字节
Git for Windows(被误收集) 930,460 字节
msys64 1,055,094 字节

版本对不上,_ssl.pyd 绑定符号失败 → 依赖 _ssl 的 psycopg 二进制实现整块不可用。

修法:_common.build_env_overrides() 在构建前清一遍 PATH —— 凡是含有与 Python 自带 DLL 同名文件的目录一律临时摘掉(系统目录永远保留), 并打印摘掉了哪些,两个构建脚本都会用。本机实测一次摘掉了 Git、msys64、 E:\polyos_qemu、ShadowBot,以及 PySide6 自己的目录。

这类问题的教训:报错点离原因很远(psycopg 说"没有 pq 实现", 实际是 OpenSSL 版本不对)。所以构建后自检必须验到"驱动能真的连上库", 光验"模块能 import"是不够的。

实现要点

packaging/
├── launcher.py            # 冻结入口(用绝对导入,不用 __main__.py 的相对导入)
├── pyinstaller.spec       # PyInstaller 配置(选项经环境变量传入)
├── build_pyinstaller.py   # 驱动 + 守卫 + 自检
├── build_nuitka.py        # 驱动 + 守卫 + 自检
└── _common.py             # 图标生成 / 私钥扫描 / PATH 清理 / 自检驱动(两个脚本共用)
  • 入口用 launcher.py 而不是 __main__.py:后者是 from .app import main 这样的 包内相对导入,被冻结工具当普通脚本编译时会因失去包上下文而失败。
  • 用源码树而不是 editable 安装:spec 里 pathex=src/、Nuitka 里设 PYTHONPATH=src/。 冻结工具对 .pth 式 editable 安装的解析并不可靠。
  • 图标是构建时生成的:_common.build_app_icon() 把主题里的盾牌 SVG 渲染成 7 个尺寸的 .ico(自己拼 ICO 容器 —— Qt 的 ICO 写入只支持单张图,小尺寸会糊), 写完立刻回读校验。所以仓库里不需要存任何美术素材。
  • 关掉 UPX:UPX 压缩容易触发杀软误报,也可能损坏 Qt DLL。
  • 裁掉用不到的 Qt 模块:PySide6 装了整套 Addons(Qt3D/WebEngine/Charts…), spec 里显式排除了一批;Nuitka 侧由 pyside6 插件负责,另有 --nofollow-import-to。
  • 构建前清理 PATH(见上一节):挡住同名 DLL 污染产物。
  • Optional 依赖:PostgreSQL 支持是 [project.optional-dependencies] postgres, 但 dev 依赖组里也列了 psycopg —— 这样 uv sync --group dev 出来的环境打出来的包 自带 PG 支持;不做 PG 的用户 uv sync 也不会被强迫装驱动。

分发提醒

  • 本工具是厂商侧工具,含签发能力,只发厂商内部同事;不要给客户,也不要放进部署产物。
  • 打包产物里的界面截图、示例数据都来自 tools/screenshot.py 的演示数据,不含真实客户信息。
  • exe 未做代码签名,同事首次运行可能遇到 SmartScreen 提示("更多信息 → 仍要运行")。 要么让 IT 走内部白名单,要么买代码签名证书后给 exe 签名。

常见问题

双击没反应 / 闪退? 用 --console 重新打一个,命令行里运行看报错。多数是缺平台插件(自检能提前发现)。

杀软报毒? PyInstaller 单文件的自解压行为常被启发式判成可疑。改用 --onedir,或换 Nuitka。

Nuitka 报找不到 C 编译器? 装 Visual Studio 的「使用 C++ 的桌面开发」工作负载;或用 --mingw64 (需 Nuitka 支持的 mingw64 版本 —— msys2 自带的那个通常不被接受)。

Nuitka 报 fatal error C1002 / LNK1257(编译器堆空间不足)? Nuitka 为每个模块生成一个上万行的 C 文件,MSVC 编译最大的那个时会耗尽编译器内部 堆空间。本项目的踩坑记录:

  • 不带 --low-memory 时崩在 chongming_license_gui.registry_tab;
  • 加上 --low-memory 后能过,但引入 psycopg 后又会崩在 psycopg.errors (该模块定义了几百个异常类,生成 C 文件 2.8 万行,MSVC 单文件上限)。

这是 MSVC 的编译上限,不是代码问题。对策:

  1. 优先用 PyInstaller 出带 PostgreSQL 支持的包 —— 它不编译 C,没有这个限制, 本项目已验证可用;
  2. 或者给 Nuitka 换编译器:--mingw64(Nuitka 会自己下载它支持的 WinLibs mingw64,msys2 自带的那份不被接受);
  3. 只在不需要 PG 时才用默认的 MSVC + --low-memory。

脚本默认已带 --low-memory(不带必崩),检测到该错误码还会自动重试一次。

Nuitka 提示 "Please install the English language pack for Visual Studio"? 中文版 VS 的报错文本是 GBK,Nuitka 解析不了(只影响日志可读性,不影响构建)。 装个英文语言包,或忽略它、直接看 packaging/build/nuitka-build.log。

Nuitka 提示 "Please install the English language pack for Visual Studio"? 中文版 VS 的报错文本是 GBK,Nuitka 解析不了(只影响日志可读性,不影响构建)。 装个英文语言包,或忽略它、直接看 packaging/build/nuitka-build.log。

体积能不能再小? --onedir 后再自行压缩分发;或继续往 spec 的 EXCLUDES 里加确定用不到的模块。 单文件模式会把所有东西解压到临时目录,体积换的是分发便利。

Nuitka 构建太慢? 时间主要花在两处:一是 C 编译(Nuitka 的缓存目录会保留中间产物,第二次快很多), 二是 LTO 链接(实测单这一步就 6 分钟以上)。加 --lto=no 可显著缩短, 代价是产物略大、优化略少。另外 .py 文件改动越多,重新编译的文件越多。

能不能打包 Linux / macOS 版? 可以,但要在目标系统上构建(冻结产物不跨平台)。packaging/_common.py 里的 原生平台名已覆盖 cocoa / xcb,自检会自动按平台跑。注意本工具的硬件指纹 来源全是 Linux 侧的,打包目标平台与授权对象设备无关。