Building Standalone Executables

This guide covers building bbl-shutter-cam into standalone executables for Windows, macOS, and Linux.

What is PyInstaller?

PyInstaller packages your Python application into a single executable that doesn’t require Python to be installed on the user’s system. Users can download and run the executable directly.

Prerequisites

  1. Python 3.9+ installed
  2. PyInstaller (installed as part of dev dependencies)

Quick Start

macOS / Linux

# Install dev dependencies (includes PyInstaller)
pip install -e ".[dev]"

# Run the build script
./scripts/build.sh

# Find your executable at: dist/bbl-shutter-cam

Windows

# Install dev dependencies
pip install -e ".[dev]"

# Run the build script
scripts\build.bat

# Find your executable at: dist\bbl-shutter-cam.exe

Manual Build (All Platforms)

If the scripts don’t work for your setup:

# Install dependencies
pip install -e ".[dev]"

# Build the executable (preferred)
pyinstaller bbl-shutter-cam.spec

# Executable will be in dist/ folder

After Building

macOS / Linux

# Make it executable (usually already is)
chmod +x dist/bbl-shutter-cam

# Test it
./dist/bbl-shutter-cam --help

# Install to system (optional)
sudo cp dist/bbl-shutter-cam /usr/local/bin/bbl-shutter-cam

Windows

# Test it
dist\bbl-shutter-cam.exe --help

# Install to PATH (optional)
# Copy dist\bbl-shutter-cam.exe to a folder in your PATH

Distribution

For sharing with others:

  1. Build the executable using the steps above
  2. Test it thoroughly - make sure configs are portable
  3. Compress for distribution:
    • macOS/Linux: tar -czf bbl-shutter-cam-linux-x64.tar.gz dist/bbl-shutter-cam
    • Windows: zip -r bbl-shutter-cam-windows.zip dist/bbl-shutter-cam.exe

If you only need Raspberry Pi binaries, use the GitHub Releases page. Each release automatically builds and attaches:

  • bbl-shutter-cam-<version>-linux-arm64
  • bbl-shutter-cam-<version>-linux-armv7

Cross-Platform Building (CI/CD)

Automated builds run on GitHub Actions for Raspberry Pi targets. When a GitHub Release is published, the workflow builds and uploads arm64 and armv7 binaries to the release.

Troubleshooting

“PyInstaller not found”

pip install pyinstaller

“Module not found” errors

Add the module to hiddenimports in bbl-shutter-cam.spec:

hiddenimports=['bleak', 'tomlkit', 'your_module_here'],

“attempted relative import with no known parent package”

This usually happens if you freeze src/bbl_shutter_cam/cli.py directly. Build using the provided spec or wrapper entry script instead:

python -m PyInstaller --onefile --console --name bbl-shutter-cam --paths src scripts/pyinstaller_entry.py

File size is large

This is normal - PyInstaller bundles Python + all dependencies. Try:

pyinstaller --onefile --strip bbl-shutter-cam.spec

macOS “App can’t be opened” error

chmod +x dist/bbl-shutter-cam
codesign --deep --force --verify --verbose --sign - dist/bbl-shutter-cam

Notes

  • Config files are stored in ~/.config/bbl-shutter-cam/ and will work across all platforms
  • Executable is standalone - no Python installation needed by end users
  • Binary size: Each executable is ~50-100MB (includes Python runtime)
  • macOS executable: This is a regular CLI executable, not a .app bundle

Testing Locally Before Release

To test the binary thoroughly before publishing a release:

Using VS Code Tasks

  1. Build: Press Ctrl+Shift+B (or Cmd+Shift+B on Mac) and select “Build executable”
  2. Test: Run task “Test local binary” to verify it launches
  3. Build and test: Run task “Build and test binary” to do both in sequence

Manual Testing Workflow

# 1. Build the executable
./scripts/build.sh

# 2. Quick smoke test
./dist/bbl-shutter-cam --help

# 3. Test with a real config (dry-run)
sudo ./dist/bbl-shutter-cam --config ~/.config/bbl-shutter-cam/config.toml \
  run --profile your-profile --dry-run --verbose

# 4. Test scan functionality
sudo ./dist/bbl-shutter-cam scan --name BBL_SHUTTER --timeout 10

# 5. Full integration test (optional)
# Create a test directory with a test config
mkdir -p binarytesting
cp ~/.config/bbl-shutter-cam/config.toml binarytesting/
sudo ./dist/bbl-shutter-cam --config binarytesting/config.toml \
  run --profile your-profile --dry-run --verbose

Pre-Release Checklist

Before tagging a release:

  • Binary builds without errors
  • --help displays correctly
  • scan command works
  • run --dry-run connects and receives shutter signals
  • Config parsing works (no errors on valid config)
  • All quality gates pass (tests, lint, type check)
  • Documentation is up to date
  • CHANGELOG.md updated with release notes

Comparing with Released Binaries

To compare your local build with a GitHub release:

# Download release binary
curl -L -o bbl-shutter-cam-release \
  https://github.com/bodybybuddha/bbl-shutter-cam/releases/download/v1.0.1/bbl-shutter-cam-v1.0.1-linux-arm64

# Compare file sizes
ls -lh dist/bbl-shutter-cam bbl-shutter-cam-release

# Test both
chmod +x bbl-shutter-cam-release
./bbl-shutter-cam-release --help
./dist/bbl-shutter-cam --help

Publishing Releases

For maintainers creating official releases, see the Release Process in CONTRIBUTING.md.

Quick Reference

  1. Test locally using the checklist above
  2. Create release branch from dev
  3. Update CHANGELOG.md, ROADMAP.md, docs
  4. Merge PR to dev
  5. Tag release: git tag -a v1.0.3 -m "..."
  6. Push tag: git push origin refs/tags/v1.0.3
  7. Create GitHub Release: https://github.com/bodybybuddha/bbl-shutter-cam/releases/new
    • Select the tag you just pushed
    • Add release notes
    • Click “Publish release” (this triggers the build workflow)
  8. Wait for binaries: GitHub Actions builds and uploads ARM binaries (~5-10 min)
  9. Verify release: Download and test a binary

Important: The automated build workflow only triggers when you publish a GitHub Release, not when you push a tag. Publishing the release will automatically build and upload the Linux ARM64 and ARMv7 binaries.