Skip to content

Building on macOS

On macOS, pyzed build makes an app bundle, dist/Shop.app: a folder that Finder shows as one app, with Python, Pyzed and your code inside.

  1. Install uv if you do not have it:

    Terminal window
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. In the project folder, build:

    Terminal window
    uv run pyzed build
  3. Try it:

    Terminal window
    open dist/Shop.app

macOS uses an .icns icon. Every Mac app also has a bundle identifier, a reverse domain name such as com.acme.shop that macOS uses to keep its settings and permissions. Set both in pyproject.toml; the identifier goes to PyInstaller through args:

pyproject.toml
[tool.pyzed.build]
icon = "assets/icon.icns"
args = ["--osx-bundle-identifier", "com.acme.shop"]

See Icons and metadata to make an .icns from a PNG.

Apps downloaded from the internet are checked by Gatekeeper. An app that is not signed and notarized is blocked (“Shop cannot be opened because Apple cannot check it for malicious software”). People can still open it with right-click → Open, or in System Settings → Privacy & Security → Open Anyway, but for a real release, sign and notarize it.

You need an Apple Developer Program membership, a Developer ID Application certificate in your keychain and an app-specific password for your Apple ID.

  1. Sign the bundle with the hardened runtime and a timestamp:

    Terminal window
    codesign --force --deep --options runtime --timestamp \
    --sign "Developer ID Application: Your Name (TEAMID)" dist/Shop.app
  2. Zip it with ditto (it keeps the bundle’s symlinks and signature) and send it to Apple’s notary service:

    Terminal window
    ditto -c -k --keepParent dist/Shop.app Shop.zip
    xcrun notarytool submit Shop.zip --wait \
    --apple-id you@example.com --team-id TEAMID --password "$APPLE_APP_PASSWORD"
  3. Staple the ticket to the app, so it opens even offline:

    Terminal window
    xcrun stapler staple dist/Shop.app

Check the result with spctl --assess --verbose dist/Shop.app (“accepted, source=Notarized Developer ID”).

Mac users expect a .dmg with the app and a shortcut to Applications to drag it into.

Terminal window
mkdir -p build/dmg
ditto dist/Shop.app build/dmg/Shop.app
ln -s /Applications build/dmg/Applications
hdiutil create -volname Shop -srcfolder build/dmg -fs HFS+ -format UDZO -ov Shop-macos-arm64.dmg
codesign --force --timestamp --sign "Developer ID Application: Your Name (TEAMID)" Shop-macos-arm64.dmg

Notarize and staple the .dmg the same way as the zip above. This is how Pyzed Design’s own .dmg is made (see macos_dmg in scripts/build_designer.py).

  • Where it lives: people drag the app to /Applications, which is writable by admin users, so the app can update itself there.
  • Quarantine: a downloaded app carries a quarantine flag until the first launch passes Gatekeeper. For your own testing you can clear it with xattr -dr com.apple.quarantine dist/Shop.app; do not ask your users to do this.
  • Permissions: if the app needs the camera, microphone or other protected resources, macOS asks the user. Add the usage descriptions to the bundle’s Info.plist with PyInstaller’s spec file options; pyzed build does not set them for you.