182 lines
4.4 KiB
Python
182 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
ETL GUI 打包脚本
|
|
|
|
使用 PyInstaller 将 GUI 应用打包为 Windows EXE
|
|
|
|
用法:
|
|
python build_exe.py [--onefile] [--console] [--clean]
|
|
|
|
参数:
|
|
--onefile 打包为单个 EXE 文件(默认为目录模式)
|
|
--console 显示控制台窗口(调试用)
|
|
--clean 打包前清理旧的构建文件
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def get_project_root() -> Path:
|
|
"""获取项目根目录"""
|
|
return Path(__file__).resolve().parent
|
|
|
|
|
|
def clean_build():
|
|
"""清理旧的构建文件"""
|
|
project_root = get_project_root()
|
|
|
|
dirs_to_clean = [
|
|
project_root / "build",
|
|
project_root / "dist",
|
|
]
|
|
|
|
files_to_clean = [
|
|
project_root / "etl_gui.spec",
|
|
]
|
|
|
|
for d in dirs_to_clean:
|
|
if d.exists():
|
|
print(f"清理目录: {d}")
|
|
shutil.rmtree(d)
|
|
|
|
for f in files_to_clean:
|
|
if f.exists():
|
|
print(f"清理文件: {f}")
|
|
f.unlink()
|
|
|
|
|
|
def build_exe(onefile: bool = False, console: bool = False):
|
|
"""构建 EXE"""
|
|
project_root = get_project_root()
|
|
|
|
# 主入口
|
|
main_script = project_root / "gui" / "main.py"
|
|
|
|
# 资源文件
|
|
resources_dir = project_root / "gui" / "resources"
|
|
database_dir = project_root / "database"
|
|
|
|
# 构建 PyInstaller 命令
|
|
# 使用 ASCII 名称避免 Windows 控制台编码问题
|
|
cmd = [
|
|
sys.executable, "-m", "PyInstaller",
|
|
"--name", "ETL_Manager",
|
|
"--noconfirm",
|
|
]
|
|
|
|
# 单文件或目录模式
|
|
if onefile:
|
|
cmd.append("--onefile")
|
|
else:
|
|
cmd.append("--onedir")
|
|
|
|
# 窗口模式
|
|
if not console:
|
|
cmd.append("--windowed")
|
|
|
|
# 添加数据文件
|
|
# 样式表
|
|
if resources_dir.exists():
|
|
cmd.extend(["--add-data", f"{resources_dir};gui/resources"])
|
|
|
|
# 数据库 SQL 文件
|
|
if database_dir.exists():
|
|
for sql_file in database_dir.glob("*.sql"):
|
|
cmd.extend(["--add-data", f"{sql_file};database"])
|
|
|
|
# 隐式导入
|
|
hidden_imports = [
|
|
"PySide6.QtCore",
|
|
"PySide6.QtGui",
|
|
"PySide6.QtWidgets",
|
|
"psycopg2",
|
|
"psycopg2.extras",
|
|
"psycopg2.extensions",
|
|
# GUI 模块
|
|
"gui.models.task_model",
|
|
"gui.models.schedule_model",
|
|
"gui.utils.cli_builder",
|
|
"gui.utils.config_helper",
|
|
"gui.utils.app_settings",
|
|
"gui.workers.task_worker",
|
|
"gui.workers.db_worker",
|
|
"gui.widgets.settings_dialog",
|
|
]
|
|
for imp in hidden_imports:
|
|
cmd.extend(["--hidden-import", imp])
|
|
|
|
# 排除不需要的模块(减小体积)
|
|
excludes = [
|
|
"matplotlib",
|
|
"numpy",
|
|
"pandas",
|
|
"scipy",
|
|
"PIL",
|
|
"cv2",
|
|
"tkinter",
|
|
]
|
|
for exc in excludes:
|
|
cmd.extend(["--exclude-module", exc])
|
|
|
|
# 工作目录
|
|
cmd.extend(["--workpath", str(project_root / "build")])
|
|
cmd.extend(["--distpath", str(project_root / "dist")])
|
|
cmd.extend(["--specpath", str(project_root)])
|
|
|
|
# 主脚本
|
|
cmd.append(str(main_script))
|
|
|
|
print("执行命令:")
|
|
print(" ".join(cmd))
|
|
print()
|
|
|
|
# 执行打包
|
|
result = subprocess.run(cmd, cwd=str(project_root))
|
|
|
|
if result.returncode == 0:
|
|
print()
|
|
print("=" * 50)
|
|
print("打包成功!")
|
|
print(f"输出目录: {project_root / 'dist'}")
|
|
print("=" * 50)
|
|
else:
|
|
print()
|
|
print("打包失败,请检查错误信息")
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="ETL GUI 打包工具")
|
|
parser.add_argument("--onefile", action="store_true", help="打包为单个 EXE")
|
|
parser.add_argument("--console", action="store_true", help="显示控制台窗口")
|
|
parser.add_argument("--clean", action="store_true", help="打包前清理")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# 检查 PyInstaller
|
|
try:
|
|
import PyInstaller
|
|
print(f"PyInstaller 版本: {PyInstaller.__version__}")
|
|
except ImportError:
|
|
print("错误: 未安装 PyInstaller")
|
|
print("请运行: pip install pyinstaller")
|
|
sys.exit(1)
|
|
|
|
# 清理
|
|
if args.clean:
|
|
clean_build()
|
|
|
|
# 构建
|
|
build_exe(onefile=args.onefile, console=args.console)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|