Skip to content

Commit d264606

Browse files
committed
feat: Add a initial version of the action
1 parent f2c80ad commit d264606

3 files changed

Lines changed: 692 additions & 1 deletion

File tree

README.md

Lines changed: 278 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,278 @@
1-
# python-binary-action
1+
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/espressif/python-binary-action/master.svg)](https://results.pre-commit.ci/latest/github/espressif/python-binary-action/master)
2+
3+
# Python Binary Build Action
4+
5+
A GitHub Action for building Python applications into standalone executables using `PyInstaller` across multiple architectures and platforms.
6+
7+
## Overview
8+
9+
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.
10+
11+
### Motivation
12+
13+
This action provides several key benefits:
14+
15+
**Centralized Build Logic**: Eliminates code duplication across repositories by providing a single, centrally maintained build solution. This ensures consistent build processes and easier maintenance.
16+
17+
**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.
18+
19+
**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:
20+
21+
- Users unfamiliar with Python installation and package management
22+
- Legacy systems with unsupported Python versions
23+
- Deployment scenarios requiring minimal dependencies
24+
25+
## Supported Platforms
26+
27+
- **Windows**: `windows-amd64`
28+
- **Linux**: `linux-amd64`, `linux-armv7`, `linux-aarch64`
29+
- **macOS**: `macos-amd64`, `macos-arm64`
30+
31+
## Features
32+
33+
- **Multi-architecture support**: Builds for x86_64 and ARM architectures
34+
- **Cross-platform compatibility**: Supports Windows, macOS, and Linux
35+
- **Automatic dependency handling**: Installs Python dependencies and system packages
36+
- **Flexible data file inclusion**: Supports per-script configuration with wildcard support
37+
- **Executable verification**: Tests built executables to ensure they run correctly
38+
- **Windows icon support**: Allows custom icons for Windows executables
39+
- **Flexible `PyInstaller` configuration**: Supports additional `PyInstaller` arguments
40+
- **ARM cross-compilation**: Uses Public Preview runners for aarch64 and Docker containers with Qemu for ARMv7 architecture builds
41+
- **GLIBC 2.31+ support**: Uses older Linux images in Docker for best compatibility with Linux targets
42+
43+
## Usage Examples
44+
45+
For more detailed explanation of input variables and advanced use cases, please see the [Inputs](#inputs) section below.
46+
47+
### Basic Usage
48+
49+
```yaml
50+
- name: Build Python executable
51+
uses: espressif/python-binary-action@master
52+
with:
53+
scripts: 'main.py'
54+
output-dir: './dist'
55+
target-platform: 'linux-amd64'
56+
```
57+
58+
### Multi-File Build with Data Files
59+
60+
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.
61+
62+
For example with the following project structure:
63+
64+
```txt
65+
my_script/
66+
src/
67+
├── assets/
68+
| └── image.svg
69+
├── config/
70+
| └── config.json
71+
├── app.py
72+
├── main.py
73+
├── icon.ico
74+
├── pyproject.toml
75+
└── README.md
76+
```
77+
78+
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:
79+
80+
```yaml
81+
- name: Build multiple executables
82+
uses: espressif/python-binary-action@master
83+
with:
84+
# Mandatory args
85+
scripts: 'main.py app.py'
86+
output-dir: './binaries'
87+
target-platform: 'windows-amd64'
88+
# Optional args; non-python files to include
89+
include-data-dirs: |
90+
{
91+
"app.py": ["./assets"],
92+
"*": ["./config"]
93+
}
94+
icon-file: './icon.ico' # Icon for Windows executable
95+
```
96+
97+
There are two options how to define data files to be included for all scripts:
98+
99+
1. With wildcard `*`
100+
101+
```json
102+
{
103+
"*": ["./config"]
104+
}
105+
```
106+
107+
2. Simple list
108+
109+
```json
110+
["./config"]
111+
```
112+
113+
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`.
114+
115+
### Custom Executable Names
116+
117+
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:
118+
119+
```txt
120+
my_script/
121+
├── src/
122+
│ ├── __init__.py
123+
│ └── __main__.py
124+
├── pyproject.toml
125+
└── README.md
126+
```
127+
128+
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).
129+
130+
```yaml
131+
- name: Build with custom names
132+
uses: espressif/python-binary-action@master
133+
with:
134+
scripts: 'src/__main__.py'
135+
script-name: 'my_script'
136+
output-dir: './dist'
137+
target-platform: 'linux-amd64'
138+
```
139+
140+
### ARM Architecture Build
141+
142+
```yaml
143+
- name: Build for ARMv7
144+
uses: espressif/python-binary-action@master
145+
with:
146+
scripts: 'main.py'
147+
output-dir: './arm-binaries'
148+
target-platform: 'linux-armv7'
149+
additional-arm-packages: 'openssl libffi-dev libffi7 libssl-dev'
150+
python-version: '3.11'
151+
```
152+
153+
### Custom PyInstaller Configuration
154+
155+
```yaml
156+
- name: Build with custom options
157+
uses: espressif/python-binary-action@master
158+
with:
159+
scripts: 'app.py'
160+
output-dir: './dist'
161+
target-platform: 'macos-arm64'
162+
additional-args: '--hidden-import=requests --hidden-import=urllib3 --strip'
163+
pyinstaller-version: '6.3.0'
164+
test-command-args: '--version'
165+
```
166+
167+
### Complete Workflow
168+
169+
Here you can see a simplified version of workflow used in [esptool](https://github.com/espressif/esptool/) repository:
170+
171+
```yaml
172+
name: Build Executables
173+
174+
on: [push, pull_request]
175+
176+
jobs:
177+
build:
178+
runs-on: ${{ matrix.runner }}
179+
strategy:
180+
fail-fast: false # Avoid failure of all action in case of one of them fails
181+
# Define platform matrix to build on multiple platforms at the same time
182+
matrix:
183+
platform: [windows-amd64, linux-amd64, macos-arm64, linux-aarch64]
184+
include:
185+
- platform: windows-amd64
186+
runner: windows-latest
187+
- platform: linux-amd64
188+
runner: ubuntu-latest
189+
- platform: macos-arm64
190+
runner: macos-latest
191+
- platform: linux-aarch64
192+
runner: ubuntu-24.04-arm
193+
194+
env:
195+
# Used for additional data to be included in executables
196+
# Env variable is only for action simplification
197+
STUBS_DIR: ./esptool/targets/stub_flasher/
198+
199+
steps:
200+
- name: Checkout code
201+
uses: actions/checkout@v4
202+
203+
- name: Set up Python
204+
uses: actions/setup-python@v5
205+
with:
206+
python-version: '3.13'
207+
208+
- name: Build executable
209+
uses: espressif/python-binary-action@master
210+
with:
211+
# Required options
212+
scripts: 'esptool.py' # Building from 'esptool.py' file
213+
output-dir: './${{ matrix.platform }}'
214+
target-platform: ${{ matrix.platform }}
215+
# Optional args; non-python files that will be added to build executable
216+
# We want to include the content of subdirectories `1` and `2` of the directory stored in environment variable `STUBS_DIR`.
217+
include-data-dirs: |
218+
{
219+
"esptool.py": [
220+
"${{ env.STUBS_DIR }}1",
221+
"${{ env.STUBS_DIR }}2",
222+
]
223+
}
224+
225+
- name: Add license and readme
226+
shell: bash
227+
run: cp LICENSE README.md ./${{ matrix.platform }}
228+
229+
- name: Upload artifacts
230+
uses: actions/upload-artifact@v4
231+
with:
232+
name: executable-${{ matrix.platform }}
233+
path: ./${{ matrix.platform }}
234+
```
235+
236+
## Inputs
237+
238+
### Required Inputs
239+
240+
| Input | Description | Example |
241+
|-------------------|-------------------------------------------------|----------------------------|
242+
| `scripts` | Space-separated list of Python scripts to build | `"esptool.py espefuse.py"` |
243+
| `output-dir` | Output directory for built executables | `"./dist-linux-amd64"` |
244+
| `target-platform` | Target platform for the build | `"linux-amd64"` |
245+
246+
### Optional Inputs
247+
248+
| Input | Description | Default | Example |
249+
|---------------------------|-------------------------------------------|---------------------------------------------|----------------------------------------------|
250+
| `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"` |
251+
| `include-data-dirs` | Mapping script names to data directories to include. Supports wildcards (*). | `[]` | `{"main.py": ["./data"], "*": ["./common"]}` |
252+
| `icon-file` | Path to icon file (Windows only) | `""` | `"./icon.ico"` |
253+
| `python-version` | Python version to use for building | `"3.13"` | `"3.12"` |
254+
| `pyinstaller-version` | PyInstaller version to install | `6.11.1` | `""` (use latest) |
255+
| `additional-args` | Additional PyInstaller arguments | `""` | `"--hidden-import=module"` |
256+
| `pip-extra-index-url` | Extra pip index URL | `https://dl.espressif.com/pypi` | `""` |
257+
| `install-deps-command` | Command to install project dependencies | `"pip install --user --prefer-binary -e ."` | `"pip install -r requirements.txt"` |
258+
| `additional-arm-packages` | ARMv7 ONLY: Additional system packages | `""` | `"openssl libffi-dev"` |
259+
| `test-command-args` | Command arguments to test executables | `"--help"` | `"--version"` |
260+
261+
> [!IMPORTANT]
262+
> 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](https://www.virustotal.com/gui/home/upload).
263+
264+
## Outputs
265+
266+
| Output | Description |
267+
|------------------------|----------------------------------------------------------------|
268+
| `executable-extension` | File extension of built executables (e.g., `.exe` for Windows) |
269+
| `build-success` | Whether the build was successful (`true`/`false`) |
270+
271+
## Notes
272+
273+
- For 32-bit ARM architecture (linux-armv7), the action uses Docker containers to provide the necessary build environment
274+
- 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](https://docs.github.com/en/actions/how-tos/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources).
275+
- Windows builds automatically include `.exe` extensions
276+
- The action automatically tests built executables using the specified test command arguments
277+
- 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.
278+
- It is recommended to add `fail-fast: false` to your matrix strategy to prevent one platform failure from stopping all builds

0 commit comments

Comments
 (0)