From bbcba090a839aefe766eaf2abc241c7ae30ff0f7 Mon Sep 17 00:00:00 2001 From: wsb1224 Date: Thu, 7 Aug 2025 10:55:41 +0800 Subject: [PATCH] 8.7 --- Exeprotector/pyinstallertools.py | 328 +++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 Exeprotector/pyinstallertools.py diff --git a/Exeprotector/pyinstallertools.py b/Exeprotector/pyinstallertools.py new file mode 100644 index 0000000..ca87b5b --- /dev/null +++ b/Exeprotector/pyinstallertools.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +""" +PyInstaller Helper Tool +用于调试和解决PyInstaller编译问题 +""" + +import os +import sys +import subprocess +import shutil +import tempfile +from pathlib import Path + + +class PyInstallerHelper: + def __init__(self): + self.temp_dir = None + + def check_pyinstaller(self): + """检查PyInstaller是否正确安装""" + try: + result = subprocess.run(['pyinstaller', '--version'], + capture_output=True, text=True, timeout=10) + if result.returncode == 0: + version = result.stdout.strip() + print(f"✓ PyInstaller已安装: {version}") + return True, version + else: + return False, "PyInstaller命令执行失败" + except FileNotFoundError: + return False, "PyInstaller未安装" + except Exception as e: + return False, f"检查PyInstaller时出错: {str(e)}" + + def install_pyinstaller(self): + """安装PyInstaller""" + try: + print("正在安装PyInstaller...") + result = subprocess.run([sys.executable, '-m', 'pip', 'install', 'pyinstaller'], + capture_output=True, text=True, timeout=60) + if result.returncode == 0: + print("✓ PyInstaller安装成功") + return True + else: + print(f"✗ PyInstaller安装失败: {result.stderr}") + return False + except Exception as e: + print(f"✗ 安装过程出错: {str(e)}") + return False + + def create_spec_file(self, python_file, exe_name, output_dir): + """创建PyInstaller规格文件""" + spec_content = f'''# -*- mode: python ; coding: utf-8 -*- + +a = Analysis( + ['{python_file}'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=['mysql.connector', 'cryptography', 'tkinter'], + hookspath=[], + hooksconfig={{}}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='{exe_name}', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +''' + + spec_file_path = os.path.join(output_dir, f'{exe_name}.spec') + with open(spec_file_path, 'w', encoding='utf-8') as f: + f.write(spec_content) + + return spec_file_path + + def compile_with_spec(self, spec_file, output_dir): + """使用spec文件编译""" + try: + original_cwd = os.getcwd() + os.chdir(output_dir) + + try: + cmd = ['pyinstaller', '--clean', '--noconfirm', spec_file] + print(f"执行命令: {' '.join(cmd)}") + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + return True, "编译成功" + else: + return False, f"编译失败:\nSTDOUT: {result.stdout}\nSTDERR: {result.stderr}" + finally: + os.chdir(original_cwd) + + except subprocess.TimeoutExpired: + return False, "编译超时(超过5分钟)" + except Exception as e: + return False, f"编译过程出错: {str(e)}" + + def compile_python_to_exe(self, python_file, output_exe=None, console=False): + """将Python文件编译为EXE""" + try: + # 验证输入文件 + if not os.path.exists(python_file): + return False, f"Python文件不存在: {python_file}" + + # 检查PyInstaller + installed, msg = self.check_pyinstaller() + if not installed: + print(f"PyInstaller检查失败: {msg}") + if input("是否自动安装PyInstaller? (y/n): ").lower() == 'y': + if not self.install_pyinstaller(): + return False, "PyInstaller安装失败" + else: + return False, "需要安装PyInstaller才能继续" + + # 确定输出文件名 + if output_exe is None: + output_exe = python_file.replace('.py', '.exe') + + file_dir = os.path.dirname(os.path.abspath(python_file)) + file_name = os.path.basename(python_file) + exe_name = os.path.basename(output_exe).replace('.exe', '') + + print(f"编译文件: {python_file}") + print(f"输出目录: {file_dir}") + print(f"EXE名称: {exe_name}") + + # 方法1: 直接编译 + success = self.direct_compile(python_file, exe_name, file_dir, console) + if success: + return True, "编译成功" + + # 方法2: 使用spec文件编译 + print("直接编译失败,尝试使用spec文件...") + spec_file = self.create_spec_file(file_name, exe_name, file_dir) + success, msg = self.compile_with_spec(spec_file, file_dir) + + # 清理临时文件 + self.cleanup_temp_files(file_dir, exe_name) + + if success: + # 检查输出文件 + expected_exe = os.path.join(file_dir, 'dist', f'{exe_name}.exe') + final_exe = os.path.join(file_dir, f'{exe_name}.exe') + + if os.path.exists(expected_exe): + # 移动文件到最终位置 + if os.path.exists(final_exe): + os.remove(final_exe) + shutil.move(expected_exe, final_exe) + + # 清理dist目录 + dist_dir = os.path.join(file_dir, 'dist') + if os.path.exists(dist_dir): + shutil.rmtree(dist_dir, ignore_errors=True) + + return True, f"编译成功: {final_exe}" + else: + return False, f"编译完成但找不到输出文件: {expected_exe}" + else: + return False, msg + + except Exception as e: + return False, f"编译过程出现异常: {str(e)}" + + def direct_compile(self, python_file, exe_name, file_dir, console=False): + """直接编译方法""" + try: + original_cwd = os.getcwd() + os.chdir(file_dir) + + try: + cmd = [ + 'pyinstaller', + '--onefile', + '--clean', + '--noconfirm', + f'--name={exe_name}', + '--distpath=.', + ] + + if not console: + cmd.append('--noconsole') + + # 添加隐式导入 + hidden_imports = ['mysql.connector', 'cryptography', 'tkinter', 'tkinter.messagebox'] + for imp in hidden_imports: + cmd.extend(['--hidden-import', imp]) + + cmd.append(os.path.basename(python_file)) + + print(f"执行直接编译: {' '.join(cmd)}") + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + expected_exe = f'{exe_name}.exe' + if os.path.exists(expected_exe): + print(f"✓ 直接编译成功: {expected_exe}") + return True + + print(f"直接编译失败: {result.stderr}") + return False + + finally: + os.chdir(original_cwd) + + except Exception as e: + print(f"直接编译出错: {str(e)}") + return False + + def cleanup_temp_files(self, file_dir, exe_name): + """清理临时文件""" + try: + # 清理build目录 + build_dir = os.path.join(file_dir, 'build') + if os.path.exists(build_dir): + shutil.rmtree(build_dir, ignore_errors=True) + + # 清理spec文件 + spec_file = os.path.join(file_dir, f'{exe_name}.spec') + if os.path.exists(spec_file): + os.remove(spec_file) + + print("✓ 临时文件清理完成") + + except Exception as e: + print(f"清理临时文件时出错: {str(e)}") + + def test_exe(self, exe_file): + """测试生成的EXE文件""" + if not os.path.exists(exe_file): + return False, f"EXE文件不存在: {exe_file}" + + try: + # 检查文件大小 + size = os.path.getsize(exe_file) + if size < 1024: # 小于1KB + return False, f"EXE文件太小,可能编译失败: {size} bytes" + + print(f"✓ EXE文件大小: {size:,} bytes") + + # 尝试运行(仅检查是否能启动,不等待结果) + try: + if os.name == 'nt': # Windows + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + proc = subprocess.Popen([exe_file], startupinfo=startupinfo) + # 等待一秒看是否立即崩溃 + import time + time.sleep(1) + if proc.poll() is None: + proc.terminate() + return True, "EXE文件可以正常启动" + else: + return False, f"EXE文件启动后立即退出,退出码: {proc.returncode}" + else: + return True, "非Windows系统,跳过运行测试" + except Exception as e: + return False, f"运行测试失败: {str(e)}" + + except Exception as e: + return False, f"测试EXE文件时出错: {str(e)}" + + +def main(): + """主函数""" + if len(sys.argv) < 2: + print("用法: python pyinstaller_helper.py [output_exe] [--console]") + print("示例: python pyinstaller_helper.py validator.py validator.exe") + return + + python_file = sys.argv[1] + output_exe = sys.argv[2] if len(sys.argv) > 2 else None + console = '--console' in sys.argv + + helper = PyInstallerHelper() + + print("=" * 50) + print("PyInstaller编译助手") + print("=" * 50) + + success, msg = helper.compile_python_to_exe(python_file, output_exe, console) + + print("\n" + "=" * 50) + if success: + print(f"✓ 编译成功: {msg}") + + # 测试生成的EXE + if output_exe and os.path.exists(output_exe): + print("\n测试EXE文件...") + test_success, test_msg = helper.test_exe(output_exe) + if test_success: + print(f"✓ {test_msg}") + else: + print(f"✗ {test_msg}") + else: + print(f"✗ 编译失败: {msg}") + print("=" * 50) + + +if __name__ == "__main__": + main() \ No newline at end of file