Skip to content

Commit 18b0fb0

Browse files
authored
Merge pull request #56 from espressif/feat/manylinux
Added Docker image for older ARMv7 architecture support because of glibc issues
2 parents 119dec3 + 8d7e2a9 commit 18b0fb0

10 files changed

+193
-123
lines changed

.github/workflows/build-wheels-platforms.yml

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
- macOS ARM
2929
- Linux ARM64
3030
- Linux ARMv7
31+
- Linux ARMv7 Legacy
3132
include:
3233
- os: Windows
3334
runner: windows-latest
@@ -47,11 +48,14 @@ jobs:
4748
- os: Linux ARMv7
4849
runner: ubuntu-latest
4950
arch: linux-armv7
51+
- os: Linux ARMv7 Legacy
52+
runner: ubuntu-latest
53+
arch: linux-armv7legacy
5054
python-version: ['${{ needs.get-supported-versions.outputs.oldest_supported_python }}']
5155

5256
steps:
5357
- name: Set up QEMU for ARMv7
54-
if: matrix.os == 'Linux ARMv7'
58+
if: matrix.os == 'Linux ARMv7' || matrix.os == 'Linux ARMv7 Legacy'
5559
uses: docker/setup-qemu-action@v3
5660
with:
5761
platforms: linux/arm/v7
@@ -79,20 +83,20 @@ jobs:
7983
8084
- name: Setup Python
8185
# Skip setting python on ARM because of missing compatibility: https://github.com/actions/setup-python/issues/108
82-
if: matrix.os != 'Linux ARMv7'
86+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
8387
uses: actions/setup-python@v5
8488
with:
8589
python-version: ${{ matrix.python-version }}
8690

8791

8892
- name: Install build dependencies
89-
if: matrix.os != 'Linux ARMv7'
93+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
9094
run: |
9195
python -m pip install --upgrade pip
9296
python -m pip install -r build_requirements.txt
9397
9498
- name: Get Tools versions
95-
if: matrix.os != 'Linux ARMv7'
99+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
96100
run: |
97101
python --version
98102
pip show pip setuptools
@@ -123,6 +127,7 @@ jobs:
123127
-e MIN_IDF_MINOR_VERSION=${{ needs.get-supported-versions.outputs.min_idf_minor_version }} \
124128
-e GH_TOKEN="${GH_TOKEN}" \
125129
-e PIP_NO_CACHE_DIR=1 \
130+
-e LDFLAGS="-Wl,-z,max-page-size=0x1000" \
126131
python:${{ matrix.python-version }}-bookworm \
127132
bash -c "
128133
set -e
@@ -136,8 +141,34 @@ jobs:
136141
python build_wheels.py
137142
"
138143
144+
- name: Build wheels for IDF - ARMv7 Legacy (in Docker)
145+
# Build on Bullseye (glibc 2.31) for compatibility with older systems
146+
# Note: Buster has Python 3.7 which is too old for our dependencies
147+
if: matrix.os == 'Linux ARMv7 Legacy'
148+
run: |
149+
docker run --rm --platform linux/arm/v7 \
150+
-v $(pwd):/work \
151+
-w /work \
152+
-e MIN_IDF_MAJOR_VERSION=${{ needs.get-supported-versions.outputs.min_idf_major_version }} \
153+
-e MIN_IDF_MINOR_VERSION=${{ needs.get-supported-versions.outputs.min_idf_minor_version }} \
154+
-e GH_TOKEN="${GH_TOKEN}" \
155+
-e PIP_NO_CACHE_DIR=1 \
156+
-e LDFLAGS="-Wl,-z,max-page-size=0x1000" \
157+
python:${{ matrix.python-version }}-bullseye \
158+
bash -c "
159+
set -e
160+
python --version
161+
# Install pip packages without cache to reduce memory usage
162+
python -m pip install --no-cache-dir --upgrade pip
163+
python -m pip install --no-cache-dir -r build_requirements.txt
164+
bash os_dependencies/linux_arm.sh
165+
# Source Rust environment after installation
166+
. \$HOME/.cargo/env
167+
python build_wheels.py
168+
"
169+
139170
- name: Build wheels for IDF - Linux/macOS
140-
if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7'
171+
if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
141172
run: |
142173
# Set ARCHFLAGS for macOS to prevent universal2 wheels
143174
if [ "${{ matrix.os }}" = "macOS ARM" ]; then

