#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 快速打包脚本 - 避免spec文件缩进问题 """ import os import subprocess import shutil 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=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', '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'): shutil.rmtree('dist/MultiPlatformGUI') os.rename('dist/main', 'dist/MultiPlatformGUI') print("✅ 已重命名为: dist/MultiPlatformGUI/") except Exception as e: print(f"⚠️ 重命名失败: {e}") print("💡 可以手动重命名: dist/main -> dist/MultiPlatformGUI") 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) ## 🎯 特点 - 支持多平台同时监听 - 无平台间冲突 - 自动生成日志文件 ## 📁 文件结构 ``` MultiPlatformGUI/ ├── main.exe # 主程序 ├── qianniu_exe_*.log # 运行日志 └── _internal/ # 程序依赖文件 ├── Utils/ │ ├── PythonNew32/ # 千牛DLL文件 │ ├── JD/ # 京东模块 │ ├── Dy/ # 抖音模块 │ └── Pdd/ # 拼多多模块 └── ... ``` """ 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,再检查main目录 base_dirs = ["dist/MultiPlatformGUI", "dist/main"] for base_dir in base_dirs: if os.path.exists(base_dir): exe_path = f"{base_dir}/main.exe" dll_path = f"{base_dir}/_internal/Utils/PythonNew32/SaiNiuApi.dll" if os.path.exists(exe_path): size = os.path.getsize(exe_path) print(f"✅ 主程序: {exe_path} ({size:,} bytes)") if os.path.exists(dll_path): dll_size = os.path.getsize(dll_path) print(f"✅ 千牛DLL: SaiNiuApi.dll ({dll_size:,} bytes)") print(f"📁 有效路径: {base_dir}/") print("✅ 验证通过") return True else: print("❌ 千牛DLL不存在") else: print(f"❌ {exe_path} 不存在") print("❌ 未找到有效的打包结果") return False 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") elif os.path.exists("dist/main"): print("📁 打包结果: dist/main/") print("🚀 运行方式: cd dist/main && .\\main.exe") print("💡 提示: 可手动重命名为 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❌ 请检查错误信息并重试")