Files
ChouJuGEO/build_scripts/build.py
T
2026-05-30 12:51:19 +08:00

230 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
GEO Tool — 跨平台打包构建脚本
用法:
python build_scripts/build.py # 打包 Python 可执行文件
python build_scripts/build.py --clean # 先清理旧构建再打包
python build_scripts/build.py tauri # 打包 Python 可执行文件 + 构建 Tauri 桌面应用
输出:
dist_python/geo_tool_app (macOS / Linux)
dist_python/geo_tool_app.exe (Windows)
src-tauri/target/release/GEO工具 (Tauri 桌面应用)
依赖:
pip install pyinstaller
cargo tauri build (for tauri 命令)
"""
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
# ── 项目根目录 ────────────────────────────────────────────────
PROJECT_ROOT = Path(__file__).resolve().parent.parent
SPEC_FILE = PROJECT_ROOT / "geo_tool_app.spec"
DIST_DIR = PROJECT_ROOT / "dist_python"
BUILD_DIR = PROJECT_ROOT / "build_python"
TAURI_DIR = PROJECT_ROOT / "src-tauri"
def check_prerequisites():
"""检查必要工具是否已安装"""
# 检查 PyInstaller
try:
import PyInstaller # noqa: F401
except ImportError:
print("正在安装 PyInstaller ...")
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "pyinstaller"],
cwd=str(PROJECT_ROOT),
)
# 检查项目依赖是否已安装
try:
import streamlit # noqa: F401
except ImportError:
print("正在安装项目依赖 ...")
# 优先用 uv,否则 pip install pyproject.toml
for installer in ["uv", "pip"]:
try:
subprocess.check_call(
[installer, "sync"] if installer == "uv" else [installer, "install", "."],
cwd=str(PROJECT_ROOT),
)
break
except (subprocess.CalledProcessError, FileNotFoundError):
continue
def clean_build():
"""清理之前的构建产物"""
for d in [DIST_DIR, BUILD_DIR]:
if d.exists():
print(f" 清理 {d} ...")
shutil.rmtree(d)
# 清理 spec 生成的临时目录
temp_spec_dir = PROJECT_ROOT / "__pycache__"
if temp_spec_dir.exists():
shutil.rmtree(temp_spec_dir)
def build_tauri():
"""先打包 Python 可执行文件,然后构建 Tauri 桌面应用"""
build()
print("\n正在构建 Tauri 桌面应用 ...\n")
result = subprocess.run(
["cargo", "tauri", "build"],
cwd=str(TAURI_DIR),
)
if result.returncode != 0:
print(f"\n[错误] Tauri 构建失败 (返回码: {result.returncode})")
sys.exit(1)
print("\n" + "=" * 60)
print(" Tauri 构建完成!")
release_dir = TAURI_DIR / "target" / "release"
if sys.platform == "darwin":
bundles = list(release_dir.glob("*.dmg")) + list((release_dir / "bundle" / "dmg").glob("*.dmg")) + list((release_dir / "bundle" / "macos").glob("*.app"))
elif sys.platform == "win32":
bundles = list(release_dir.glob("*.msi")) + list((release_dir / "bundle" / "msi").glob("*.msi"))
else:
bundles = list(release_dir.glob("GEO工具")) + list(release_dir.glob("*.AppImage"))
for b in bundles:
size_mb = b.stat().st_size / (1024 * 1024) if b.is_file() else sum(f.stat().st_size for f in b.rglob("*")) / (1024 * 1024)
print(f" 输出: {b} ({size_mb:.1f} MB)")
print("=" * 60)
def build():
"""执行 PyInstaller 打包"""
# 确保 run_bundle.py 存在
entry_point = PROJECT_ROOT / "run_bundle.py"
if not entry_point.exists():
print(f"[错误] 未找到入口文件: {entry_point}")
sys.exit(1)
# 检查关键资源是否存在(给出警告而非中断)
resources = [
("geo_tool.py", "file"),
("config.json", "file"),
("geo_data.db", "file"),
(".streamlit", "dir"),
("modules", "dir"),
]
for path, _kind in resources:
p = PROJECT_ROOT / path
if not p.exists():
print(f"[警告] 资源不存在: {p}")
# 构建命令
cmd = [
sys.executable,
"-m",
"PyInstaller",
str(SPEC_FILE),
"--distpath",
str(DIST_DIR),
"--workpath",
str(BUILD_DIR),
"--clean",
"--noconfirm",
]
print("=" * 60)
print(" GEO Tool — 打包构建")
print("=" * 60)
print(f" 平台: {sys.platform}")
print(f" Python: {sys.version}")
print(f" 入口: {entry_point}")
print(f" Spec: {SPEC_FILE}")
print(f" 输出目录: {DIST_DIR}")
print("=" * 60)
# 执行打包
result = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
if result.returncode != 0:
print(f"\n[错误] 打包失败 (返回码: {result.returncode})")
print(f" 请检查 {BUILD_DIR}/ 下的日志文件获取详细错误信息。")
sys.exit(1)
# 输出结果
print("\n" + "=" * 60)
print(" 打包完成!")
print("=" * 60)
# 查找产物
if sys.platform == "win32":
output = DIST_DIR / "geo_tool_app.exe"
elif sys.platform == "darwin":
output = DIST_DIR / "geo_tool_app"
else:
output = DIST_DIR / "geo_tool_app"
if output.exists():
size_mb = output.stat().st_size / (1024 * 1024)
print(f" 输出文件: {output}")
print(f" 文件大小: {size_mb:.1f} MB")
else:
# macOS 下可能生成 .app
app_bundle = DIST_DIR / "geo_tool_app.app"
if app_bundle.exists():
size_mb = sum(f.stat().st_size for f in app_bundle.rglob("*")) / (1024 * 1024)
print(f" 输出文件: {app_bundle}")
print(f" 文件大小: {size_mb:.1f} MB")
print(f" 运行方式: 双击 {output.name} 或终端执行 {output}")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description="GEO Tool 跨平台打包脚本",
)
parser.add_argument(
"mode",
nargs="?",
default="python",
choices=["python", "tauri"],
help="打包模式: python (默认) 或 tauri (Python + 桌面壳)",
)
parser.add_argument(
"--clean",
action="store_true",
help="构建前清理旧的 dist 和 build 目录",
)
parser.add_argument(
"--skip-deps",
action="store_true",
help="跳过依赖检查(仅安装 PyInstaller",
)
args = parser.parse_args()
# 切换到项目根目录
os.chdir(str(PROJECT_ROOT))
# 清理
if args.clean:
print("清理旧的构建产物 ...")
clean_build()
# 检查环境
if not args.skip_deps:
check_prerequisites()
# 打包
if args.mode == "tauri":
build_tauri()
else:
build()
if __name__ == "__main__":
main()