Files
PlantAI-rag-new/tooling/compile/tools/generate_stubs.py
jerryandClaude Fable 5 b813cff005 P1/M2: tooling/compile/compile_qa.sh(参数化 + normalized_release 补丁)
- 收编工具链遗漏主项目 08-29 新增 src/rag/normalized_release.py(backend_guard 依赖)
- 编译树 45 模块全部 .so,audit 0 错误,7922 健康检查金标过
- 修复:compile 目录相对依赖 tools/(elf_visibility.py)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 13:26:18 +08:00

89 lines
2.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
为 Cython 编译产物生成 .py stub 文件。
每个 stub 同时支持:
1. 子进程调用 (python apps/cli/run_xxx.py --args)
-> __name__ == "__main__" 分支加载 .so 并调用 main()
2. 模块导入 (from apps.cli.run_xxx import SomeClass)
-> 模块级 __getattr__ 懒加载 .so ,透明转发属性访问
用法ᅳ
python generate_stubs.py <output_dir> [<output_dir2> ...]
"""
import sys
from pathlib import Path
STUB_TEMPLATE = (
"# Auto-generated stub — delegates to the compiled Cython extension.\n"
"# Supports both subprocess execution and module import.\n"
"import importlib.util as _ilu\n"
"import sys as _sys\n"
"from pathlib import Path as _P\n"
"\n"
"_stem = _P(__file__).stem\n"
"_so = next(_P(__file__).parent.glob(_stem + '.*.so'), None)\n"
"_cached_mod = None\n"
"\n"
"\n"
"def _load_compiled():\n"
" global _cached_mod\n"
" if _cached_mod is None:\n"
" if _so is None:\n"
" raise FileNotFoundError(\n"
" f'Compiled module not found for {__file__}'\n"
" )\n"
" _spec = _ilu.spec_from_file_location(_stem, _so)\n"
" _cached_mod = _ilu.module_from_spec(_spec)\n"
" _spec.loader.exec_module(_cached_mod)\n"
" return _cached_mod\n"
"\n"
"\n"
"def __getattr__(name: str):\n"
" \"\"\"Transparent proxy: forward attribute access to the compiled module.\"\"\"\n"
" return getattr(_load_compiled(), name)\n"
"\n"
"\n"
"if __name__ == '__main__':\n"
" _mod = _load_compiled()\n"
" _sys.exit(_mod.main() if hasattr(_mod, 'main') else 0)\n"
)
def generate_stubs(output_dir: Path) -> int:
created = 0
for so_path in sorted(output_dir.rglob("*.so")):
stem = so_path.name.split(".")[0]
if stem == "__init__":
continue
py_path = so_path.parent / f"{stem}.py"
if py_path.exists():
continue
py_path.write_text(STUB_TEMPLATE, encoding="utf-8")
print(f" stub: {py_path.relative_to(output_dir)}")
created += 1
return created
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <output_dir> [<output_dir2> ...]", file=sys.stderr)
sys.exit(1)
total = 0
for arg in sys.argv[1:]:
d = Path(arg)
if not d.is_dir():
print(f"目录不存在,跳过: {d}", file=sys.stderr)
continue
print(f"[generate_stubs] 扫描: {d}")
n = generate_stubs(d)
print(f"[generate_stubs] 已生成 {n} 个 stub: {d}")
total += n
print(f"[generate_stubs] 合计: {total} 个 stub")
if __name__ == "__main__":
main()