Skip to content

Latest commit

 

History

History
340 lines (261 loc) · 15.4 KB

File metadata and controls

340 lines (261 loc) · 15.4 KB

pre-commit.ci status

Python Binary Build Action

A GitHub Action for building Python applications into standalone executables using PyInstaller across multiple architectures and platforms.

Overview

This action automates the process of creating standalone executables from Python applications using PyInstaller. It supports multiple target platforms including Windows, macOS, and Linux (both x86_64 and ARM architectures). The action handles platform-specific configurations, dependency installation, and binary verification automatically.

Motivation

This action provides several key benefits:

Centralized Build Logic: Eliminates code duplication across repositories by providing a single, centrally maintained build solution. This ensures consistent build processes and easier maintenance.

Reduced Antivirus False Positives: Antivirus software often flags PyInstaller-generated executables as suspicious. By centrally controlling PyInstaller versions and configurations, this action helps minimize false positive reports through tested and optimized settings.

User Accessibility: Many users prefer standalone executables over Python scripts, as they eliminate the need to install Python and manage dependencies. This is particularly valuable for:

  • Users unfamiliar with Python installation and package management
  • Legacy systems with unsupported Python versions
  • Deployment scenarios requiring minimal dependencies

Supported Platforms

  • Windows: windows-amd64
  • Linux: linux-amd64, linux-armv7, linux-aarch64
  • macOS: macos-amd64, macos-arm64

Features

  • Multi-architecture support: Builds for x86_64 and ARM architectures
  • Cross-platform compatibility: Supports Windows, macOS, and Linux
  • Automatic dependency handling: Installs Python dependencies and system packages
  • Flexible data file inclusion: Supports per-script configuration with wildcard support
  • Executable verification: Tests built executables to ensure they run correctly
  • Windows icon support: Allows custom icons for Windows executables
  • Flexible PyInstaller configuration: Supports additional PyInstaller arguments
  • ARM cross-compilation: Uses Public Preview runners for aarch64 and Docker containers with Qemu for ARMv7 architecture builds
  • GLIBC 2.31+ support: Uses older Linux images in Docker for best compatibility with Linux targets

Usage Examples

For more detailed explanation of input variables and advanced use cases, please see the Inputs section below.

Basic Usage

- name: Build Python executable
  uses: espressif/python-binary-action@master
  with:
    scripts: 'main.py'
    output-dir: './dist'
    target-platform: 'linux-amd64'

Multi-File Build with Data Files

Sometimes your Python script might require additional non-Python files, like assets (images), JSON or YAML files. These can be included for either all scripts or you can define files per script.

For example with the following project structure:

my_script/
src/
├── assets/
|   └── image.svg
├── config/
|   └── config.json
├── app.py
├── main.py
├── icon.ico
├── pyproject.toml
└── README.md

There is a config file that is used in both app.py and main.py, but only app.py requires images. The action can look something like this:

- name: Build multiple executables
  uses: espressif/python-binary-action@master
  with:
    # Mandatory args
    scripts: 'main.py app.py'
    output-dir: './binaries'
    target-platform: 'windows-amd64'
    # Optional args; non-python files to include
    include-data-dirs: |
      {
        "app.py": ["./assets"],
        "*": ["./config"]
      }
    icon-file: './icon.ico'  # Icon for Windows executable

There are two options how to define data files to be included for all scripts:

  1. With wildcard *
{
  "*": ["./config"]
}
  1. Simple list
["./config"]

Both options are equivalent, but the wildcard allows you to define additional data files that are specific for one script. As can be seen in the above example for app.py.

Custom Executable Names

Sometimes it might be useful to have a control over the name of build binary (executable). For example if we have a following structure of the project:

my_script/
├── src/
│   ├── __init__.py
│   └── __main__.py
├── pyproject.toml
└── README.md

Building the project with default configuration will result in script name __main__.py, which is probably not desirable. To solve this issue, we can pass optional argument script-name that will be used as basename for build binaries (executables).

- name: Build with custom names
  uses: espressif/python-binary-action@master
  with:
    scripts: 'src/__main__.py'
    script-name: 'my_script'
    output-dir: './dist'
    target-platform: 'linux-amd64'

ARM Architecture Build

The ARMv7 build image already includes most of the required system packages by default. However, if you need to add extra system packages, you can do so as shown in the example below. For more details about the ARMv7 image, refer to its Dockerfile definition.

- name: Build for ARMv7
  uses: espressif/python-binary-action@master
  with:
    scripts: 'main.py'
    output-dir: './arm-binaries'
    target-platform: 'linux-armv7'
    additional-arm-packages: 'openssl libffi-dev'
    python-version: '3.11'

Custom PyInstaller Configuration

- name: Build with custom options
  uses: espressif/python-binary-action@master
  with:
    scripts: 'app.py'
    output-dir: './dist'
    target-platform: 'macos-arm64'
    additional-args: '--hidden-import=requests --hidden-import=urllib3 --strip'
    pyinstaller-version: '6.3.0'
    test-command-args: '--version'

Signing Windows Binaries

If you would like to sign Windows binaries, you can configure Azure Key Vault credentials and the action will automatically sign all binaries after building. Signing is only performed for windows-amd64 platform builds.

The action uses the espressif/release-sign action internally, which requires Azure credentials to access a certificate stored in Azure Key Vault. If the Azure client secret is not set, signing will be skipped with a warning message.

