A LoRa chip on the host's own SPI bus (the uConsole AIO) is now a transport like serial, BLE, and TCP: --spi, an SPI profile, or the picker's row for /dev/spidev1.0. MeshTerm starts a software node as a child process under whichever Python has openhop-core, talks to it over a loopback port, and ends it on disconnect; the node also exits with its parent, so the pins are free whenever MeshTerm is not running. The node keeps identity, prefs, channels, and contacts under ~/.meshterm/radio/<spidev>/, and on first use copies the bridge's identity, contacts, and radio settings so it stays the same node. Board wiring lives in a profile's spi table; radio settings are the node's own, edited on Device config. Closes #21. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
109 lines
4.8 KiB
RPMSpec
109 lines
4.8 KiB
RPMSpec
# SPDX-License-Identifier: Apache-2.0
|
|
# 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.
|
|
|
|
import sys
|
|
|
|
from PyInstaller.utils.hooks import collect_submodules
|
|
|
|
# `packaging/notices.py` builds THIRD-PARTY-NOTICES.txt at build time; it sits beside
|
|
# this spec rather than under `meshterm/` because — like `entry.py` above — it has no
|
|
# business being importable from the installed package, only from the freezer. `SPECPATH`
|
|
# is one of the names PyInstaller injects into a running .spec's namespace (the directory
|
|
# holding this file, resolved from the path given to `pyinstaller`, not the process's
|
|
# cwd), so this finds it however the spec was invoked.
|
|
sys.path.insert(0, SPECPATH)
|
|
import notices # noqa: E402 (import must follow the sys.path edit above)
|
|
|
|
# Generated fresh on every build rather than checked into git: it is a function of
|
|
# whichever distributions are actually installed in *this* interpreter, and a stale copy
|
|
# would drift the moment a dependency's license text changed upstream without anyone
|
|
# noticing. `workpath` is PyInstaller's own scratch directory for exactly this kind of
|
|
# build-time artefact — cleaned by `--clean`, never mistaken for a source file. It lands
|
|
# at `<workpath>/<specname>/THIRD-PARTY-NOTICES.txt` (PyInstaller appends the spec's own
|
|
# name, minus its extension, to whatever workpath it was given — see `build()` in
|
|
# `PyInstaller/building/build_main.py`), which for this spec means `build/meshterm/`.
|
|
third_party_notices = notices.write_third_party_notices(
|
|
os.path.join(workpath, "THIRD-PARTY-NOTICES.txt")
|
|
)
|
|
|
|
# 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"),
|
|
# The SPI radio's node is run by path under *another* Python — the one with the radio
|
|
# library, which this build doesn't carry — so it has to exist as a source file, beside
|
|
# where `spiradio.node_script()` looks for it, not only as bytecode in the archive.
|
|
("../meshterm/core/radionode.py", "meshterm/core"),
|
|
# A one-file build is a *copy* of MeshTerm, and Apache-2.0 §4(a)/(d) want MeshTerm's
|
|
# own LICENSE and NOTICE distributed with every copy — not folded into the app's own
|
|
# assets, but sitting at the bundle root the way they sit at the repository root.
|
|
("../LICENSE", "."),
|
|
("../NOTICE", "."),
|
|
# Every MIT/BSD/PSF dependency's own notice wants to travel with copies the same way;
|
|
# see packaging/notices.py for how this is built.
|
|
(str(third_party_notices), "."),
|
|
]
|
|
|
|
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,
|
|
)
|