fix(import-closure): 核验范围从包归属改为仓库归属——modules/pbls只有app/scripts没有包目录,入口modules/pbls/app/pbls.py的import断裂(from world.api实测断)是真实事故面,只按包算会漏;跨仓库方向按写入文件所属仓库拥有的包算(pbl_common事故形状不变)。单测+3例=14全绿(含无包仓库入口纳入/断裂抓到/存在不误报)
This commit is contained in:
parent
b4c2ecbbe1
commit
2e8d2abec7
@ -108,6 +108,25 @@ def build_package_map(space_dir):
|
||||
return pkgs
|
||||
|
||||
|
||||
def build_repo_map(space_dir):
|
||||
"""modules/<repo> 与 apps/<repo> 的仓库根映射:[(仓库绝对路径, 仓库名)]。
|
||||
|
||||
仓库 ≠ 包:modules/pbls/ 只有 app/ scripts/ 没有包目录,但入口文件
|
||||
modules/pbls/app/pbls.py 的 import 断裂同样是真实事故面(pbls 入口
|
||||
from world.api import 实测断裂)——门禁范围必须按仓库归属算,不能只按包。
|
||||
"""
|
||||
repos = []
|
||||
for group in ("modules", "apps"):
|
||||
base = os.path.join(space_dir, group)
|
||||
if not os.path.isdir(base):
|
||||
continue
|
||||
for repo in sorted(os.listdir(base)):
|
||||
repo_dir = os.path.join(base, repo)
|
||||
if os.path.isdir(repo_dir) and not repo.startswith("."):
|
||||
repos.append((os.path.abspath(repo_dir), repo))
|
||||
return repos
|
||||
|
||||
|
||||
def _resolve_module_file(mod, pkgs):
|
||||
"""绝对模块名 → 文件路径;'EXT'=外部包;None=包内但文件不存在。"""
|
||||
top = mod.split(".")[0]
|
||||
@ -263,35 +282,61 @@ class _ClosureChecker:
|
||||
yield os.path.join(root, fn)
|
||||
|
||||
|
||||
def check_closure_for_delivery(space_dir, written_files, max_problems=30):
|
||||
"""deliver 门禁用:核验范围=本任务写入 .py 所属包 + 全空间引用这些包的文件。
|
||||
def _repo_of(path, repos):
|
||||
"""文件归属仓库名(最长前缀匹配)。"""
|
||||
ap = os.path.abspath(path)
|
||||
best = None
|
||||
for repo_dir, name in repos:
|
||||
if ap.startswith(repo_dir + os.sep):
|
||||
if best is None or len(repo_dir) > len(best[0]):
|
||||
best = (repo_dir, name)
|
||||
return best[1] if best else None
|
||||
|
||||
返回 (problems, involved_pkgs);核验器自身异常返回 (None, set())(逃逸阀,
|
||||
|
||||
def check_closure_for_delivery(space_dir, written_files, max_problems=30):
|
||||
"""deliver 门禁用:核验范围=本任务写入 .py 所属【仓库】+ 全空间引用这些仓库的文件。
|
||||
|
||||
仓库归属而非包归属(2026-09-16 修正):modules/pbls 只有 app/ scripts/
|
||||
没有包目录,但入口 modules/pbls/app/pbls.py 的 `from world.api import`
|
||||
断裂同样是真实事故面——只按包算会漏掉。跨仓库方向:写入文件所属仓库
|
||||
拥有的包(包根在仓库目录下)被别处 import 断裂也拦(pbl_common 事故形状)。
|
||||
|
||||
返回 (problems, involved_names);核验器自身异常返回 (None, set())(逃逸阀,
|
||||
调用方放行并记日志——门禁故障不阻断交付,但必须在日志可见)。
|
||||
"""
|
||||
try:
|
||||
ck = _ClosureChecker(space_dir)
|
||||
if not ck.pkgs:
|
||||
repos = build_repo_map(space_dir)
|
||||
if not repos:
|
||||
return [], set()
|
||||
# 本任务写入 .py 所属包
|
||||
# 本任务写入 .py 所属仓库
|
||||
involved = set()
|
||||
for f in dict.fromkeys(written_files or []):
|
||||
if not f.endswith(".py"):
|
||||
continue
|
||||
po = ck._pkg_of_file(f)
|
||||
if po:
|
||||
involved.add(po[0])
|
||||
rn = _repo_of(f, repos)
|
||||
if rn:
|
||||
involved.add(rn)
|
||||
if not involved:
|
||||
return [], set()
|
||||
# 核验对象:涉及包内全部文件 + 全空间 import 涉及包的文件
|
||||
# 涉及仓库拥有的包(跨仓库引用方向)
|
||||
repo_dirs = {name: rd for rd, name in repos if name in involved}
|
||||
owned_pkgs = set()
|
||||
for pkg_name, pkg_root in ck.pkgs.items():
|
||||
pkg_abs = os.path.abspath(os.path.join(pkg_root, pkg_name))
|
||||
for rn, rd in repo_dirs.items():
|
||||
if pkg_abs.startswith(rd + os.sep):
|
||||
owned_pkgs.add(pkg_name)
|
||||
break
|
||||
# 核验对象:涉及仓库内全部文件 + 全空间 import 涉及包的文件
|
||||
targets = set()
|
||||
for py in ck.all_py_files():
|
||||
po = ck._pkg_of_file(py)
|
||||
if po and po[0] in involved:
|
||||
rn = _repo_of(py, repos)
|
||||
if rn in involved:
|
||||
targets.add(py)
|
||||
continue
|
||||
for _node, mod in ck.iter_imports(py):
|
||||
if mod.split(".")[0] in involved:
|
||||
if mod.split(".")[0] in owned_pkgs:
|
||||
targets.add(py)
|
||||
break
|
||||
problems = []
|
||||
@ -304,7 +349,7 @@ def check_closure_for_delivery(space_dir, written_files, max_problems=30):
|
||||
if k not in seen:
|
||||
seen.add(k)
|
||||
uniq.append(p)
|
||||
return uniq[:max_problems], involved
|
||||
return uniq[:max_problems], involved | owned_pkgs
|
||||
except Exception as e:
|
||||
logger.warning("import closure check failed (escape, deliver 放行): %r", e)
|
||||
return None, set()
|
||||
|
||||
@ -109,5 +109,17 @@ w("modules/pbl_rel/pbl_rel/bad.py", "from . import NOT_THERE\n")
|
||||
probs8, _ = check_closure_for_delivery(ROOT, [os.path.join(ROOT, "modules/pbl_rel/pbl_rel/bad.py")])
|
||||
check("相对导入断裂也抓", any(p["symbol"] == "NOT_THERE" for p in (probs8 or [])), str(probs8))
|
||||
|
||||
# ── 用例 8:仓库归属——无包目录的仓库(modules/pbls 形态)入口断裂也拦 ──
|
||||
w("modules/pbls/app/pbls.py", "from world.api import load_world\nfrom pbl_common.kernel import PblError\n")
|
||||
w("modules/world/world/__init__.py", "def load_world(env=None):\n return True\n")
|
||||
w("modules/world/__init__.py", "from .world import load_world\n")
|
||||
probs9, involved9 = check_closure_for_delivery(
|
||||
ROOT, [os.path.join(ROOT, "modules/pbls/app/pbls.py")])
|
||||
check("无包仓库入口纳入核验", "pbls" in involved9, str(involved9))
|
||||
check("抓到 world.api 断裂", any(p["symbol"] == "world.api" for p in (probs9 or [])),
|
||||
str(probs9))
|
||||
check("pbl_common.kernel 存在不误报", not any("pbl_common.kernel" in str(p) for p in (probs9 or [])),
|
||||
str(probs9))
|
||||
|
||||
print("\nFAILS:", FAILS or "NONE")
|
||||
sys.exit(1 if FAILS else 0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user