2025-11-02 22:06:42 +08:00
|
|
|
|
#!/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,
|
2025-11-03 22:28:58 +08:00
|
|
|
|
"--onedir", # 构建单个可执行文件
|
2025-11-02 22:06:42 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# 设置控制台选项
|
|
|
|
|
|
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())
|