Skip to content

How To: Cross-Platform Builds

This guide explains how to protect your application for multiple operating systems and architectures.


Supported Platforms

Platform ID OS Architecture File Extension
win-x64 Windows x86_64 .exe
linux-x64 Linux x86_64 (none)
linux-arm64 Linux aarch64 (none)
mac-x64 macOS Intel .app
mac-arm64 macOS Apple Silicon .app

Strategy

Cross-platform protection requires:

  1. Building your application separately on each target platform (or using cross-compilation)
  2. Protecting each platform artifact individually with PyLocket

PyLocket bundles a platform-specific native runtime for each target.

The correct PyInstaller mode is platform-specific:

Platform PyInstaller Flags Output Why
macOS --onedir --windowed .app Produces a proper .app bundle with Contents/Frameworks/Python.framework/ physically present (~6.5 MB dylib). The .app appears as a single file in Finder and is the standard format for macOS distribution.
Windows --onefile --windowed .exe Single .exe with the Python runtime and all DLLs compressed inside. Extracts to a temp directory at launch.
Linux --onefile binary Single self-contained binary. Same extraction behaviour as Windows.

Do not use --onefile on macOS

--onefile on macOS compresses everything — including the Python runtime and all native libraries — into a binary blob inside the executable. The resulting .app bundle does not contain Contents/Frameworks/Python.framework/ as a physical directory. Python is buried inside the compressed executable and unusable by the system. This produces a non-standard .app that can cause code-signature failures and "damaged" errors after protection. Always use --onedir --windowed for macOS.


Register All Platforms at Once

pylocket apps create \
  --name "MyApp" \
  --platform win-x64 linux-x64 linux-arm64 mac-x64 mac-arm64

Build Per Platform

Option A: Build on Native Hardware

Build on each target platform using the recommended build mode:

# On Windows
pyinstaller --onefile --windowed myapp.py
# → dist/myapp.exe
# On Linux x64
pyinstaller --onefile myapp.py
# → dist/myapp
# On macOS — always use --onedir --windowed
pyinstaller --onedir --windowed myapp.py
# → dist/MyApp.app  (complete .app bundle with Python.framework in Contents/Frameworks/)

Option B: CI/CD Matrix Build

Use a CI/CD matrix to build on multiple runners:

# GitHub Actions
jobs:
  build:
    strategy:
      matrix:
        include:
          - os: windows-latest
            platform: win-x64
            artifact: dist/myapp.exe
          - os: ubuntu-latest
            platform: linux-x64
            artifact: dist/myapp
          - os: macos-latest
            platform: mac-arm64
            artifact: dist/MyApp.app
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install pyinstaller -r requirements.txt

      # Windows: single-file windowed binary
      - if: runner.os == 'Windows'
        run: pyinstaller --onefile --windowed myapp.py

      # Linux: single-file binary
      - if: runner.os == 'Linux'
        run: pyinstaller --onefile myapp.py

      # macOS: directory mode for a proper .app bundle
      - if: runner.os == 'macOS'
        run: pyinstaller --onedir --windowed myapp.py

      - uses: actions/upload-artifact@v4
        with:
          name: build-${{ matrix.platform }}
          path: ${{ matrix.artifact }}

macOS CI artifact

The macOS --onedir --windowed step produces a dist/MyApp.app directory. upload-artifact handles directories natively, so no extra zipping is required in CI.


Protect Each Platform

# Windows
pylocket protect --app <APP_ID> --artifact dist/myapp.exe --platform win-x64 --python 3.12

# Linux x64
pylocket protect --app <APP_ID> --artifact dist/myapp --platform linux-x64 --python 3.12

# macOS ARM (.app bundle from --onedir --windowed)
pylocket protect --app <APP_ID> --artifact dist/MyApp.app --platform mac-arm64 --python 3.12

Each command produces a separate Build ID. Track them independently.

Uploading macOS .app bundles

When you upload a .app directory, the CLI automatically zips it for transfer. On the backend, PyLocket extracts the inner binary from Contents/MacOS/, protects it, and reassembles the full .app bundle with your Info.plist, icons, and frameworks intact.


Platform-Specific Native Runtime

Each platform requires its own native runtime library. The correct runtime is automatically bundled during protection. You do not need to manage this manually.


Licensing Across Platforms

License keys are platform-independent. A single license key works on any platform for the same app. Device fingerprints are platform-specific, so activating on Windows and macOS counts as two separate devices toward the device limit.


Delivery

When you have multiple platforms protected for the same app, the PyLocket delivery page automatically shows labeled download buttons for each available platform (e.g., "Windows", "macOS", "Linux"). Customers see only the platforms you have uploaded builds for.

Tip: For a full end-to-end walkthrough covering building, protecting, and distributing for all three OSes, see the Multi-OS Publishing Tutorial.


See Also