135 lines
6.2 KiB
Markdown
135 lines
6.2 KiB
Markdown
# 打包 svfs-studio / svfs-client 为独立可执行文件
|
||||
|
|
|
|||
|
|
把两个 PySide6 应用打成"发给谁都能双击运行"的形态。支持两个后端:
|
|||
|
|
**PyInstaller**(推荐:构建快、Qt/QtWebEngine 钩子成熟)和
|
|||
|
|
**Nuitka**(真编译:启动更快、更难反编译,代价是构建慢 + 需要 C 编译器)。
|
|||
|
|
|
|||
|
|
## 环境准备(一次性)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
uv sync --extra gui # PySide6(完整版,含 WebEngine)
|
|||
|
|
uv pip install pyinstaller # PyInstaller 路线
|
|||
|
|
# 或
|
|||
|
|
uv pip install nuitka # Nuitka 路线(还需 C 编译器,见下文)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## PyInstaller(推荐)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 目录版(推荐分发方式:启动快;整个目录一起拷贝分发)
|
|||
|
|
uv run python packaging/build.py --tool pyinstaller --app both
|
|||
|
|
|
|||
|
|
# 只打一个
|
|||
|
|
uv run python packaging/build.py --tool pyinstaller --app client
|
|||
|
|
uv run python packaging/build.py --tool pyinstaller --app studio
|
|||
|
|
|
|||
|
|
# 单文件版(SVFS_ONEFILE=1 传给 spec;QtWebEngine 启动前要先解包,明显更慢)
|
|||
|
|
uv run python packaging/build.py --tool pyinstaller --app both --onefile
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
产物:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
dist/SVFS-Studio/SVFS-Studio.exe
|
|||
|
|
dist/SVFS-Client/SVFS-Client.exe # --onefile 时是 dist/SVFS-Client.exe
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
要点:
|
|||
|
|
|
|||
|
|
* 配置在 `svfs_studio.spec` / `svfs_client.spec`(数据文件、隐藏导入都在那里声明);
|
|||
|
|
* QtWebEngine 的运行时(`QtWebEngineProcess.exe`、icudtl.dat、资源包、locale)
|
|||
|
|
由 pyinstaller-hooks-contrib 的 PySide6 钩子自动收集,**请保持 PyInstaller ≥ 6**;
|
|||
|
|
* 入口不是 `src/*/__main__.py`(有相对导入),而是本目录的
|
|||
|
|
`run_studio.py` / `run_client.py`(绝对导入);
|
|||
|
|
* `svfs_studio` 的图标资源随包打进 `resources/`,
|
|||
|
|
`sample.repo_root()` 已适配冻结环境(PyInstaller 找 `sys._MEIPASS`,
|
|||
|
|
Nuitka 找可执行文件同级目录)。
|
|||
|
|
|
|||
|
|
## Nuitka
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 先看看会执行什么命令
|
|||
|
|
uv run python packaging/build.py --tool nuitka --app client --dry-run
|
|||
|
|
|
|||
|
|
# 正式构建(首次缺 C 编译器时会自动下载 MinGW64;也可自备 MSVC)
|
|||
|
|
uv run python packaging/build.py --tool nuitka --app both
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
产物在 `<输出目录>/run_<app>.dist/` 下(默认输出目录 `build/nuitka`;
|
|||
|
|
**路径含非 ASCII 字符时自动退到系统临时目录**——Windows 的 MSVC 链接器
|
|||
|
|
在中文路径下会 LNK1104)。
|
|||
|
|
|
|||
|
|
要点:
|
|||
|
|
|
|||
|
|
* `--enable-plugin=pyside6` 负责 Qt 全家桶(含 WebEngine 子进程与资源);
|
|||
|
|
* standalone 模式 + `--windows-console-mode=disable`(GUI 程序不弹控制台);
|
|||
|
|
* 构建慢(整包编译,几分钟到十几分钟),但产物启动比 PyInstaller 快、
|
|||
|
|
没有解包临时目录的问题。
|
|||
|
|
|
|||
|
|
**⚠ 已知问题(2026-09 实测)**:Nuitka 4.2.1 + PySide6 6.11.2 的产物运行时报
|
|||
|
|
`Unable to import Shiboken`(编译后的 `shiboken6.Shiboken` 无法导入)。
|
|||
|
|
用最小 PySide6 程序即可复现,属工具链兼容问题,与本仓库代码无关;
|
|||
|
|
`--include-package=shiboken6`、`--nofollow-import-to` + 数据目录两种绕法均无效。
|
|||
|
|
解决途径:等 Nuitka 发版修复,或临时降级 PySide6(`uv pip install "PySide6<6.10"`)
|
|||
|
|
后再构建。**PyInstaller 路线不受影响,当前请优先使用。**
|
|||
|
|
|
|||
|
|
## 体积裁剪
|
|||
|
|
|
|||
|
|
两个 spec 在 Analysis 之后按**白名单**过滤 `a.binaries` / `a.datas`
|
|||
|
|
(PySide6 的钩子会把 wheel 里所有 Qt 模块的 DLL/pyd 整包搬进来,黑名单删不干净)。
|
|||
|
|
实测效果(PyInstaller 6.22 + PySide6 6.11,Windows x64):
|
|||
|
|
|
|||
|
|
| 应用 | 原始 | 目录版(白名单) | 单文件版(LZMA) |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| SVFS-Studio | 116 MB | **66 MB** | **29 MB** |
|
|||
|
|
| SVFS-Client | 559 MB | **300 MB** | **126 MB** |
|
|||
|
|
|
|||
|
|
Client 的白名单来自 objdump 实测的导入表(不是猜的):
|
|||
|
|
|
|||
|
|
* `Qt6WebEngineCore` 链 Core/Gui/Network/Positioning/Qml/Quick/WebChannel,
|
|||
|
|
`WebEngineWidgets` 另需 PrintSupport/QuickWidgets/Widgets,`Qt6Quick` 另需
|
|||
|
|
Qt6OpenGL 与 QmlMeta/QmlModels —— 这些 DLL 必须留;
|
|||
|
|
* pyd 只留 Python 层实测 import 链用到的 8 个绑定(QtCore/QtGui/QtWidgets/
|
|||
|
|
QtNetwork/QtPrintSupport/QtWebChannel/QtWebEngineCore/QtWebEngineWidgets),
|
|||
|
|
注意 pyd 的导入链比 DLL 的依赖多两个(QtNetwork/QtPrintSupport);
|
|||
|
|
* 删掉的:Quick3D 全家、QuickControls2 样式、Pdf、ShaderTools、Svg、
|
|||
|
|
opengl32sw.dll(20M,Chromium 自带 SwiftShader 兜底)、DevTools 资源包(85M)、
|
|||
|
|
QML 组件目录(29M)、Qt 译文、虚拟键盘、qmltooling。
|
|||
|
|
|
|||
|
|
裁不动的:`Qt6WebEngineCore.dll` ~195M(Chromium 本体)+ `icudtl.dat` 10M。
|
|||
|
|
|
|||
|
|
已知取舍:
|
|||
|
|
|
|||
|
|
* DevTools 资源包删了 → 插件页面里 F12 检查元素无资源,调试请用开发环境;
|
|||
|
|
* opengl32sw 删了 → 极端无 GPU 环境若渲染异常,把它加回 spec 的保留例外重打;
|
|||
|
|
* Qt 译文删了 → Qt 内建对话框退回英文,界面自有文案不受影响。
|
|||
|
|
|
|||
|
|
**UPX 实测无效**:对 Qt DLL 用 `--best` 只把 Client 目录版从 300M 压到 290M
|
|||
|
|
(pyd 全部压缩失败),收益 3%,还要承担杀软误报——不推荐。
|
|||
|
|
想要更小就用单文件版(上表第三列),代价是每次启动先解包到临时目录
|
|||
|
|
(Client 单文件启动明显慢,QtWebEngine 尤甚)。
|
|||
|
|
|
|||
|
|
## 冒烟验证
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# client:--help 在进入 GUI 之前返回(退出码 0 即装配成功)
|
|||
|
|
./dist/SVFS-Client/SVFS-Client.exe --help
|
|||
|
|
|
|||
|
|
# client:真实加载一个插件页面(--open 启动后直开指定插件;
|
|||
|
|
# 页面加载后 web/ 下会出现 Chromium 的 Local Storage/GPUCache 等痕迹)
|
|||
|
|
./dist/SVFS-Client/SVFS-Client.exe --data <已装插件的数据目录> --open <插件id>
|
|||
|
|
|
|||
|
|
# studio:offscreen 截图模式,跑完自动退出并在目录里留下每页一张 PNG
|
|||
|
|
./dist/SVFS-Studio/SVFS-Studio.exe --shots ./smoke-shots
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
|
|||
|
|
* **杀软误报**:PyInstaller 单文件版常见,目录版较少;Nuitka 编译产物基本不误报。
|
|||
|
|
* **体积**:QtWebEngine 应用的目录版约 150~250 MB,属正常(Chromium 运行时)。
|
|||
|
|
* **数据目录**:两个应用的用户数据都写在
|
|||
|
|
`%LOCALAPPDATA%/<应用名>`(client)与 QSettings 里(studio 的窗口几何),
|
|||
|
|
与安装位置无关,覆盖更新不需要迁移。
|
|||
|
|
* **离线构建**:`--assume-yes-for-downloads` 会在缺编译器时下载 MinGW64;
|
|||
|
|
全离线环境请预装 MSVC Build Tools 或自行提供 MinGW64。
|