118 lines
5.3 KiB
Python
118 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""gdstyle_gate.py — S13 第三方质量规则闸件(game/tests/static_scan.sh 调用)
|
||
|
||
跑 gdstyle(github.com/atelico/gdstyle,Rust 单二进制,版本钉 v0.4.0)质量规则扫描,
|
||
与存量基线 tools/gdstyle_baseline.txt 做差集——只有基线外的新违规才红。
|
||
规则口径见 tools/gdstyle.toml 头注(只开 quality/*+syntax/reserved-identifier,
|
||
format/naming/order 全关)。扫描面=产品码三目录(game/core、game/data、game/presentation,
|
||
与 S9 类型闸同口径——probe/sim/tests 测量面直构惯例不在面)。
|
||
|
||
基线键=文件|规则|消息 三元组:消息含违规对象标识(如 duplicated load 的路径),行漂移稳定
|
||
(gdstyle 的 JSON 输出不含源码行文本,行号键会随编辑成片假红)。基线重生成=人工专用
|
||
(--refresh),禁在闸内自动重生成(防清基线换绿)。
|
||
|
||
二进制:本机固定路径 D:/code/gdstyle/gdstyle.exe(不入库不入包),GDSTYLE_BIN 环境变量覆盖;
|
||
版本与钉定版不符即红——升级工具须重测语法兼容性并人工重生成基线。
|
||
|
||
退出码:0=无基线外新违规;1=有新违规/二进制缺失或版本不符/扫描面为空/输出不可解析;2=用法错。
|
||
用法:python tools/gdstyle_gate.py # 闸判定
|
||
python tools/gdstyle_gate.py --refresh # 人工重生成基线(覆盖旧文件)
|
||
"""
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
CONFIG = ROOT / "tools" / "gdstyle.toml"
|
||
BASELINE = ROOT / "tools" / "gdstyle_baseline.txt"
|
||
SCAN_DIRS = ["game/core", "game/data", "game/presentation"]
|
||
PINNED_VERSION = "0.4.0"
|
||
DEFAULT_BIN = "D:/code/gdstyle/gdstyle.exe"
|
||
MAX_REPORT = 50
|
||
|
||
|
||
def fail(msg: str) -> int:
|
||
print("[gdstyle_gate] FAIL — %s" % msg)
|
||
return 1
|
||
|
||
|
||
def run_gate() -> int:
|
||
if hasattr(sys.stdout, "reconfigure"):
|
||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||
|
||
refresh = "--refresh" in sys.argv[1:]
|
||
if sys.argv[1:] and not refresh:
|
||
print("[gdstyle_gate] 用法: python tools/gdstyle_gate.py [--refresh]")
|
||
return 2
|
||
|
||
binary = os.environ.get("GDSTYLE_BIN", DEFAULT_BIN)
|
||
try:
|
||
ver = subprocess.run([binary, "--version"], capture_output=True, text=True, timeout=30)
|
||
except (FileNotFoundError, OSError):
|
||
return fail("gdstyle 二进制不可用:%s(本机固定路径,GDSTYLE_BIN 可覆盖;"
|
||
"v%s Windows 版见 github.com/atelico/gdstyle releases)" % (binary, PINNED_VERSION))
|
||
if PINNED_VERSION not in (ver.stdout or ""):
|
||
return fail("gdstyle 版本不符:钉定 v%s,实得 %r——升级工具须重测语法兼容性并人工重生成基线"
|
||
% (PINNED_VERSION, (ver.stdout or "").strip()))
|
||
|
||
face = [p for d in SCAN_DIRS for p in Path(ROOT, d).rglob("*.gd")]
|
||
if not face:
|
||
return fail("扫描面为空——%s 下零个 .gd(路径写错导致的静默空转)" % "、".join(SCAN_DIRS))
|
||
|
||
try:
|
||
proc = subprocess.run(
|
||
[binary, "check", "--format", "json", "--no-color", "-c", str(CONFIG)] + SCAN_DIRS,
|
||
capture_output=True, text=True, timeout=300, cwd=str(ROOT), encoding="utf-8",
|
||
)
|
||
except subprocess.TimeoutExpired:
|
||
return fail("gdstyle 扫描超时(300s)")
|
||
try:
|
||
findings = json.loads(proc.stdout or "[]")
|
||
except json.JSONDecodeError:
|
||
return fail("gdstyle 输出不可解析为 JSON(stderr 头部:%r)" % (proc.stderr or "")[:200])
|
||
|
||
keys = set()
|
||
for f in findings:
|
||
if not isinstance(f, dict) or "rule" not in f:
|
||
continue
|
||
file_path = str(f.get("file", "")).replace("\\", "/")
|
||
keys.add("%s|%s|%s" % (file_path, f["rule"], f.get("message", "")))
|
||
|
||
if refresh:
|
||
lines = [
|
||
"# gdstyle 存量基线(S13)——文件|规则|消息 三元组,每行一条;只记基线不记豁免理由。",
|
||
"# 重生成=人工专用:python tools/gdstyle_gate.py --refresh(禁清基线换绿;改规则口径/工具升级须随批重生成)。",
|
||
]
|
||
lines += sorted(keys)
|
||
BASELINE.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||
print("[gdstyle_gate] 基线已重生成:%s 条(%s)" % (len(keys), BASELINE.name))
|
||
return 0
|
||
|
||
base = set()
|
||
if BASELINE.exists():
|
||
for line in BASELINE.read_text(encoding="utf-8").splitlines():
|
||
s = line.strip()
|
||
if s and not s.startswith("#"):
|
||
base.add(s)
|
||
else:
|
||
return fail("基线文件不存在:%s(首次落位跑 python tools/gdstyle_gate.py --refresh,须经用户批准)" % BASELINE.name)
|
||
|
||
new = sorted(keys - base)
|
||
print("[gdstyle_gate] 扫描面 %d 个 .gd;命中 %d 条,基线内 %d 条,基线外新违规 %d 条"
|
||
% (len(face), len(keys), len(keys & base), len(new)))
|
||
if new:
|
||
for k in new[:MAX_REPORT]:
|
||
print(" NEW " + k)
|
||
if len(new) > MAX_REPORT:
|
||
print(" ...(截断,共 %d 条)" % len(new))
|
||
return fail("基线外新违规 %d 条——新码须修到基线口径内,或经用户批准随批重生成基线" % len(new))
|
||
print("[gdstyle_gate] ok")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(run_gate())
|