Python Briefcase Native Apps — Deep Dive
How Briefcase bundles Python
Unlike PyInstaller (which embeds CPython), Briefcase uses platform-specific Python distributions:
- macOS/iOS: A framework build of CPython compiled as a macOS framework, embedded in the
.appbundle. - Windows: An embeddable CPython distribution placed alongside the executable.
- Linux: System Python or a bundled AppImage Python.
- Android: A cross-compiled CPython for ARM/ARM64, loaded by a Java wrapper activity.
This approach means the Python runtime is properly integrated with each platform’s conventions — code signing, library loading paths, and security sandboxing all work as the OS expects.
Project structure internals
After briefcase create macOS, the build directory contains:
build/myapp/macos/app/
├── My App.app/
│ ├── Contents/
│ │ ├── Info.plist
│ │ ├── MacOS/
│ │ │ └── My App # launcher binary
│ │ ├── Resources/
│ │ │ ├── app/ # your Python source
│ │ │ ├── app_packages/ # pip-installed dependencies
│ │ │ ├── python-stdlib/ # standard library
│ │ │ └── icon.icns
│ │ └── Frameworks/
│ │ └── Python.framework/
Briefcase manages this structure — briefcase update syncs your source code into the existing build without recreating everything.
Android development
Setup
briefcase create android
briefcase build android
briefcase run android
Briefcase downloads the Android SDK, NDK, and a cross-compiled Python automatically. On first run, it launches an emulator or deploys to a connected device.
Permissions
[tool.briefcase.app.myapp.android]
requires = ["toga-android>=0.4.0"]
permission.INTERNET = true
permission.CAMERA = true
permission.ACCESS_FINE_LOCATION = true
These translate to AndroidManifest.xml entries. Runtime permission requests are handled by Toga’s permission API:
from toga import App
import toga
async def request_camera(app):
granted = await app.request_permission(toga.Permission.CAMERA)
if granted:
# access camera
pass
Accessing Android APIs via Rubicon-Java
from rubicon.java import JavaClass
Toast = JavaClass("android/widget/Toast")
context = JavaClass("org/beeware/android/MainActivity").singletonThis
def show_toast(message):
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast.show()
Rubicon-Java provides a bridge to any Android Java/Kotlin API without writing Java code.
macOS code signing and notarization
Signing
[tool.briefcase.app.myapp.macOS]
identity = "Developer ID Application: Your Name (TEAM_ID)"
briefcase package macOS --identity "Developer ID Application: Your Name (TEAM_ID)"
Briefcase automatically signs the app bundle and all embedded frameworks.
Notarization
briefcase package macOS --notarize \
--team-id TEAM_ID \
--apple-id you@example.com
This submits the .dmg to Apple’s notary service. Without notarization, macOS Catalina and later will block the app with a Gatekeeper warning.
Windows MSI customization
[tool.briefcase.app.myapp.windows]
requires = ["toga-winforms>=0.4.0"]
install_scope = "perUser" # or "perMachine"
The MSI installer includes:
- Start Menu shortcut
- Add/Remove Programs entry
- Optional desktop shortcut
- Proper uninstallation support
For advanced customization, Briefcase generates WiX Toolset XML that you can modify.
Linux Flatpak distribution
briefcase create linux flatpak
briefcase package linux flatpak
Output is a .flatpak file ready for distribution or submission to Flathub. The Flatpak manifest handles sandboxing, desktop file integration, and icon installation.
For AppImage output:
briefcase create linux appimage
briefcase package linux appimage
CI/CD multi-platform pipeline
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: macos-latest
platform: macOS
- os: windows-latest
platform: windows
- os: ubuntu-latest
platform: linux
format: flatpak
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install briefcase
- name: Build and Package
run: |
briefcase create ${{ matrix.platform }} ${{ matrix.format || '' }}
briefcase build ${{ matrix.platform }} ${{ matrix.format || '' }}
briefcase package ${{ matrix.platform }} ${{ matrix.format || '' }}
- uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.platform }}
path: dist/
This produces macOS .dmg, Windows .msi, and Linux .flatpak artifacts from a single workflow.
Using non-Toga GUI frameworks
Briefcase works with any GUI framework:
[tool.briefcase.app.myapp]
requires = ["PyQt6"]
[tool.briefcase.app.myapp.macOS]
requires = ["PyQt6"]
The key constraint: the framework must support the target platform. PyQt6 works for desktop but not mobile. For cross-platform including mobile, Toga remains the primary option.
Testing with Briefcase
# Run tests in the development environment
briefcase dev --test
# Run tests in the packaged environment
briefcase run --test
Briefcase executes your test suite (tests/ directory) inside the actual bundled environment, catching path resolution and dependency issues that unit tests in development would miss.
Plugin and template system
Briefcase uses Cookiecutter templates for each platform. You can create custom templates:
[tool.briefcase.app.myapp.macOS]
template = "https://github.com/yourorg/custom-macos-template"
Custom templates let you modify the app launcher, add native code, or integrate platform-specific build steps (Rust extensions, C libraries, etc.).
Limitations and trade-offs
- Build speed: First build downloads platform toolchains (Android SDK: ~2GB). Subsequent builds are faster with
briefcase update. - Mobile maturity: Android support is stable. iOS works but requires macOS and Xcode. Both are less mature than React Native or Flutter ecosystems.
- Toga widget coverage: Toga covers common widgets (buttons, tables, trees, web views) but lacks the breadth of Qt or native UIKit. Complex custom UIs may need platform-specific code.
- Bundle size: A minimal Toga app is ~20-30 MB on desktop, ~30-50 MB on Android, due to the bundled Python interpreter.
One thing to remember: Briefcase’s value is not just packaging — it is the full native distribution story: project scaffolding, platform-specific builds, code signing, notarization, and app store submission, all driven from pyproject.toml and a consistent CLI.
See Also
- Python Appimage Distribution An AppImage is like a portable app on a USB stick — download one file, double-click it, and your Python program runs on any Linux computer without installing anything.
- Python Flatpak Packaging Flatpak wraps your Python app in a safe bubble that works on every Linux system — like a snow globe that keeps your program perfect inside.
- Python Mypyc Compilation Your type hints are not just for documentation — mypyc turns them into speed boosts by compiling typed Python into fast C extensions.
- Python Nuitka Compilation What if your Python code could run as fast as a race car instead of a bicycle? Nuitka translates Python into C to make that happen.
- Python Pex Executables Imagine zipping your entire Python project into a single magic file that runs anywhere Python lives — that's what PEX does.