From 3c7ad9c75cc3a1c3760e5d4c535eaa5b40413043 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 12 Oct 2026 18:49:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(skill):=20=E4=BF=AE=E6=AD=A3=20@build=20?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=A0=81=E5=8F=A3=E5=BE=84=E4=B8=8D=E7=AC=A6?= =?UTF-8?q?=EF=BC=88=E7=8E=AF=E5=A2=83=E9=94=99=E8=AF=AF=201=E2=86=922?= =?UTF-8?q?=E3=80=81=E7=94=A8=E6=B3=95=202=E2=86=923=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AGENTS.md` 与 `keil-build` / `new-machine-setup` 的 SKILL.md 一直写着 "2=UV4 未找到/无效(环境错误)、3=用法",但实现是反的: - 环境错误走 `raise SystemExit("...")` → **实际退出码 1**(与"构建失败" 撞号,调用方分不清"编译不过"和"UV4 没配好"); - 用法错误由 argparse 抛 `SystemExit(2)` → 恰好占用了文档里"环境错误" 的位置,于是 `new-machine-setup` 里"报 exit 2 先分清真没设还是没继承" 这条排障指引**从未生效**。 改法(让实现服从既有文档口径,而非改文档): - 新增 `BuildEnvError`;`find_uv4` / `find_project` / `load_config` 失败 一律抛它,由 `main()` 捕获 → 打印 + 退出码 **2**; - 覆盖 argparse 的 `error()`(默认 `SystemExit(2)`)→ 用法错误退出码 **3**; - `run_build` 的档案解析失败原为 `return 3`(用法),实属环境错误 → 并入 2。 调用方安全性:`test/run_all_suites.py` 与 `task-worktree` 的 `run_script` 都只判 `rc == 0` / `rc != 0`,不区分具体码,故本次改动不影响它们。 验证(全部实测,用假 UV4 避免与在途构建争 UV4 与 build 锁): | 场景 | 期望 | 实测 | |---|---|---| | `--help` | 0 | 0 | | 成功构建(假 UV4 写 0E0W) | 0 | 0(BUILD_OK) | | 构建失败(假 UV4 写 `error:`) | 1 | 1(BUILD_FAIL) | | `--project` 不存在 | 2 | 2 | | 档案 `uvprojx` 不存在 | 2 | 2 | | 档案 JSON 解析失败 | 2 | 2 | | 未知参数 `--bogus` | 3 | 3 | | 参数缺值 `--timeout` | 3 | 3 | | build 锁被占用 | 4 | 4(B9 在构建时实测) | 同步更新 `AGENTS.md` 的退出码行(补 2 的完整含义与 4)与 `keil-build` SKILL.md(记录本次修正的来龙去脉)。pre-commit 全绿。 --- .dsh/skills/keil-build/SKILL.md | 9 ++- .dsh/skills/keil-build/scripts/keil_build.py | 58 ++++++++++++++------ AGENTS.md | 2 +- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/.dsh/skills/keil-build/SKILL.md b/.dsh/skills/keil-build/SKILL.md index ec2f8846..c8b40f70 100644 --- a/.dsh/skills/keil-build/SKILL.md +++ b/.dsh/skills/keil-build/SKILL.md @@ -18,8 +18,13 @@ whenToUse: 需要编译 Keil MDK 工程、验证代码改动是否 0 错误 0 解析顺序(命令行 > 项目档案 `.serialtest/config.json` > 环境 > 探测): 工程 `uvprojx`(档案缺省时自动搜 cwd 下唯一 `*.uvprojx`);UV4 `uv4` > `$DSH_KEIL_UV4` > `$DTU_KEIL_UV4` > PATH(**显式设了但路径无效 = 立即报错,不静默回退**)。 -退出码:`0`=PASS(0错0警) / `1`=构建失败(打印报错行)/ `2`=UV4 未找到/无效 / `3`=用法 / -`4`=build 锁被占用(并发 `-b` 不可行)。 +退出码:`0`=PASS(0错0警) / `1`=构建失败(打印报错行)/ **`2`=环境错误**(UV4 找不到或 +路径无效、工程找不到、档案解析失败)/ `3`=用法错误 / `4`=build 锁被占用(并发 `-b` 不可行)。 + +> 2026-10-12 修正:旧实现把环境错误写成 `raise SystemExit("...")` → 实际退出码是 **1**, +> 而用法错误由 argparse 抛 `SystemExit(2)` → 恰好占了"环境错误"的位置。故 `AGENTS.md` / +> `new-machine-setup` 里写的"报 exit 2 = 环境问题"当时**不成立**。现按文档口径改正: +> 环境错误 → 2(`BuildEnvError`),用法错误 → 3(覆盖 argparse 的 `error()`)。 ## build 锁:脚本自持自释(2026-10-12) diff --git a/.dsh/skills/keil-build/scripts/keil_build.py b/.dsh/skills/keil-build/scripts/keil_build.py index b9c23b8b..8657ea13 100644 --- a/.dsh/skills/keil-build/scripts/keil_build.py +++ b/.dsh/skills/keil-build/scripts/keil_build.py @@ -33,8 +33,17 @@ - 调用方**已持有** build 锁时(如 `task-worktree done` 的门禁),设 `DTU_LOCK_HELD=build` 让本脚本跳过自持自释,避免自我死锁 -退出码: 0=PASS(0错0警) 1=构建失败(看打印的报错行/日志) 2=UV4 未找到/无效 - 3=用法错误 4=build 锁被占用(并发 -b 不可行) +退出码(与 task-worktree 同一约定;2026-10-12 修正——此前实现与文档不符): + 0 = PASS(0 错 0 警) + 1 = 构建失败(看打印的报错行 / 日志) + 2 = **环境/前置错误**(UV4 找不到或路径无效、工程找不到、档案解析失败) + 3 = 用法错误 + 4 = build 锁被占用(并发 -b 不可行) + +⚠ 修正说明:旧实现把环境错误写成 `raise SystemExit("...")` → **实际退出码是 1**, +而用法错误由 argparse 抛 SystemExit(2) → 恰好占用了文档里"环境错误"的位置。 +故 AGENTS.md / `new-machine-setup` SKILL.md 里写的"报 exit 2 = 环境问题"当时**不成立**。 +现已按文档口径改正:环境错误 `BuildEnvError` → 2,argparse 用法错误 → 3。 """ import argparse @@ -48,6 +57,21 @@ from pathlib import Path LOCK_NAME = "build" +class BuildEnvError(Exception): + """环境/前置错误(UV4 或工程找不到、档案解析失败)→ 退出码 2。""" + + +class _ArgumentParser(argparse.ArgumentParser): + """用法错误 → 退出码 3。 + + argparse 默认 `error()` 走 `SystemExit(2)`,与"环境错误=2"冲突,故覆盖之。 + """ + + def error(self, message): + self.print_usage(sys.stderr) + self.exit(3, "{}: error: {}\n".format(self.prog, message)) + + def find_config_file(): env = os.environ.get("DSH_SERIAL_CONFIG") if env: @@ -84,26 +108,26 @@ def find_uv4(cli_arg, cfg): if val: if Path(val).is_file(): return Path(val), label - raise SystemExit(("BUILD_ENV_ERROR: {} is set but not a file: {} " - "(fix the value; no silent fallback)").format(label, val)) + raise BuildEnvError(("BUILD_ENV_ERROR: {} is set but not a file: {} " + "(fix the value; no silent fallback)").format(label, val)) w = shutil.which("UV4.exe") or shutil.which("UV4") if w: return Path(w), "PATH" - raise SystemExit("BUILD_ENV_ERROR: UV4.exe not found " - "(--uv4 / config 'uv4' / $DSH_KEIL_UV4 / $DTU_KEIL_UV4 / PATH)") + raise BuildEnvError("BUILD_ENV_ERROR: UV4.exe not found " + "(--uv4 / config 'uv4' / $DSH_KEIL_UV4 / $DTU_KEIL_UV4 / PATH)") def find_project(cli_arg, cfg): if cli_arg: p = Path(cli_arg) if not p.is_file(): - raise SystemExit("BUILD_ENV_ERROR: --project not found: {}".format(p)) + raise BuildEnvError("BUILD_ENV_ERROR: --project not found: {}".format(p)) return p v = cfg.get("uvprojx") if v: p = Path(v) if not p.is_file(): - raise SystemExit("BUILD_ENV_ERROR: config 'uvprojx' not found: {}".format(p)) + raise BuildEnvError("BUILD_ENV_ERROR: config 'uvprojx' not found: {}".format(p)) return p hits = sorted(p for p in Path.cwd().rglob("*.uvprojx") if not any(part.startswith(".") for part in p.parts)) @@ -111,11 +135,11 @@ def find_project(cli_arg, cfg): print("PROJ: auto-detected {}".format(hits[0])) return hits[0] if not hits: - raise SystemExit("BUILD_ENV_ERROR: no *.uvprojx under cwd " - "(--project or config 'uvprojx')") - raise SystemExit("BUILD_ENV_ERROR: multiple .uvprojx found, pick one " - "(--project or config 'uvprojx'):\n " + - "\n ".join(str(h) for h in hits)) + raise BuildEnvError("BUILD_ENV_ERROR: no *.uvprojx under cwd " + "(--project or config 'uvprojx')") + raise BuildEnvError("BUILD_ENV_ERROR: multiple .uvprojx found, pick one " + "(--project or config 'uvprojx'):\n " + + "\n ".join(str(h) for h in hits)) # --------------------------------------------------------------- build 锁(自持自释) @@ -188,8 +212,7 @@ def run_build(args): """真正的构建主体(锁由 main 负责)。返回退出码。""" cfg, err = load_config() if err: - print("BUILD_ENV_ERROR: {}".format(err)) - return 3 + raise BuildEnvError("BUILD_ENV_ERROR: {}".format(err)) proj = find_project(args.project, cfg) uv4, src = find_uv4(args.uv4, cfg) @@ -261,7 +284,7 @@ def main(argv=None): except Exception: pass - ap = argparse.ArgumentParser(description="Keil UV4 CLI build + verdict (pure stdlib)") + ap = _ArgumentParser(description="Keil UV4 CLI build + verdict (pure stdlib)") ap.add_argument("--project", default=None, help="*.uvprojx (default: config/auto-detect)") ap.add_argument("--uv4", default=None, help="UV4.exe path") ap.add_argument("--target", default=None, help="build target name (-t; default: project default)") @@ -292,6 +315,9 @@ def main(argv=None): try: return run_build(args) + except BuildEnvError as e: + print(str(e)) + return 2 finally: if state == "ok": ok, out = lock_cli(tool, "release", task) diff --git a/AGENTS.md b/AGENTS.md index 86da624d..5d264845 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,7 +76,7 @@ ## 构建与验证 -- `@build` = `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(仓库根执行)。退出码 **0=PASS / 1=构建失败 / 2=UV4 未找到 / 3=用法**;日志 `MDK-ARM/build_log.txt`;换机必设 `DTU_KEIL_UV4` → 细节 **`keil-build` skill** + `docs/环境与部署/本地工具路径环境变量方案.md` +- `@build` = `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(仓库根执行)。退出码 **0=PASS / 1=构建失败 / 2=环境错误(UV4/工程/档案)/ 3=用法 / 4=build 锁被占用**;日志 `MDK-ARM/build_log.txt`;换机必设 `DTU_KEIL_UV4` → 细节 **`keil-build` skill** + `docs/环境与部署/本地工具路径环境变量方案.md` - `@test` = `& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py`:开串口+清日志 → 烧录(**固件身份戳**保证烧进去的就是这份构建、**回读校验**)→ wait-idle → 采日志 → 标记校验(含启动锚点计数)。退出码 **0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法**;戳提取规则、锚点计数判据与按模块定制标记见 `docs/测试说明/自动化测试流程.md` - Forbidden 标记含 `[ERR]`:**四个 Fault 的崩溃日志都以 `[ERR]` 开头 → 出现 Fault 即本次不通过**(刻意) - **烧录必须 `-Rst -Run`**(`stlink-flash` skill 的 `flash`):裸 `-P` 会报 `Programming Complete`,但目标核处于死循环/异常时写入并不落地("反复重下载、芯片一直是旧镜像、二分法失效"的根因)。**烧后必须回读校验**(`stlink_flash.py cmp` = `-CmpFile`,`No difference found` 才算真正写入);⚠ `-CmpFile` 连接时会**复位目标** → 必须排在**采日志之后** → `docs/测试说明/ST-LINK_CLI_复位使用说明.md`