From 3b0a73db5e6e8a1a63358d9ea9a3d3599b3fb36c Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 23 Aug 2026 14:03:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=5Ftranslate=5Ffile=20=E4=BB=A5=20o?= =?UTF-8?q?ut=5Fpath=20=E6=96=87=E4=BB=B6=E5=90=8D=E4=BD=9C=20sha1?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=BC=80=E6=BC=82=E7=A7=BB?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E5=A4=B1=E8=B4=A5=20-=20=E5=8E=9F=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E9=87=8D=E8=AF=BB=E6=96=87=E4=BB=B6=E9=87=8D=E7=AE=97?= =?UTF-8?q?=20sha1=20=E8=A6=86=E7=9B=96=20=5Fmodule=5Fsha1=EF=BC=9A?= =?UTF-8?q?=E4=B8=BB=E6=B5=81=E7=A8=8B=E6=94=B6=E9=9B=86=E4=B8=8E=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E9=97=B4=E9=9A=99=E6=96=87=E4=BB=B6=E5=8F=98=E5=8C=96?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=87=BD=E6=95=B0=E5=AE=9A=E4=B9=89=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E4=B8=8E=E6=96=87=E4=BB=B6=E5=90=8D/=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E5=BC=95=E7=94=A8=E5=89=8D=E7=BC=80=E4=B8=8D?= =?UTF-8?q?=E4=B8=80=E8=87=B4=EF=BC=88696d41ca86e0a29a.BuildTopoSortedIndi?= =?UTF-8?q?ces=20=E8=A2=AB=E7=94=9F=E6=88=90=E6=88=90=205536=E2=80=A6=20?= =?UTF-8?q?=E5=89=8D=E7=BC=80=EF=BC=89=E2=86=92=20ld.lld=20undefined=20sym?= =?UTF-8?q?bol=20-=20=E6=94=B9=E4=B8=BA=E4=BB=A5=20out=5Fpath=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=EF=BC=88=E4=B8=BB=E6=B5=81=E7=A8=8B=20sha1?= =?UTF-8?q?=EF=BC=89=E4=B8=BA=E5=87=86=E5=B9=B6=E5=91=8A=E8=AD=A6=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=8F=98=E5=8C=96=EF=BC=8C=E4=BF=9D=E8=AF=81=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Projectrans/Phase2Translator.py | 34 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index 778bdfe..5d45618 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -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,在包级别注册导出符号"""