新增 cubemx-regen、board-test-suite、new-machine-setup、mem-budget 四个项目级 skill

This commit is contained in:
2026-10-10 14:41:30 +08:00
parent 883c9dd0cc
commit 7c4b724057
8 changed files with 419 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
__pycache__/
+55
View File
@@ -0,0 +1,55 @@
---
name: new-machine-setup
description: 换机/新克隆环境自检与搭建:venv 重建、DTU_* 工具路径环境变量(用户级、无管理员)、驱动确认、验收三连(selftest/build/list)。自带独立自检脚本 env_check.py(纯标准库),含"会话不继承用户级变量"实测坑。任何嵌入式项目通用模式。
whenToUse: 换新机器/新克隆要把环境搭起来、构建或烧录报"环境变量未设/工具找不到"、CI 之外任何机器第一次跑本项目、或怀疑本机环境缺件时。
---
# new-machine-setup — 换机环境自检与搭建
## 先跑自检(只读,不改任何东西)
```powershell
# cwd = 项目根目录
& <任一 python ≥3.10> ".dsh\skills\new-machine-setup\scripts\env_check.py"
```
逐项 PASS/WARN/FAIL + 缺件时打印**可直接执行的修复命令**;退出码 0=可用 / 1=有 FAIL。
## 搭建顺序(自检 FAIL 时的完整路径)
1. **venv**:`& <任一 python ≥3.10> -m venv .venv` →
`& '.venv\Scripts\python.exe' -m pip install -r requirements.txt`
(含 pyserial/fontTools/pre-commit;`requirements.txt` 必须纯 ASCII)。
PATH 默认 `python` 可能是 msys64 的——依赖第三方库的脚本一律用 `.venv`。
2. **工具路径环境变量**(用户级即可,无需管理员;约定见
`docs/环境与部署/本地工具路径环境变量方案.md`):
```powershell
[Environment]::SetEnvironmentVariable('DTU_KEIL_UV4', '<本机>\UV4.exe', 'User')
[Environment]::SetEnvironmentVariable('DTU_STLINK_CLI', '<本机>\ST-LINK_CLI.exe', 'User') # 已入 PATH 可不设
```
语义统一:**显式设了但路径无效 = 立即报错不回退**;不设则走 PATH/自动发现。
3. **驱动**:CH340(调试串口)与 ST-Link 的 USB 驱动装好——
`Get-PnpDevice -Class Ports` 能看到 COMx、`reset.bat list` 能看到探针 SN。
4. **验收三连**(全绿才算环境就绪):
```powershell
& '.venv\Scripts\python.exe' tools/serial_tool/serial_tool.py selftest # 23/23 PASS
& "MDK-ARM\build.bat" # exit 0
& "MDK-ARM\reset.bat" list # 列出探针
```
## 实测坑(本会话两次撞上)
- **agent/服务派生的 shell 不继承"服务启动之后"设置的用户级环境变量**:
`build.bat` 报 exit 2 先分清"真没设"还是"没继承"——查
`[Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')`,有值就
`$env:DTU_KEIL_UV4 = <该值>` 注入当前会话再跑,别急着让用户重设。
(`env_check.py` 对这种情况报 WARN 并给注入命令。)
- 换机后 COM 口几乎必变:只改 `.serialtest/config.json` 的 `portName`,
然后 `serial_tool.py restart`(daemon 启动时缓存配置)。
- 串口"拒绝访问"先查残留旧 daemon(AGENTS.md 串口助手节),不是驱动问题。
## 其它项目怎么套
模式通用:本机差异全部走 `*_TOOL_PATH` 类环境变量(显式无效=硬错)、
仓库内路径全部相对解析、环境就绪判据 = 三条独立验收命令(工具链构建 /
烧录器在位 / 采集自检),自检脚本只读不修、FAIL 行自带修复命令。
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""env_check.py - 换机/新克隆环境自检(纯标准库,只读不修)。
用法(项目根目录,任一 python3 ≥3.8):
python <skill>/scripts/env_check.py [--venv .venv]
检查项: venv 与关键包 / DTU_* 工具路径变量(会话 vs 用户级)/ UV4 /
ST-LINK_CLI / COM 口在位 / pre-commit。每行 PASS|WARN|FAIL,FAIL 行附
可直接执行的修复命令。退出码: 0=可用(无 FAIL) / 1=有 FAIL。
"""
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
def line(status, name, detail=""):
print("{:4} {:24} {}".format(status, name, detail))
return status
def env_user_scope(name):
"""读用户级环境变量(Windows 注册表,标准库 winreg;其他平台回退 os.environ)。"""
if os.name != "nt":
return os.environ.get(name)
try:
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") as k:
v, _ = winreg.QueryValueEx(k, name)
return v
except OSError:
return None
def check_tool_env(name, venv_py):
"""工具路径变量:会话值 > 用户级值;区分'没设'与'设了没继承'。"""
sess = os.environ.get(name)
if sess:
if Path(sess).is_file():
return line("PASS", name, sess)
return line("FAIL", name, "已设但路径无效: {}(改正值本身)".format(sess))
user = env_user_scope(name)
if user:
return line("WARN", name, "用户级已设({}),本会话未继承——注入: $env:{} = '{}'".format(
user, name, user))
return None # 未设:由调用方决定 WARN/FAIL
def py_import_ok(py, mod):
if not py or not Path(py).is_file():
return False
r = subprocess.run([str(py), "-c", "import " + mod], capture_output=True, timeout=30)
return r.returncode == 0
def main(argv=None):
for s in (sys.stdout, sys.stderr):
try:
s.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
ap = argparse.ArgumentParser(description="environment self-check (read-only)")
ap.add_argument("--venv", default=".venv", help="venv dir (default ./.venv)")
args = ap.parse_args(argv)
fails = 0
root = Path.cwd()
# 1) venv 与关键包
venv_py = root / args.venv / "Scripts" / "python.exe"
if not venv_py.is_file():
venv_py = root / args.venv / "bin" / "python" # *nix
if line("FAIL" if not venv_py.is_file() else "PASS", "venv",
str(venv_py) if venv_py.is_file() else
"缺失——& <任一 python ≥3.10> -m venv {}; & '{}\\Scripts\\python.exe' -m pip install -r requirements.txt".format(
args.venv, args.venv)) == "FAIL":
fails += 1
else:
for mod, pipname in (("serial", "pyserial"), ("fontTools", "fontTools"),
("pre_commit", "pre-commit")):
if line("PASS" if py_import_ok(venv_py, mod) else "FAIL",
"pkg:" + mod, "" if py_import_ok(venv_py, mod) else
"缺失——& '{}' -m pip install {}".format(venv_py, pipname)) == "FAIL":
fails += 1
# 2) 工具路径变量
r = check_tool_env("DTU_KEIL_UV4", venv_py)
if r is None:
w = shutil.which("UV4.exe") or shutil.which("UV4")
if line("FAIL" if not w else "WARN", "UV4 可达",
w or "DTU_KEIL_UV4 未设且 PATH 无 UV4——[Environment]::SetEnvironmentVariable('DTU_KEIL_UV4','<本机>\\UV4.exe','User')"):
fails += 1
r = check_tool_env("DTU_STLINK_CLI", venv_py)
if r is None:
w = shutil.which("ST-LINK_CLI.exe") or shutil.which("ST-LINK_CLI")
line("PASS" if w else "WARN", "ST-LINK_CLI 可达",
w or "未设 DTU_STLINK_CLI 且 PATH 无(烧录机需二者之一)")
# 3) COM 口在位(用 venv 的 pyserial,不碰 daemon)
if py_import_ok(venv_py, "serial.tools.list_ports"):
r = subprocess.run([str(venv_py), "-c",
"import serial.tools.list_ports as p;"
"print(';'.join(x.device for x in p.comports()))"],
capture_output=True, timeout=30)
ports = (r.stdout or b"").decode("utf-8", "replace").strip()
if line("PASS" if ports else "FAIL", "COM 口",
ports or "无任何 COM 设备——装 CH340 驱动 / 检查 USB"):
if not ports:
fails += 1
else:
line("WARN", "COM 口", "跳过(pyserial 不可用)")
# 4) 项目配置存在
cfg = root / ".serialtest" / "config.json"
if line("PASS" if cfg.is_file() else "FAIL", "串口配置档案",
str(cfg) if cfg.is_file() else
"缺失——建 .serialtest/config.json(portName/baudRate,见 .dsh/skills/serial-boot-test/config.example.json)"):
if not cfg.is_file():
fails += 1
print("SUMMARY: {}".format("OK - 环境可用(WARN 项按提示处理)" if fails == 0
else "{} 项 FAIL - 按上面修复命令处理".format(fails)))
return 0 if fails == 0 else 1
if __name__ == "__main__":
sys.exit(main())