Files
STM32F4-Base/docs/门禁说明/generated-fresh.md
T
wandering1 41c277a8ae docs(门禁): 抢跑等价门禁自查表落地——把「谁漏跑一次,这道检查这次就不存在」写死
今天三次 --no-verify 共放过三类真问题:5 条 CODE-RULE-17(.c/.h 配对)、2 个缺尾换行加
1 个漏提文件(HEAD 一度是「代码已整行独占、旁边 runner 文档串还写着无锁直发」)、
以及共享尾段 +80 B 把 iec104Task 压到 1,024 B ISR 底线之下。根因不是「不该抢跑」,
而是**抢跑时只跑了部分等价门禁**——doc-links、id-check、字节卫生都跑了,唯独漏了
stack_report,而它恰恰是数字与安全边界那一类。

§2.4 三组清单(任何人可用,不是 Lead 特权说明):A 抢跑前必跑(stack_report、
ram_report、ram_baseline --gate 含「跳过不等于有余量」、coding-standard、bom-drift
含「别用有 BOM 就是错的外行判据」、mojibake、受影响模块单测、test-coverage、id-check、
doc-links、generated-fresh 必须 .venv、ci_local 并注明它**不含** stack_report 与
ram_baseline 所以不能当替身);B 手跑代替不了真提交的三类(改写型钩子、要靠 stash
才能正确归因的、只有真提交才碰到的 gitlint 与正文每行 80 列);C 抢跑之后:
git show --stat 对交付清单 + git status 里不该再出现声明过的文件(含 ??,漏提 runner
就是这么发现的),并强制在正文点名「哪些没跑、为什么、谁在飞」。

一条结构性说明:stack_report 与 ram_baseline **不在钩子清单里**,所以「人漏跑一次 =
这道检查这次不存在」;要机械化得先解决「磁盘 .htm/.map 属于最后一次构建的那个人」
——与 stash 覆盖面那条同源,不是加一根钩子就完事。另写明 --all-files **也不 stash**,
会让改写型钩子当场改全库,只能在静默窗口跑、跑完复核。

文档里的命令必须能直接跑:只读等价那条一行式刻意纯 ASCII——Windows PowerShell 5.1 按
ANSI 读无 BOM 的 .ps1,带中文标签的版本会碎成 SyntaxError(作者踩过并复现),改完后
从文档里抽出来重跑验证:checked 40 files (incl untracked),problems 0。

同篇顺手核掉的同源不同源(可单独回滚):§6 钩子数 40 → 50(口径 = YAML 解析条目;
直接 grep 声明行会多算一条被注释掉的候选 = 51)、主机侧单测「16 个合一篇」→ 22 根
专属钩子 + 18 套走 test-coverage 总闸;两处跨行断行的表格行修好,全文 8 个表格块
列数一致(脚本核过)。generated-fresh.md §4 补上解释器解析口径与 [ENV] 输出。
2026-10-26 23:42:41 +08:00

18 KiB
Raw Blame History

generated-fresh — 生成器幂等性(重跑生成物 diff 必须为空)

一句话:生成物与它的生成器不许分家——改完生成器(或它的输入)没重跑, 下一个碰巧重跑的人就会看到一段「不是我改的」diff。本钩子把「重跑一遍看有没有变化」机械化。 规范正文(为什么生成物必须与生成器同笔提交)→ docs/文档规范.md §4.4。

  • 配置:entry: python scripts/check_generated_fresh.py,pass_filenames: false, files: 见 .pre-commit-config.yaml 第 16) 段注释(覆盖生成器 / 输入 / 生成物 / 脚本自身 / 本配置)。
  • 数据源:判定表与存量基线写在脚本里(GENS / BASELINE),没有第三方数据文件—— 生成器与产物就是一张三行表,做成 JSON 只会多一处会过期的地方。

1. 它在查什么

对每个在册生成器做同一件事:把声明的输入按相对路径拷进系统临时目录 → 把生成器脚本 也拷进去 → 在临时目录里重跑 → 遍历临时目录里除 __pycache__ 外的每个文件,与工作区同路径 逐字节比对:

