TG-PlatformPlus/build_package.py

226 lines
6.9 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
2026-03-19 16:00:40 +08:00
# -*- coding: utf-8 -*-
"""
GPCT-standalone build script.
- Cleans build output
- Updates version.json releaseTime
- Builds via PyInstaller (using venv Python only)
- Copies extra runtime files
- Verifies output
2026-03-19 16:00:40 +08:00
"""
from __future__ import annotations
2026-03-19 16:00:40 +08:00
import json
import shutil
import subprocess
import sys
2026-03-19 16:00:40 +08:00
from datetime import datetime
from pathlib import Path
PROJECT_DIR = Path(__file__).resolve().parent
2026-03-19 16:00:40 +08:00
VENV_DIR = PROJECT_DIR / "venv"
DIST_DIR = PROJECT_DIR / "dist"
BUILD_DIR = PROJECT_DIR / "build"
SPEC_DIR = PROJECT_DIR / "spec"
NSIS_SCRIPT = PROJECT_DIR / "GPCT-standalone.nsi"
VERSION_FILE = PROJECT_DIR / "version.json"
def run_command(cmd: list[str], cwd: Path | None = None, check: bool = True) -> subprocess.CompletedProcess:
print(f"Running: {' '.join(cmd)}")
2026-03-19 16:00:40 +08:00
result = subprocess.run(
cmd,
cwd=str(cwd or PROJECT_DIR),
2026-03-19 16:00:40 +08:00
capture_output=True,
text=False,
2026-03-19 16:00:40 +08:00
)
# Decode safely to avoid UnicodeDecodeError on Windows tool output
stdout_text = result.stdout.decode("utf-8", errors="replace") if isinstance(result.stdout, (bytes, bytearray)) else str(result.stdout)
stderr_text = result.stderr.decode("utf-8", errors="replace") if isinstance(result.stderr, (bytes, bytearray)) else str(result.stderr)
2026-03-19 16:00:40 +08:00
if result.returncode != 0 and check:
print("[ERROR] Command failed")
print("stdout:")
print(stdout_text)
print("stderr:")
print(stderr_text)
sys.exit(result.returncode)
2026-03-19 16:00:40 +08:00
return result
def venv_python() -> Path:
py = VENV_DIR / "Scripts" / "python.exe"
if not py.exists():
print(f"[ERROR] venv python not found: {py}")
print("Please create venv and install dependencies.")
2026-03-19 16:00:40 +08:00
sys.exit(1)
return py
def clean_build() -> None:
print("\n[1/6] Cleaning old build output...")
for dir_path in (DIST_DIR, BUILD_DIR):
2026-03-19 16:00:40 +08:00
if dir_path.exists():
shutil.rmtree(dir_path)
print(f" - Removed {dir_path}")
def update_version() -> None:
print("\n[2/6] Updating version.json releaseTime...")
if not VERSION_FILE.exists():
print(f" [WARN] version.json not found: {VERSION_FILE}")
return
with VERSION_FILE.open("r", encoding="utf-8") as f:
version_data = json.load(f)
version_data["releaseTime"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with VERSION_FILE.open("w", encoding="utf-8") as f:
json.dump(version_data, f, ensure_ascii=False, indent=4)
print(f" - version: {version_data.get('version', 'N/A')}")
print(f" - releaseTime: {version_data['releaseTime']}")
def build_with_pyinstaller() -> None:
print("\n[3/6] Building with PyInstaller...")
python_exe = venv_python()
2026-03-19 16:00:40 +08:00
spec_files = list(SPEC_DIR.glob("*.spec"))
if not spec_files:
print(f"[ERROR] No .spec files in {SPEC_DIR}")
2026-03-19 16:00:40 +08:00
sys.exit(1)
spec_file = SPEC_DIR / "GPCT-standalone.spec"
if not spec_file.exists():
spec_file = spec_files[0]
cmd = [str(python_exe), "-m", "PyInstaller", str(spec_file), "--clean", "--noconfirm"]
run_command(cmd)
print(" - PyInstaller build finished")
def copy_additional_files() -> None:
print("\n[4/6] Copying extra files...")
onefile_exe = DIST_DIR / "GPCT-standalone.exe"
onedir_dir = DIST_DIR / "GPCT-standalone"
if onefile_exe.exists() and not onedir_dir.exists():
output_dir = DIST_DIR
2026-03-19 16:00:40 +08:00
else:
output_dir = onedir_dir
internal_dir = output_dir / "_internal"
target_dir = internal_dir if internal_dir.exists() else output_dir
datafile_dir = target_dir / "dataFile"
2026-03-19 16:00:40 +08:00
datafile_dir.mkdir(parents=True, exist_ok=True)
for filename in ("config.json", "data.db"):
src = PROJECT_DIR / "dataFile" / filename
if src.exists():
shutil.copy2(src, datafile_dir / filename)
print(f" - Copied {filename}")
for filename in ("Syunew3D_x64.dll", "influxd.exe"):
src = PROJECT_DIR / filename
if src.exists():
shutil.copy2(src, target_dir / filename)
print(f" - Copied {filename}")
def build_nsis_installer() -> None:
print("\n[5/6] Building NSIS installer...")
2026-03-19 16:00:40 +08:00
if not NSIS_SCRIPT.exists():
print(f" [SKIP] NSIS script not found: {NSIS_SCRIPT}")
2026-03-19 16:00:40 +08:00
return
candidates = [
2026-03-19 16:00:40 +08:00
r"C:\Program Files (x86)\NSIS\makensis.exe",
r"C:\Program Files\NSIS\makensis.exe",
]
makensis = next((p for p in candidates if Path(p).exists()), None)
2026-03-19 16:00:40 +08:00
if not makensis:
result = subprocess.run("where makensis", shell=True, capture_output=True)
2026-03-19 16:00:40 +08:00
if result.returncode == 0:
stdout_text = result.stdout.decode("utf-8", errors="replace") if isinstance(result.stdout, (bytes, bytearray)) else str(result.stdout)
makensis = stdout_text.strip().splitlines()[0]
2026-03-19 16:00:40 +08:00
if not makensis:
print(" [SKIP] NSIS not found (makensis.exe).")
2026-03-19 16:00:40 +08:00
return
cmd = [makensis, str(NSIS_SCRIPT)]
2026-03-19 16:00:40 +08:00
result = run_command(cmd, check=False)
if result.returncode == 0:
print(" - NSIS build completed")
2026-03-19 16:00:40 +08:00
installer = PROJECT_DIR / "GPCT-standalone-Setup.exe"
if installer.exists():
print(f" - Installer: {installer}")
2026-03-19 16:00:40 +08:00
else:
print(" [WARN] NSIS build failed")
print(result.stderr)
def verify_build() -> bool:
print("\n[6/6] Verifying build...")
onedir_exe = DIST_DIR / "GPCT-standalone" / "GPCT-standalone.exe"
onefile_exe = DIST_DIR / "GPCT-standalone.exe"
if onedir_exe.exists():
exe_path = onedir_exe
output_dir = DIST_DIR / "GPCT-standalone"
elif onefile_exe.exists():
exe_path = onefile_exe
output_dir = DIST_DIR
2026-03-19 16:00:40 +08:00
else:
print(f" [ERROR] Executable not found: {onedir_exe} or {onefile_exe}")
2026-03-19 16:00:40 +08:00
return False
size_mb = exe_path.stat().st_size / (1024 * 1024)
print(f" - Executable: {exe_path}")
print(f" - Size: {size_mb:.2f} MB")
internal_dir = output_dir / "_internal"
target_dir = internal_dir if internal_dir.exists() else output_dir
print(" - Critical files check")
for filename in ("config.json", "data.db", "Syunew3D_x64.dll"):
file_path = target_dir / filename
if file_path.exists():
print(f" [OK] {filename}")
else:
print(f" [MISSING] {filename}")
return True
def main() -> int:
2026-03-19 16:00:40 +08:00
print("=" * 50)
print(" GPCT-standalone Build Script")
2026-03-19 16:00:40 +08:00
print("=" * 50)
2026-03-19 16:00:40 +08:00
if not PROJECT_DIR.exists():
print(f"[ERROR] Project dir not found: {PROJECT_DIR}")
return 1
2026-03-19 16:00:40 +08:00
clean_build()
update_version()
build_with_pyinstaller()
copy_additional_files()
build_nsis_installer()
success = verify_build()
2026-03-19 16:00:40 +08:00
print("\n" + "=" * 50)
if success:
print(" Build completed")
print(f" Output: {DIST_DIR / 'GPCT-standalone'}")
return 0
print(" Build failed")
return 1
2026-03-19 16:00:40 +08:00
if __name__ == "__main__":
sys.exit(main())