56 lines
2.4 KiB
Python
56 lines
2.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
代码指纹 (零依赖, 不连库, 不 import 业务模块)
|
|
================================================
|
|
运行: python scripts/code_fingerprint.py 只打印一行 12 位短哈希
|
|
|
|
为什么需要它
|
|
------------
|
|
**源码是打进镜像的**, `docker compose run --rm pms-web python scripts/xxx.py` 跑的是
|
|
**镜像里的那份**, 不是工作树里刚 `git pull` 下来的那份。于是有一条特别难发现的路径:
|
|
|
|
git pull → make test → ALL SUITES PASS
|
|
|
|
这个 PASS 是**旧代码的 PASS**。它读起来像"新改的代码验过了", 实际上新代码一行都没跑过 ——
|
|
连单测清单都还是旧的 (新增的一批测试文件根本不在镜像里, `run_tests.py` 只会打印
|
|
"跳过 xxx (文件不存在)" 然后照样 ALL SUITES PASS)。2026-07-31 实机就是这样:
|
|
`make t-pre` 的返回里少了新加的字段, 才反推出容器跑的是旧镜像。
|
|
|
|
这跟本项目一直在治的是同一种病 —— **一个不成立的结论长得像成立**。所以给它一个指纹:
|
|
`make test` 会把容器里的指纹和工作树的指纹对一遍, 对不上就喊, 而不是让那个 PASS 蒙人。
|
|
|
|
指纹只覆盖 .py 源码 (app/ scripts/ config/)。改 Makefile、compose、DDL 不影响它 ——
|
|
那几样不进 Python 运行时, 另有各自的生效路径。
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import sys
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
DIRS = ("app", "scripts", "config")
|
|
|
|
|
|
def fingerprint(root: str = ROOT) -> str:
|
|
h = hashlib.sha256()
|
|
files = []
|
|
for d in DIRS:
|
|
base = os.path.join(root, d)
|
|
for dirpath, dirnames, filenames in os.walk(base):
|
|
# __pycache__ 里是编译产物, 跟着源码走, 不该进指纹
|
|
dirnames[:] = [x for x in dirnames if x != "__pycache__"]
|
|
for fn in filenames:
|
|
if fn.endswith(".py"):
|
|
files.append(os.path.join(dirpath, fn))
|
|
for p in sorted(files, key=lambda x: os.path.relpath(x, root)):
|
|
rel = os.path.relpath(p, root).replace(os.sep, "/")
|
|
h.update(rel.encode("utf-8"))
|
|
h.update(b"\0")
|
|
with open(p, "rb") as f:
|
|
h.update(hashlib.sha256(f.read()).digest())
|
|
return h.hexdigest()[:12]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 只打印指纹本身, 一行, 不带任何前后缀 —— 调用方要拿它做字符串比较
|
|
sys.stdout.write(fingerprint() + "\n")
|