Files
cimtechniques-service-suite/packaging/suite.spec
2026-06-08 10:55:54 -04:00

109 lines
3.7 KiB
Python

# PyInstaller spec for the CIMTechniques Service Suite.
#
# One-folder build (more antivirus-friendly than --onefile and faster to start).
# Build from the repo root:
# .venv\Scripts\pyinstaller packaging\suite.spec
# Output: packaging\dist\CIM-Service-Suite\CIM-Service-Suite.exe
#
# To produce the installer afterwards, compile packaging\installer.iss with Inno
# Setup (ISCC.exe).
import os
import sys
block_cipher = None
# SPECPATH is the directory containing this .spec (…/packaging); the repo root is
# 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.
_iomodbus_res = os.path.join(_repo_root, "cim_suite", "modules", "iomodbus", "resources")
# Version is single-sourced from pyproject.toml and the "What's new" dialog renders
# CHANGELOG.md; both are read at runtime from the bundle root (sys._MEIPASS).
_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=[],
runtime_hooks=[],
excludes=[
# Trim large PySide6 modules we don't use to keep the bundle smaller.
"PySide6.QtQml",
"PySide6.QtQuick",
"PySide6.QtNetwork",
"PySide6.Qt3DCore",
"PySide6.QtWebEngineCore",
"PySide6.QtWebEngineWidgets",
"PySide6.QtMultimedia",
"tkinter",
],
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="CIM-Service-Suite",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
disable_windowed_traceback=False,
icon=_icon,
version=_version_file,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
name="CIM-Service-Suite",
)