.github/workflows/build-wheels-python-dependent.yml

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
- macOS ARM
2929
- Linux ARM64
3030
- Linux ARMv7
31+
- Linux ARMv7 Legacy
3132
include:
3233
- os: Windows
3334
runner: windows-latest
@@ -47,6 +48,9 @@ jobs:
4748
- os: Linux ARMv7
4849
runner: ubuntu-latest
4950
arch: linux-armv7
51+
- os: Linux ARMv7 Legacy
52+
runner: ubuntu-latest
53+
arch: linux-armv7legacy
5054
python-version: ${{ fromJson(inputs.supported_python_versions) }}
5155
exclude:
5256
# Exclude oldest supported Python since it's already built in the platform builds
@@ -62,10 +66,15 @@ jobs:
6266
os: Linux ARM64
6367
- python-version: ${{ inputs.oldest_supported_python }}
6468
os: Linux ARMv7
69+
- python-version: ${{ inputs.oldest_supported_python }}
70+
os: Linux ARMv7 Legacy
71+
# Python 3.14 doesn't have bullseye images for ARM
72+
- python-version: '3.14'
73+
os: Linux ARMv7 Legacy
6574

6675
steps:
6776
- name: Set up QEMU for ARMv7
68-
if: matrix.os == 'Linux ARMv7'
77+
if: matrix.os == 'Linux ARMv7' || matrix.os == 'Linux ARMv7 Legacy'
6978
uses: docker/setup-qemu-action@v3
7079
with:
7180
platforms: linux/arm/v7
@@ -75,19 +84,19 @@ jobs:
7584

7685
- name: Setup Python
7786
# Skip setting python on ARMv7 (runs in Docker)
78-
if: matrix.os != 'Linux ARMv7'
87+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
7988
uses: actions/setup-python@v5
8089
with:
8190
python-version: ${{ matrix.python-version }}
8291

8392
- name: Get Python version
84-
if: matrix.os != 'Linux ARMv7'
93+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
8594
run: |
8695
python --version
8796
python -m pip install --upgrade pip
8897
8998
- name: Install dependencies
90-
if: matrix.os != 'Linux ARMv7'
99+
if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
91100
run: python -m pip install -r build_requirements.txt
92101

93102
- name: Install additional OS dependencies - Ubuntu
@@ -125,6 +134,7 @@ jobs:
125134
-e PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 \
126135
-e GH_TOKEN="${GH_TOKEN}" \
127136
-e PIP_NO_CACHE_DIR=1 \
137+
-e LDFLAGS="-Wl,-z,max-page-size=0x1000" \
128138
python:${{ matrix.python-version }}-bookworm \
129139
bash -c "
130140
set -e
@@ -138,8 +148,32 @@ jobs:
138148
python build_wheels_from_file.py dependent_requirements_${{ matrix.arch }}
139149
"
140150
151+
- name: Build Python dependent wheels - ARMv7 Legacy (in Docker)
152+
# Build on Bullseye (glibc 2.31) for compatibility with older systems
153+
if: matrix.os == 'Linux ARMv7 Legacy'
154+
run: |
155+
docker run --rm --platform linux/arm/v7 \
156+
-v $(pwd):/work \
157+
-w /work \
158+
-e PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 \
159+
-e GH_TOKEN="${GH_TOKEN}" \
160+
-e PIP_NO_CACHE_DIR=1 \
161+
-e LDFLAGS="-Wl,-z,max-page-size=0x1000" \
162+
python:${{ matrix.python-version }}-bullseye \
163+
bash -c "
164+
set -e
165+
python --version
166+
# Install pip packages without cache to reduce memory usage
167+
python -m pip install --no-cache-dir --upgrade pip
168+
python -m pip install --no-cache-dir -r build_requirements.txt
169+
bash os_dependencies/linux_arm.sh
170+
# Source Rust environment after installation
171+
. \$HOME/.cargo/env
172+
python build_wheels_from_file.py dependent_requirements_${{ matrix.arch }}
173+
"
174+
141175
- name: Build Python dependent wheels - Linux/macOS
142-
if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7'
176+
if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy'
143177
run: |
144178
# Set ARCHFLAGS for macOS to prevent universal2 wheels
145179
if [ "${{ matrix.os }}" = "macOS ARM" ]; then

