The Windows build launched, printed help, drew the specimen card, and showed a
menu containing one item: Quit. Not a permissions problem and nothing to do with
Bluetooth — the tools were simply not in the bundle.
`meshterm/tools/__init__.py` finds every tool with `pkgutil.iter_modules` and
imports it so its `@register` decorator fires. Nothing static ever names a tool
module, so PyInstaller had no reason to include one, and the registry came up
empty: no menu entries, no CLI subcommands, an app that starts and does nothing.
`collect_submodules("meshterm.tools")` fixes it, which is the same treatment
`bleak.backends` already had for the same reason — the spec knew about other
people's dynamic imports and not about ours.
The worse half is that CI said this was fine. The smoke test ran `--help` and
`specimen`, and a build with no tools at all passes both. It now runs a
tool-provided subcommand, so an empty registry fails the pipeline instead of
reaching a release page. Green tests that cannot see the defect are worse than no
tests, because they are believed.
`MESHTERM_HOME` comes out of the same session. Every copy of MeshTerm reads and
writes `~/.meshterm`, so trying a downloaded build meant pointing it at the same
34MB history database as the checkout it was built from, with no way to keep them
apart. The variable moves the whole directory. An empty or whitespace value falls
back rather than resolving to the current directory, because a shell exporting it
blank is common and scattering someone's history into their downloads folder is a
poor way to find that out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
2.5 KiB
RPMSpec
73 lines
2.5 KiB
RPMSpec
# PyInstaller spec for the downloadable builds. Run from the repository root:
|
|
#
|
|
# pyinstaller packaging/meshterm.spec
|
|
#
|
|
# One file, console mode. Console is not a detail: MeshTerm *is* a terminal program, and
|
|
# a windowed build would start with nowhere to draw and exit immediately.
|
|
#
|
|
# Built on the machine it targets. There is no cross-compiling here — the Windows build
|
|
# comes off a Windows runner, the macOS one off macOS. That is what the workflow is for.
|
|
|
|
from PyInstaller.utils.hooks import collect_submodules
|
|
|
|
# The assets have to land at `meshterm/assets`, because `ui/about.py` finds them with
|
|
# `Path(__file__).parent.parent / "assets"` and that path has to keep resolving inside
|
|
# the bundle exactly as it does in a checkout.
|
|
datas = [
|
|
("../meshterm/assets", "meshterm/assets"),
|
|
]
|
|
|
|
hiddenimports = [
|
|
# Every tool module. `meshterm.tools` finds these with `pkgutil.iter_modules` at
|
|
# import time, so nothing static ever names one, and a frozen build without this line
|
|
# starts with an empty registry: no menu entries but Quit, and no CLI subcommands.
|
|
# The app launches and looks like it works, which is the worst way for this to fail.
|
|
*collect_submodules("meshterm.tools"),
|
|
# bleak picks its backend at runtime by platform, so static analysis never sees the
|
|
# one that actually gets used. Collect all of them and let the unused ones sit.
|
|
*collect_submodules("bleak.backends"),
|
|
# Same story for the companion library's transports.
|
|
*collect_submodules("meshcore"),
|
|
# Rich and prompt_toolkit both reach for things dynamically.
|
|
"rich.console",
|
|
"prompt_toolkit.output.win32",
|
|
"prompt_toolkit.output.vt100",
|
|
]
|
|
|
|
a = Analysis(
|
|
["entry.py"],
|
|
pathex=[".."],
|
|
binaries=[],
|
|
datas=datas,
|
|
hiddenimports=hiddenimports,
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[],
|
|
# Nothing here is used, and each one drags in a GUI toolkit or a test framework.
|
|
excludes=["tkinter", "pytest", "IPython", "matplotlib", "numpy", "PIL"],
|
|
noarchive=False,
|
|
)
|
|
|
|
pyz = PYZ(a.pure)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
a.binaries,
|
|
a.datas,
|
|
[],
|
|
name="meshterm",
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
# UPX is off deliberately: it saves a few megabytes and gets the result flagged by
|
|
# antivirus often enough that it is not a trade worth making for a first release.
|
|
upx=False,
|
|
console=True,
|
|
disable_windowed_traceback=False,
|
|
argv_emulation=False,
|
|
target_arch=None,
|
|
codesign_identity=None,
|
|
entitlements_file=None,
|
|
)
|