224 lines
6.7 KiB
Python
224 lines
6.7 KiB
Python
#!/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()) |