0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
155 lines
6.8 KiB
Python
155 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""分层方向门禁:Drivers/BSP/** 不得 include App/** 的头(反向依赖增量拦截)。
|
||
|
||
背景(2026-10-22 加)
|
||
----------------
|
||
AGENTS.md 与 docs/架构设计/项目架构总览.md 都写着"六层单向依赖:业务 → UI/基础服务
|
||
→ 中间件 → BSP 驱动 → HAL/RTOS → 硬件",但**此前没有任何机械检查**:Drivers/BSP/ 里
|
||
可以随手 include "cfg_file.h" 而无人报警。后果是可测量的口径漂移——2026-09 审查报告
|
||
记"NETCFG 一处",2026-10-22 实测已长到 8 个文件 9 处。边界的静默腐蚀只能靠门禁止住:
|
||
先把现状钉成基线,然后**只拦新增**。
|
||
|
||
与 scripts/check_ids.py 的"增量拦截"同构:
|
||
· 新出现的 (引用方, App 头) 组合 → FAIL(拦提交)
|
||
· 基线里的组合已消失 → WARN,并提示收紧(基线只减不增,绝没有 --write 往里加)
|
||
· 横切基础设施头走 ALLOW 永久豁免:它们是"任何层都要用的叶子能力",不是上层服务
|
||
|
||
用法
|
||
----
|
||
python scripts/check_layer_deps.py # 门禁模式(pre-commit / CI)
|
||
python scripts/check_layer_deps.py --verbose # 列出全部反向边与判定
|
||
python scripts/check_layer_deps.py --prune # 只报告可以删掉的基线条目
|
||
|
||
退出码:0 = 通过;1 = 发现新增反向依赖;2 = 用法/环境错误
|
||
"""
|
||
|
||
import argparse
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
BSP_PREFIX = "Drivers/BSP/"
|
||
APP_PREFIX = "App/"
|
||
SRC_EXT = (".c", ".h")
|
||
|
||
# 横切基础设施:任何层都可用,允许被 BSP include。
|
||
# 新增条目必须先回答"它为什么不是上层服务"——否则就是给自己开后门。
|
||
ALLOW = {
|
||
"App/log/console_log.h", # 日志后端:BSP 的 dbg_log.h 本来就建在它上面
|
||
"App/util/sys_clock.h", # 时间源:日志时间戳的唯一提供者
|
||
"App/util/sha256.h", # 纯算法工具:无状态、不依赖任何上层服务
|
||
}
|
||
|
||
# 存量基线(2026-10-22 清点 6 条,只减不增)。每条 = (引用方, 被引用的 App 头)。
|
||
# 修法是"注入/传参",不是把上层服务搬进 BSP。
|
||
# 2026-10-25 道B(task/UI-rec-reader)还清 UI 记录 3 条:ui_w_records 改注入式
|
||
# ui_rec_reader_t(契约 UI_数据供给设计说明.md §7.2),runlog/selftest/records.h
|
||
# 不再 include sys_log.h,绑定在 App/task/ui_task.c 与 simulator/main.c。
|
||
BASELINE = {
|
||
("Drivers/BSP/NETCFG/net_cfg.c", "App/cfg/cfg_file.h"), # 配置引擎属主 = defaultTask
|
||
("Drivers/BSP/NETCFG/ftp_cfg.c", "App/cfg/cfg_file.h"), # 同上([ftp] 段注册)
|
||
("Drivers/BSP/NET/lftpd/lftpd_io.c", "App/fs/vol_lock.h"), # 卷锁在 App/fs
|
||
}
|
||
|
||
INC_RE = re.compile(r'^[ \t]*#[ \t]*include[ \t]*"([^"]+)"', re.M)
|
||
|
||
|
||
def tracked_files():
|
||
"""以 git ls-files 为准(不扫构建产物);git 不可用时退回 os.walk。"""
|
||
try:
|
||
out = subprocess.check_output(["git", "ls-files", "-z"], cwd=REPO)
|
||
return [p.decode("utf-8") for p in out.split(b"\0") if p]
|
||
except Exception: # noqa: BLE001
|
||
files = []
|
||
for root, dirs, names in os.walk(REPO):
|
||
dirs[:] = [d for d in dirs if d not in (".git", ".venv", "build")];
|
||
for n in names:
|
||
files.append(os.path.relpath(os.path.join(root, n), REPO).replace(os.sep, "/"))
|
||
return files
|
||
|
||
|
||
def app_header_index(files):
|
||
idx = {}
|
||
for f in files:
|
||
if f.startswith(APP_PREFIX) and f.endswith(".h"):
|
||
idx.setdefault(os.path.basename(f), []).append(f)
|
||
return idx
|
||
|
||
|
||
def scan(files, idx):
|
||
edges = set()
|
||
for f in files:
|
||
if not f.startswith(BSP_PREFIX) or not f.endswith(SRC_EXT):
|
||
continue
|
||
try:
|
||
with open(os.path.join(REPO, f), encoding="utf-8-sig") as fh:
|
||
text = fh.read()
|
||
except (OSError, UnicodeDecodeError) as exc:
|
||
print("[WARN] 读取失败,跳过:%s(%s)" % (f, exc))
|
||
continue
|
||
for m in INC_RE.finditer(text):
|
||
literal = m.group(1)
|
||
owners = idx.get(os.path.basename(literal))
|
||
if not owners:
|
||
continue
|
||
norm = os.path.normpath(literal).replace(os.sep, "/")
|
||
exact = [o for o in owners if norm == o or o.endswith("/" + norm)]
|
||
for o in (exact or owners):
|
||
edges.add((f, o))
|
||
return sorted(edges)
|
||
|
||
|
||
def main(argv=None):
|
||
ap = argparse.ArgumentParser(description="BSP 不得反向 include App 的头(增量拦截)")
|
||
ap.add_argument("--verbose", action="store_true", help="列出全部反向边与判定")
|
||
ap.add_argument("--prune", action="store_true", help="只报告已消失、可从基线删除的条目")
|
||
args = ap.parse_args(argv)
|
||
|
||
files = tracked_files()
|
||
edges = scan(files, app_header_index(files))
|
||
live = set(e for e in edges if e[1] not in ALLOW)
|
||
allowed = [e for e in edges if e[1] in ALLOW]
|
||
new = sorted(live - BASELINE)
|
||
gone = sorted(BASELINE - live)
|
||
|
||
if args.verbose:
|
||
print("== 反向 include 全量边(含豁免)==")
|
||
for src, hdr in edges:
|
||
tag = "豁免" if hdr in ALLOW else ("基线" if (src, hdr) in BASELINE else "新增")
|
||
print(" [%s] %s -> %s" % (tag, src, hdr))
|
||
|
||
if args.prune:
|
||
if not gone:
|
||
print("[OK] 基线无需收紧(%d 条存量全部仍成立)" % len(BASELINE))
|
||
return 0
|
||
print("以下基线条目已不再成立,请从 BASELINE 删除(基线只减不增):")
|
||
for src, hdr in gone:
|
||
print(' ("%s", "%s"),' % (src, hdr))
|
||
return 0
|
||
|
||
print("分层方向:BSP 反向 include App 共 %d 处(豁免 %d / 基线内 %d / 新增 %d)"
|
||
% (len(edges), len(allowed), len(live & BASELINE), len(new)))
|
||
if gone:
|
||
print("[WARN] %d 条基线已消失,建议收紧(跑 --prune 看明细)" % len(gone))
|
||
if new:
|
||
print("")
|
||
print("[FAIL] 新增反向依赖 %d 处——BSP 只依赖相邻下层,不许 include App 层的头:" % len(new))
|
||
for src, hdr in new:
|
||
print(" %s: include \"%s\"" % (src, hdr))
|
||
print("")
|
||
print(" 正确做法(三选一):")
|
||
print(" ① 把上层能力注入给 BSP(函数指针/回调,参照 net_cfg_set_save_req_cb 的先例);")
|
||
print(" ② 由上层把数据取好再传进来(参数/结构体),BSP 不自己去拿;")
|
||
print(" ③ 若它其实是叶子工具(无状态、不依赖上层服务),可加进 ALLOW 并注明理由。")
|
||
print(" 顺手修掉存量也欢迎:跑 --prune 看哪些基线可以删。")
|
||
return 1
|
||
print("[OK] 无新增反向依赖(存量基线只减不增)")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|