From 292881595e586a7b32feaa80b2cbfd381bdd555e Mon Sep 17 00:00:00 2001 From: K2cr2O1 <2221577113@qq.com> Date: Fri, 23 Jan 2026 17:15:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(compile=5Fmachine=5Fcode):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=BC=96=E8=AF=91=E8=84=9A=E6=9C=AC=E5=B9=B6=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=86=97=E4=BD=99=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(getting-started): 删除中文依赖说明 refactor(image_manager): 简化base64图片生成逻辑 --- core/managers/image_manager.py | 8 +- docs/getting-started.md | 2 - scripts/compile_machine_code.py | 664 +++++++++++++++++++++++++++----- 3 files changed, 573 insertions(+), 101 deletions(-) diff --git a/core/managers/image_manager.py b/core/managers/image_manager.py index ed96e4d..c77fc88 100644 --- a/core/managers/image_manager.py +++ b/core/managers/image_manager.py @@ -122,13 +122,7 @@ class ImageManager(Singleton): content = f.read() mime_type = "image/jpeg" if image_type == "jpeg" else "image/png" - base64_str = base64.b64encode(content).decode("utf-8") - - # 记录摘要日志,避免刷屏 - log_message = f"Base64 图片已生成 (MIME: {mime_type}, Size: {len(base64_str)/1024:.2f} KB, Preview: {base64_str[:30]}...{base64_str[-30:]})" - logger.debug(log_message) - - return f"data:{mime_type};base64," + base64_str + return f"data:{mime_type};base64," + base64.b64encode(content).decode("utf-8") except Exception as e: logger.error(f"读取图片文件失败: {e}") return None diff --git a/docs/getting-started.md b/docs/getting-started.md index 7f30bde..baff467 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -44,8 +44,6 @@ source venv/bin/activate pip install -r requirements.txt ``` -这个文件里包含了所有需要的 Python 库,比如 `aiohttp` (HTTP 请求), `orjson` (JSON 解析), `jinja2` (模板渲染), `psutil` (系统监控) 等等。 - ### d. 安装 Playwright 依赖 我们用 Playwright 来截图画画,它需要一个浏览器核心。 diff --git a/scripts/compile_machine_code.py b/scripts/compile_machine_code.py index 5b549a4..8eaf17c 100644 --- a/scripts/compile_machine_code.py +++ b/scripts/compile_machine_code.py @@ -1,38 +1,40 @@ #!/usr/bin/env python3 """ -自动化跨平台 Python 模块编译脚本 (v2.2) +优化版跨平台 Python 模块编译脚本 -将核心 Python 模块编译为机器码 (.pyd 或 .so) 以提升性能。 -此版本实现了全自动清理和保守的编译范围,以确保稳定性。 +将核心 Python 模块编译为机器码(.pyd 或 .so)以提升性能。 +此版本基于对项目结构的深入分析,包含了更多高频使用的模块。 支持的平台: - Windows: 生成 .pyd 文件 - Linux: 生成 .so 文件 使用方法: - python scripts/compile_machine_code.py + python compile_machine_code.py [options] -脚本会自动执行以下步骤: - 1. 清理所有旧的编译文件 (.pyd, .so) 和 build 目录。 - 2. 编译模块列表中所有兼容的模块。 - 3. 列出所有成功编译的模块。 +选项: + --compile, -c 编译指定的模块(默认) + --list, -l 列出已编译的模块 + --clean, -k 清理编译生成的文件 + --help, -h 显示帮助信息 注意: 1. 需要安装 C 编译器 (Windows 上需要 Visual Studio Build Tools, Linux 上需要 GCC) 2. 需要安装 mypyc: pip install mypyc - 3. 编译后的文件是平台相关的,不能跨平台复制。 + 3. 编译后的文件是平台相关的,不能跨平台复制 + 4. 建议在部署的目标环境上运行此脚本 + 5. Mypyc 不支持动态特性,如 eval/exec/getattr/setattr 等 """ import os import sys import glob import subprocess import shutil - -# --- 配置区 --- +import argparse # 检测当前平台和 Python 版本 PLATFORM = sys.platform -PYTHON_VERSION = f"{sys.version_info.major}{sys.version_info.minor}" +PYTHON_VERSION = f"{sys.version_info.major}{sys.version_info.minor}" # 例如 "314" if PLATFORM.startswith('win'): EXTENSION = '.pyd' @@ -46,159 +48,637 @@ else: print(f"不支持的平台: {PLATFORM}") sys.exit(1) -# 经过测试的稳定编译模块列表 +# 根据项目分析,优化要编译的模块列表 +# 这些是项目中使用频率最高的模块,编译后能显著提升性能 MODULES = [ - # 工具模块 - 'core/utils/executor.py', - 'core/utils/exceptions.py', - 'core/utils/logger.py', + # 工具模块 - 高频使用 + 'core/utils/json_utils.py', # JSON 处理 - 高频使用 + 'core/utils/executor.py', # 代码执行引擎 - 高频使用 + 'core/utils/exceptions.py', # 自定义异常 - 基础组件 + 'core/utils/performance.py', # 性能监控工具 - 重要组件 + 'core/utils/logger.py', # 日志模块 - 高频使用 + 'core/utils/singleton.py', # 单例模式 - 基础组件 - # 核心基础模块 - 'core/ws.py', - 'core/config_loader.py', + # 核心管理模块 - 高频使用 + # 'core/managers/command_manager.py', # 指令匹配和分发 - 包含动态特性,不适合编译 + # 'core/managers/admin_manager.py', # 管理员管理 - 包含动态特性,不适合编译 + # 'core/managers/permission_manager.py', # 权限管理 - 包含动态特性,不适合编译 + # 'core/managers/plugin_manager.py', # 插件管理器 - 包含动态特性,不适合编译 + # 'core/managers/redis_manager.py', # Redis 管理器 - 包含动态特性,不适合编译 + # 'core/managers/image_manager.py', # 图片管理器 - 包含动态特性,不适合编译 - # 数据模型 (仅包含安全的、无复杂元类的部分) - 'models/message.py', - 'models/sender.py', - 'models/objects.py', + # 核心基础模块 - 高频使用 + 'core/ws.py', # WebSocket 核心 - 核心通信,被10个文件引用 + # 'core/bot.py', # Bot 核心抽象 - 使用多重继承,不适合编译 + 'core/config_loader.py', # 配置加载 - 启动必需,被7个文件引用 + # 'core/config_models.py', # 配置模型 - 包含复杂类型定义,不适合编译 + # 'core/permission.py', # 权限枚举 - 包含动态属性,不适合编译 + + # 数据模型 - 高频使用 + 'models/message.py', # 消息段模型 - 高频消息处理 + 'models/sender.py', # 发送者模型 - 高频消息处理 + 'models/objects.py', # API 响应数据模型 - 高频数据处理 + + # 事件处理相关 - 高频使用 + 'core/handlers/event_handler.py', # 事件处理器 - 核心事件处理 + + # 事件模型 - 高频使用,但包含dataclass,可能有编译问题,暂时排除 + # 'models/events/message.py', # 消息事件 - 最高频事件类型 + # 'models/events/notice.py', # 通知事件 - 高频事件类型 + # 'models/events/request.py', # 请求事件 - 高频事件类型 + # 'models/events/meta.py', # 元事件 - 高频事件类型 + + # 注意:以下文件不适合编译 + # - 主程序文件(main.py) + # - 测试文件(tests/目录) + # - 插件文件(plugins/目录) + # - 编译(脚本compile_machine_code.py等) + # - 包含复杂动态特性的文件 + # - API 基础类(由于多重继承问题) ] -# --- 功能函数 --- - def list_compiled_modules(): """列出已编译的模块""" print(f"\n已编译的 {PLATFORM} 模块:") print("=" * 50) - compiled_files = glob.glob(f'**/*{EXTENSION}', recursive=True) + # 查找所有编译后的文件 + compiled_files = [] + for ext in [EXTENSION, f'__mypyc{EXTENSION}']: + compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True)) + + # 过滤掉虚拟环境中的文件 compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f] if compiled_files: for f in sorted(compiled_files): - try: - size = os.path.getsize(f) // 1024 - print(f"{f} ({size} KB)") - except FileNotFoundError: - continue + size = os.path.getsize(f) // 1024 # KB + print(f"{f} ({size} KB)") else: print(f"未找到已编译的 {EXTENSION} 文件") print(f"\n总计: {len(compiled_files)} 个文件") def clean_compiled_files(): - """清理所有编译生成的文件和目录""" - print("--- 步骤 1: 清理旧的编译文件 ---") + """清理编译生成的文件""" + print(f"\n清理编译生成的 {EXTENSION} 文件...") - compiled_files = glob.glob(f'**/*{EXTENSION}', recursive=True) + # 查找所有编译后的文件 + compiled_files = [] + for ext in [EXTENSION, f'__mypyc{EXTENSION}']: + compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True)) + + # 过滤掉虚拟环境中的文件 compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f] if compiled_files: for f in sorted(compiled_files): try: os.remove(f) - print(f" 已删除: {f}") + print(f"已删除: {f}") except Exception as e: - print(f" 删除失败 {f}: {e}") + print(f"删除失败 {f}: {e}") + + # 清理 build 目录 + if os.path.exists('build'): + try: + shutil.rmtree('build') + print("已删除 build 目录") + except Exception as e: + print(f"删除 build 目录失败: {e}") else: - print(" 没有可清理的文件") - - if os.path.exists('build'): - try: - shutil.rmtree('build') - print(" 已删除 build 目录") - except Exception as e: - print(f" 删除 build 目录失败: {e}") - print("-" * 35) + print(f"没有可清理的 {EXTENSION} 文件") + +def get_platform_specific_module_name(module_path): + """获取平台特定的模块文件名""" + module_name = module_path.replace('.py', '') + return f"{module_name}.{BUILD_PREFIX}{EXTENSION}" def compile_module(module_path): """编译单个模块""" print(f"\n编译: {module_path}") try: + # 直接调用 mypyc 命令行工具 + # 使用二进制模式捕获输出以避免编码问题 result = subprocess.run( [sys.executable, '-m', 'mypyc', module_path], capture_output=True, - check=True, - text=True, - encoding='utf-8', - errors='replace' + check=True ) - # 智能查找编译产物 - base_name = os.path.basename(module_path).replace('.py', '') - search_pattern = os.path.join(BUILD_PATH, '**', f'{base_name}.*{EXTENSION}') + # 解码输出时处理可能的编码错误 + try: + stdout_text = result.stdout.decode('utf-8', errors='replace') + stderr_text = result.stderr.decode('utf-8', errors='replace') + except AttributeError: + # 如果已经是字符串(Python 3.7+),则直接使用 + stdout_text = result.stdout + stderr_text = result.stderr - found_files = glob.glob(search_pattern, recursive=True) + # 获取平台特定的模块名 + platform_module = get_platform_specific_module_name(module_path) + mypyc_platform_module = platform_module.replace(EXTENSION, f'__mypyc{EXTENSION}') - if found_files: - build_module_path = found_files[0] - dest_path = os.path.join(os.path.dirname(module_path), os.path.basename(build_module_path)) - - os.makedirs(os.path.dirname(dest_path), exist_ok=True) - shutil.copy2(build_module_path, dest_path) - print(f" ✓ 编译成功: {dest_path}") + # 检查编译产物是否在当前目录 + if os.path.exists(platform_module): + print(f" ✓ 编译成功: {platform_module}") return True else: - print(f" ✗ 编译失败:在 {BUILD_PATH} 中找不到编译产物") - if result.stdout: print(f" 输出: {result.stdout[:500]}...") - if result.stderr: print(f" 错误: {result.stderr[:500]}...") - return False + # 检查 build 目录中是否有编译产物 + build_module_path = os.path.join(BUILD_PATH, platform_module) + build_mypyc_path = os.path.join(BUILD_PATH, mypyc_platform_module) + if os.path.exists(build_module_path): + # 如果在 build 目录中,复制到正确位置 + os.makedirs(os.path.dirname(platform_module), exist_ok=True) + shutil.copy2(build_module_path, platform_module) + if os.path.exists(build_mypyc_path): + shutil.copy2(build_mypyc_path, mypyc_platform_module) + print(f" ✓ 编译成功(已从 build 目录复制): {platform_module}") + return True + else: + print(" ✗ 编译失败:找不到编译产物") + if result.stdout: + print(f" 编译输出:{stdout_text[:500]}...") + if result.stderr: + print(f" 错误信息:{stderr_text[:500]}...") + return False + except subprocess.CalledProcessError as e: - print(f" ✗ 编译失败 (Exit Code: {e.returncode})") - if e.stdout: print(f" 输出: {e.stdout[:500]}...") - if e.stderr: print(f" 错误: {e.stderr[:500]}...") + print(f" ✗ 编译失败,退出码: {e.returncode}") + if hasattr(e, 'stdout') and e.stdout: + try: + stdout_text = e.stdout.decode('utf-8', errors='replace') if isinstance(e.stdout, bytes) else e.stdout + print(f" 编译输出:{stdout_text[:500]}...") + except Exception: + print(f" 编译输出:{str(e.stdout)[:500]}...") + if hasattr(e, 'stderr') and e.stderr: + try: + stderr_text = e.stderr.decode('utf-8', errors='replace') if isinstance(e.stderr, bytes) else e.stderr + print(f" 错误信息:{stderr_text[:500]}...") + except Exception: + print(f" 错误信息:{str(e.stderr)[:500]}...") return False except Exception as e: - print(f" ✗ 编译时发生意外错误: {e}") + print(f" ✗ 编译失败,意外错误: {e}") + import traceback + traceback.print_exc() return False +def should_skip_module(module_path): + """检查模块是否应该被跳过编译""" + try: + with open(module_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 检查是否包含抽象基类相关代码 + if 'from abc import ABC' in content or 'from abc import abstractmethod' in content: + return True, "包含抽象基类,不适合编译" + + # 检查是否包含危险的动态特性 + # 注意:我们允许基本的动态特性,如getattr,但对于eval、exec等危险操作仍然阻止 + if ('eval(' in content or 'exec(' in content or + 'compile(' in content): + return True, "包含危险动态特性,不适合编译" + + # 检查是否包含复杂的动态属性访问 + if ('__dict__' in content or '__class__' in content or + '__module__' in content or '__bases__' in content): + return True, "包含复杂动态特性,不适合编译" + + # 检查是否包含复杂的动态属性访问 + if '.__dict__' in content or '.__class__' in content: + return True, "包含复杂动态特性,不适合编译" + + return False, "" + except Exception as e: + return True, f"读取文件时出错: {e}" + def compile_all_modules(): """编译所有指定的模块""" - print("\n--- 步骤 2: 开始编译模块 ---") + print(f"\n开始编译 {len(MODULES)} 个模块 (平台: {PLATFORM})") + print("=" * 60) + # 验证模块文件是否存在并检查是否适合编译 valid_modules = [] - for module in MODULES: - if os.path.exists(module): - valid_modules.append(module) - else: - print(f"警告: 模块 {module} 不存在,已跳过") - - print(f"\n找到 {len(valid_modules)} 个有效模块进行编译。") + skipped_modules = [] + for module_path in MODULES: + if os.path.exists(module_path): + should_skip, reason = should_skip_module(module_path) + if should_skip: + print(f"跳过: {module_path} ({reason})") + skipped_modules.append((module_path, reason)) + else: + valid_modules.append(module_path) + else: + print(f"警告: 模块 {module_path} 不存在,将被跳过") + + print(f"\n有效模块: {len(valid_modules)}, 跳过模块: {len(skipped_modules)}") + + if not valid_modules: + print("错误: 没有有效的模块可编译") + return False + + # 编译模块 success_count = 0 failed_modules = [] - for module in valid_modules: - if compile_module(module): + for module_path in valid_modules: + if compile_module(module_path): success_count += 1 else: - failed_modules.append(module) + failed_modules.append(module_path) - print("\n" + "=" * 50) + print("\n" + "=" * 60) print(f"编译完成: {success_count}/{len(valid_modules)} 个模块成功") if failed_modules: print(f"失败模块: {failed_modules}") - print("✗ 部分模块编译失败") - else: + + if success_count == len(valid_modules): print("✓ 所有模块编译成功") - print("=" * 50) + return True + else: + print("✗ 部分模块编译失败") + return False def main(): - """主函数:执行清理、编译和列出结果的全过程""" + """主函数""" + # 检查 Python 版本 if not (sys.version_info.major == 3 and sys.version_info.minor >= 8): - print("警告: 推荐使用 Python 3.8+ 以获得最佳性能。") + print("警告: 推荐使用 Python 3.8+ 以获得最佳性能") + print(f"当前版本: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") + print("继续编译可能导致兼容性问题") + print() + parser = argparse.ArgumentParser(description='优化版跨平台 Python 模块编译脚本') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--compile', '-c', action='store_true', default=True, + help='编译指定的模块 (默认)') + group.add_argument('--list', '-l', action='store_true', + help='列出已编译的模块') + group.add_argument('--clean', '-k', action='store_true', + help='清理编译生成的文件') + + args = parser.parse_args() + + # 检查是否安装了 mypyc try: import mypyc except ImportError: - print("错误: 未安装 mypyc,请运行: pip install mypyc") + print("错误: 未安装 mypyc,请先安装: pip install mypyc") sys.exit(1) - - clean_compiled_files() - compile_all_modules() - list_compiled_modules() + + if args.list: + list_compiled_modules() + elif args.clean: + clean_compiled_files() + else: + compile_all_modules() + print("\n使用 --list 选项查看已编译的模块") + print("使用 --clean 选项清理编译文件") if __name__ == '__main__': - main() + main()#!/usr/bin/env python3 +""" +跨平台 Python 模块编译脚本 + +将核心 Python 模块编译为机器码(.pyd 或 .so)以提升性能。 + +支持的平台: +- Windows: 生成 .pyd 文件 +- Linux: 生成 .so 文件 + +使用方法: + python compile_machine_code.py [options] + +选项: + --compile, -c 编译指定的模块(默认) + --list, -l 列出已编译的模块 + --clean, -k 清理编译生成的文件 + --help, -h 显示帮助信息 + +注意: + 1. 需要安装 C 编译器 (Windows 上需要 Visual Studio Build Tools, Linux 上需要 GCC) + 2. 需要安装 mypyc: pip install mypyc + 3. 编译后的文件是平台相关的,不能跨平台复制 + 4. 建议在部署的目标环境上运行此脚本 +""" +import os +import sys + +# 检测当前平台 +PLATFORM = sys.platform +if PLATFORM.startswith('win'): + EXTENSION = '.pyd' + BUILD_PREFIX = 'cp314-win_amd64' + BUILD_PATH = os.path.join('build', 'lib.win-amd64-cpython-314') +elif PLATFORM.startswith('linux'): + EXTENSION = '.so' + BUILD_PREFIX = 'cp314-x86_64-linux-gnu' + BUILD_PATH = os.path.join('build', 'lib.linux-x86_64-cpython-314') +else: + print(f"不支持的平台: {PLATFORM}") + sys.exit(1) + +# 要编译的模块列表 +# 注意:Mypyc 对动态特性支持有限,只选择计算密集或类型明确的模块 +MODULES = [ + # 工具模块 - 高频使用 + 'core/utils/json_utils.py', # JSON 处理 - 高频使用 + 'core/utils/executor.py', # 代码执行引擎 - 高频使用 + 'core/utils/exceptions.py', # 自定义异常 - 基础组件 + 'core/utils/performance.py', # 性能监控工具 - 重要组件 + 'core/utils/logger.py', # 日志模块 - 高频使用 + 'core/utils/singleton.py', # 单例模式 - 基础组件 + + # 核心管理模块 - 高频使用 + # 'core/managers/command_manager.py', # 指令匹配和分发 - 包含动态特性,不适合编译 + # 'core/managers/admin_manager.py', # 管理员管理 - 包含动态特性,不适合编译 + # 'core/managers/permission_manager.py', # 权限管理 - 包含动态特性,不适合编译 + # 'core/managers/plugin_manager.py', # 插件管理器 - 包含动态特性,不适合编译 + # 'core/managers/redis_manager.py', # Redis 管理器 - 包含动态特性,不适合编译 + # 'core/managers/image_manager.py', # 图片管理器 - 包含动态特性,不适合编译 + + # 核心基础模块 - 高频使用 + 'core/ws.py', # WebSocket 核心 - 核心通信,被10个文件引用 + # 'core/bot.py', # Bot 核心抽象 - 使用多重继承,不适合编译 + 'core/config_loader.py', # 配置加载 - 启动必需,被7个文件引用 + # 'core/config_models.py', # 配置模型 - 包含复杂类型定义,不适合编译 + # 'core/permission.py', # 权限枚举 - 包含动态属性,不适合编译 + + # 数据模型 - 高频使用 + 'models/message.py', # 消息段模型 - 高频消息处理 + 'models/sender.py', # 发送者模型 - 高频消息处理 + 'models/objects.py', # API 响应数据模型 - 高频数据处理 + + # 事件处理相关 - 高频使用 + 'core/handlers/event_handler.py', # 事件处理器 - 核心事件处理 + + # 事件模型 - 高频使用,但包含dataclass,可能有编译问题,暂时排除 + # 'models/events/message.py', # 消息事件 - 最高频事件类型 + # 'models/events/notice.py', # 通知事件 - 高频事件类型 + # 'models/events/request.py', # 请求事件 - 高频事件类型 + # 'models/events/meta.py', # 元事件 - 高频事件类型 + + # 注意:以下文件不适合编译 + # - 主程序文件(main.py) + # - 测试文件(tests/目录) + # - 插件文件(plugins/目录) + # - 编译(脚本compile_machine_code.py等) + # - 包含复杂动态特性的文件 + # - API 基础类(由于多重继承问题) +] + +def list_compiled_modules(): + """列出已编译的模块""" + print(f"\n已编译的 {PLATFORM} 模块:") + print("=" * 50) + + # 查找所有编译后的文件 + compiled_files = [] + for ext in [EXTENSION, f'__mypyc{EXTENSION}']: + compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True)) + + # 过滤掉虚拟环境中的文件 + compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f] + + if compiled_files: + for f in sorted(compiled_files): + size = os.path.getsize(f) // 1024 # KB + print(f"{f} ({size} KB)") + else: + print(f"未找到已编译的 {EXTENSION} 文件") + + print(f"\n总计: {len(compiled_files)} 个文件") + +def clean_compiled_files(): + """清理编译生成的文件""" + print(f"\n清理编译生成的 {EXTENSION} 文件...") + + # 查找所有编译后的文件 + compiled_files = [] + for ext in [EXTENSION, f'__mypyc{EXTENSION}']: + compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True)) + + # 过滤掉虚拟环境中的文件 + compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f] + + if compiled_files: + for f in sorted(compiled_files): + try: + os.remove(f) + print(f"已删除: {f}") + except Exception as e: + print(f"删除失败 {f}: {e}") + + # 清理 build 目录 + if os.path.exists('build'): + try: + shutil.rmtree('build') + print("已删除 build 目录") + except Exception as e: + print(f"删除 build 目录失败: {e}") + else: + print(f"没有可清理的 {EXTENSION} 文件") + +def get_platform_specific_module_name(module_path): + """获取平台特定的模块文件名""" + # 只获取模块名,不包含路径 + module_name = os.path.basename(module_path).replace('.py', '') + return f"{module_name}.{BUILD_PREFIX}{EXTENSION}" + +def compile_module(module_path): + """编译单个模块""" + print(f"\n编译: {module_path}") + + try: + # 直接调用 mypyc 命令行工具 + # 使用二进制模式捕获输出以避免编码问题 + result = subprocess.run( + [sys.executable, '-m', 'mypyc', module_path], + capture_output=True, + check=True + ) + + # 解码输出时处理可能的编码错误 + try: + stdout_text = result.stdout.decode('utf-8', errors='replace') + stderr_text = result.stderr.decode('utf-8', errors='replace') + except AttributeError: + # 如果已经是字符串(Python 3.7+),则直接使用 + stdout_text = result.stdout + stderr_text = result.stderr + + # 获取平台特定的模块名 + # 获取模块名和目录 + module_dir = os.path.dirname(module_path) + module_basename = os.path.basename(module_path).replace('.py', '') + + # 生成平台特定的模块文件名(仅文件名,不含路径) + platform_module_name = f"{module_basename}.{BUILD_PREFIX}{EXTENSION}" + mypyc_platform_module_name = f"{module_basename}__mypyc.{BUILD_PREFIX}{EXTENSION}" + + # 完整路径构造 + platform_module = os.path.join(module_dir, platform_module_name) + mypyc_platform_module = os.path.join(module_dir, mypyc_platform_module_name) + + # 检查编译产物是否在当前目录 + if os.path.exists(platform_module): + print(f" ✓ 编译成功: {platform_module}") + return True + else: + # 检查 build 目录中是否有编译产物 + build_module_path = os.path.join(BUILD_PATH, module_dir, platform_module_name) + build_mypyc_path = os.path.join(BUILD_PATH, module_dir, mypyc_platform_module_name) + + if os.path.exists(build_module_path): + # 如果在 build 目录中,复制到正确位置 + os.makedirs(module_dir, exist_ok=True) + shutil.copy2(build_module_path, platform_module) + if os.path.exists(build_mypyc_path): + shutil.copy2(build_mypyc_path, mypyc_platform_module) + print(f" ✓ 编译成功(已从 build 目录复制): {platform_module}") + return True + else: + print(" ✗ 编译失败:找不到编译产物") + if result.stdout: + print(f" 编译输出:{stdout_text[:500]}...") + if result.stderr: + print(f" 错误信息:{stderr_text[:500]}...") + return False + + except subprocess.CalledProcessError as e: + print(f" ✗ 编译失败,退出码: {e.returncode}") + if hasattr(e, 'stdout') and e.stdout: + try: + stdout_text = e.stdout.decode('utf-8', errors='replace') if isinstance(e.stdout, bytes) else e.stdout + print(f" 编译输出:{stdout_text[:500]}...") + except Exception: + print(f" 编译输出:{str(e.stdout)[:500]}...") + if hasattr(e, 'stderr') and e.stderr: + try: + stderr_text = e.stderr.decode('utf-8', errors='replace') if isinstance(e.stderr, bytes) else e.stderr + print(f" 错误信息:{stderr_text[:500]}...") + except Exception: + print(f" 错误信息:{str(e.stderr)[:500]}...") + return False + except Exception as e: + print(f" ✗ 编译失败,意外错误: {e}") + import traceback + traceback.print_exc() + return False + +def should_skip_module(module_path): + """检查模块是否应该被跳过编译""" + try: + with open(module_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 检查是否包含抽象基类相关代码 + if 'from abc import ABC' in content or 'from abc import abstractmethod' in content: + return True, "包含抽象基类,不适合编译" + + # 检查是否包含危险的动态特性 + # 注意:我们允许基本的动态特性,如getattr,但对于eval、exec等危险操作仍然阻止 + if ('eval(' in content or 'exec(' in content or + 'compile(' in content): + return True, "包含危险动态特性,不适合编译" + + # 检查是否包含复杂的动态属性访问 + if ('__dict__' in content or '__class__' in content or + '__module__' in content or '__bases__' in content): + return True, "包含复杂动态特性,不适合编译" + + # 检查是否包含复杂的动态属性访问 + if '.__dict__' in content or '.__class__' in content: + return True, "包含复杂动态特性,不适合编译" + + return False, "" + except Exception as e: + return True, f"读取文件时出错: {e}" + +def compile_all_modules(): + """编译所有指定的模块""" + print(f"\n开始编译 {len(MODULES)} 个模块 (平台: {PLATFORM})") + print("=" * 60) + + # 验证模块文件是否存在并检查是否适合编译 + valid_modules = [] + skipped_modules = [] + + for module_path in MODULES: + if os.path.exists(module_path): + should_skip, reason = should_skip_module(module_path) + if should_skip: + print(f"跳过: {module_path} ({reason})") + skipped_modules.append((module_path, reason)) + else: + valid_modules.append(module_path) + else: + print(f"警告: 模块 {module_path} 不存在,将被跳过") + + print(f"\n有效模块: {len(valid_modules)}, 跳过模块: {len(skipped_modules)}") + + if not valid_modules: + print("错误: 没有有效的模块可编译") + return False + + # 编译模块 + success_count = 0 + failed_modules = [] + + for module_path in valid_modules: + if compile_module(module_path): + success_count += 1 + else: + failed_modules.append(module_path) + + print("\n" + "=" * 60) + print(f"编译完成: {success_count}/{len(valid_modules)} 个模块成功") + + if failed_modules: + print(f"失败模块: {failed_modules}") + + if success_count == len(valid_modules): + print("✓ 所有模块编译成功") + return True + else: + print("✗ 部分模块编译失败") + return False + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description='跨平台 Python 模块编译脚本') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--compile', '-c', action='store_true', default=True, + help='编译指定的模块 (默认)') + group.add_argument('--list', '-l', action='store_true', + help='列出已编译的模块') + group.add_argument('--clean', '-k', action='store_true', + help='清理编译生成的文件') + + args = parser.parse_args() + + # 检查是否安装了 mypyc + try: + import mypyc + except ImportError: + print("错误: 未安装 mypyc,请先安装: pip install mypyc") + sys.exit(1) + + if args.list: + list_compiled_modules() + elif args.clean: + clean_compiled_files() + else: + compile_all_modules() + print("\n使用 --list 选项查看已编译的模块") + print("使用 --clean 选项清理编译文件") + +if __name__ == '__main__': + main() \ No newline at end of file