fix(packaging): app/custom 扩展双声明随桌面包发布, 修复 AI 对话助手 405 (#459)

loader 运行时 importlib+pkgutil 发现 app/custom/*, 静态分析不可见;
未声明时冻结包里扩展静默消失, 助手路由未注册 → SPA 兜底返回 405。
datas 落盘 _internal/app/custom + hiddenimports 进 PYZ, 仿 fuyao 双声明;
新增 test_packaging_spec.py 锁定打包不变量。
This commit is contained in:
shy3130
2026-10-08 10:36:26 +08:00
parent 85b8a3f930
commit 0929253620
2 changed files with 42 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
"""tsp.spec 打包不变量 — 运行时动态发现的模块必须随冻结产物发布 (issue #459)。
app/extensions/loader.py 用 importlib.import_module + pkgutil.iter_modules 在
运行时发现 app/custom/*, 两条链路对 PyInstaller 静态分析都不可见; tsp.spec
未声明时冻结产物里扩展静默消失(桌面版 AI 对话助手 405, 且无任何日志)。
锁定双声明契约:
- hiddenimports(collect_submodules) 进 PYZ —— 保 import_module 可导入;
- datas 落盘到 app/custom —— frozen 下 app.__path__ 解析到 _internal/app,
保 pkgutil.iter_modules 可枚举(与 fuyao 插件同一处理方式)。
"""
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
SPEC_TEXT = (REPO_ROOT / "packaging" / "tsp.spec").read_text(encoding="utf-8")
# 只断言代码行, 避免被注释里的字样误判为已声明
SPEC_CODE = "\n".join(
line for line in SPEC_TEXT.splitlines() if not line.lstrip().startswith("#")
)
def test_spec_declares_custom_extensions():
"""app/custom/* 双声明: PYZ 可导入 + 磁盘可枚举, 缺一冻结后扩展静默消失。"""
assert 'collect_submodules("app.custom")' in SPEC_CODE
assert '"app/custom"' in SPEC_CODE
def test_spec_declares_builtin_plugin_fuyao():
"""fuyao 插件双声明不被回归 (同类动态发现问题, 见 spec 内注释)。"""
assert "app.plugins.fuyao.provider" in SPEC_CODE
assert "app/plugins/fuyao" in SPEC_CODE
+11
View File
@@ -76,6 +76,17 @@ hiddenimports += [
"app.plugins.fuyao.client",
]
# ── 后端源码扩展 (app/custom/*) ──────────────────────────────────────
# extensions/loader.py 运行时用 importlib.import_module + pkgutil.iter_modules
# 发现 app/custom/* — 与插件同样对静态分析不可见, 必须双声明 (issue #459:
# 未声明时冻结包里扩展静默消失, 助手路由未注册 → 请求落到 GET-only SPA 兜底
# 返回 405):
# datas 落盘到 _internal/app/custom — frozen 下 app.__path__ 解析到
# _internal/app, 保 pkgutil 枚举与磁盘可见性;
# hiddenimports 进 PYZ — 保 importlib.import_module 可导入。
datas += [(str(ROOT / "backend" / "app" / "custom"), "app/custom")]
hiddenimports += collect_submodules("app.custom")
# ── pywebview 平台后端 (动态导入, PyInstaller 默认抓不到) ────────────
hiddenimports += collect_submodules("webview")
hiddenimports += collect_submodules("webview.platforms")