Files
STM32F4-Base/docs/门禁说明/layer-deps.md
T
wandering1 88601422c3 docs(文档规范): 清空壳变更记录节,例外清单改成可复跑口径
17 篇文档的变更记录(含修订记录、带编号与中文序号标题)节里只剩一句
「不再记流水账」的占位话,整节删掉,连带它前面的分隔线。
仍保留真台账的只有 5 篇(看门狗 / 配置文件设计 / RAM 预算台账 /
内容页架构 / 本地工具路径方案),外加 scripts/archive 归档区。

份数口径改成可复跑的普查:文档规范 §5.1 补自查命令,
stale-doc-count-sweep 的速查表与陷阱各加一条——只 grep 裸标题会漏
带编号与中文序号的两种写法,实测 5 篇只数得出 3 篇,差点据此把
全仓正确的份数改错。

门禁:逐篇与全量 check_doc_links 除 simulator/SDL2 联接假红外 0 问题;
skill-size rc=0;表格列数与字节体检相对 HEAD 零新增问题。
2026-10-29 21:21:59 +08:00

6.2 KiB
Raw Blame History

layer-deps — 分层方向(Drivers/BSP/** 不得 include App/** 的头)

一句话:它只查一条边——下层(BSP)有没有反向依赖上层(App)。它不管同层乱依赖、 不管传递依赖,也不管"这行 include 是不是真的需要"。

  • 配置:entry: python scripts/check_layer_deps.py、pass_filenames: false、 files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$。
  • 规范出处:docs/架构设计/项目架构总览.md(六层单向:业务 → UI/基础服务 → 中间件 → BSP 驱动 → HAL/RTOS → 硬件)、AGENTS.md 分层边界。

1. 它在查什么

  1. 取 git ls-files 的全部文件(不扫构建产物);
  2. 用 App/** 下的 .h 建**按文件名(basename)**的索引;
  3. 扫 Drivers/BSP/** 的 .c/.h,抓 #include "..." 形式的引号路径;
  4. 命中 App 头 → 记一条边 (引用方文件, App 头);先按完整路径精确匹配,精确命中为空才退回同名候选;
  5. 头在 ALLOW 集合里 → 永久豁免(横切基础设施);否则与 BASELINE 比对:新增即拦,消失即提示收紧。

ALLOW 三条(新增条目必须先回答"它为什么不是上层服务",否则就是给自己开后门): App/log/console_log.h(日志后端,BSP 的 dbg_log.h 本来就建在它上面)、 App/util/sys_clock.h(时间源,日志时间戳的唯一提供者)、App/util/sha256.h(纯算法、无状态)。

BASELINE 现存三条(2026-10-22 清点六条 → 2026-10-25 道B 还清 UI 记录三条 → 现三条;只减不增): NETCFG/net_cfg.c、NETCFG/ftp_cfg.c → App/cfg/cfg_file.h(配置引擎属主是 defaultTask); NET/lftpd/lftpd_io.c → App/fs/vol_lock.h。 已还清:UI/ui_page_runlog.c、UI/ui_page_selftest.c、UI/ui_w_records.h → App/log/sys_log.h (2026-10-25 task/UI-rec-reader:ui_w_records 改注入式 ui_rec_reader_t,契约见 docs/UI设计/UI_数据供给设计说明.md §7.2,绑定在 App/task/ui_task.c 与 simulator/main.c)。 修法是注入 / 传参(上层把数据或回调递进 BSP),不是把上层服务搬进 BSP。

2. 为什么要它

  • 这句话在两份文档里写了很久,但此前没有任何机械检查:BSP 里可以随手 #include "cfg_file.h" 而无人报警。

  • 口径漂移是可测量的证据:2026-09 审查报告记"NETCFG 一处",2026-10-22 实测已长到 8 个文件 9 处。边界的静默腐蚀只能靠门禁止住——先钉基线,再只拦新增。

  • 实测自证(落地当天):临时给 Drivers/BSP/RS485/rs485.c 加一行 #include "cfg_file.h" → 钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉即恢复。挂任何门禁前都该做一次这种注入自证。

3. 触发面

  • pass_filenames: false:每次都全库扫(反向依赖是全局事实,只看暂存文件会漏)。
  • 只看 Drivers/BSP/** 里的 .c/.h;App/** 内部谁依赖谁、UI 是否越层,一概不看。

4. 报错怎么读

[FAIL] 新增反向依赖(BSP -> App):
   - Drivers/BSP/XXX/x.c  ->  App/cfg/cfg_file.h
  • 单独跑:--verbose 列出全部边并标注 豁免/基线/新增;--prune 只报已消失、可删的基线条目。
  • 退出码 0 通过 / 1 有新增 / 2 用法或环境错误。
  • ⚠ pre-commit 会 stash 未暂存改动:改完要先 git add 再跑钩子,否则看到的是旧树。

报错时给的是三条修法(按优先级,脚本失败信息里也写着):

  1. 把上层能力注入给 BSP(函数指针 / 回调)——参照既有先例 net_cfg_set_save_req_cb、 net_cfg_request_apply;
  2. 由上层把数据递进来(UI 页面要读记录,就让上层读好再把缓冲传进来);
  3. 确认它其实是横切叶子能力 → 才谈 ALLOW(见 §7)。

⚠ 本门不是运行时缺陷清单:运行时属主纪律本来就没破(UI 只投递 net_cfg_save_request(), 真正写盘在 defaultTask;模拟器也早在主机侧跑整套逻辑)。它拦的是静态 include 方向这件事本身。

5. 好处

  • 把"分层"从 PPT 变成一条命令的事实;债务总量随时可查(--verbose 就是清单)。
  • 存量与新增分离,不阻塞正常工作,但保证它只会变小。

6. 限制与盲区

  1. 只查直接 include,查不到传递依赖。BSP 的 a.h 里 include 了 App 头,BSP 的 b.c 只 include a.h——b.c 这条边不出现,但 a.h 那条会报。原基线 ui_w_records.h 的 注释专门写着"(组件头,会外溢到 include 链)"(该条 2026-10-25 已还清),就是提醒这种外溢要人来看。
  2. 只认 #include "引号" 形式;#include <App/...> 这类写法不在管辖内(也违反 include 规范, 由 coding-standard 的 CODE-RULE-11 兜)。
  3. 按 basename 匹配:如果 App/ 与别处存在同名头,会按同名候选放宽(先精确后同名), 可能把边记到错误的属主上;反向地,如果 App 头改名而 BSP 用别的路径引到它,判定也会变。
  4. 只管 BSP → App 一个方向。App → UI、UI → 驱动内部 这类越层不在本门范围。
  5. 它不看是否真的用到那个头里的东西:删掉最后一处使用却忘了删 include,边仍然存在, --prune 也就不会提示你收紧基线。
  6. git 不可用时退回 os.walk(跳过 .git/.venv/build)——两条路径的清单可能不同, 在无 git 环境下跑出的"通过"不能当证据。
  7. 绿灯的含义只是"没有新增反向边":现存 3 条存量债务仍然在那里(2026-10-25 还清 3 条)。

7. 豁免与基线

  • BASELINE 与 ALLOW 都写在脚本里(不是外部台账)。只减不增,脚本没有 --write。
  • 两类豁免语义不同,别混:ALLOW = 永久横切能力(判"它是不是叶子");BASELINE = 待还债(判"这条边什么时候拆")。

8. 改这个门禁本身

  • 往 ALLOW 加条目要写理由,并想清楚会不会变成"给上层开后门";能进基线就别进 ALLOW。
  • 还掉一条债就跑 --prune 并删掉那一行;基线只减不增,加行等于放弃约束。