fix(tpc): _translate_file 以 out_path 文件名作 sha1,修复前缀漂移链接失败 - 原实现重读文件重算 sha1 覆盖 _module_sha1:主流程收集与翻译间隙文件变化时,函数定义前缀与文件名/调用方引用前缀不一致(696d41ca86e0a29a.BuildTopoSortedIndices 被生成成 5536… 前缀)→ ld.lld undefined symbol - 改为以 out_path 文件名(主流程 sha1)为准并告警内容变化,保证链接一致

This commit is contained in:
TermiNexus committed 2026-08-23 14:03:34 +08:00
1 parent 1dc23ffdde
commit 3b0a73db5e
1 file changed
+24 -10
+24 -10
View File
@@ -196,7 +196,7 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
防污染:SymbolTable 是编译期唯一被写的共享对象(import insert/类注册),
批内每个文件翻译前从 symbol_table_bytes 反序列化出干净副本,前文件的
符号写入不会残留到后文件(否则跨模块 struct 类型前缀错位)。
返回 (错误列表[(src_path, err)], batch)。
返回 (错误列表[(src_path, err)], batch, per_file_times[(src_path, elapsed, rel)])。
"""
try:
trans = Phase2Translator(src_root, temp_dir, output_dir,
@@ -235,13 +235,16 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
trans._build_shared_symbol_data()
batch_errors: list = []
per_file_times: list[tuple[str, float, str]] = []
for _i, src_path, out_path, _sha1, msm in batch:
_f0: float = time.time()
try:
# 每文件前重置 SymbolTable 为干净副本(防跨文件污染)
if symbol_table_bytes is not None:
trans._shared_symbol_table = pickle.loads(symbol_table_bytes)
trans._shared_symbol_table.translator = None
trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=msm)
per_file_times.append((src_path, time.time() - _f0, os.path.relpath(src_path, src_root)))
except Exception as e:
# fail-fast(2026-08-18):任意文件编译出错立即终止整个编译进程,
# 不再收集错误继续翻译批内剩余文件(避免浪费大量时间)。主进程
@@ -251,9 +254,9 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
f"{traceback.format_exc()}\n")
sys.stderr.flush()
os._exit(88)
return (batch_errors, batch)
return (batch_errors, batch, per_file_times)
except Exception as e:
return ([(item[1], f'{e}\n{traceback.format_exc()}') for item in batch], batch)
return ([(item[1], f'{e}\n{traceback.format_exc()}') for item in batch], batch, [])
# ==================================================================
@@ -895,13 +898,15 @@ class Phase2Translator:
for future in as_completed(futures):
bidx, rel_first, bcount = futures[future]
try:
batch_errors, batch = future.result()
done_files += len(batch)
batch_errors, batch, per_file_times = future.result()
for src, elapsed, rel in per_file_times:
done_files += 1
_vlog().info(f" [{done_files}/{total_files}] {rel} ({elapsed:.1f}s)")
for src, err in batch_errors:
rel = os.path.relpath(src, self.src_root)
_vlog().error(f" {rel}: {err}")
errors.append((rel, err))
_vlog().info(f" [{done_files}/{total_files}] 批次完成({bcount} 文件, 首个 {rel_first})")
_vlog().info(f" [批次完成] {bcount} 文件, 首个 {rel_first}")
except Exception as e:
# fail-fast:worker 已 os._exit(文件编译出错)或进程池
# 已 broken → 立即取消全部剩余批次并终止编译,不空等。
@@ -1376,8 +1381,17 @@ class Phase2Translator:
with open(src_path, 'r', encoding='utf-8') as f:
code = f.read()
# SHA1 必须在自动导入注入之前计算,以保持与 Phase1 生成的文件名/函数名前缀一致
sha1 = compute_sha1(code)
# SHA1 以主流程 out_path 文件名为准(= 主流程 compute_sha1(parse_python_file 内容)),
# 必须在自动导入注入之前确定。不可在此重读文件重算:主流程收集(need_translate)
# 与本次实际翻译之间若文件内容变化(如编辑器保存),重算会得到不同 sha1,
# 导致函数定义前缀 ≠ 文件名/调用方引用前缀 → 链接 undefined symbol
# (实例:696d41ca86e0a29a.BuildTopoSortedIndices 定义被生成成 5536… 前缀)。
sha1 = os.path.splitext(os.path.basename(out_path))[0]
cur_sha1 = compute_sha1(code)
if cur_sha1 != sha1:
_vlog().warning(
f"模块内容在收集后已变化(文件名sha1={sha1}, 当前sha1={cur_sha1}): {src_path},"
f"仍按主流程 sha1 生成前缀以保证链接一致;下次构建将采用新 sha1", "SHA1")
# 自动导入: 检测 list 容器使用,自动注入 import _list
code = self._inject_auto_imports(code, src_path)
@@ -1594,7 +1608,7 @@ class Phase2Translator:
f.write(text_content)
self._save_deps(sha1, ModuleSha1Map)
_vlog().info(f" -> {os.path.basename(out_path)} {os.path.relpath(src_path, self.src_root) if src_path else ''} ({time.time() - _t0:.1f}s)")
# 进度日志由调用方统一打印(主进程按文件粒度 [x/total]),此处不再重复输出。
else:
stub_path = self.stub_files.get(sha1)
if stub_path and os.path.exists(stub_path):
@@ -1605,7 +1619,7 @@ class Phase2Translator:
stub_content = f.read()
with open(out_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(stub_content)
_vlog().info(f" -> {os.path.basename(out_path)} {os.path.relpath(src_path, self.src_root) if src_path else ''} (仅声明, {time.time() - _t0:.1f}s)")
# 进度日志由调用方统一打印。
def _register_package_reexports(self, trans, init_pyi_path: str, package_name: str) -> None:
"""解析 __init__.pyi 中的 from .xxx import yyy 或 from __xxx import yyy,在包级别注册导出符号"""