365 lines
12 KiB
Python
365 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
快速打包脚本 - 避免spec文件缩进问题
|
||
"""
|
||
|
||
import os
|
||
import subprocess
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
|
||
def clean_build():
|
||
"""清理构建目录"""
|
||
print("🧹 清理旧的构建文件...")
|
||
|
||
# 跳过进程结束步骤,避免影响当前脚本运行
|
||
print("🔄 跳过进程结束步骤(避免脚本自终止)...")
|
||
|
||
# 等待少量时间确保文件句柄释放
|
||
import time
|
||
print("⏳ 等待文件句柄释放...")
|
||
time.sleep(0.5)
|
||
|
||
dirs_to_clean = ['dist', 'build']
|
||
for dir_name in dirs_to_clean:
|
||
print(f"🔍 检查目录: {dir_name}")
|
||
if os.path.exists(dir_name):
|
||
try:
|
||
print(f"🗑️ 正在删除: {dir_name}")
|
||
shutil.rmtree(dir_name)
|
||
print(f"✅ 已删除: {dir_name}")
|
||
except PermissionError as e:
|
||
print(f"⚠️ {dir_name} 被占用,尝试强制删除... 错误: {e}")
|
||
try:
|
||
subprocess.run(['rd', '/s', '/q', dir_name],
|
||
shell=True, check=True)
|
||
print(f"✅ 强制删除成功: {dir_name}")
|
||
except Exception as e2:
|
||
print(f"❌ 无法删除 {dir_name}: {e2}")
|
||
print("💡 请手动删除或运行 force_clean_dist.bat")
|
||
except Exception as e:
|
||
print(f"❌ 删除 {dir_name} 时出错: {e}")
|
||
else:
|
||
print(f"ℹ️ {dir_name} 不存在,跳过")
|
||
|
||
print("✅ 清理阶段完成")
|
||
|
||
|
||
def build_with_command():
|
||
"""使用命令行参数直接打包"""
|
||
print("🚀 开始打包...")
|
||
|
||
cmd = [
|
||
'pyinstaller',
|
||
'--name=main',
|
||
'--onedir', # 相当于 --exclude-binaries
|
||
'--windowed', # 相当于 -w
|
||
'--add-data=config.py;.',
|
||
'--add-data=exe_file_logger.py;.',
|
||
'--add-data=windows_taskbar_fix.py;.',
|
||
'--add-data=Utils/PythonNew32;Utils/PythonNew32',
|
||
'--add-data=Utils/JD;Utils/JD',
|
||
'--add-data=Utils/Dy;Utils/Dy',
|
||
'--add-data=Utils/Pdd;Utils/Pdd',
|
||
'--add-data=Utils/QianNiu;Utils/QianNiu',
|
||
'--add-data=Utils/message_models.py;Utils',
|
||
'--add-data=Utils/__init__.py;Utils',
|
||
'--add-data=WebSocket;WebSocket',
|
||
'--add-data=static;static',
|
||
'--add-binary=Utils/PythonNew32/SaiNiuApi.dll;Utils/PythonNew32',
|
||
'--add-binary=Utils/PythonNew32/SaiNiuSys.dll;Utils/PythonNew32',
|
||
'--add-binary=Utils/PythonNew32/SaiNiuServer.dll;Utils/PythonNew32',
|
||
'--add-binary=Utils/PythonNew32/python32.exe;Utils/PythonNew32',
|
||
'--add-binary=Utils/PythonNew32/python313.dll;Utils/PythonNew32',
|
||
'--add-binary=Utils/PythonNew32/vcruntime140.dll;Utils/PythonNew32',
|
||
'--hidden-import=PyQt5.QtCore',
|
||
'--hidden-import=PyQt5.QtGui',
|
||
'--hidden-import=PyQt5.QtWidgets',
|
||
'--hidden-import=websockets',
|
||
'--hidden-import=asyncio',
|
||
'--hidden-import=Utils.QianNiu.QianNiuUtils',
|
||
'--hidden-import=Utils.JD.JdUtils',
|
||
'--hidden-import=Utils.Dy.DyUtils',
|
||
'--hidden-import=Utils.Pdd.PddUtils',
|
||
'--hidden-import=WebSocket.backend_singleton',
|
||
'--hidden-import=WebSocket.BackendClient',
|
||
'--hidden-import=windows_taskbar_fix',
|
||
'main.py'
|
||
]
|
||
|
||
try:
|
||
print(f"执行命令: {' '.join(cmd[:5])}... (共{len(cmd)}个参数)")
|
||
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
||
|
||
if result.returncode == 0:
|
||
print("✅ 打包成功!")
|
||
print("📁 打包结果: dist/main/")
|
||
|
||
# 重命名目录为统一的输出路径
|
||
if os.path.exists('dist/main'):
|
||
try:
|
||
# 如果目标目录已存在,先删除
|
||
if os.path.exists('dist/MultiPlatformGUI'):
|
||
print("🗑️ 删除旧的 MultiPlatformGUI 目录...")
|
||
shutil.rmtree('dist/MultiPlatformGUI')
|
||
|
||
# 重命名
|
||
os.rename('dist/main', 'dist/MultiPlatformGUI')
|
||
print("✅ 已重命名为: dist/MultiPlatformGUI/")
|
||
|
||
# 验证重命名结果
|
||
if os.path.exists('dist/MultiPlatformGUI/main.exe'):
|
||
exe_size = os.path.getsize('dist/MultiPlatformGUI/main.exe') / 1024 / 1024
|
||
print(f"✅ 主程序验证通过: main.exe ({exe_size:.1f} MB)")
|
||
else:
|
||
print("⚠️ 重命名后主程序文件验证失败")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 重命名失败: {e}")
|
||
print("💡 可以手动重命名: dist/main -> dist/MultiPlatformGUI")
|
||
print("📁 当前可用路径: dist/main/")
|
||
else:
|
||
print("⚠️ 未找到 dist/main 目录,重命名跳过")
|
||
|
||
# 创建使用说明
|
||
try:
|
||
create_usage_guide()
|
||
except:
|
||
create_usage_guide_fallback()
|
||
|
||
return True
|
||
else:
|
||
print("❌ 打包失败")
|
||
if result.stderr:
|
||
print("错误信息:")
|
||
print(result.stderr)
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"❌ 打包过程出错: {e}")
|
||
return False
|
||
|
||
|
||
def create_usage_guide():
|
||
"""创建使用说明"""
|
||
guide_content = r"""# 多平台客服GUI使用说明
|
||
|
||
## 🚀 运行方式
|
||
```
|
||
cd dist/MultiPlatformGUI
|
||
.\main.exe
|
||
```
|
||
|
||
## 📝 支持平台
|
||
- 千牛 (淘宝)
|
||
- 京东 (JD)
|
||
- 抖音 (DY)
|
||
- 拼多多 (PDD)
|
||
|
||
## 🎯 特点
|
||
- 支持多平台同时监听
|
||
- 无平台间冲突
|
||
- 自动生成日志文件
|
||
- 美观的系统托盘图标
|
||
- 现代化UI界面
|
||
|
||
## 📁 文件结构
|
||
```
|
||
MultiPlatformGUI/
|
||
├── main.exe # 主程序
|
||
└── _internal/ # 程序依赖文件
|
||
├── static/ # 图标资源
|
||
│ ├── ai_assistant_icon_16.png
|
||
│ ├── ai_assistant_icon_32.png
|
||
│ └── ai_assistant_icon_64.png
|
||
├── Utils/
|
||
│ ├── PythonNew32/ # 千牛DLL文件
|
||
│ ├── JD/ # 京东模块
|
||
│ ├── Dy/ # 抖音模块
|
||
│ └── Pdd/ # 拼多多模块
|
||
└── ...
|
||
```
|
||
|
||
## 🔧 故障排除
|
||
1. **图标不显示**:检查 _internal/static/ 目录下的图标文件是否存在
|
||
2. **连接失败**:检查网络连接和令牌是否正确
|
||
3. **平台无响应**:查看日志文件了解详细错误信息
|
||
|
||
## 📞 技术支持
|
||
如有问题,请提供:
|
||
- 错误截图
|
||
- 具体的错误描述
|
||
- 操作步骤
|
||
"""
|
||
|
||
try:
|
||
with open('dist/MultiPlatformGUI/使用说明.txt', 'w', encoding='utf-8') as f:
|
||
f.write(guide_content)
|
||
print("✅ 已生成使用说明文件")
|
||
except:
|
||
print("⚠️ 使用说明生成失败")
|
||
|
||
|
||
def create_usage_guide_fallback():
|
||
"""备用使用说明生成"""
|
||
guide_content = r"""# 多平台客服GUI使用说明
|
||
|
||
## 🚀 运行方式
|
||
```
|
||
cd dist/main
|
||
.\main.exe
|
||
```
|
||
|
||
## 📝 支持平台
|
||
- 千牛 (淘宝)
|
||
- 京东 (JD)
|
||
- 抖音 (DY)
|
||
- 拼多多 (PDD)
|
||
|
||
## 🎯 特点
|
||
- 支持多平台同时监听
|
||
- 无平台间冲突
|
||
- 自动生成日志文件
|
||
"""
|
||
|
||
try:
|
||
with open('dist/main/使用说明.txt', 'w', encoding='utf-8') as f:
|
||
f.write(guide_content)
|
||
print("✅ 已生成使用说明文件 (备用路径)")
|
||
except:
|
||
print("⚠️ 使用说明生成失败")
|
||
|
||
|
||
def verify_result():
|
||
"""验证打包结果"""
|
||
print("\n🔍 验证打包结果...")
|
||
|
||
# 优先检查MultiPlatformGUI(标准输出目录)
|
||
target_dir = "dist/MultiPlatformGUI"
|
||
fallback_dir = "dist/main"
|
||
|
||
if os.path.exists(target_dir):
|
||
return _verify_directory(target_dir)
|
||
elif os.path.exists(fallback_dir):
|
||
print(f"⚠️ 发现备用目录: {fallback_dir}")
|
||
print("💡 建议重命名为: dist/MultiPlatformGUI")
|
||
return _verify_directory(fallback_dir)
|
||
else:
|
||
print("❌ 未找到任何打包输出目录")
|
||
return False
|
||
|
||
def _verify_directory(base_dir):
|
||
"""验证指定目录的打包结果"""
|
||
exe_path = f"{base_dir}/main.exe"
|
||
internal_dir = f"{base_dir}/_internal"
|
||
dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll"
|
||
static_dir = f"{base_dir}/_internal/static"
|
||
|
||
print(f"📁 验证目录: {base_dir}")
|
||
|
||
# 检查主程序
|
||
if os.path.exists(exe_path):
|
||
size = os.path.getsize(exe_path) / 1024 / 1024 # MB
|
||
print(f"✅ 主程序: main.exe ({size:.1f} MB)")
|
||
else:
|
||
print("❌ 主程序 main.exe 不存在")
|
||
return False
|
||
|
||
# 检查_internal目录
|
||
if os.path.exists(internal_dir):
|
||
file_count = len([f for f in os.listdir(internal_dir) if os.path.isfile(os.path.join(internal_dir, f))])
|
||
dir_count = len([d for d in os.listdir(internal_dir) if os.path.isdir(os.path.join(internal_dir, d))])
|
||
print(f"✅ 依赖目录: _internal/ ({file_count} 个文件, {dir_count} 个子目录)")
|
||
else:
|
||
print("❌ _internal 目录不存在")
|
||
return False
|
||
|
||
# 检查关键DLL(可选)
|
||
if os.path.exists(dll_path):
|
||
dll_size = os.path.getsize(dll_path) / 1024 # KB
|
||
print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:.1f} KB)")
|
||
else:
|
||
print("⚠️ 千牛DLL不存在(可能正常)")
|
||
|
||
# 检查静态资源
|
||
if os.path.exists(static_dir):
|
||
icon_files = list(Path(static_dir).glob("*.png"))
|
||
print(f"✅ 静态资源: static/ ({len(icon_files)} 个图标文件)")
|
||
else:
|
||
print("⚠️ static 目录不存在")
|
||
|
||
print(f"✅ 目录 {base_dir} 验证通过")
|
||
return True
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
print("🔥 多平台客服GUI快速打包工具")
|
||
print("=" * 60)
|
||
|
||
try:
|
||
# 检查依赖
|
||
print("🔍 检查打包依赖...")
|
||
try:
|
||
import subprocess
|
||
result = subprocess.run(['pyinstaller', '--version'],
|
||
capture_output=True, text=True, check=False)
|
||
if result.returncode == 0:
|
||
print(f"✅ PyInstaller 版本: {result.stdout.strip()}")
|
||
else:
|
||
print("❌ PyInstaller 未安装或不可用")
|
||
print("💡 请运行: pip install pyinstaller")
|
||
return False
|
||
except Exception as e:
|
||
print(f"❌ 检查PyInstaller时出错: {e}")
|
||
return False
|
||
|
||
# 清理
|
||
print("\n📍 开始清理阶段...")
|
||
clean_build()
|
||
print("📍 清理阶段完成")
|
||
|
||
# 打包
|
||
print("\n📍 开始打包阶段...")
|
||
if not build_with_command():
|
||
print("❌ 打包阶段失败")
|
||
return False
|
||
print("📍 打包阶段完成")
|
||
|
||
# 验证
|
||
print("\n📍 开始验证阶段...")
|
||
if not verify_result():
|
||
print("❌ 验证阶段失败")
|
||
return False
|
||
print("📍 验证阶段完成")
|
||
|
||
print("\n" + "=" * 60)
|
||
print("🎉 打包完成!")
|
||
|
||
# 智能显示可用路径
|
||
if os.path.exists("dist/MultiPlatformGUI"):
|
||
print("📁 打包结果: dist/MultiPlatformGUI/")
|
||
print("🚀 运行方式: cd dist/MultiPlatformGUI && .\\main.exe")
|
||
print("📦 安装包构建: python installer/build_installer.py")
|
||
elif os.path.exists("dist/main"):
|
||
print("📁 打包结果: dist/main/")
|
||
print("🚀 运行方式: cd dist/main && .\\main.exe")
|
||
print("⚠️ 建议重命名: dist/main -> dist/MultiPlatformGUI")
|
||
|
||
print("=" * 60)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 打包失败: {e}")
|
||
return False
|
||
|
||
|
||
if __name__ == "__main__":
|
||
success = main()
|
||
if success:
|
||
print("\n✅ 可以开始测试了!")
|
||
else:
|
||
print("\n❌ 请检查错误信息并重试") |