Files
svfs/packaging/svfs_studio.spec
T
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

127 lines
3.8 KiB
RPMSpec
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- mode: python ; coding: utf-8 -*-
"""PyInstaller 打包配置:SVFS Studio(PySide6 桌面工具,无 WebEngine)。
用法(或直接用 packaging/build.py 编排)::
uv sync --extra gui
uv pip install pyinstaller
uv run pyinstaller --noconfirm packaging/svfs_studio.spec
# 目录版产物: dist/SVFS-Studio/SVFS-Studio.exe
单文件版(启动稍慢,Qt 会先解包到临时目录)::
SVFS_ONEFILE=1 uv run pyinstaller --noconfirm packaging/svfs_studio.spec
"""
import os
import re
from PyInstaller.utils.hooks import collect_submodules
# spec 文件在 <repo>/packaging/ 下,仓库根与 src 由此反推
REPO = os.path.dirname(os.path.dirname(os.path.abspath(SPEC)))
SRC = os.path.join(REPO, "src")
ONEFILE = os.environ.get("SVFS_ONEFILE", "") == "1"
ICON = os.path.join(REPO, "resources", "ico", "logo.ico")
a = Analysis(
[os.path.join(REPO, "packaging", "run_studio.py")],
pathex=[SRC],
binaries=[],
datas=[
# app_icon() / repo_root() 在冻结环境下从包内找 resources/(见 sample.py)
(os.path.join(REPO, "resources", "ico"), "resources/ico"),
(os.path.join(REPO, "resources", "images"), "resources/images"),
],
hiddenimports=[
# 界面里通过 getattr(build_<key>) 取视图工厂,收集整个包最稳妥
*collect_submodules("svfs_studio"),
*collect_submodules("sqlite_vfs"),
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=["tkinter"],
noarchive=False,
)
# ----------------------------------------------------------------------
# 体积裁剪(白名单制):PySide6 的钩子会把 wheel 里**所有** Qt 模块的
# DLL/pyd 整包搬进来,而本应用只用 QtCore/QtGui/QtWidgets:
# * 未使用的 Qt 模块 DLL(Qml/Quick/Quick3D/Pdf/Network/OpenGL/Svg/
# VirtualKeyboard/ShaderTools…,约 25M)
# * 未使用的 python 绑定 pyd
# * opengl32sw.dll(20M 软件渲染回退;Widgets 走光栅绘制用不到,
# 无显卡的机器出问题就把它加回 KEEP 外的例外)
# * Qt 自带译文(*.qm,6.7M;界面文案全部硬编码中文)
# * 依赖被删模块的插件(networkinformation/tls/qsvg)
# ----------------------------------------------------------------------
KEEP_QT6_DLLS = {"Qt6Core", "Qt6Gui", "Qt6Widgets"}
KEEP_PYDS = {"QtCore", "QtGui", "QtWidgets"}
QT_DLL_RE = re.compile(r"PySide6[/\\](Qt6[^/\\]*\.dll)$", re.IGNORECASE)
PYD_RE = re.compile(r"PySide6[/\\]([^/\\]*\.pyd)$", re.IGNORECASE)
DROP_STUDIO = re.compile(
r"PySide6[/\\](?:"
r"opengl32sw\.dll"
r"|d3dcompiler_47\.dll"
r"|translations[/\\]"
r"|plugins[/\\](?:networkinformation|tls)[/\\]"
r"|plugins[/\\]imageformats[/\\]qsvg\.dll"
r"|plugins[/\\]iconengines[/\\]qsvgicon\.dll"
r")",
re.IGNORECASE,
)
def _keep(entry_name):
if DROP_STUDIO.search(entry_name):
return False
match = QT_DLL_RE.search(entry_name)
if match:
return match.group(1).rsplit(".", 1)[0] in KEEP_QT6_DLLS
match = PYD_RE.search(entry_name)
if match:
return match.group(1).rsplit(".", 1)[0] in KEEP_PYDS
return True
a.binaries = [entry for entry in a.binaries if _keep(entry[0])]
a.datas = [entry for entry in a.datas if _keep(entry[0])]
pyz = PYZ(a.pure)
exe_kwargs = dict(
name="SVFS-Studio",
console=False, # GUI 程序,不弹控制台
icon=ICON if os.path.exists(ICON) else None,
upx=False,
)
if ONEFILE:
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
**exe_kwargs,
)
else:
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
**exe_kwargs,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
name="SVFS-Studio",
)