Files
FreePEP/build_exe.py
T
2026-08-31 10:22:43 +08:00

136 lines
5.2 KiB
Python

"""
FreePEP 一键打包分发脚本 (build_exe.py)
功能:
1. 检查并自动安装 PyInstaller
2. 打包 WebUI 为独立可执行文件 FreePEP.exe
3. 自动抓取并内嵌 Chromium 便携版浏览器内核至 dist/FreePEP/browsers/
4. 生成开箱即用的完全独立便携版压缩包 (FreePEP-Windows-x64.zip)
"""
import os
import sys
import shutil
import zipfile
import subprocess
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
def check_and_install_pyinstaller():
"""检查 PyInstaller 是否已安装,未安装则自动安装"""
try:
import PyInstaller
print("[+] 检测到 PyInstaller 已安装。")
except ImportError:
print("[*] 未检测到 PyInstaller,正在通过 pip 自动安装...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyinstaller"])
print("[+] PyInstaller 安装完成!")
def find_playwright_browsers_dir():
"""定位本机 Playwright 下载的浏览器内核路径"""
local_app_data = os.environ.get("LOCALAPPDATA", "")
ms_playwright = os.path.join(local_app_data, "ms-playwright")
if os.path.exists(ms_playwright):
return ms_playwright
return None
def build_package():
print("=" * 65)
print(" 🚀 FreePEP 独立 EXE 便携发行包打包脚本")
print("=" * 65)
check_and_install_pyinstaller()
dist_dir = os.path.abspath("./dist")
build_dir = os.path.abspath("./build")
out_app_dir = os.path.join(dist_dir, "FreePEP")
# 清理旧的编译产物
if os.path.exists(out_app_dir):
print(f"[*] 清理旧输出目录: {out_app_dir}")
shutil.rmtree(out_app_dir, ignore_errors=True)
# 1. 组装 PyInstaller 命令
print("\n[1/3] 正在使用 PyInstaller 编译 Python 代码与依赖...")
cmd = [
sys.executable, "-m", "PyInstaller",
"--name=FreePEP",
"--onedir", # 目录模式(比单文件解压更快、更利于内嵌浏览器)
"--collect-all=playwright", # 打包 playwright driver 与所有依赖
"--hidden-import=uvicorn.logging",
"--hidden-import=uvicorn.loops",
"--hidden-import=uvicorn.loops.auto",
"--hidden-import=uvicorn.protocols",
"--hidden-import=uvicorn.protocols.http",
"--hidden-import=uvicorn.protocols.http.auto",
"--hidden-import=uvicorn.protocols.websockets",
"--hidden-import=uvicorn.protocols.websockets.auto",
"--hidden-import=uvicorn.lifespans",
"--hidden-import=uvicorn.lifespans.on",
"--clean",
"-y",
"webui.py"
]
subprocess.check_call(cmd)
print("[+] 核心程序 FreePEP.exe 编译成功!")
# 2. 拷贝 Playwright Chromium 绿色便携浏览器到目标目录
print("\n[2/3] 正在打包绿色便携版 Chromium 浏览器内核...")
pw_src = find_playwright_browsers_dir()
target_browsers_dir = os.path.join(out_app_dir, "_internal", "browsers")
target_browsers_dir2 = os.path.join(out_app_dir, "browsers")
if pw_src:
print(f"[*] 从本机提取 Chromium 内核: {pw_src}")
os.makedirs(target_browsers_dir2, exist_ok=True)
for item in os.listdir(pw_src):
if "chromium" in item.lower() or "ffmpeg" in item.lower():
s_path = os.path.join(pw_src, item)
d_path = os.path.join(target_browsers_dir2, item)
if not os.path.exists(d_path):
print(f" -> 拷贝内核组件: {item}")
if os.path.isdir(s_path):
shutil.copytree(s_path, d_path)
else:
shutil.copy2(s_path, d_path)
print("[+] 便携浏览器内核内嵌完成!分发给用户无需安装任何浏览器或 Python。")
else:
print("[!] 警告: 本机未找到 Playwright 缓存的 Chromium,建议先运行 'playwright install chromium'。")
# 拷贝默认说明和依赖
for f in ["README.md", "pep_catalog.json"]:
if os.path.exists(f):
shutil.copy2(f, os.path.join(out_app_dir, f))
# 3. 压缩为发布 ZIP 包
print("\n[3/3] 正在生成发布压缩包...")
zip_name = "FreePEP-Windows-x64.zip"
zip_path = os.path.join(dist_dir, zip_name)
if os.path.exists(zip_path):
os.remove(zip_path)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(out_app_dir):
for file in files:
abs_f = os.path.join(root, file)
rel_f = os.path.relpath(abs_f, dist_dir)
zipf.write(abs_f, rel_f)
zip_size_mb = round(os.path.getsize(zip_path) / (1024 * 1024), 2)
print("\n" + "=" * 65)
print(f"🎉 打包全部完成!")
print(f"📁 绿色便携文件夹: {out_app_dir}")
print(f"📦 最终分发压缩包: {zip_path} ({zip_size_mb} MB)")
print("=" * 65)
print("\n👉 分发使用说明:")
print(" 直接将 FreePEP-Windows-x64.zip 发送给用户即可。")
print(" 用户解压后双击 FreePEP.exe,无需配置 Python,会自动弹出浏览器打开页面!\n")
if __name__ == "__main__":
build_package()