#!/usr/bin/env python3 """ 构建脚本 - 将Python Core打包成独立可执行文件 """ import os import sys import subprocess import shutil import platform from pathlib import Path def get_project_root(): """获取项目根目录""" current_file = Path(__file__).resolve() # 从 scripts/build_python_core.py 回到项目根目录 return current_file.parent.parent def install_dependencies(): """安装构建依赖""" print("📦 Installing build dependencies...") dependencies = [ 'pyinstaller>=5.0', 'requests', 'Pillow', 'opencv-python-headless', # 使用headless版本减少依赖 ] for dep in dependencies: print(f"Installing {dep}...") result = subprocess.run([ sys.executable, '-m', 'pip', 'install', dep ], capture_output=True, text=True) if result.returncode != 0: print(f"❌ Failed to install {dep}") print(result.stderr) return False else: print(f"✅ Installed {dep}") return True def clean_build_directory(python_core_dir): """清理构建目录""" print("🧹 Cleaning build directories...") dirs_to_clean = ['build', 'dist', '__pycache__'] for dir_name in dirs_to_clean: dir_path = python_core_dir / dir_name if dir_path.exists(): shutil.rmtree(dir_path) print(f"Removed {dir_path}") def build_executable(python_core_dir): """构建可执行文件""" print("🔨 Building executable...") spec_file = python_core_dir / 'build.spec' if not spec_file.exists(): print(f"❌ Spec file not found: {spec_file}") return False # 运行PyInstaller cmd = [ sys.executable, '-m', 'PyInstaller', '--clean', '--noconfirm', str(spec_file) ] print(f"Running: {' '.join(cmd)}") result = subprocess.run( cmd, cwd=python_core_dir, capture_output=True, text=True ) if result.returncode != 0: print("❌ Build failed!") print("STDOUT:", result.stdout) print("STDERR:", result.stderr) return False print("✅ Build completed successfully!") return True def copy_to_sidecar_directory(project_root, python_core_dir): """复制可执行文件到Tauri sidecar目录""" print("📁 Copying executable to sidecar directory...") # 确定可执行文件名 system = platform.system().lower() if system == 'windows': exe_name = 'mixvideo-python-core.exe' else: exe_name = 'mixvideo-python-core' # 源文件路径 source_exe = python_core_dir / 'dist' / exe_name if not source_exe.exists(): print(f"❌ Executable not found: {source_exe}") return False # 目标目录 sidecar_dir = project_root / 'src-tauri' / 'binaries' sidecar_dir.mkdir(exist_ok=True) # 复制文件 target_exe = sidecar_dir / exe_name shutil.copy2(source_exe, target_exe) # 在Unix系统上设置执行权限 if system != 'windows': os.chmod(target_exe, 0o755) print(f"✅ Copied to: {target_exe}") return True def update_tauri_config(project_root): """更新Tauri配置文件以包含sidecar""" print("⚙️ Updating Tauri configuration...") import json config_file = project_root / 'src-tauri' / 'tauri.conf.json' if not config_file.exists(): print(f"❌ Tauri config not found: {config_file}") return False # 读取配置 with open(config_file, 'r', encoding='utf-8') as f: config = json.load(f) # 添加sidecar配置 if 'bundle' not in config: config['bundle'] = {} if 'externalBin' not in config['bundle']: config['bundle']['externalBin'] = [] # 检查是否已经存在 sidecar_name = 'mixvideo-python-core' existing = any( item.get('name') == sidecar_name for item in config['bundle']['externalBin'] if isinstance(item, dict) ) if not existing: config['bundle']['externalBin'].append({ "name": sidecar_name, "src": f"binaries/{sidecar_name}", "targets": ["all"] }) # 写回配置 with open(config_file, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) print("✅ Updated Tauri configuration") else: print("ℹ️ Sidecar already configured in Tauri") return True def verify_build(project_root): """验证构建结果""" print("🔍 Verifying build...") system = platform.system().lower() exe_name = 'mixvideo-python-core.exe' if system == 'windows' else 'mixvideo-python-core' sidecar_exe = project_root / 'src-tauri' / 'binaries' / exe_name if not sidecar_exe.exists(): print(f"❌ Sidecar executable not found: {sidecar_exe}") return False # 测试执行 try: result = subprocess.run([ str(sidecar_exe), '--version' ], capture_output=True, text=True, timeout=10) if result.returncode == 0: print("✅ Executable is working correctly") print(f"Version output: {result.stdout.strip()}") return True else: print(f"❌ Executable test failed: {result.stderr}") return False except subprocess.TimeoutExpired: print("❌ Executable test timed out") return False except Exception as e: print(f"❌ Executable test error: {e}") return False def main(): """主函数""" print("🚀 Building MixVideo V2 Python Core...") project_root = get_project_root() python_core_dir = project_root / 'python_core' print(f"Project root: {project_root}") print(f"Python core directory: {python_core_dir}") if not python_core_dir.exists(): print(f"❌ Python core directory not found: {python_core_dir}") sys.exit(1) # 步骤1: 安装依赖 if not install_dependencies(): print("❌ Failed to install dependencies") sys.exit(1) # 步骤2: 清理构建目录 clean_build_directory(python_core_dir) # 步骤3: 构建可执行文件 if not build_executable(python_core_dir): print("❌ Failed to build executable") sys.exit(1) # 步骤4: 复制到sidecar目录 if not copy_to_sidecar_directory(project_root, python_core_dir): print("❌ Failed to copy executable") sys.exit(1) # 步骤5: 更新Tauri配置 if not update_tauri_config(project_root): print("❌ Failed to update Tauri configuration") sys.exit(1) # 步骤6: 验证构建 if not verify_build(project_root): print("❌ Build verification failed") sys.exit(1) print("🎉 Build completed successfully!") print("\n📋 Next steps:") print("1. Test the sidecar: cargo tauri dev") print("2. Build the app: cargo tauri build") print("3. The Python core will be bundled with your app") if __name__ == "__main__": main()