.github/workflows/wheels-repair.yml

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
- Linux x86_64
2424
- Linux ARM64
2525
- Linux ARMv7
26+
- Linux ARMv7 Legacy
2627
include:
2728
- platform: Windows
2829
runner: windows-latest
@@ -58,6 +59,15 @@ jobs:
5859
qemu_platform: arm
5960
docker_platform: linux/arm/v7
6061
arch: linux-armv7
62+
- platform: Linux ARMv7 Legacy
63+
runner: ubuntu-latest
64+
tool: auditwheel
65+
manylinux_platform: idf-python-wheels-armv7l-legacy
66+
docker_image: ghcr.io/espressif/github-esp-dockerfiles/idf-python-wheels-armv7l-legacy:v1
67+
setup_qemu: true
68+
qemu_platform: arm
69+
docker_platform: linux/arm/v7
70+
arch: linux-armv7legacy
6171

6272
steps:
6373
- name: Checkout repository
@@ -66,7 +76,9 @@ jobs:
6676
- name: Download wheel artifacts
6777
uses: actions/download-artifact@v4
6878
with:
69-
pattern: wheels-download-directory-*
79+
# Download only artifacts for this specific architecture to avoid mixing wheels
80+
# (especially important for ARMv7 vs ARMv7 Legacy which produce same-named wheels)
81+
pattern: wheels-download-directory-${{ matrix.arch }}-*
7082
path: ./downloaded_wheels
7183
merge-multiple: true
7284

@@ -131,6 +143,7 @@ jobs:
131143
"
132144
133145
- name: Repair Linux ARMv7 wheels in manylinux container
146+
# Using --break-system-packages and --ignore-installed to avoid conflicts with system packages
134147
if: matrix.platform == 'Linux ARMv7'
135148
run: |
136149
docker run --rm \
@@ -141,7 +154,23 @@ jobs:
141154
bash -c "
142155
bash os_dependencies/linux_arm.sh
143156
python3 -m pip install --break-system-packages --upgrade pip
144-
python3 -m pip install --break-system-packages -r build_requirements.txt
157+
python3 -m pip install --break-system-packages --ignore-installed -r build_requirements.txt
158+
python3 repair_wheels.py
159+
"
160+
161+
- name: Repair Linux ARMv7 Legacy wheels in manylinux container
162+
# Using --break-system-packages and --ignore-installed to avoid conflicts with system packages
163+
if: matrix.platform == 'Linux ARMv7 Legacy'
164+
run: |
165+
docker run --rm \
166+
--platform ${{ matrix.docker_platform }} \
167+
-v $(pwd):/work \
168+
-w /work \
169+
${{ matrix.docker_image }} \
170+
bash -c "
171+
bash os_dependencies/linux_arm.sh
172+
python3 -m pip install --break-system-packages --upgrade pip
173+
python3 -m pip install --break-system-packages --ignore-installed -r build_requirements.txt
145174
python3 repair_wheels.py
146175
"
147176

.pre-commit-config.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ci:
1010
autofix_prs: true
1111
autoupdate_commit_msg: 'ci: Bump pre-commit hooks'
1212
autoupdate_schedule: quarterly
13+
skip: ['mypy']
1314

1415
repos:
1516
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -23,20 +24,20 @@ repos:
2324
- id: check-yaml
2425

2526
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: v0.13.3
27+
rev: v0.14.10
2728
hooks:
2829
- id: ruff-check
2930
args: [--fix, --exit-non-zero-on-fix]
3031
- id: ruff-format
3132

