modify: 台账更改插件
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# 打包分发
|
||||
|
||||
把 GUI 打成**免安装 exe**,发给厂商内部同事(商务/售后)。两种方式各一个脚本,
|
||||
都自带"私钥守卫 + 产物自检"。
|
||||
|
||||
```bash
|
||||
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()`,只在内存里渲染。
|
||||
|
||||
手动跑:
|
||||
|
||||
```bash
|
||||
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 侧的,打包目标平台与授权对象设备无关。
|
||||
Reference in New Issue
Block a user