NanoVNA-Saver ships official binaries for Windows and Raspberry Pi only. This repo builds a self-contained .app bundle for macOS (Apple Silicon), then slims it from the default ~1.3 GB pip install down to ~250 MB by stripping unused Qt modules and the x86_64 universal-binary slices.
./build.sh # builds v0.7.3 → ~/Applications/NanoVNASaver.app
./build.sh v0.7.3 # explicit version
./build.sh v0.7.3 /Applications/NanoVNASaver.app # custom destinationRequires Python 3.10+ at /opt/homebrew/bin/python3.12 (override with PYTHON=...). Apple Silicon assumed — the lipo thinning step is skipped on Intel.
- Creates a fresh
.appbundle withContents/{MacOS,Resources}layout. - Builds an isolated Python venv inside
Contents/Resources/venv/. - Adds a
pythonsymlink (the upstreamsetup.pycalls barepythonand breaks without it). pip installs NanoVNASaver from the GitHub tag.- Compiles
.uiand.qrcfiles manually withpyside6-uic/pyside6-rcc— the wheel install skips this step. - Patches the generated
about.pyto fix a brokenimport main_rc(changes it tofrom . import main_rc).- macOS UI sizing fixes — upstream hard-codes control sizes (
setFixedHeight(20),setFixedWidth(60)) tuned for Windows/Linux. On macOS the native control chrome (taller fields, focus-ring margins, larger system font) needs more room, so Qt clips text (50kHz→50kH,Sweep→Sweer,Rescan→Rescar). The script relaxes the fixed sizes to minimums and widens the control column slightly.
- macOS UI sizing fixes — upstream hard-codes control sizes (
- Writes a bash launcher at
Contents/MacOS/NanoVNASaverthat execs the bundled venv Python; logs go to~/Library/Logs/NanoVNASaver/nanovnasaver.log. - Generates
Info.plistand a multi-resolution.icnsfrom the committedassets/AppIcon.png— a 1024px macOS-style icon (rounded square + the NanoVNASaver Smith-chart logo). Regenerate it withassets/make_icon.py. Falls back to the upstream 128px logo if the asset is missing. - Slim Qt — keeps only the frameworks NanoVNASaver actually imports (
QtCore,QtGui,QtWidgets) plus safe runtime deps (QtNetwork,QtDBus,QtPrintSupport,QtSvg,QtSvgWidgets). Deletes everything else, includingQtWebEngineCore(~590 MB of Chromium), all 3D/Qml/Multimedia/Charts/etc., plusQt/qml,Qt/translations,Qt/metatypes,Qt/libexec. - Slim plugins — keeps
platforms,imageformats,styles,iconengines,tls,networkinformation,platforminputcontexts,generic. Drops the rest. - Thin — runs
lipo -thin arm64over every universal Mach-O in the bundle, since Python itself is arm64-only and the x86_64 slices are dead weight.
| Stage | Size |
|---|---|
Raw pip install |
~1.3 GB |
| After Qt slim | ~360 MB |
| After arm64 thin | ~250 MB |
App runs natively on Apple Silicon (no Rosetta).
- Universal2 build (Intel + Apple Silicon in one bundle). Requires installing python.org's universal2 Python 3.12 (one-time
sudo installer -pkg), then rebuilding with thelipo -thinstep skipped. Result is a single.appthat runs natively on both architectures. Alternative: separatearm64andx86_64zips on the release page (build the Intel one witharch -x86_64+lipo -thin x86_64). - Proper Developer ID codesigning + notarization, so the
xattrquarantine workaround is no longer needed. - CI: GitHub Actions on
macos-14(arm64) andmacos-13(Intel) runners to auto-build on tag push.
Licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later), matching the upstream NanoVNA-Saver license. The bundled .app redistributes the upstream Python source and PySide6 (LGPL-3.0); both are compatible with GPL-3.0.
These are upstream quirks that make a vanilla pip install non-functional on macOS:
pythonsymlink —setuptools_wrapper.pycallssubprocess.call(['python', ...]). A fresh venv only haspython3, so the wheel build aborts withFileNotFoundError.- Manual UI compile — the wheel-build hook tries to invoke
pyside6-uicbut the generated files don't end up in the installed package. ImportingNanoVNASaverfails withModuleNotFoundError: NanoVNASaver.Windows.ui.about. main_rcimport —pyside6-uicwritesimport main_rc(top-level) but the file lives inside the package. Python 3 raisesModuleNotFoundError: No module named 'main_rc'.