feat(packaging): embed app icon + version metadata in the frozen exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 10:55:54 -04:00
parent 60186b8eba
commit db56cda0ca
2 changed files with 27 additions and 1 deletions

2
.gitignore vendored
View File

@@ -16,6 +16,8 @@ dist/
# PyInstaller output
/packaging/build/
/packaging/dist/
# Generated at build time from cim_suite.__version__ (see packaging/suite.spec)
/packaging/_version_info.generated.txt
# Packaged portable-build zips — transient test artifacts, regenerated per build
/packaging/CIM-Service-Suite_*.zip

View File

@@ -9,6 +9,7 @@
# Setup (ISCC.exe).
import os
import sys
block_cipher = None
@@ -16,10 +17,21 @@ block_cipher = None
# its parent and must be on pathex so `import cim_suite` resolves at build time.
_repo_root = os.path.dirname(SPECPATH)
# Make `import cim_suite` and the local `verinfo` helper importable while the spec runs.
for _p in (_repo_root, SPECPATH):
if _p not in sys.path:
sys.path.insert(0, _p)
import cim_suite # noqa: E402 (single-sourced version)
import verinfo # noqa: E402 (packaging/verinfo.py)
# Bundled brand fonts (Lato) live in the theme package and must be copied into the
# frozen app at the same package-relative path so theme.fonts can find them.
_font_src = os.path.join(_repo_root, "cim_suite", "core", "ui", "theme", "fonts")
# The runtime window icon (cim_suite/shell/branding.py resolves it package-relative).
_app_icon_src = os.path.join(_repo_root, "cim_suite", "shell", "resources")
# IOModbus ships its device catalog (register maps) as a package resource; it must be
# copied into the frozen app at the same package-relative path so config.catalog_text
# (importlib.resources) can read it.
@@ -30,15 +42,26 @@ _iomodbus_res = os.path.join(_repo_root, "cim_suite", "modules", "iomodbus", "re
_pyproject = os.path.join(_repo_root, "pyproject.toml")
_changelog = os.path.join(_repo_root, "CHANGELOG.md")
# End-user run guide shipped at the bundle root (and inside the portable zip).
_readme = os.path.join(SPECPATH, "READ-ME-FIRST.txt")
# Exe icon + Windows version resource, both generated from single sources.
_icon = os.path.join(SPECPATH, "icon.ico")
_version_file = os.path.join(SPECPATH, "_version_info.generated.txt")
with open(_version_file, "w", encoding="utf-8") as _vf:
_vf.write(verinfo.render_version_resource(cim_suite.__version__))
a = Analysis(
[os.path.join(SPECPATH, "suite_launcher.py")],
pathex=[_repo_root],
binaries=[],
datas=[
(_font_src, os.path.join("cim_suite", "core", "ui", "theme", "fonts")),
(_app_icon_src, os.path.join("cim_suite", "shell", "resources")),
(_iomodbus_res, os.path.join("cim_suite", "modules", "iomodbus", "resources")),
(_pyproject, "."),
(_changelog, "."),
(_readme, "."),
],
hiddenimports=["serial.tools.list_ports", "openpyxl", "PySide6.QtCharts"],
hookspath=[],
@@ -71,7 +94,8 @@ exe = EXE(
upx=False,
console=False,
disable_windowed_traceback=False,
icon=None,
icon=_icon,
version=_version_file,
)
coll = COLLECT(
exe,