266 lines
7.6 KiB
Python
266 lines
7.6 KiB
Python
#!/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, use_simple=False):
|
||
"""构建可执行文件"""
|
||
print("🔨 Building executable...")
|
||
|
||
if use_simple:
|
||
spec_file = python_core_dir / 'build_simple.spec'
|
||
print("Using simplified build for testing...")
|
||
else:
|
||
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: 构建可执行文件
|
||
# 首先尝试简化版本
|
||
print("Trying simplified build first...")
|
||
if not build_executable(python_core_dir, use_simple=True):
|
||
print("❌ Simplified build failed, trying full build...")
|
||
if not build_executable(python_core_dir, use_simple=False):
|
||
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()
|