This commit is contained in:
wsb1224 2025-08-07 10:55:41 +08:00
parent ce13eb62f0
commit bbcba090a8

View File

@ -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 <python_file> [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()