diff --git a/Agent.md b/Agent.md index 1d60a245..82f90e7f 100644 --- a/Agent.md +++ b/Agent.md @@ -118,7 +118,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg ``` -Python: `uv run pytest tests/ -v` (1051) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (1052) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (259: 45 daemon_client + 20 conn-manager + 22 app-commands + 129 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/emrg/server/prompts/upgrade_prompt.j2 b/emrg/server/prompts/upgrade_prompt.j2 index 209c05dc..4de15308 100644 --- a/emrg/server/prompts/upgrade_prompt.j2 +++ b/emrg/server/prompts/upgrade_prompt.j2 @@ -51,6 +51,16 @@ release installer" means for this version. `previous-version.txt`. - `{{ install_dir }}/bin/` launcher scripts (emrg/emrgd/emrg.cmd/emrgd.cmd etc.) — sync if the target tag changed them. +- `{{ install_dir }}/bin/pyvenv.cfg` — Windows layout requirement (rant + 2026-08-24T21:46:53, follow-up to #966): `bin\python.exe` / `bin\python3.exe` + are copies of the standalone interpreter and need a same-directory + `pyvenv.cfg` to locate the stdlib inside `bin\python-dist\`. Write it + idempotently with the **native absolute path** (backslashes on Windows), + i.e. one line: `home = {{ install_dir }}\bin\python-dist` — expand + {{ install_dir }} to the real path at runtime. A relative `home` fails with + "Fatal Python error: Failed to import encodings" — never use a relative + path. On POSIX layouts skip this file (the `bin/python` wrapper already + handles stdlib discovery). - GUI **must be synced too** (rant 2026-08-24T17:50: GUI packages had been stuck at 0.2.72 while the daemon moved on — the old "no node/npm, skip" reasoning was a false premise: node/npm exist on the host (asdf), they are diff --git a/packaging/make-installer.sh b/packaging/make-installer.sh index 05bec8c9..60eb4dbb 100755 --- a/packaging/make-installer.sh +++ b/packaging/make-installer.sh @@ -376,10 +376,31 @@ begin BroadcastEnvironmentChange; end; +// R131: 安装后写 {app}\bin\pyvenv.cfg(rant 2026-08-24T21:46:53,#966 的 +// Windows 后续)。bin\python.exe / python3.exe 是 python-dist 解释器的复制品, +// 需要同目录 pyvenv.cfg 才能定位标准库(CPython 在 exe 所在目录查找 +// pyvenv.cfg,home 指向 python-dist)。home 必须为绝对路径——相对路径实测 +// Fatal Python error: Failed to import encodings(venv 内 python 无法定位 +// 标准库,任务无法安装科研依赖)。pyvenv.cfg 不在 payload 里(build-runtime +// 不生成),由安装器 ssPostInstall 写入;升级安装覆盖同内容,幂等; +// 卸载由 [UninstallDelete] 连同 {app} 一起删除。 +procedure WritePyvenvCfg; +var + CfgPath: string; + CfgContent: AnsiString; +begin + CfgPath := ExpandConstant('{app}\bin\pyvenv.cfg'); + CfgContent := 'home = ' + ExpandConstant('{app}\bin\python-dist') + #13#10; + SaveStringToFile(CfgPath, CfgContent, False); +end; + procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssPostInstall then + begin + WritePyvenvCfg; AddBinDirToPath; + end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); diff --git a/packaging/smoke-test.sh b/packaging/smoke-test.sh index 30cd7b0d..33b2ce87 100755 --- a/packaging/smoke-test.sh +++ b/packaging/smoke-test.sh @@ -185,11 +185,24 @@ else fi # 14. bundled python venv(rant 2026-08-24T21:46:53:bin/python 软链 → wrapper, -# pyvenv.cfg home 修复)。回归用例:install python 创建 venv 后必须能导入标准库 -# + pip 可用。Windows 用 python-dist 根 exe(bin/python.exe 复制品缺 DLL,R100)。 +# pyvenv.cfg home 修复;Windows 后续:bin\python.exe 复制品依赖同目录 +# bin\pyvenv.cfg 定位标准库)。回归用例:install python 创建 venv 后必须能 +# 导入标准库 + pip 可用。POSIX 走 bin/python wrapper(#966);Windows 走 +# bin\python.exe 本体——先复刻安装器 [Code] ssPostInstall 写的 bin\pyvenv.cfg +# (home = 绝对路径;相对路径实测 "Failed to import encodings"),再验证。 say "14. bundled python venv (pyvenv.cfg home fix)" PY_BIN="$HOME/.emrg/install/bin/python" -if [ -e "$HOME/.emrg/install/bin/python-dist/python.exe" ]; then +if [ -n "${WINDIR:-}" ]; then + # R131:dist/runtime 未经安装器(pyvenv.cfg 由 make-installer.sh [Code] 在 + # ssPostInstall 写入),smoke 在此复刻同款内容。有了 cfg 后 bin\python.exe + # 本体即可用(R100 "缺 DLL" 结论修正:DLL 同在 bin\,真正缺的是 cfg)。 + INSTALL_BIN_WIN="$(cygpath -w "$HOME/.emrg/install/bin")" + printf 'home = %s\\python-dist\n' "$INSTALL_BIN_WIN" > "$HOME/.emrg/install/bin/pyvenv.cfg" + PY_BIN="$HOME/.emrg/install/bin/python.exe" + if [ ! -e "$PY_BIN" ]; then + PY_BIN="$HOME/.emrg/install/bin/python-dist/python.exe" + fi +elif [ -e "$HOME/.emrg/install/bin/python-dist/python.exe" ]; then PY_BIN="$HOME/.emrg/install/bin/python-dist/python.exe" elif [ -e "$HOME/.emrg/install/bin/python-dist/python3.13.exe" ]; then PY_BIN="$HOME/.emrg/install/bin/python-dist/python3.13.exe" diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index 28d0259e..0a0cdf6b 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -312,6 +312,40 @@ def test_make_installer_iss_has_prepare_to_install(): assert "restart the computer" in content +def test_make_installer_iss_writes_pyvenv_cfg(): + """rant 2026-08-24T21:46:53 — #966 的 Windows 后续:bin\\python.exe / python3.exe + 是 python-dist 解释器的复制品,缺同目录 bin\\pyvenv.cfg → venv 内 python 无法 + 定位标准库(Could not find platform independent libraries + No module named + 'encodings')。修复 = 安装器 [Code] ssPostInstall 写 bin\\pyvenv.cfg + (home 指向 {app}\\bin\\python-dist 绝对路径;相对路径实测 "Failed to import + encodings")。纯文本断言钉死接线(不执行 iscc/cmd —— macOS/CI 无 Windows)。 + """ + content = _read("packaging/make-installer.sh") + # WritePyvenvCfg 过程:SaveStringToFile 的 S 参数是 AnsiString(与 LogText 同规, + # 6.7.1 → 7.x 全版本一致);UnicodeString 不匹配 → 声明 AnsiString 变量 + assert "procedure WritePyvenvCfg;" in content + assert "CfgContent: AnsiString;" in content + assert "CfgContent: string;" not in content + assert "SaveStringToFile(CfgPath, CfgContent, False)" in content + # 写 {app}\\bin\\pyvenv.cfg,home = 绝对路径(ExpandConstant 展开 {app}) + assert "ExpandConstant('{app}\\bin\\pyvenv.cfg')" in content + assert "CfgContent := 'home = ' + ExpandConstant('{app}\\bin\\python-dist')" in content + # ssPostInstall 钩子:先写 cfg 再补 PATH(同一个 CurStepChanged) + step_src = content.split("procedure CurStepChanged")[1].split("procedure CurUninstallStepChanged")[0] + assert "WritePyvenvCfg;" in step_src + assert "AddBinDirToPath;" in step_src + assert step_src.index("WritePyvenvCfg;") < step_src.index("AddBinDirToPath;") + # smoke-test.sh:Windows 分支复刻同款 cfg(home 绝对路径)再验证 venv + smoke = _read("packaging/smoke-test.sh") + assert 'if [ -n "${WINDIR:-}" ]; then' in smoke + assert "printf 'home = %s\\\\python-dist\\n'" in smoke + assert 'PY_BIN="$HOME/.emrg/install/bin/python.exe"' in smoke + # upgrade_prompt.j2:本地等价安装也要写 bin\\pyvenv.cfg(绝对 home) + prompt = _read("emrg/server/prompts/upgrade_prompt.j2") + assert "bin/pyvenv.cfg" in prompt + assert "home = {{ install_dir }}\\bin\\python-dist" in prompt + + def test_make_installer_iss_old_python_health_check(): """rant 2026-08-19T00:44:52 — 安装器韧性:旧版 python-dist 损坏(encodings 崩, v0.2.49/v0.2.50 -I 事故遗留)时,升级安装不得因坏 python 硬中止。