3233
- repo: https://github.com/pre-commit/mirrors-mypy
33-
rev: v1.18.2
34+
rev: v1.18.2 # frozen: minimum Python version compatibility (newer versions require Python 3.9+)
3435
hooks:
3536
- id: mypy
3637
additional_dependencies: ['types-all-latest']
3738

3839
- repo: https://github.com/espressif/conventional-precommit-linter
39-
rev: v1.10.0
40+
rev: v1.11.0
4041
hooks:
4142
- id: conventional-precommit-linter
4243
stages: [commit-msg]

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,9 @@ The main file is `build-wheels-platforms.yml` which is scheduled to run periodic
141141
> Python dependent wheels are wheels which depend on the [CPython’s Application Binary Interface (ABI)](https://docs.python.org/3/c-api/stable.html). These are checked based on the [wheel filename format](https://peps.python.org/pep-0491/#file-format) where the `abi tag` is checked for `cp`. Such wheels need to be build also for all supported Python versions, not only for the minimum Python version supported by [ESP-IDF].
142142
143143

144+
## Custom Docker images
145+
Docker files are in its own repository where there are build and published from. https://github.com/espressif/github-esp-dockerfiles
146+
- ARMv7 runner - [manylinux_armv7](https://github.com/espressif/github-esp-dockerfiles/pkgs/container/github-esp-dockerfiles%2Fidf-python-wheels-armv7l)
147+
- ARMv7 runner (older OSes) - [manylinux_armv7_legacy](https://github.com/espressif/github-esp-dockerfiles/pkgs/container/github-esp-dockerfiles%2Fidf-python-wheels-armv7l-legacy)
148+
144149
[ESP-IDF]: https://github.com/espressif/esp-idf

_helper_functions.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,55 @@
33
#
44
# SPDX-License-Identifier: Apache-2.0
55
#
6+
import platform
7+
import re
8+
69
from colorama import Fore
710
from colorama import Style
811
from packaging.requirements import Requirement
912

13+
# Packages that should be built from source on Linux to ensure correct library linking
14+
# These packages often have pre-built wheels on PyPI that link against different library versions
15+
# NOTE: This only applies to Linux (especially ARM) - Windows and macOS pre-built wheels work fine
16+
# NOTE: Do NOT add packages with Rust components (cryptography, pynacl, bcrypt) here
17+
# as they have complex build requirements and may not support all Python versions
18+
FORCE_SOURCE_BUILD_PACKAGES_LINUX = [
19+
"cffi",
20+
"pillow",
21+
"pyyaml",
22+
"brotli",
23+
"greenlet",
24+
"bitarray",
25+
]
26+
27+
28+
def get_no_binary_args(requirement_name: str) -> list:
29+
"""Get --no-binary arguments if this package should be built from source.
30+
31+
This only applies on Linux platforms where pre-built wheels may link against
32+
different library versions. On Windows and macOS, pre-built wheels work correctly.
33+
34+
Args:
35+
requirement_name: Package name or requirement string (e.g., "cffi" or "cffi>=1.0")
36+
37+
Returns:
38+
List with --no-binary arguments if package should be built from source, empty list otherwise
39+
"""
40+
# Only force source builds on Linux (where we have library version issues)
41+
if platform.system() != "Linux":
42+
return []
43+
44+
# Extract package name from requirement string (e.g., "cffi>=1.0" -> "cffi")
45+
match = re.match(r"^([a-zA-Z0-9_-]+)", str(requirement_name).strip())
46+
if not match:
47+
return []
48+
pkg_name = match.group(1).lower().replace("-", "_")
49+
50+
for pkg in FORCE_SOURCE_BUILD_PACKAGES_LINUX:
51+
if pkg.lower().replace("-", "_") == pkg_name:
52+
return ["--no-binary", match.group(1)]
53+
return []
54+
1055

1156
def print_color(text: str, color: str = Fore.BLUE):
1257
"""Print colored text specified by color argument based on colorama

0 commit comments

Comments
 (0)