generated from hulk/gd32f4xx_template_cmake_vscode
Add PA11 power-hold output (PMOS self-latch keep-alive)
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
把 filmforge_gui.py 打包成单文件 exe(工具名: FilmForge)
|
||||
================================
|
||||
使用 PyInstaller --onefile --noconsole: Python 解释器、PySide6 和
|
||||
PySide6-Fluent-Widgets 全部打进一个 exe, 目标机器无需安装依赖。
|
||||
|
||||
用法:
|
||||
python build_exe.py 自动安装 PyInstaller(如缺)并打包 + 生成发布 zip
|
||||
|
||||
产物:
|
||||
release/FilmForge.exe 完整可直接运行的发布文件夹成员
|
||||
release/FilmForge-GUI-v<版本>-win64.zip exe + README 的发布包(供 publish_gitea.py 上传)
|
||||
首次运行 onefile exe 会先解压到临时目录, 启动比脚本慢一两秒, 属正常现象。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
# 本脚本位于固件仓 GUI/, 打包产物输出到 GUI/release/
|
||||
SRC = os.path.dirname(os.path.abspath(__file__))
|
||||
DIST = os.path.join(SRC, 'release')
|
||||
WORK = os.path.join(SRC, 'build')
|
||||
ENTRY = os.path.join(SRC, 'filmforge_gui.py')
|
||||
README = os.path.join(SRC, 'README.md')
|
||||
ICO = os.path.join(SRC, 'logos', 'filmforge.ico')
|
||||
|
||||
|
||||
def app_version():
|
||||
"""从 app_fluent.py 源码读 APP_VERSION(避免构建机导入 PySide6)"""
|
||||
with open(os.path.join(SRC, 'app_fluent.py'), encoding='utf-8') as f:
|
||||
m = re.search(r'APP_VERSION\s*=\s*"([^"]+)"', f.read())
|
||||
return m.group(1) if m else '0.0.0'
|
||||
|
||||
|
||||
def ensure_pyinstaller():
|
||||
try:
|
||||
import PyInstaller # noqa: F401
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
print('PyInstaller 未安装, 正在安装...')
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'pyinstaller'])
|
||||
|
||||
|
||||
def extra_collect_args():
|
||||
"""Fluent Widgets 的主题资源必须收集; PyInstaller 自带 Qt hooks 会处理 PySide6。"""
|
||||
try:
|
||||
import qfluentwidgets # noqa: F401
|
||||
except ImportError:
|
||||
print('警告: 未安装 PySide6-Fluent-Widgets, 请先执行 pip install PySide6-Fluent-Widgets')
|
||||
return []
|
||||
return ['--collect-all', 'qfluentwidgets']
|
||||
|
||||
|
||||
def qt_icu_args():
|
||||
"""Qt6Core imports the unversioned Windows ICU API. PyInstaller can
|
||||
otherwise pick Poppler's ICU 78 from PATH, whose suffixed exports make
|
||||
QtCore.pyd fail with WinError 127 on startup."""
|
||||
if os.name == 'nt' and os.path.exists(r'C:\Windows\System32\icuuc.dll'):
|
||||
return ['--add-binary', r'C:\Windows\System32\icuuc.dll;.']
|
||||
return []
|
||||
|
||||
|
||||
def remove_stale(exe_path):
|
||||
"""删除旧 exe; 若被占用(上次验证的进程还在), 先结束同名进程再重试"""
|
||||
if not os.path.exists(exe_path):
|
||||
return
|
||||
try:
|
||||
os.remove(exe_path)
|
||||
return
|
||||
except PermissionError:
|
||||
pass
|
||||
import time
|
||||
print(f'{exe_path} 被占用, 结束残留进程后重试...')
|
||||
subprocess.run(['taskkill', '/F', '/IM', os.path.basename(exe_path)],
|
||||
capture_output=True)
|
||||
time.sleep(1.0)
|
||||
os.remove(exe_path)
|
||||
|
||||
|
||||
def build():
|
||||
remove_stale(os.path.join(DIST, 'FilmForge.exe'))
|
||||
cmd = [sys.executable, '-m', 'PyInstaller',
|
||||
'--onefile', '--noconsole', '--clean', '--name', 'FilmForge',
|
||||
'--distpath', DIST, '--workpath', WORK, '--specpath', WORK,
|
||||
] + extra_collect_args() + qt_icu_args() + [ENTRY]
|
||||
if os.path.exists(ICO):
|
||||
# exe 文件图标 + 运行时窗口图标资源(从 _MEIPASS 读取)
|
||||
cmd += ['--icon', ICO, '--add-data', f'{ICO};.']
|
||||
else:
|
||||
print('提示: 未找到 logos/filmforge.ico, 本次打包不带图标')
|
||||
print('\n>>', ' '.join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
out = os.path.join(DIST, 'FilmForge.exe')
|
||||
print(f'完成: {out} ({os.path.getsize(out) / 1024 / 1024:.1f} MB)')
|
||||
return out
|
||||
|
||||
|
||||
def make_zip(exe_path):
|
||||
"""exe + README 打成发布 zip(命名供 publish_gitea.py 从文件名推 tag)"""
|
||||
version = app_version()
|
||||
zip_path = os.path.join(DIST, f'FilmForge-GUI-v{version}-win64.zip')
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
z.write(exe_path, 'FilmForge.exe')
|
||||
if os.path.exists(README):
|
||||
z.write(README, 'README.md')
|
||||
print(f'发布包: {zip_path} ({os.path.getsize(zip_path) / 1024 / 1024:.1f} MB)')
|
||||
return zip_path
|
||||
|
||||
|
||||
def main():
|
||||
ensure_pyinstaller()
|
||||
make_zip(build())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user