#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 快速打包脚本 - 避免spec文件缩进问题 """ import os import subprocess import shutil from pathlib import Path # Build optimization: Control whether to force full clean # Set environment variable FORCE_FULL_CLEAN=true to clean both dist and build directories # Default: false (incremental build, faster) FORCE_FULL_CLEAN = os.getenv('FORCE_FULL_CLEAN', 'false').lower() == 'true' def clean_build(): """清理构建目录""" print("Cleaning old build files...") # 跳过进程结束步骤,避免影响当前脚本运行 print("Skipping process termination step...") # 等待少量时间确保文件句柄释放 import time print("Waiting for file handles to release...") time.sleep(0.5) # Build optimization: Preserve build cache for faster incremental builds # The build directory contains PyInstaller analysis cache # Preserving it can reduce build time from ~7min to ~3min if FORCE_FULL_CLEAN: dirs_to_clean = ['dist', 'build'] print("FORCE_FULL_CLEAN enabled - cleaning all directories including build cache") else: dirs_to_clean = ['dist'] print("Incremental build mode - preserving build cache for faster builds") # Always remove spec file to ensure PyInstaller regenerates it from command-line args spec_file = 'MultiPlatformGUI.spec' if os.path.exists(spec_file): try: os.remove(spec_file) print(f"Removed old spec file: {spec_file}") except Exception as e: print(f"WARNING: Failed to remove spec file: {e}") for dir_name in dirs_to_clean: print(f"Checking directory: {dir_name}") if os.path.exists(dir_name): try: print(f"Deleting: {dir_name}") shutil.rmtree(dir_name) print(f"Deleted: {dir_name}") except PermissionError as e: print(f"WARNING: {dir_name} is in use, attempting force delete... Error: {e}") try: subprocess.run(['rd', '/s', '/q', dir_name], shell=True, check=True) print(f"Force delete successful: {dir_name}") except Exception as e2: print(f"ERROR: Cannot delete {dir_name}: {e2}") print("TIP: Please delete manually or run force_clean_dist.bat") except Exception as e: print(f"ERROR: Failed to delete {dir_name}: {e}") else: print(f"INFO: {dir_name} does not exist, skipping") print("Cleaning phase completed") def build_with_command(): """使用命令行参数直接打包""" print("Starting build...") cmd = [ 'pyinstaller', '--name=MultiPlatformGUI', # 直接使用最终目录名 '--onedir', # 相当于 --exclude-binaries '--windowed', # 相当于 -w '--noconfirm', # Build optimization: Skip confirmation prompts '--log-level=WARN', # Build optimization: Reduce log output (only warnings and errors) '--noupx', # Build optimization: Skip UPX compression (saves time, minimal size impact) '--icon=static/ai_assistant_icon_64.png', # 添加主程序图标 '--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("Build successful!") print("Build result: dist/MultiPlatformGUI/") # Debug: List files in dist directory if os.path.exists('dist/MultiPlatformGUI'): print("DEBUG: Files in dist/MultiPlatformGUI/:") for item in os.listdir('dist/MultiPlatformGUI'): item_path = os.path.join('dist/MultiPlatformGUI', item) if os.path.isfile(item_path): size_mb = os.path.getsize(item_path) / 1024 / 1024 print(f" - {item} ({size_mb:.1f} MB)") else: print(f" - {item}/ (directory)") else: print("WARNING: dist/MultiPlatformGUI directory does not exist!") # 验证输出目录(不需要重命名了) if os.path.exists('dist/MultiPlatformGUI/MultiPlatformGUI.exe'): # PyInstaller 生成的 exe 名称是 MultiPlatformGUI.exe # 重命名为 main.exe(保持兼容性) try: old_exe = 'dist/MultiPlatformGUI/MultiPlatformGUI.exe' new_exe = 'dist/MultiPlatformGUI/main.exe' if os.path.exists(new_exe): os.remove(new_exe) os.rename(old_exe, new_exe) print("Main executable renamed: MultiPlatformGUI.exe -> main.exe") exe_size = os.path.getsize(new_exe) / 1024 / 1024 print(f"Main executable verified: main.exe ({exe_size:.1f} MB)") except Exception as e: print(f"WARNING: Failed to rename main executable: {e}") elif os.path.exists('dist/MultiPlatformGUI/main.exe'): # 如果已经是 main.exe,直接验证 exe_size = os.path.getsize('dist/MultiPlatformGUI/main.exe') / 1024 / 1024 print(f"Main executable verified: main.exe ({exe_size:.1f} MB)") else: print("WARNING: Main executable file not found") # 创建使用说明 try: create_usage_guide() except: create_usage_guide_fallback() return True else: print("ERROR: Build failed") if result.stderr: print("Error messages:") print(result.stderr) return False except Exception as e: print(f"ERROR: Build process error: {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("Usage guide file generated") except: print("WARNING: Usage guide generation failed") 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("Usage guide file generated (fallback path)") except: print("WARNING: Usage guide generation failed") def verify_result(): """验证打包结果""" print("\nVerifying build result...") # 检查MultiPlatformGUI目录(统一输出目录) target_dir = "dist/MultiPlatformGUI" if os.path.exists(target_dir): return _verify_directory(target_dir) else: print("ERROR: Build output directory not found: dist/MultiPlatformGUI") print("TIP: Please check if PyInstaller executed successfully") return False def _verify_directory(base_dir): """验证指定目录的打包结果""" # 检查可能的 exe 名称 exe_path = f"{base_dir}/main.exe" if not os.path.exists(exe_path): exe_path = f"{base_dir}/MultiPlatformGUI.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"Verifying directory: {base_dir}") # 检查主程序 if os.path.exists(exe_path): size = os.path.getsize(exe_path) / 1024 / 1024 # MB print(f"Main executable: main.exe ({size:.1f} MB)") else: print("ERROR: Main executable main.exe does not exist") 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"Dependencies directory: _internal/ ({file_count} files, {dir_count} subdirectories)") else: print("ERROR: _internal directory does not exist") return False # 检查关键DLL(可选) if os.path.exists(dll_path): dll_size = os.path.getsize(dll_path) / 1024 # KB print(f"QianNiu DLL: SaiNiuApi.dll ({dll_size:.1f} KB)") else: print("WARNING: QianNiu DLL does not exist (may be normal)") # 检查静态资源 if os.path.exists(static_dir): icon_files = list(Path(static_dir).glob("*.png")) print(f"Static resources: static/ ({len(icon_files)} icon files)") else: print("WARNING: static directory does not exist") print(f"Directory {base_dir} verification passed") return True def main(): """主函数""" print("Multi-Platform Customer Service GUI Quick Build Tool") print("=" * 60) try: # 检查依赖 print("Checking build dependencies...") try: import subprocess result = subprocess.run(['pyinstaller', '--version'], capture_output=True, text=True, check=False) if result.returncode == 0: print(f"PyInstaller version: {result.stdout.strip()}") else: print("ERROR: PyInstaller not installed or unavailable") print("TIP: Run: pip install pyinstaller") return False except Exception as e: print(f"ERROR: Failed to check PyInstaller: {e}") return False # 清理 print("\nCleaning phase started...") clean_build() print("Cleaning phase completed") # 打包 print("\nBuild phase started...") if not build_with_command(): print("ERROR: Build phase failed") return False print("Build phase completed") # 验证 print("\nVerification phase started...") if not verify_result(): print("ERROR: Verification phase failed") return False print("Verification phase completed") print("\n" + "=" * 60) print("Build completed successfully!") # 显示打包结果 if os.path.exists("dist/MultiPlatformGUI"): print("Build result: dist/MultiPlatformGUI/") print("Run command: cd dist/MultiPlatformGUI && .\\main.exe") print("Create installer: python installer/build_installer.py") print("=" * 60) return True except Exception as e: print(f"ERROR: Build failed: {e}") return False if __name__ == "__main__": success = main() if success: print("\nReady for testing!") else: print("\nERROR: Please check error messages and retry")