Files
STM32F4-Base/docs/门禁说明/layer-deps.md
T
wandering1 c4edf352fe refactor(UI): 记录页改注入式 reader,layer-deps 基线 6->3(道B task-6)
B-6 分层收紧(UI_数据供给设计说明.md §7.2 契约原样落地):
- ui_w_records:删 #include "sys_log.h",改 ui_rec_reader_t 只读函数表 +
  ui_w_records_bind() 模块级注入(sys_log 读 API 本为全局,函数表共享、
  实例读盘缓存仍各自独享——切页陈旧行防御不变);log_id 换不透明编号
  UI_W_REC_LOG_RUN/SELFTEST;缓冲容量宏本地化 UI_W_REC_TS/DESC_LEN
  (20/29,与 sys_log 契约下限同宽;读失败退化空行防御保留)
- ui_page_runlog / ui_page_selftest:去 sys_log include,实例改不透明编号
- 两端薄包装适配层(编号→syslog_id_t 显式映射,错误码 0/-1/-2 直通不改写):
  App/task/ui_task.c 与 simulator/main.c(无头+窗口两个 init 点),均在
  ui_menu_init 之前 ui_w_records_bind;未绑定窗口组件按空态防御不读野
- scripts/check_layer_deps.py:BASELINE 6→3(--prune 实测恰 3 条 UI 记录
  边消失后写回;剩 NETCFG×2 + lftpd_io 与 UI 无关)——道B 独占收紧口径
- 文档同步:门禁说明 layer-deps.md §1/§6/§9 与 README 计数 6→3;
  UI_数据供给设计说明.md §7.2 加落地注记、§14"计划削减"改"已兑现"
- 首提被 id-check 拦下:注释/文档里自造"批卡编号"(B-6/道B-3)命中
  "未登记前缀"增量拦截——已全数改回 task-6/道B 普通指称(编号不回收、
  禁自造,教训入本提交);id-check 复跑 RC=0(S10 告警为存量非本改动)

验证:
- 模拟器 0 警告;菜单 8 状态 + 内容页 11 页黄金参考逐字节 PASS
  (记录页数据流经 reader 后像素与基线一致,无基线更新提交)
- cppcheck/coding-standard/bsp-err/font-charset/no-mojibake 单跑全绿;
  layer-deps 复跑:6 处反向 include = 豁免 3 / 基线内 3 / 新增 0,
  --prune 显示"基线无需收紧(3 条存量全部仍成立)"
- doc-links --all 的 35 处告警全为本仓检出路径含空格误扫 SDL2 自带 md
  (环境问题、非本改动引入;提交门禁按暂存文件名过滤不受影响)
- @build 0 Error 0 Warning(终树复跑 BUILD_OK);uvprojx 无 UV4 抖动、
  无新文件不涉注册;其余 38 钩全绿(ui-golden/font-charset/coding-standard/
  bsp-err/id-check/cppcheck…单跑复验 RC=0)

增量(A/B 同树 .map,基线 = 合入 c1b425d 后 HEAD):
- Flash:Code +104 B、RO +8 B(两端薄包装代码 + reader 函数表 const)
- RAM:RW +4 B(s_reader 指针)/ ZI +4 B(两页实例 struct 枚举改 uint8
  本应 -8 B,链接段对齐后净 +4 B);RW+ZI 合计 +8 B

gate: only mem-budget/mem-numbers pending lead ledger backfill
- pre-commit run --all-files 实测仅这两条失败(台账/RAM 文档 vs .map 漂移,
  根因即上列 Code +104/RO +8/RW +4/ZI +4),其余 38 钩全绿;按 Lead 裁决
  (task-4 口径"以后照此办理",含 Flash/RAM 漂移同例外)--no-verify 提交,
  合并后请主树回填台账(scripts/ram_baseline.py --write 由 Lead 侧执行)
- 手工自查通过:5 个 BOM 文件 BOM=EF BB BF 齐全、docs/py/main.c 保持无 BOM,
  全部 10 文件 CR=0、U+FFFD=0
- 台账增量供回填:Flash Code +104 B、RO +8 B;RAM RW +4 B、ZI +4 B
2026-10-25 05:21:05 +08:00

6.5 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 分层边界;整改背景见报告 §0.4 的 S-1 行。

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 并删掉那一行;基线只减不增,加行等于放弃约束。

9. 变更记录

日期 变更
2026-10-23 首篇逐项文档(在册 6 条基线逐条列出,含每条的属主背景)
2026-10-25 道B(task/UI-rec-reader)还清 UI 记录 3 条:ui_w_records 注入式 reader(§7.2 契约落地),基线 6 → 3