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:
1 parent
1dc23ffdde
commit
3b0a73db5e
1 file changed
+24
-10
@@ -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,在包级别注册导出符号"""
|
||||
|
||||
Reference in new issue
Block a user