import os import sys import shutil from lib.Projectrans.Phase1Generator import Phase1Generator from lib.Projectrans.Phase2Translator import Phase2Translator def save_sha1_map(temp_dir: str, sha1_map: dict[str, str]): """将 SHA1 映射表保存到文件""" map_path = os.path.join(temp_dir, '_sha1_map.txt') with open(map_path, 'w', encoding='utf-8') as f: for sha1, rel in sorted(sha1_map.items()): f.write(f"{sha1}:{rel}\n") def Load_sha1_map(temp_dir: str) -> dict[str, str]: """从文件加载 SHA1 映射表""" map_path = os.path.join(temp_dir, '_sha1_map.txt') result = {} if os.path.exists(map_path): with open(map_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if ':' in line: sha1, rel = line.split(':', 1) result[sha1] = rel return result def Load_project_config(project_file: str) -> dict: """从 project.json 加载配置""" import json with open(project_file, 'r', encoding='utf-8') as f: return json.load(f) def resolve_paths(config: dict, project_file: str) -> dict: """将配置中的相对路径转换为绝对路径""" project_dir = os.path.dirname(os.path.abspath(project_file)) result = dict(config) def resolve(p): if p is None: return None if os.path.isabs(p): return p return os.path.normpath(os.path.join(project_dir, p)) if 'source_dir' in result: result['source_dir'] = resolve(result.get('source_dir')) elif 'sources' in result: resolved_sources = [] for src in result['sources']: if '*' in src or '?' in src: import glob as glob_module for matched in glob_module.glob(os.path.join(project_dir, src), recursive=True): if os.path.isfile(matched): resolved_sources.append(matched) else: resolved_sources.append(resolve(src)) result['sources'] = [s for s in resolved_sources if os.path.isfile(s)] result['temp_dir'] = resolve(config.get('temp_dir')) result['output_dir'] = resolve(config.get('output_dir')) if 'includes' in result: result['includes'] = [resolve(inc) for inc in result['includes']] result['_project_dir'] = project_dir return result def main(): import argparse parser = argparse.ArgumentParser( description='TransPyC 工程翻译器 - 基于 project.json 的两阶段翻译', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' 示例: # 从 project.json 读取配置并执行(默认查找当前目录) python ProjectTrans.py # 指定 project.json 文件 python ProjectTrans.py --project ./TestProject/project.json # 使用命令行参数覆盖 project.json 配置 python ProjectTrans.py --src ./src --temp ./decl --output ./out --phase all # 仅运行阶段一 python ProjectTrans.py --phase 1 # 仅运行阶段二 python ProjectTrans.py --phase 2 # 清理临时目录 python ProjectTrans.py --clean ''' ) parser.add_argument('--project', help='project.json 路径(默认查找当前目录)') parser.add_argument('--src', help='源文件目录(覆盖 project.json)') parser.add_argument('--temp', help='声明接口临时目录(覆盖 project.json)') parser.add_argument('--output', help='输出目录(覆盖 project.json)') parser.add_argument('--phase', choices=['1', '2', 'all'], help='阶段: 1=生成声明, 2=翻译+编译, all=全部') parser.add_argument('--cc', help='LLVM 编译器命令(覆盖 project.json)') parser.add_argument('--clean', action='store_true', help='清理 output 和 temp 目录') args = parser.parse_args() project_file = args.project if not project_file: candidates = ['project.json', './project.json'] for c in candidates: if os.path.exists(c): project_file = c break if project_file and os.path.exists(project_file): print(f"[配置] 读取: {project_file}") config = Load_project_config(project_file) project_dir = os.path.dirname(os.path.abspath(project_file)) print(f"[配置] 项目目录: {project_dir}") else: print("[错误] 未找到 project.json,请使用 --project 指定") sys.exit(1) resolved = resolve_paths(config, project_file) src = args.src or resolved.get('source_dir') or resolved.get('sources') temp_dir = args.temp or resolved.get('temp_dir') output_dir = args.output or resolved.get('output_dir') phase = args.phase or 'all' cc_cmd = args.cc or resolved.get('compiler', {}).get('cmd', 'llc') cc_flags = resolved.get('compiler', {}).get('flags', ['-filetype=obj']) slice_level = resolved.get('options', {}).get('slice_level', 3) project_include_dirs = resolved.get('includes', []) target_triple = resolved.get('target', {}).get('triple') target_datalayout = resolved.get('target', {}).get('datalayout') if isinstance(src, list): if len(src) == 1 and os.path.isdir(src[0]): src = src[0] else: src = src[0] if len(src) == 1 else os.path.dirname(os.path.commonpath(src)) if all(os.path.exists(p) for p in src) else src[0] print(f"[配置] 源目录: {src}") print(f"[配置] 临时目录: {temp_dir}") print(f"[配置] 输出目录: {output_dir}") print(f"[配置] 阶段: {phase}") print(f"[配置] 编译器: {cc_cmd} {' '.join(cc_flags)}") if target_triple: print(f"[配置] 目标三元组: {target_triple}") if target_datalayout: print(f"[配置] 数据布局: {target_datalayout}") if args.clean: if output_dir and os.path.exists(output_dir): for f in os.listdir(output_dir): fpath = os.path.join(output_dir, f) try: if os.path.isfile(fpath) or os.path.islink(fpath): os.remove(fpath) elif os.path.isdir(fpath): shutil.rmtree(fpath, ignore_errors=True) except: pass print(f"[清理] output: 已清空 {output_dir}") if temp_dir and os.path.exists(temp_dir): for f in os.listdir(temp_dir): fpath = os.path.join(temp_dir, f) try: if os.path.isfile(fpath) or os.path.islink(fpath): os.remove(fpath) elif os.path.isdir(fpath): shutil.rmtree(fpath, ignore_errors=True) except: pass print(f"[清理] temp: 已清空 {temp_dir}") print("[清理] 完成") if (not phase or phase == 'all') and not args.project: return if phase in ('1', 'all'): if isinstance(src, list): print("[错误] 阶段一需要指定源目录,不支持文件列表") sys.exit(1) all_include_dirs = list(project_include_dirs) if project_include_dirs else [] for d in Phase2Translator.INCLUDE_DIRS: if os.path.isdir(d): d = os.path.abspath(d) if d not in [os.path.abspath(x) for x in all_include_dirs]: all_include_dirs.append(d) gen = Phase1Generator(src, temp_dir, include_dirs=all_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout) gen.run() save_sha1_map(temp_dir, gen.sha1_map) if phase in ('2', 'all'): if not output_dir: print("[错误] 阶段二需要指定 --output 目录") sys.exit(1) if not os.path.exists(temp_dir): print(f"[错误] 声明目录不存在,请先运行阶段一: {temp_dir}") sys.exit(1) if isinstance(src, list): print("[错误] 阶段二需要指定源目录,不支持文件列表") sys.exit(1) linker_cmd = resolved.get('linker', {}).get('cmd') linker_flags = resolved.get('linker', {}).get('flags', []) linker_output = resolved.get('linker', {}).get('output') target_triple = resolved.get('target', {}).get('triple') target_datalayout = resolved.get('target', {}).get('datalayout') startup = resolved.get('options', {}).get('startup') trans = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, startup=startup) trans.sha1_map = Load_sha1_map(temp_dir) trans.slice_level = slice_level trans.run()