初始化
This commit is contained in:
391
build_app.py
Normal file
391
build_app.py
Normal file
@@ -0,0 +1,391 @@
|
||||
"""
|
||||
奶酪云工具箱 - 打包脚本
|
||||
支持 Windows (7/10/11) 32位/64位, macOS 打包
|
||||
|
||||
使用方法:
|
||||
python build_app.py # 打包当前平台
|
||||
python build_app.py --all # 尝试打包所有平台(需要对应环境)
|
||||
python build_app.py --platform win64 # 指定平台
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import platform
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# 项目配置
|
||||
APP_NAME = "奶酪云工具箱"
|
||||
APP_NAME_EN = "CheeseCloudTools"
|
||||
VERSION = "1.0.0"
|
||||
AUTHOR = "奶酪源码"
|
||||
DESCRIPTION = "多功能图片/PDF/Excel处理工具"
|
||||
|
||||
# 路径配置
|
||||
PROJECT_ROOT = Path(__file__).parent
|
||||
DIST_DIR = PROJECT_ROOT / "dist"
|
||||
BUILD_DIR = PROJECT_ROOT / "build"
|
||||
ICON_SOURCE = PROJECT_ROOT / "image" / "生成奶酪商城官方店介绍.png"
|
||||
ICON_DIR = PROJECT_ROOT / "build_icons"
|
||||
|
||||
# 需要包含的数据文件
|
||||
DATA_FILES = [
|
||||
("resources", "resources"),
|
||||
("image", "image"),
|
||||
]
|
||||
|
||||
|
||||
def create_icon_dir():
|
||||
"""创建图标目录"""
|
||||
ICON_DIR.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def convert_png_to_ico(png_path: Path, ico_path: Path, sizes=None):
|
||||
"""将PNG转换为ICO格式(Windows图标)"""
|
||||
if sizes is None:
|
||||
sizes = [(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)]
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
img = Image.open(png_path)
|
||||
|
||||
# 创建不同尺寸的图标
|
||||
icon_images = []
|
||||
for size in sizes:
|
||||
resized = img.copy()
|
||||
resized.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
# 确保尺寸正确
|
||||
if resized.size != size:
|
||||
new_img = Image.new('RGBA', size, (0, 0, 0, 0))
|
||||
offset = ((size[0] - resized.size[0]) // 2,
|
||||
(size[1] - resized.size[1]) // 2)
|
||||
new_img.paste(resized, offset)
|
||||
resized = new_img
|
||||
icon_images.append(resized)
|
||||
|
||||
# 保存为ICO
|
||||
icon_images[0].save(
|
||||
ico_path,
|
||||
format='ICO',
|
||||
sizes=[(img.size[0], img.size[1]) for img in icon_images]
|
||||
)
|
||||
print(f"✅ 已创建 Windows 图标: {ico_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 创建 ICO 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def convert_png_to_icns(png_path: Path, icns_path: Path):
|
||||
"""将PNG转换为ICNS格式(macOS图标)"""
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
img = Image.open(png_path)
|
||||
|
||||
# macOS 需要特定尺寸
|
||||
sizes = [16, 32, 64, 128, 256, 512, 1024]
|
||||
|
||||
# 创建临时 iconset 目录
|
||||
iconset_dir = icns_path.parent / f"{icns_path.stem}.iconset"
|
||||
iconset_dir.mkdir(exist_ok=True)
|
||||
|
||||
for size in sizes:
|
||||
# 标准分辨率
|
||||
resized = img.copy()
|
||||
resized.thumbnail((size, size), Image.Resampling.LANCZOS)
|
||||
resized.save(iconset_dir / f"icon_{size}x{size}.png")
|
||||
|
||||
# 2x 分辨率 (Retina)
|
||||
if size <= 512:
|
||||
resized_2x = img.copy()
|
||||
resized_2x.thumbnail((size * 2, size * 2), Image.Resampling.LANCZOS)
|
||||
resized_2x.save(iconset_dir / f"icon_{size}x{size}@2x.png")
|
||||
|
||||
# 使用 iconutil 转换(仅macOS可用)
|
||||
if platform.system() == "Darwin":
|
||||
subprocess.run(["iconutil", "-c", "icns", str(iconset_dir), "-o", str(icns_path)])
|
||||
print(f"✅ 已创建 macOS 图标: {icns_path}")
|
||||
else:
|
||||
# 在非macOS上,复制PNG作为替代
|
||||
shutil.copy(png_path, icns_path.with_suffix('.png'))
|
||||
print(f"⚠️ 非macOS环境,已复制PNG: {icns_path.with_suffix('.png')}")
|
||||
|
||||
# 清理临时目录
|
||||
shutil.rmtree(iconset_dir, ignore_errors=True)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 创建 ICNS 失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def prepare_icons():
|
||||
"""准备各平台图标"""
|
||||
create_icon_dir()
|
||||
|
||||
ico_path = ICON_DIR / "app.ico"
|
||||
icns_path = ICON_DIR / "app.icns"
|
||||
|
||||
if ICON_SOURCE.exists():
|
||||
convert_png_to_ico(ICON_SOURCE, ico_path)
|
||||
convert_png_to_icns(ICON_SOURCE, icns_path)
|
||||
else:
|
||||
print(f"⚠️ 图标源文件不存在: {ICON_SOURCE}")
|
||||
|
||||
return ico_path, icns_path
|
||||
|
||||
|
||||
def get_pyinstaller_args(target_platform: str, ico_path: Path, icns_path: Path):
|
||||
"""获取 PyInstaller 参数"""
|
||||
|
||||
# 基础参数
|
||||
args = [
|
||||
"pyinstaller",
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
"--name", f"{APP_NAME_EN}",
|
||||
"--windowed", # GUI应用,不显示控制台
|
||||
"--onedir", # 打包为目录(更稳定)
|
||||
]
|
||||
|
||||
# 添加数据文件
|
||||
for src, dst in DATA_FILES:
|
||||
src_path = PROJECT_ROOT / src
|
||||
if src_path.exists():
|
||||
args.extend(["--add-data", f"{src_path}{os.pathsep}{dst}"])
|
||||
|
||||
# 隐藏导入
|
||||
hidden_imports = [
|
||||
"PySide6.QtSvg",
|
||||
"PySide6.QtSvgWidgets",
|
||||
"PIL",
|
||||
"PIL.Image",
|
||||
"fitz",
|
||||
"pandas",
|
||||
"openpyxl",
|
||||
"matplotlib",
|
||||
"matplotlib.backends.backend_qtagg",
|
||||
]
|
||||
for hi in hidden_imports:
|
||||
args.extend(["--hidden-import", hi])
|
||||
|
||||
# 平台特定参数
|
||||
if target_platform.startswith("win"):
|
||||
if ico_path.exists():
|
||||
args.extend(["--icon", str(ico_path)])
|
||||
# Windows 版本信息
|
||||
args.extend([
|
||||
"--version-file", str(PROJECT_ROOT / "version_info.txt"),
|
||||
])
|
||||
elif target_platform.startswith("mac"):
|
||||
if icns_path.exists():
|
||||
args.extend(["--icon", str(icns_path)])
|
||||
# macOS bundle 标识符
|
||||
args.extend([
|
||||
"--osx-bundle-identifier", "com.naiyuanma.cheesetools",
|
||||
])
|
||||
|
||||
# 输出目录
|
||||
output_dir = DIST_DIR / target_platform
|
||||
args.extend(["--distpath", str(output_dir)])
|
||||
args.extend(["--workpath", str(BUILD_DIR / target_platform)])
|
||||
args.extend(["--specpath", str(BUILD_DIR)])
|
||||
|
||||
# 主入口文件
|
||||
args.append(str(PROJECT_ROOT / "main.py"))
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def create_version_info():
|
||||
"""创建 Windows 版本信息文件"""
|
||||
version_parts = VERSION.split(".")
|
||||
while len(version_parts) < 4:
|
||||
version_parts.append("0")
|
||||
|
||||
version_tuple = ", ".join(version_parts)
|
||||
|
||||
content = f'''# UTF-8
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
filevers=({version_tuple}),
|
||||
prodvers=({version_tuple}),
|
||||
mask=0x3f,
|
||||
flags=0x0,
|
||||
OS=0x40004,
|
||||
fileType=0x1,
|
||||
subtype=0x0,
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo(
|
||||
[
|
||||
StringTable(
|
||||
u'080404b0',
|
||||
[
|
||||
StringStruct(u'CompanyName', u'{AUTHOR}'),
|
||||
StringStruct(u'FileDescription', u'{DESCRIPTION}'),
|
||||
StringStruct(u'FileVersion', u'{VERSION}'),
|
||||
StringStruct(u'InternalName', u'{APP_NAME_EN}'),
|
||||
StringStruct(u'LegalCopyright', u'Copyright (C) 2024 {AUTHOR}'),
|
||||
StringStruct(u'OriginalFilename', u'{APP_NAME_EN}.exe'),
|
||||
StringStruct(u'ProductName', u'{APP_NAME}'),
|
||||
StringStruct(u'ProductVersion', u'{VERSION}'),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
VarFileInfo([VarStruct(u'Translation', [2052, 1200])])
|
||||
]
|
||||
)
|
||||
'''
|
||||
|
||||
version_file = PROJECT_ROOT / "version_info.txt"
|
||||
with open(version_file, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print(f"✅ 已创建版本信息文件: {version_file}")
|
||||
|
||||
|
||||
def build_for_platform(target_platform: str):
|
||||
"""为指定平台打包"""
|
||||
print(f"\n{'='*50}")
|
||||
print(f"🔨 开始打包: {target_platform}")
|
||||
print(f"{'='*50}")
|
||||
|
||||
# 准备图标
|
||||
ico_path, icns_path = prepare_icons()
|
||||
|
||||
# 创建版本信息(Windows)
|
||||
if target_platform.startswith("win"):
|
||||
create_version_info()
|
||||
|
||||
# 获取 PyInstaller 参数
|
||||
args = get_pyinstaller_args(target_platform, ico_path, icns_path)
|
||||
|
||||
print(f"📦 执行命令: {' '.join(args)}")
|
||||
|
||||
# 执行打包
|
||||
try:
|
||||
result = subprocess.run(args, check=True)
|
||||
print(f"\n✅ {target_platform} 打包成功!")
|
||||
print(f"📁 输出目录: {DIST_DIR / target_platform}")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\n❌ {target_platform} 打包失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_current_platform():
|
||||
"""获取当前平台标识"""
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "windows":
|
||||
if machine in ["amd64", "x86_64"]:
|
||||
return "win64"
|
||||
else:
|
||||
return "win32"
|
||||
elif system == "darwin":
|
||||
if machine == "arm64":
|
||||
return "mac_arm64"
|
||||
else:
|
||||
return "mac_x64"
|
||||
elif system == "linux":
|
||||
if machine in ["amd64", "x86_64"]:
|
||||
return "linux64"
|
||||
else:
|
||||
return "linux32"
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def clean_build():
|
||||
"""清理构建目录"""
|
||||
print("🧹 清理构建目录...")
|
||||
if BUILD_DIR.exists():
|
||||
shutil.rmtree(BUILD_DIR)
|
||||
if ICON_DIR.exists():
|
||||
shutil.rmtree(ICON_DIR)
|
||||
print("✅ 清理完成")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="奶酪云工具箱打包脚本")
|
||||
parser.add_argument("--platform", "-p",
|
||||
choices=["win32", "win64", "mac_x64", "mac_arm64", "current"],
|
||||
default="current",
|
||||
help="目标平台")
|
||||
parser.add_argument("--all", "-a", action="store_true",
|
||||
help="打包所有平台(需要对应环境)")
|
||||
parser.add_argument("--clean", "-c", action="store_true",
|
||||
help="清理构建目录")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"""
|
||||
╔══════════════════════════════════════════════╗
|
||||
║ 奶酪云工具箱 - 打包工具 ║
|
||||
║ 版本: {VERSION} ║
|
||||
╚══════════════════════════════════════════════╝
|
||||
""")
|
||||
|
||||
if args.clean:
|
||||
clean_build()
|
||||
return
|
||||
|
||||
# 检查 PyInstaller
|
||||
try:
|
||||
import PyInstaller
|
||||
print(f"✅ PyInstaller 版本: {PyInstaller.__version__}")
|
||||
except ImportError:
|
||||
print("❌ 未安装 PyInstaller,正在安装...")
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "pyinstaller"])
|
||||
|
||||
# 确定要打包的平台
|
||||
if args.all:
|
||||
# 打包当前系统支持的所有架构
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
# Windows 可以同时打包 32位和64位(如果有对应Python)
|
||||
platforms = ["win64"] # 默认打包64位
|
||||
print(f"📋 Windows 系统将打包: {platforms}")
|
||||
print(f"⚠️ 32位版本需要在32位Python环境中单独打包")
|
||||
elif system == "darwin":
|
||||
# macOS
|
||||
machine = platform.machine().lower()
|
||||
if machine == "arm64":
|
||||
platforms = ["mac_arm64"]
|
||||
else:
|
||||
platforms = ["mac_x64"]
|
||||
print(f"📋 macOS 系统将打包: {platforms}")
|
||||
else:
|
||||
platforms = [get_current_platform()]
|
||||
print(f"📋 当前平台: {platforms}")
|
||||
elif args.platform == "current":
|
||||
platforms = [get_current_platform()]
|
||||
else:
|
||||
platforms = [args.platform]
|
||||
|
||||
# 创建输出目录
|
||||
DIST_DIR.mkdir(exist_ok=True)
|
||||
BUILD_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 打包
|
||||
success_count = 0
|
||||
for plat in platforms:
|
||||
if build_for_platform(plat):
|
||||
success_count += 1
|
||||
|
||||
print(f"\n{'='*50}")
|
||||
print(f"📊 打包完成: {success_count}/{len(platforms)} 成功")
|
||||
print(f"📁 输出目录: {DIST_DIR}")
|
||||
print(f"{'='*50}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user