224 lines
6.2 KiB
Python
224 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Helper script to bundle pro3_uart.py into an executable together with the
|
|
files that flash.py expects.
|
|
|
|
Usage:
|
|
python package_pro3_exe.py
|
|
|
|
Prerequisites:
|
|
pip install pyinstaller
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
import re
|
|
|
|
try:
|
|
from PyInstaller import __main__ as pyinstaller_main
|
|
except ImportError as exc:
|
|
print("PyInstaller is required. Install it with 'pip install pyinstaller'.")
|
|
raise SystemExit(1) from exc
|
|
|
|
|
|
APP_ROOT = Path(__file__).resolve().parent
|
|
PROJECT_BASE_NAME = "pro3_uart"
|
|
|
|
|
|
def derive_version_from_folder() -> str:
|
|
root_name = APP_ROOT.name
|
|
match = re.search(r"_v([0-9][0-9.\-_]*)$", root_name, re.IGNORECASE)
|
|
if not match:
|
|
return ""
|
|
return match.group(1)
|
|
|
|
|
|
def read_version_file() -> tuple[str, str]:
|
|
version_file = APP_ROOT / "version_info.py"
|
|
if not version_file.exists():
|
|
return "", ""
|
|
namespace: dict[str, str] = {}
|
|
try:
|
|
with version_file.open("r", encoding="utf-8") as handle:
|
|
exec(handle.read(), namespace)
|
|
except Exception:
|
|
return "", ""
|
|
canonical = str(namespace.get("version", "")).strip()
|
|
display = str(namespace.get("display_version", "")).strip()
|
|
return canonical, display
|
|
|
|
|
|
def write_version_file(version: str, display: str) -> None:
|
|
version_file = APP_ROOT / "version_info.py"
|
|
template = f'''#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2024 Realtek Semiconductor Corp.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
version = "{version}"
|
|
display_version = "{display}"
|
|
'''
|
|
with version_file.open("w", encoding="utf-8") as handle:
|
|
handle.write(template)
|
|
|
|
|
|
def determine_version_tag() -> str:
|
|
folder_version = derive_version_from_folder()
|
|
stored_canonical, stored_display = read_version_file()
|
|
chosen = folder_version or stored_display or stored_canonical
|
|
sanitized = re.sub(r"[^0-9A-Za-z._-]", "_", chosen)
|
|
if not sanitized:
|
|
sanitized = "unknown"
|
|
canonical = canonicalize_version_number(sanitized)
|
|
if canonical != stored_canonical or sanitized != stored_display:
|
|
write_version_file(canonical, sanitized)
|
|
return sanitized
|
|
|
|
|
|
def canonicalize_version_number(tag: str) -> str:
|
|
cleaned = tag.replace("-", ".").replace("_", ".")
|
|
parts = [re.sub(r"\D+", "", chunk) or "0" for chunk in cleaned.split(".") if chunk]
|
|
if not parts:
|
|
parts = ["0"]
|
|
while len(parts) < 4:
|
|
parts.append("0")
|
|
return ".".join(parts[:4])
|
|
|
|
|
|
VERSION_TAG = determine_version_tag()
|
|
RESOURCE_VERSION = canonicalize_version_number(VERSION_TAG)
|
|
DIST_NAME = f"{PROJECT_BASE_NAME}_v{VERSION_TAG}"
|
|
DIST_DIR = APP_ROOT / "dist" / DIST_NAME
|
|
BUILD_DIR = APP_ROOT / "build"
|
|
FLASH_SPEC = APP_ROOT / "flash.spec"
|
|
FLASH_EXE_NAME = "flash.exe"
|
|
FLASH_OUTPUT_PATHS = [
|
|
APP_ROOT / "dist" / FLASH_EXE_NAME,
|
|
APP_ROOT / "dist" / "flash" / FLASH_EXE_NAME,
|
|
]
|
|
|
|
RESOURCE_ITEMS = [
|
|
("base", "base"),
|
|
("devices", "devices"),
|
|
("fw", "fw"),
|
|
("Reburn.cfg", "Reburn.cfg"),
|
|
("Reset.cfg", "Reset.cfg"),
|
|
("Settings.json", "Settings.json"),
|
|
("pylink", "pylink"),
|
|
]
|
|
|
|
PYLINK_DIR = APP_ROOT / "pylink"
|
|
JLINK_DLL_NAME = "JLinkARM.dll"
|
|
JLINK_DLL_SOURCE = PYLINK_DIR / JLINK_DLL_NAME
|
|
|
|
|
|
def clean_path(path: Path) -> None:
|
|
if path.exists():
|
|
shutil.rmtree(path)
|
|
|
|
|
|
def cleanup_old_version_specs() -> None:
|
|
pattern = f"{PROJECT_BASE_NAME}_v*.spec"
|
|
for spec_path in APP_ROOT.glob(pattern):
|
|
try:
|
|
spec_path.unlink()
|
|
print(f"Removed outdated spec: {spec_path.name}")
|
|
except Exception as exc:
|
|
print(f"Warning: unable to remove {spec_path}: {exc}")
|
|
|
|
|
|
def run_pyinstaller_for_pro3() -> None:
|
|
if DIST_DIR.exists():
|
|
shutil.rmtree(DIST_DIR)
|
|
clean_path(BUILD_DIR)
|
|
|
|
args = [
|
|
"--noconfirm",
|
|
"--clean",
|
|
"--windowed",
|
|
"--onedir",
|
|
"--name",
|
|
DIST_NAME,
|
|
str(APP_ROOT / "pro3_uart.py"),
|
|
]
|
|
|
|
print("Running PyInstaller...")
|
|
pyinstaller_main.run(args)
|
|
|
|
|
|
def run_pyinstaller_for_flash() -> None:
|
|
if not FLASH_SPEC.exists():
|
|
raise SystemExit(f"flash.spec not found at {FLASH_SPEC}")
|
|
clean_path(BUILD_DIR)
|
|
print("Building flash.exe via flash.spec...")
|
|
pyinstaller_main.run(["--noconfirm", str(FLASH_SPEC)])
|
|
|
|
|
|
def copy_resources() -> None:
|
|
if not DIST_DIR.exists():
|
|
raise SystemExit(f"PyInstaller output not found: {DIST_DIR}")
|
|
|
|
for src, dest in RESOURCE_ITEMS:
|
|
src_path = APP_ROOT / src
|
|
dest_path = DIST_DIR / dest
|
|
if not src_path.exists():
|
|
print(f"Warning: resource '{src}' not found, skipping.")
|
|
continue
|
|
|
|
if src_path.is_dir():
|
|
if dest_path.exists():
|
|
shutil.rmtree(dest_path)
|
|
shutil.copytree(src_path, dest_path)
|
|
else:
|
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(src_path, dest_path)
|
|
print(f"Copied {src_path} -> {dest_path}")
|
|
if src == "pylink":
|
|
dll_path = dest_path / JLINK_DLL_NAME
|
|
if not dll_path.exists():
|
|
raise SystemExit(f"{JLINK_DLL_NAME} was not found in '{src_path}'.")
|
|
|
|
|
|
def copy_flash_executable() -> None:
|
|
source = None
|
|
for candidate in FLASH_OUTPUT_PATHS:
|
|
if candidate.exists():
|
|
source = candidate
|
|
break
|
|
if not source:
|
|
print("Warning: flash.exe build not found, skipping copy.")
|
|
return
|
|
dest = DIST_DIR / FLASH_EXE_NAME
|
|
shutil.copy2(source, dest)
|
|
print(f"Copied {source} -> {dest}")
|
|
|
|
|
|
def verify_jlink_dll() -> None:
|
|
if not JLINK_DLL_SOURCE.exists():
|
|
raise SystemExit(
|
|
f"{JLINK_DLL_NAME} is required but missing from '{PYLINK_DIR}'. "
|
|
"Copy the DLL into the pylink folder before packaging."
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
cleanup_old_version_specs()
|
|
verify_jlink_dll()
|
|
run_pyinstaller_for_pro3()
|
|
run_pyinstaller_for_flash()
|
|
copy_resources()
|
|
copy_flash_executable()
|
|
exe_path = DIST_DIR / f"{DIST_NAME}.exe"
|
|
if exe_path.exists():
|
|
print(f"\nBuild complete: {exe_path}")
|
|
else:
|
|
print("\nBuild complete. Exe is located inside:", DIST_DIR)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|