#!/usr/bin/env python # -*- coding: utf-8 -*- """ 应用程序构建脚本 使用PyInstaller将Python应用打包为可执行文件 """ import os import sys import subprocess import shutil from pathlib import Path # 项目根目录 PROJECT_ROOT = Path(__file__).parent.absolute() # 主程序入口 MAIN_SCRIPT = "main.py" # 输出目录 OUTPUT_DIR = PROJECT_ROOT / "dist" BUILD_DIR = PROJECT_ROOT / "build" # 应用名称 APP_NAME = "LeonPan" APP_NAME_CONSOLE = "LeonPan_Console" # 图标文件 ICON_FILE = "logo.png" # 需要包含的额外文件和目录 EXTRA_DATA = [ ("_internal", "_internal"), # 编辑器和相关资源 ("logo.png", "logo.png"), # 应用图标 ("welcome_video.py", "welcome_video.py"), # 欢迎视频脚本 ("app/resource", "app/resource") # 应用资源 ] def run_command(command, cwd=None): """执行命令并返回结果""" print(f"执行命令: {' '.join(command)}") try: result = subprocess.run( command, cwd=cwd or PROJECT_ROOT, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) print(f"命令执行成功: {result.stdout[:100]}..." if len(result.stdout) > 100 else f"命令执行成功: {result.stdout}") return True except subprocess.CalledProcessError as e: print(f"命令执行失败: {e.stderr}") return False def check_dependencies(): """检查构建依赖是否已安装""" print("检查构建依赖...") # 检查pyinstaller try: import PyInstaller print(f"PyInstaller 已安装,版本: {PyInstaller.__version__}") except ImportError: print("PyInstaller 未安装,正在安装...") if not run_command([sys.executable, "-m", "pip", "install", "pyinstaller"]): print("安装PyInstaller失败,请手动安装后重试") return False # 安装项目依赖 requirements_file = PROJECT_ROOT / "requirements.txt" if requirements_file.exists(): print(f"安装项目依赖: {requirements_file}") if not run_command([sys.executable, "-m", "pip", "install", "-r", str(requirements_file)]): print("安装项目依赖失败,请检查requirements.txt文件") return False return True def clean_output(): """清理之前的构建输出""" print("清理之前的构建输出...") # 删除dist目录 if OUTPUT_DIR.exists(): try: shutil.rmtree(OUTPUT_DIR) print(f"已删除目录: {OUTPUT_DIR}") except Exception as e: print(f"删除 {OUTPUT_DIR} 失败: {e}") # 删除build目录 if BUILD_DIR.exists(): try: shutil.rmtree(BUILD_DIR) print(f"已删除目录: {BUILD_DIR}") except Exception as e: print(f"删除 {BUILD_DIR} 失败: {e}") # 删除.spec文件 spec_file = PROJECT_ROOT / f"{APP_NAME}.spec" if spec_file.exists(): try: os.remove(spec_file) print(f"已删除文件: {spec_file}") except Exception as e: print(f"删除 {spec_file} 失败: {e}") def build_app(): """使用PyInstaller构建应用""" print("开始构建应用...") # 构建两个版本:无控制台版本和有控制台版本 builds = [ { "name": APP_NAME, # 无控制台版本 "console": False }, { "name": APP_NAME_CONSOLE, # 有控制台版本 "console": True } ] # 为每个版本执行构建 for build_config in builds: build_name = build_config["name"] use_console = build_config["console"] print(f"\n构建版本: {build_name} (控制台: {'启用' if use_console else '禁用'})") # 构建pyinstaller命令 cmd = [ sys.executable, "-m", "PyInstaller", MAIN_SCRIPT, "--name", build_name, "--onedir", # 构建单个可执行文件 ] # 设置控制台选项 if use_console: cmd.append("--console") else: cmd.extend(["--windowed", "--noconsole"]) # 添加图标 icon_path = PROJECT_ROOT / ICON_FILE if icon_path.exists(): cmd.extend(["--icon", str(icon_path)]) print(f"使用图标: {icon_path}") else: print(f"警告: 图标文件 {icon_path} 不存在,将使用默认图标") # 添加额外的数据文件 for src, dst in EXTRA_DATA: src_path = PROJECT_ROOT / src if src_path.exists(): cmd.extend(["--add-data", f"{src_path}{os.pathsep}{dst}"]) print(f"添加额外数据: {src} -> {dst}") else: print(f"警告: 额外数据 {src_path} 不存在,跳过") # 执行构建命令 if not run_command(cmd): print(f"{build_name} 构建失败!") return False print("所有版本构建成功!") return True def copy_additional_files(): """复制额外的文件到输出目录""" print("复制额外文件到输出目录...") if not OUTPUT_DIR.exists(): print(f"错误: 输出目录 {OUTPUT_DIR} 不存在") return False # 复制_internal目录(两个可执行文件共用) internal_src = PROJECT_ROOT / "_internal" internal_dst = OUTPUT_DIR / "_internal" if internal_src.exists(): try: if internal_dst.exists(): shutil.rmtree(internal_dst) shutil.copytree(internal_src, internal_dst) print(f"已复制 _internal 目录到 {internal_dst}(两个可执行文件共用)") except Exception as e: print(f"复制 _internal 目录失败: {e}") return True def main(): """主函数""" print(f"开始构建 {APP_NAME} 应用...") print(f"项目根目录: {PROJECT_ROOT}") # 检查依赖 if not check_dependencies(): print("依赖检查失败,终止构建") return 1 # 清理输出 clean_output() # 构建应用 if not build_app(): print("应用构建失败,终止") return 1 # 复制额外文件 copy_additional_files() print("\n构建完成!") print(f"无控制台版本可执行文件: {OUTPUT_DIR / APP_NAME}{'.exe' if sys.platform == 'win32' else ''}") print(f"有控制台版本可执行文件: {OUTPUT_DIR / APP_NAME_CONSOLE}{'.exe' if sys.platform == 'win32' else ''}") print(f"共用资源目录: {OUTPUT_DIR / '_internal'}") return 0 if __name__ == "__main__": sys.exit(main())