To enable signing, you must explicitly pass the Azure credentials as inputs from your workflow. Set the following secrets in your repository and pass them to the action:

- name: Build Python executable
  uses: espressif/python-binary-action@master
  with:
    scripts: 'app.py'
    output-dir: './dist'
    target-platform: 'windows-amd64'
    # Azure credentials for signing (must be explicitly passed)
    azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
    azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
    azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
    azure-keyvault-uri: ${{ secrets.AZURE_KEYVAULT_URI }}
    azure-keyvault-cert-name: ${{ secrets.AZURE_KEYVAULT_CERT_NAME }}

Complete Workflow

Here you can see a simplified version of workflow used in esptool repository:

name: Build Executables

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false  # Avoid failure of all action in case of one of them fails
      # Define platform matrix to build on multiple platforms at the same time
      matrix:
        platform: [windows-amd64, linux-amd64, macos-arm64, linux-aarch64]
        include:
          - platform: windows-amd64
            runner: windows-latest
          - platform: linux-amd64
            runner: ubuntu-latest
          - platform: macos-arm64
            runner: macos-latest
          - platform: linux-aarch64
            runner: ubuntu-24.04-arm

    env:
      # Used for additional data to be included in executables
      # Env variable is only for action simplification
      STUBS_DIR: ./esptool/targets/stub_flasher/

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: Build executable
        uses: espressif/python-binary-action@master
        with:
          # Required options
          scripts: 'esptool.py'  # Building from 'esptool.py' file
          output-dir: './${{ matrix.platform }}'
          target-platform: ${{ matrix.platform }}
          # Optional args; non-python files that will be added to build executable
          # We want to include the content of subdirectories `1` and `2` of the directory stored in environment variable `STUBS_DIR`.
          include-data-dirs: |
            {
              "esptool.py": [
                "${{ env.STUBS_DIR }}1",
                "${{ env.STUBS_DIR }}2",
              ]
            }

      - name: Add license and readme
        shell: bash
        run: cp LICENSE README.md ./${{ matrix.platform }}

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

Inputs

Required Inputs

Input Description Example
scripts Space-separated list of Python scripts to build "esptool.py espefuse.py"
output-dir Output directory for built executables "./dist-linux-amd64"
target-platform Target platform for the build "linux-amd64"

Optional Inputs

Input Description Default Example
script-name Custom names for the output executables. Must provide exactly one name per script in the same order. (On Windows .exe suffix will be added to each name) "" "foo bar"
include-data-dirs Mapping script names to data directories to include. Supports wildcards (*). [] {"main.py": ["./data"], "*": ["./common"]}
icon-file Path to icon file (Windows only) "" "./icon.ico"
python-version Python version to use for building "3.13" "3.12"
pyinstaller-version PyInstaller version to install 6.11.1 "" (use latest)
additional-args Additional PyInstaller arguments "" "--hidden-import=module"
pip-extra-index-url Extra Python package index URL https://dl.espressif.com/pypi ""
install-deps-command Command to install project dependencies "uv pip install -e ." "uv pip install -r requirements.txt"
additional-arm-packages ARMv7 ONLY: Extra apt packages (the Espressif ARMv7 image already ships most build deps) "" "openssl libssl-dev"
test-command-args Command arguments to test executables "--help" "--version"

Important

Be careful when changing pyinstaller-version as it might lead to increased false positives with anti-virus software. It is recommended to check your executables with antivirus software such as Virustotal.

Optional Inputs for Signing Binaries

For signing binaries on Windows, this action uses the espressif/release-sign action. The following inputs are optional but required if you want to sign your Windows executables.

Signing is optional but strongly recommended. The action will produce a warning if a Windows executable was built but was not signed.

Input Description Default Example
azure-client-id Azure client ID for signing "" ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret Azure client secret for signing "" ${{ secrets.AZURE_CLIENT_SECRET }}
azure-tenant-id Azure tenant ID for signing "" ${{ secrets.AZURE_TENANT_ID }}
azure-keyvault-uri Azure key vault URI for signing "" ${{ secrets.AZURE_KEYVAULT_URI }}
azure-keyvault-cert-name Azure key vault certificate name "" ${{ secrets.AZURE_KEYVAULT_CERT_NAME }}

Outputs

Output Description
executable-extension File extension of built executables (e.g., .exe for Windows)
build-success Whether the build was successful (true/false)

Known Limitations

Using rich Package

When using this action to build a binary that depends on rich it is recommended to add the following to the inputs:

with:
  additional-args: "--collect-submodules=rich._unicode_data"

This is required for rich>=14.3.0, which introduced a new way of loading Unicode data.

rich uses dynamic imports. As a result, PyInstaller cannot always identify these imports as required, and it might not include them in the built binary.

Without the additional arguments to PyInstaller, executing the built binary might result in an error similar to the following:

ModuleNotFoundError: No module named 'rich._unicode_data.unicode17-0-0'

Notes

  • For 32-bit ARM architecture (linux-armv7), the action uses Docker containers to provide the necessary build environment
  • For 64-bit ARM architecture please use the GitHub provided runners, e.g. ubuntu-24.04-arm. Please note that this is still in public preview so there might be some changes to images. For more details see available runners.
  • Windows builds automatically include .exe extensions
  • The action automatically tests built executables using the specified test command arguments
  • System packages for ARMv7 builds can be customized using the additional-arm-packages input. For other systems, this can be done before running this action.
  • It is recommended to add fail-fast: false to your matrix strategy to prevent one platform failure from stopping all builds