Building packages¶
All release artifacts are PyInstaller onedir builds, wrapped per platform. CI builds everything on tag push (release flow); locally:
macOS (.app / DMG)¶
uv sync --group build
uv run pyinstaller packaging/nparseplus.spec --noconfirm # dist/nParse+.app
codesign --force --deep -s - "dist/nParse+.app" # ad-hoc sign
uv run dmgbuild -s packaging/dmg_settings.py "nParse+" dist/nParse+.dmg
Windows (zip)¶
uv sync --group build
uv run pyinstaller packaging/nparseplus.spec --noconfirm
Compress-Archive -Path dist/nparseplus -DestinationPath dist/nparseplus-win64.zip
That gives you a working build, but not the one CI ships: the release job compiles the PyInstaller bootloader from source first, because the prebuilt one in the wheel is byte-identical for every PyInstaller user on earth and is what heuristic antivirus engines match on (#122). To do the same locally you need MSVC installed, and then:
uv sync --group build
uv run python tools/pyinstaller_source_pin.py requirements -o pyi-source.txt
$env:PYINSTALLER_COMPILE_BOOTLOADER = "1"
uv pip install --no-deps --reinstall-package pyinstaller `
--no-binary pyinstaller --require-hashes -r pyi-source.txt
uv run --no-sync pyinstaller packaging/nparseplus.spec --noconfirm
PYINSTALLER_COMPILE_BOOTLOADER is not optional: PyInstaller's sdist ships
the prebuilt Windows bootloaders too, so a plain source install packages the
exact bytes this is meant to replace. CI proves the swap happened by hashing
the result against the published wheel's copy — see
Release flow.
macOS and Linux stay on the wheel. The macOS bootloader build is
byte-reproducible (a local waf build of 6.21.0 reproduces the published
runw exactly), so rebuilding it there would change nothing.
Linux (tarball + Flatpak + Debian package)¶
uv sync --group build
uv run pyinstaller packaging/nparseplus.spec --noconfirm
tar -C dist -czf nparseplus-linux-x86_64.tar.gz nparseplus
The Debian package wraps the same onedir build too:
uv run python packaging/deb/build_deb.py \
--dist-dir dist/nparseplus --version "$(uv run python -c \
'import nparseplus; print(nparseplus.__version__)')" --outdir dist
Build it on Debian 12 or the artifact is pointless. The whole reason it
exists is that an artifact's glibc floor is its build host's, so producing it
anywhere newer just makes a .deb with the tarball's floor. CI runs the build
inside a debian:12 container and passes the measured floor in with
--glibc-floor; locally, docker run --rm -v "$PWD:/src" -w /src debian:12
is the equivalent. See
Linux: two builds, two glibc floors.
The Flatpak wraps the same onedir build via the manifest in
packaging/flatpak/
— building it needs a Linux box with flatpak-builder (commands are in
the manifest header; CI does this on release). Release builds are
GPG-signed and published to an OSTree repo on GitHub Pages, which is what
makes flatpak update
work.
Documentation site¶
uv sync --group docs
uv run mkdocs serve # live preview at http://127.0.0.1:8000
uv run mkdocs build --strict # the CI gate
Versioned deploys use mike; CI
handles them (release flow). Screenshots go in
docs/assets/screenshots/ — missing ones render as placeholders, and
docs/dev-notes/screenshot-checklist.md lists what's wanted.