Files
chakcy ae9fc8dbe4
tests / ubuntu-latest / py3.13 (push) Waiting to run
tests / windows-latest / py3.13 (push) Waiting to run
tests / ubuntu-latest / py3.13 (pull_request) Has been cancelled
tests / windows-latest / py3.13 (pull_request) Has been cancelled
chore: 三包一起分发、桌面应用打包脚本与 CI,README 对齐实现
* 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)。
2026-09-16 17:15:55 +08:00

6.2 KiB
Raw Permalink Blame History

打包 svfs-studio / svfs-client 为独立可执行文件

把两个 PySide6 应用打成"发给谁都能双击运行"的形态。支持两个后端: PyInstaller(推荐:构建快、Qt/QtWebEngine 钩子成熟)和 Nuitka(真编译:启动更快、更难反编译,代价是构建慢 + 需要 C 编译器)。

环境准备(一次性)

uv sync --extra gui              # PySide6(完整版,含 WebEngine)
uv pip install pyinstaller       # PyInstaller 路线
# 或
uv pip install nuitka            # Nuitka 路线(还需 C 编译器,见下文)

PyInstaller(推荐)

# 目录版(推荐分发方式:启动快;整个目录一起拷贝分发)
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

产物:

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

# 先看看会执行什么命令
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 尤甚)。

冒烟验证

# 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。