* pyproject:wheel 打三个包(sqlite_vfs + svfs_studio + svfs_client)——两个桌面 应用的 console script 都声明在同一个发行版里,只打核心包会让 pip 装完 `svfs-client` 命令存在而模块缺失(跑起来是 ImportError);extras 拆成 client(完整 PySide6,WebEngine 必需)/ studio(PySide6-Essentials 够用)/ gui(两者合集,保留旧写法)。实测重建 wheel 三个包齐全、纯标准库层在无 PySide6 的环境里可导入; * packaging/:PyInstaller spec(白名单裁剪 Qt 体积,DevTools/QML/opengl32sw 的 裁剪依据都记在注释里)与 build.py 编排;Nuitka 路线的已知问题(编译后 Shiboken 无法导入)写在脚本注释里,推荐走 PyInstaller; * .github/workflows/tests.yml:windows + ubuntu 双跑。文件头注明了两个尚未在真 runner 上验证过的点——依赖索引(pyproject 默认指清华镜像,CI 里用 UV_DEFAULT_INDEX 覆盖回 PyPI)与 Linux 上 Qt 需要的系统库; * README:按实现重新对齐——登录与认证(凭据存储、会话失效反馈、离线宽限)、 可靠性与诊断(客户端自更新、网络超时/重试/代理/CA、日志落盘)、plugin.json 的 net_hosts、数据目录(credentials.dat / logs)、桥 API、安装方式与 extras。 uv.lock 同步(dev 组与 extras)。
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。
|