fix(skill): 修正 @build 退出码口径不符(环境错误 1→2、用法 2→3)

`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 全绿。
This commit is contained in:
2026-10-12 18:49:24 +08:00
parent 8cd815d046
commit 3c7ad9c75c
3 changed files with 50 additions and 19 deletions
+7 -2
View File
@@ -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)
+42 -16
View File
@@ -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)