8.7
This commit is contained in:
parent
ce13eb62f0
commit
bbcba090a8
328
Exeprotector/pyinstallertools.py
Normal file
328
Exeprotector/pyinstallertools.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user