新增 cubemx-regen、board-test-suite、new-machine-setup、mem-budget 四个项目级 skill
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: board-test-suite
|
||||
description: 板级测试套件运行与恢复:test_config.h 套件互斥、破坏性开关的持久代价(NAND 坏块记账)、run_all_suites.py 一键回归(自动改宏+无条件还原)、跑后恢复产品构建、容量恢复动作 A/B/C 区分。嵌入式"编译期套件开关"模式的通用流程。
|
||||
whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/关破坏性测试、怀疑仓库里混入了测试固件构建、或跑完套件要做容量/状态恢复时。
|
||||
---
|
||||
|
||||
# board-test-suite — 板级测试套件运行与恢复(文档型 skill)
|
||||
|
||||
## 开关模型(编译期,`test/board/test_config.h`)
|
||||
|
||||
- `TEST_SUITE_CH395F / STORAGE / GD5F / RTC` **互斥**(同开 = `#error`);
|
||||
**全部注释 = 产品构建 = 默认状态**。
|
||||
- `ENABLE_DESTRUCTIVE_TESTS` 默认关:开着跑一轮的代价是**改设备持久状态**——
|
||||
实测 GD5F 整轮破坏性跑一次即多记一个 NAND 坏块(容量 95248→95200),
|
||||
计数持久化在 dhara journal checkpoint 头里,普通格式化恢复不回来。
|
||||
- 提交门禁:pre-commit 钩子 `test-suite-switch` 默认拦截测试构建
|
||||
(确需提交用 `ALLOW_TEST_BUILD=1` 显式放行)——**跑完务必改回全注释并 `@build`**。
|
||||
|
||||
## 一键回归(推荐路径,勿手工循环)
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件:改宏→build→烧录→采集判读
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(无需硬件,防"某套件长期编不过")
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
|
||||
```
|
||||
|
||||
- 脚本 try/finally **无条件还原 `test_config.h`**(含 Ctrl-C 路径)——这是它存在的核心
|
||||
理由:跑完忘还原 = 测试固件进库。`--keep-config` 仅调试用。
|
||||
- 退出码 0=全 PASS / 1=有 FAIL / 2=环境错误;4 套件真机一轮数分钟起,**仅回归窗口用,绝不挂 pre-commit**。
|
||||
- 手工循环时的顺序(每轮):改宏(只开一个)→ `@build` 0错0警 → 烧录 →
|
||||
`serial-assistant` 采集(open→clear→烧录→wait-idle)→ 判读套件汇总行。
|
||||
|
||||
## 跑后检查清单(必做)
|
||||
|
||||
1. `test_config.h` 恢复全注释(用 run_all_suites.py 则已自动);
|
||||
2. `@build` 确认产品构建 0 错 0 警;
|
||||
3. 若开过破坏性开关:核对开机日志容量数字(健康满值 96064 pages / 187 MB),
|
||||
掉了 → 按 `docs/测试说明/存储维护与容量恢复.md` 走恢复动作;
|
||||
4. 提交前 `test-suite-switch` 门禁会再兜一道。
|
||||
|
||||
## 三个维护动作别混用(存储类)
|
||||
|
||||
| 目标 | 开关 | 代价 |
|
||||
|---|---|---|
|
||||
| A 卷/BBT 恢复 | `TEST_SUITE_STORAGE` + `ENABLE_STORAGE_RECOVERY_TESTS` | 卷全失,**不恢复容量** |
|
||||
| B 容量恢复 | A + `STORAGE_RECOVERY_FULL_WIPE`(擦净 2044 块 + 断电重启) | 卷全失,约 4s 擦除 |
|
||||
| C 日常回归 | 只开 `TEST_SUITE_*`,不开破坏性 | 无(只读模式,横幅 `mode: READ-ONLY`) |
|
||||
|
||||
原理与完整步骤见 `docs/测试说明/存储维护与容量恢复.md`;各套件用例清单/判读见
|
||||
`docs/测试说明/` 对应 `*_Test_Guide.md`;流程规范 `docs/测试说明/测试开发标准流程.md` §3。
|
||||
|
||||
## 其它项目怎么套
|
||||
|
||||
模式通用:套件选择收进单一编译期头文件 + 互斥 `#error` + 默认产品构建 +
|
||||
提交期门禁 + 编排脚本"改宏→构建→烧录→判读→**无条件还原**"。破坏性用例必须
|
||||
独立开关且默认关,跑前想清楚持久代价与恢复路径。
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: cubemx-regen
|
||||
description: CubeMX 重生成存活工作流(L1 写进 .ioc → L2 USER CODE 区/宏改名 → L3 审计兜底):重生成前后各跑什么、哪些定制会被静默覆盖、审计脚本 check_cubemx_survival.py 的 --check/--write 语义。STM32CubeMX 项目通用方法论 + 本仓库清单。
|
||||
whenToUse: 要用 CubeMX 重新生成工程(改 .ioc / 外设配置)、改动 Src/Inc 生成区附近代码、或排查"重生成后定制丢失/行为变了但编译不报错"时。
|
||||
---
|
||||
|
||||
# cubemx-regen — CubeMX 重生成存活(文档型 skill)
|
||||
|
||||
**核心事实**:`.ioc` 的 `KeepUserCode=true` **只**保护 `USER CODE BEGIN/END` 标记之间;
|
||||
标记之外的一切手工改动重生成即被模板覆盖,且**多数不报编译错**(崩溃采集被打回
|
||||
`while(1)`、加固宏被整块删)——只在真出故障时暴露。
|
||||
|
||||
## 三层优先级(防丢失的唯一正确顺序)
|
||||
|
||||
1. **L1 写进 `.ioc`**(权限最高):凡 CubeMX 界面能表达的配置(任务栈/优先级/队列/
|
||||
事件组/堆大小/IWDG 参数/FreeRTOS 钩子开关)必须在 GUI 里设——重生成只会照写。
|
||||
⚠ **别凭印象断言"界面里没有该项"**,先翻标签页或查 `db/mcu/IP/*_Modes.xml`
|
||||
(实测 `configCHECK_FOR_STACK_OVERFLOW=2` 在 FreeRTOS → Config parameters →
|
||||
Hook function related definitions 里,自造宏区被抹两次后才找到)。
|
||||
2. **L2 搬进 `USER CODE` 区**:只信 CubeMX **当前模板真实生成**的区名(优先
|
||||
`USER CODE BEGIN 0` 这类从未变过的标准位)。**不要依赖"自造标记区"**——
|
||||
模板换代时自造区连同内容一起消失(实测中断函数模板换代的教训)。
|
||||
生成区内要**去掉一句调用** → 宏改名:`USER CODE BEGIN 0` 里放空实现 +
|
||||
`#define HAL_IWDG_Init(a_h) iwdg_deferred_start(a_h)`,生成句自动变空操作
|
||||
(`.map` 里 `Removing ... N bytes` 验证;生成区零 diff)。
|
||||
要**替换生成实体定义**(如 4 个 Fault Handler 本体)→ 我们的定义放
|
||||
`USER CODE BEGIN 0`,**必须写在 `#define X X_Gen` 之前**(写在之后会被自己的宏改名),
|
||||
生成区恢复原样,链接器自动移除未引用的 `*_Gen`。
|
||||
3. **L3 审计兜底**:清单 + 哈希基线漂移检测(见下)。
|
||||
|
||||
## 重生成前后固定动作
|
||||
|
||||
```powershell
|
||||
# 重生成前:把能在 GUI 表达的都写进 .ioc(L1 检查)
|
||||
# 重生成后【立刻】:
|
||||
& '.venv\Scripts\python.exe' scripts/check_cubemx_survival.py # 人读报告
|
||||
& '.venv\Scripts\python.exe' scripts/check_cubemx_survival.py --check # 门禁模式(pre-commit 同款)
|
||||
# 确认 diff 全部是"想要的改动"后重建基线:
|
||||
& '.venv\Scripts\python.exe' scripts/check_cubemx_survival.py --write
|
||||
```
|
||||
|
||||
- 退出码 0=通过 / 1=有 FAIL 项;已挂 pre-commit `cubemx-survival` 钩子
|
||||
(改 `.ioc`/`Src/`/`Inc/`/`.uvprojx` 触发)。
|
||||
- **L3 清单已清空 = 重生成应当零 diff**;仍有 diff 就是被覆盖了,直接跑脚本定位。
|
||||
- `uvprojx` 不参与漂移检测(每次构建被 UV4 改写),只保留"源文件仍挂载"断言。
|
||||
|
||||
## 实测陷阱清单
|
||||
|
||||
- CubeMX 对 System Exception(HardFault/MemManage/BusFault/UsageFault)**只生成优先级、
|
||||
从不生成使能代码**;`.ioc` 里 `NVIC.MemoryManagement_IRQn=true` ≠ 已使能(只是向量可见)。
|
||||
`SHCSR` 使能位只能手工写(L2)+ 审计项兜底。
|
||||
- 启用 FreeRTOS 后模板会生成 `USER CODE BEGIN 4 / 5` 区(默认空桩)——**钩子实现必须
|
||||
整体写进这两个区**,且 helper 函数放更早的区(同名两处定义 = 编译错)。
|
||||
- 生成文件行尾/末尾空行按工具自身风格,**勿用改写型钩子去修剪**(已豁免,勿"顺手格式化")。
|
||||
- 重生成后除审计脚本外,照常走 `@build`(0 错 0 警)+ 必要时 `@test` 闭环。
|
||||
|
||||
## 其它 CubeMX 项目怎么套
|
||||
|
||||
L1/L2/L3 方法论与"宏改名两招"完全通用;本仓库的清单/基线在
|
||||
`scripts/check_cubemx_survival.py`(21 项 + `.cubemx_survival_baseline.json`),
|
||||
新项目照它建自己的清单。方法论详见 `docs/子系统设计/看门狗设计说明.md` §10.1/§10.2。
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: mem-budget
|
||||
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + DBG 日志成本模型(每条约 90B Flash/216B 栈)。"改任务/缓冲/栈之前必须核算"的固定动作与判读口径。
|
||||
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、或需要回答"这个改动花多少 RAM/Flash"时。
|
||||
---
|
||||
|
||||
# mem-budget — RAM/栈预算核算
|
||||
|
||||
## 固定动作(改前、改后各跑一次)
|
||||
|
||||
```powershell
|
||||
# cwd = 项目根;先 @build 保证 .htm/.map 是最新的
|
||||
& '.venv\Scripts\python.exe' ".dsh\skills\mem-budget\scripts\mem_check.py"
|
||||
# 等价于分别跑:
|
||||
# scripts/ram_report.py (.map → RAM/Flash 分区与按文件占用表)
|
||||
# scripts/stack_report.py (.htm 静态调用图 → 每任务最坏链 vs 配置栈)
|
||||
# 叠加运行时水位(有真机日志时):
|
||||
& '.venv\Scripts\python.exe' scripts/stack_report.py -s tools\serial_tool\state\serial.log
|
||||
```
|
||||
|
||||
退出码:0=全部通过 / 1=有任务不达标 / 2=解析错误(.htm/.map 缺失先重跑构建)。
|
||||
|
||||
## 判读口径
|
||||
|
||||
- **静态**:`stack_report.py` 的 Max Depth(最坏调用链累计)vs `Src/freertos.c` 配置栈;
|
||||
默认要求余量 ≥30%(`--min-free-pct`),ISR 另留 `--isr-margin`(默认 1024B)。
|
||||
- **运行时**:开机日志 `[STACK] <task> peak_used=…/… free_min=…` 与静态值对照——
|
||||
静态是上界、运行时是下界证据,**两者都要看**;余量 <1KB 底线要上报。
|
||||
- **RAM 总量**:`ram_report.py` 对 Grand Totals 与 `docs/内存管理/` 预算表核对;
|
||||
链接错 `L6406E` = 已经溢出,用按文件表找大头。
|
||||
- 任务表一致性由 pre-commit `task-table` 钩子保证(改栈后 `gen_task_table.py` 同步 AGENTS.md)。
|
||||
|
||||
## 成本模型(实测,估算用)
|
||||
|
||||
- **`DBG_*` 日志不是免费的**:每条调用点约 **90 B Flash**(字符串常量另计)+ 深链上
|
||||
每帧一份行缓冲进最坏栈(实测一条链 9 帧 ≈ 1.5 KB 纯日志缓冲)。
|
||||
已覆盖步骤勿加重复锚点,只在原先静默返回的黑洞处补。
|
||||
- **深调用链模块收紧行缓冲**:`#define DBG_LINE_BUF_MAX 128U` 写在 `#include "dbg_log.h"`
|
||||
之前;取值必须 ≥ 该模块最长日志行(时间戳 20 + 标签 + 正文),否则截断。
|
||||
- **加大局部缓冲的替代方案是静态化**(实测 lftpd 9 处大局部缓冲静态化:BSS +4.6 KB,
|
||||
任务峰值 3480B→1184B)——栈紧张时先找大局部变量。
|
||||
- **自研代码禁 C 库堆**(MicroLIB 堆仅 512B):只用 `pvPortMalloc/vPortFree`(FreeRTOS 堆),
|
||||
模拟器 CRT 堆照不出 MCU 堆分配失败。
|
||||
|
||||
## 其它项目怎么套
|
||||
|
||||
模式通用:链接器 map + 调用图 htm 是**免硬件**的栈可观测源;固定"改前改后各跑一次
|
||||
报告 + 运行时水位叠加 + 余量阈值"三件套。本仓库两个报告脚本按 Keil 输出格式解析,
|
||||
换工具链(GCC newlib + `-fstack-usage`)需替换解析层,工作流不变。
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""mem_check.py - 跑项目的 RAM/栈报告(薄运行器,纯标准库)。
|
||||
|
||||
依次调用仓库的 ram_report.py 与 stack_report.py(路径可覆盖),打印各自退出码
|
||||
与总结。用法(项目根目录):
|
||||
python <skill>/scripts/mem_check.py [--ram-script scripts/ram_report.py]
|
||||
[--stack-script scripts/stack_report.py] [--serial-log PATH] [--python PATH]
|
||||
退出码: 0=两报告都过 / 1=任一 FAIL / 2=脚本缺失或用法错误。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_python(cli):
|
||||
if cli:
|
||||
return cli
|
||||
for cand in (".venv/Scripts/python.exe", ".venv/bin/python"):
|
||||
if Path(cand).is_file():
|
||||
return cand
|
||||
return sys.executable
|
||||
|
||||
|
||||
def run(py, script, extra):
|
||||
p = Path(script)
|
||||
if not p.is_file():
|
||||
print("MISSING: {}".format(p))
|
||||
return 2
|
||||
print("=== {} ===".format(p))
|
||||
r = subprocess.run([py, str(p)] + extra)
|
||||
return r.returncode
|
||||
|
||||
|
||||
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="run RAM/stack reports")
|
||||
ap.add_argument("--ram-script", default="scripts/ram_report.py")
|
||||
ap.add_argument("--stack-script", default="scripts/stack_report.py")
|
||||
ap.add_argument("--serial-log", default=None,
|
||||
help="serial log with [STACK] lines (overlay runtime watermarks)")
|
||||
ap.add_argument("--python", default=None, help="interpreter (default .venv)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
py = find_python(args.python)
|
||||
rc_ram = run(py, args.ram_script, [])
|
||||
stack_extra = ["-s", args.serial_log] if args.serial_log else []
|
||||
rc_stack = run(py, args.stack_script, stack_extra)
|
||||
|
||||
worst = max(rc_ram, rc_stack)
|
||||
print("MEM_CHECK: ram={} stack={} -> {}".format(rc_ram, rc_stack,
|
||||
"PASS" if worst == 0 else ("FAIL" if worst == 1 else "ERROR")))
|
||||
return 0 if worst == 0 else (1 if worst == 1 else 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user