结果 含义 级别
DIFF 工作区有、内容不同 → 生成物比生成器旧 错误(在基线内则降级 WARN)
NEW 工作区没有该文件 → 重跑会凭空多出文件(漏注册 / 漏提交) 错误
生成器 rc≠0 生成器自己报错(多半是输入与产物对不上) 错误
生成器超时(300 s) 生成器卡死 错误
环境缺失(只有字体链的外部工具:node / lv_font_conv / 系统字体) 该台机器造不出这份产物 SKIP(--strict-env 升错误)
挑不出能跑字体链的解释器(仓库 .venv 没装 fontTools 或已坏) 判定用不了,不是产物漂了 错误(退出码 2,附逐个候选的探针表与修法;刻意不 SKIP)

在册生成器(GENS,加一个生成器就在此登记一处):

生成器 生成物 一并比对的副作用
tools/menu_gen/gen_menu.py Drivers/BSP/UI/ui_menu_tab.h、Drivers/BSP/UI/ui_menu_tab.c、Drivers/BSP/UI/ui_page_registry.h、Drivers/BSP/UI/ui_page_registry.c 它还会回写 MDK-ARM/STM32F407-Demo.uvprojx 与 simulator/CMakeLists.txt,两份注册表一起比
tools/font_gen/extract_cn.py ui_cn_chars.txt 唯一不按裸字节判的一项,见 §7
tools/font_gen/make_font.py Drivers/BSP/UI/ui_font_cn_12.c 需 node + 全局 lv_font_conv + 系统宋体,缺则 SKIP
tools/arch_diagram/gen_*.py(逐个) docs/images/*.svg 每个生成器各开一个临时目录,互不污染

判据是逐字节相等,不依赖文件头注释里写没写「自动生成」。生成器一律用 __file__ 的三级 父目录定位仓库根,所以拷进临时目录后它只会写临时目录——仓库一个字节都不写:不需要还原 现场,也不会 stash 别人在飞的改动(多人共用一棵工作树也能安全跑)。

2. 为什么要它

  • 实证一(本卡的由来):0f7b5a87(编号迁移)改了 tools/menu_gen/gen_menu.py 的模板 文字却没重跑生成物 → HEAD 里 Drivers/BSP/UI/ui_menu_tab.h 与模板不一致,后来是别人重跑 生成器才被顺带纠正(属主确认以生成器输出为准)。
  • 实证二(2026-10-26 接线前实测扫出):5 张 docs/images/*.svg 与各自生成器不一致, 全部是文档挪目录后 scripts/rename_ref.py 改写了 tools/ 里的引用路径、产物没跟着重跑 (docs/images/项目架构总览.svg / docs/images/配置文件分层架构.svg / docs/images/RemoDispBus帧格式.svg / docs/images/物理分区布局.svg / docs/images/存储栈分层.svg;逐条差异原因见脚本内 BASELINE)。同一天 14 个配图生成器里 9 个是干净的——所以不是「所有配图都别信」,而是只有改过脚本的那批会漂,恰好是没人会去 看 diff 的那批。
  • 事故形态值得记清楚:改生成器的人看不到任何 diff(脚本改动编译、单测、像素回归全过, 因为产物没变),代价全部转嫁给下一个碰巧重跑生成器的人——他要么把这段陌生改动连带提交, 要么 revert 掉别人真正的修复。两种处置都错,而门禁能让他不必遇到这个选择。
  • 本钩子为什么刻意「只读」(同一批接线过程中复核到的另一件事,一并写下来,后来人需要知道): pre-commit 的 run 子命令在不带 --all-files 也不带 --files 时会进入 staged_files_only 上下文 (安装目录里 commands/run.py 的判定就是 stash = not args.all_files and not args.files;进上下文后先 起一个 backup patch,钩子跑完再用 git checkout -- . 加 git apply 还原)。两个后果: ① 假红——钩子看到的是「没有你未暂存改动」的那一份树:拆笔提交时(工作区还留着下一笔要提的 改动)生成物与它的输入会被拆成两代,任何「重跑比对」类判定都会报出一段本不存在的漂移; ② 危险——那本质上是一次全树 checkout 回滚,靠补丁恢复,别人在同一棵树上在飞的改动经这一趟 就不再是原样(与本仓「绝不用 git checkout 丢弃未提交工作」是同一条铁律)。 所以本钩子的实现约束是硬性的:不写仓库、不改受管区、比对基准取工作区,代价是它自己永远不需要 还原现场,也因此可以在多人共用的工作树上随便跑。通用机制正文 → docs/代码规范/pre-commit使用说明.md §2「stash 陷阱」。

3. 触发面

files: 覆盖四类(pre-commit 4.x 已移除 always: true,没有兜底开关,必须写全): 任何生成器 tools/ 下全部、生成器的输入(Drivers/BSP/UI/ 源码、App/task/ui_task.c 与 App/task/ui_task.h、ui_menu_tree.txt、ui_cn_chars.txt)、生成物(docs/images/*.svg + 两份注册表)、以及 scripts/check_generated_fresh.py 与 .pre-commit-config.yaml 自身 (不覆盖自身 = 谁改坏这根闸的 files: 它就再也不跑且无人察觉,同 registration-check 的口径)。 扫描范围恒为工作区全量(pass_filenames: false:重跑一遍与「你改了哪个文件」无关)。

4. 报错怎么读

[FAIL] 生成器幂等性 ... menu-gen
      ---- 漂移文件 Drivers/BSP/UI/ui_menu_tab.h(生成器 tools/menu_gen/gen_menu.py)
      @@ -58 +58 @@
      -bool ui_menu_tab_has_children(uint8_t node);
        修法:重跑该生成器,然后把生成物与生成器**同一笔**提交
  • 多人同树时怎么验证接线(本会话实测口径):带 --files 才不触发 stash —— & '.venv\Scripts\python.exe' -m pre_commit run generated-fresh --files .pre-commit-config.yaml; 只核对配置文件本身用 -m pre_commit validate-config .pre-commit-config.yaml。别跑不带 --files 也不带 --all-files 的裸 run(会 stash 别人在飞的改动,见 §2 末条)。
  • 单独跑:& '.venv\Scripts\python.exe' scripts/check_generated_fresh.py (--verbose 打差异行、--only menu-gen,cn-font 只跑指定项、--list 只列声明、 --prune 打基线台账、--selftest 自证有牙、--keep-temp 保留临时目录排查)。
  • 钩子 entry 为什么不能只是裸 python(2026-10-26 真实提交实测,同日修):本钩子的 entry 与其它本地 钩子一样写 python scripts/check_generated_fresh.py,而 pre-commit 在提交环境里把 python 解析成了 机器级解释器,那次提交日志原文: [FAIL] cn-font tools/font_gen/make_font.py 退出码 1:ERROR: No module named fontTools, 解释器行打印的是 C:/Python314/python.EXE。纯标准库的 menu-gen / cn-chars 照旧绿——这是一根 只在字库链上假红的闸,换任何一台没把 .venv 排在 PATH 前面的机器或 runner 都会重犯(同族第二次 发作见 scripts/ci_local.py 文件头 2026-10-11 记的「L0 全灭」)。修法不是把 entry 写死成 .venv 里的路径(那正是 2026-10-11 那次把候选收窄、等于把门禁绑死在一台机器上的错),而是脚本自己按优先级 挑解释器并逐个探针校验:显式 DTU_GEN_PYTHON > 当前解释器 > 仓库 .venv > PATH 上探到的机器级, 且必须真能 import fontTools 才算数(装了 pre-commit 不等于装了 fontTools;Microsoft Store 那个 python 占位程序连探针都跑不起来)。整轮生成器统一用同一个解释器跑——换解释器可能换输出格式, 逐字节比对要的是同一把尺子。一个都挑不出来时打 [ENV] + 候选探针表 + 修 var / 重建 .venv 的指引, 并退出码 2,不静默 SKIP。钩子自身跑在坏解释器上时会先打一行 [WARN] 说明改用哪个(别把这条 WARN 读成钩子坏了)。
  • [BASELINE] = 存量在册(§7),不拦提交;[TIGHTEN] = 这条已还清、可以删基线了。
  • [SKIP] 只可能出现在字体链:那台机器造不出 Drivers/BSP/UI/ui_font_cn_12.c,等于没验, 别读成「验过了」。
  • ⚠ 别拿 --only 的输出判断「有没有还清存量」:[TIGHTEN] 提示只在未指定 --only 时打印。

5. 好处

  • 把「生成物与生成器同一笔提交」从口头约定 + 事后靠别人重跑纠正变成提交期机械判定;
  • 顺带把双端注册表纳入同一判定:gen_menu.py 会把绑定页写进 uvprojx 与 CMakeLists。 漏注册在 registration-check 那一头是「文件存在但没注册」,在这一头是「重跑会改注册表」, 两侧独立兜同一件事——不同判据、同一事故。
  • --selftest 自带两项牙(都不往仓库注入假改动):① 临时副本里改一行模板 → 必须报 DIFF;② 拿一个 「跑得起但看不见第三方库」的假解释器(当前解释器加 -S 关掉 site-packages)去要字体链 → 必须给出 可诊断的 [ENV] 报错与修法(退出码 2),而不是崩在 ModuleNotFoundError 上。第 ② 项就是上面那次 假红的回归用例。

6. 限制与盲区(请当正文读)

  1. 只覆盖 GENS 里登记过的生成器。没登记的(tools/refgraph/、 tools/netlist_semantics/,以及 scripts/gen_task_table.py 的受管表区——后者已有 task-table 钩子)不在闸内。新增生成器不登记 = 本钩子沉默,这是它最大的盲区。

  2. 输入靠声明(每项一份 seed 清单)。漏声明不会假绿——生成器在临时目录里读不到东西会直接 报错(FAIL),但它以「生成器报错」的形态出现,容易被误读成生成器坏了。

  3. 判定基准是工作区而不是 git 索引:别人在飞的改动会被当成输入。这是刻意的(工作树才是 事实),但意味着 UI 源码改到一半时本钩子可能报「字库比源码旧」——那是真没重跑,只是 还没轮到你重跑;正确处置是跑完生成器再提交,不是跳过。

  4. 逐字节相等不校验「生成物内容对不对」:模板本身写错、重跑照抄,本钩子全绿。

  5. 字体链 SKIP 见 §4:换机或 runner 没装 lv_font_conv 时,那一项目前是空的。

  6. 基线只覆盖 docs/images/ 下的 SVG。业务生成物(菜单表 / 页面注册表 / 字库 / 两份注册表) 一条基线都没有——这里没有豁免面可钻。

  7. 解释器探针每轮只跑一次并缓存。DTU_GEN_PYTHON 指向一个装了 fontTools 但版本古怪的解释器时本钩子 照用它——真因若出在解释器换输出格式,症状是所有生成物一起漂,先看输出开头那行解释器再查生成器。

  8. 未跟踪文件不在 stash 覆盖面里 ⇒ 别人的半成品会拖红你的提交(2026-10-26 实测,本钩子最容易 误伤的一条,机制对所有钩子通用)。pre-commit 的 stash 只清已跟踪文件的未暂存改动 (staged_files_only._unstaged_changes_cleared():git diff-index 取补丁 → git checkout -- . → 跑完 git apply),未跟踪文件压根不在 diff-index 里,git add -N 也只是撤下索引、文件本体 还在盘上。本钩子按设计以工作区为基准(§6 第 3 条),于是只要树里躺着尚未入库的 UI 页面 实例,任何碰 Drivers/BSP/UI/ 的提交都会看到下面这段红:

    [FAIL] menu-gen  tools/menu_gen/gen_menu.py 退出码 1:ERROR: 以下页面实例未被任何叶子 #page
           引用(写了却从不生效的死代码): ui_page_clock.c, ui_page_reset.c
    [GAP]  清单缺 1 字(下次重跑会加进来 → 「不是我改的」diff): 元
    [FAIL] cn-chars  txt 与生成链不等(源码在用 172 ∪ KEEP_EXTRA 39 = 184,清单 183)
    

    红的内容是对的——那两个页面实例确实是「写了却从不生效的死代码」,那个字确实该进清单; 但账不该记在这笔提交上:提交的是别人的批次,红的是第三方的半成品。正解只有两条: ① 让 owner 先把叶子绑进 ui_menu_tree.txt、重跑 gen_menu.py 与 font-regen,再按批次排序提交; ② 确实要抢跑就 --no-verify,并在正文点名是哪几个未跟踪文件拖的红。 不要为了绿去放宽 files: 或把 Drivers/BSP/UI/ 摘出触发面——那等于扔掉这条闸最值钱的保证 (「新页面实例必须与生成物同笔提交」正是目前唯一能机械拦住这种死代码的地方)。机制正文 → docs/代码规范/pre-commit使用说明.md §2.2「stash 覆盖面」行。

7. 豁免与基线(只减不增)

  • 存量:脚本内 BASELINE,形状 = (产物路径, 生成器路径, 漂移成因一句话),接线当天钉死 5 条 (§2 实证二)。还清一条删整条:默认运行会打印 [TIGHTEN] 点名「已还清仍在册」, --prune 打印整张台账。同构 layer-deps 的 BASELINE 与 registration-check 的 KNOWN_GAPS: 没有 --write 一类往基线加条目的开关,加一条要写清理由并过评审。
  • 唯一例外判定:ui_cn_chars.txt。按 font-regen 约定它 = 源码在用 ∪ KEEP_EXTRA (历史落盘记录用字,逐条理由只在 scripts/check_font_charset.py 一份),裸字节比对必然不等。 故该项改判「txt 恰等于 重跑结果 ∪ KEEP_EXTRA」,两向都不许差:少一字 = 下次重跑会加进 txt(就是「不是我改的」diff);多一字 = 下次重跑会把它洗掉(字形不可再生,历史 /log/selftest.log 那批记录渲染空白且无日志无报错)。清单 importlib 直载,不留第二份。 与 font-charset 的分工要分清:那条查白框方向的覆盖(源码在用 ⊆ txt ⊆ 字体 cmap), 本条查 txt 与生成链是否同一份(多一个未登记的字也 FAIL,那条只 WARN)。
  • 实测基线(2026-10-26 接线):menu-gen 70 个文件逐字节一致 / cn-chars 源码在用 165 ∪ KEEP_EXTRA 39 = 183 字、两向无差 / cn-font 逐字节一致(59,138 B)/ docs-svg 14 个生成器 = 9 一致 + 5 存量在册 → 退出码 0。

8. 改这个门禁本身

  • 加生成器:GENS 加一项(声明 seed 输入 + mkdir 输出目录)→ 同笔更新本页 §1 的表与 docs/文档规范.md §4.4 的在册表;先跑 --only <新id> --verbose 确认干净再合。
  • 声明 seed 要按生成器实际读了什么写,不是按「我觉得它是输入」:gen_menu.py 会 os.listdir(Drivers/BSP/UI) 找页面实例,所以整个 UI 目录 + 两份注册表必须一起拷。
  • 临时目录一律 tempfile.mkdtemp():任何「顺手写回仓库」的改动都不要加——本钩子的全部安全性 来自它不写仓库(写了就要还原,还原就要面对别人在飞的改动)。
  • 自测:--selftest。探针字符串若在生成器里被改名,自测会直接报「探针已不存在」,不会假过。

9. 变更记录

日期 变更
2026-10-26 补 §6 第 8 条(本窗口最值钱的一条机制):pre-commit 的 stash 只覆盖已跟踪文件的未暂存改动(git diff-index 取补丁 → git checkout -- .),未跟踪文件不进 stash、git add -N 也只撤索引 —— 于是别人的半成品(实测 ui_page_clock.c / ui_page_reset.c 未绑 #page)会把任何人碰 UI 的提交拖红;红的内容对、账不该记在别人身上。附现场日志与两条正解,明确禁止为绿放宽 files:。通用机制同步写进 docs/代码规范/pre-commit使用说明.md §2.2
2026-10-26 补记:§2 与 §4 补上「entry 为什么不能只是裸 python」的同一条实测线(解释器解析 + 探针校验),与本页 §9 上一行同批
2026-10-26 同日修假红:entry 的裸 python 在提交环境里被解到机器级解释器(无 fontTools),字体链
一项报 No module named fontTools——跑了一次真实提交才暴露。现由脚本按 ci_local 的优先级解析生成器
解释器并逐个探针校验(新增显式变量 DTU_GEN_PYTHON),挑不出来打 [ENV] + 候选表 + 修法、退出码 2 不
SKIP;--selftest 加「假解释器必须可诊断」第二项。判据与基线未变
2026-10-26 首篇逐项文档(task-18 接线当日):实证 = 0f7b5a87 改模板没重跑 + 接线前实测 5 张 docs/images/*.svg 已漂(钉成只减不增基线);在册 4 项 = gen_menu / extract_cn / make_font / tools/arch_diagram 全量配图;接线实跑 menu-gen 70 文件一致、字库链两向无差、14 个配图生成器 9 干净 5 在册 → PASS