Skip to content

Commit 43be523

Browse files
authored
Create a universal binary for macos. (#12)
* Build universal2 macOS binaries with PyInstaller Replace separate macos-amd64 (macos-13) and macos-aarch64 builds with a single universal2 build on macos-latest. The fat binary works natively on both Intel and Apple Silicon Macs. Upload the archive under both legacy names (macos-aarch64, macos-amd64) for backwards compatibility. Add verification step using lipo and arch -x86_64 to confirm both architectures work. * Install universal2 Python from python.org for macOS build The setup-python action installs arm64-only Python on macOS ARM runners, causing PyInstaller to fail with "not a fat binary" errors when building universal2 binaries. Install the official python.org universal2 framework build instead, so all native extensions (bitarray, cryptography, etc.) are compiled as fat binaries. * Fix macOS Python setup: add python/pip symlinks The python.org installer only provides python3/pip3 binaries. Create symlinks so bare 'python' and 'pip' commands work. Also fix comment about why we use python.org instead of setup-python. * Force universal2 native extensions via ARCHFLAGS + no-binary Pip downloads arm64-only wheels on ARM runners regardless of whether Python itself is universal2. Use --no-binary :all: with ARCHFLAGS to compile native extensions (bitarray, cffi, pyyaml, tibs) as fat binaries for the universal2 PyInstaller build. * Revert to setup-python for macOS The python.org manual install is unnecessary — setup-python already installs universal2 Python. The ARCHFLAGS + --no-binary fix handles the actual problem (pip's wheel selection). * Add hidden import for bitstring.bitstore_bitarray bitstring 4.4.0 dynamically imports bitstore_bitarray, which PyInstaller doesn't detect. Add --hidden-import for esptool and espefuse builds. * Use --collect-submodules bitstring instead of individual hidden imports bitstring 4.4.0 has many dynamically imported submodules. Collect them all rather than chasing individual ones. * Only force-rebuild specific packages as universal2, not all --no-binary :all: breaks cryptography (needs Rust toolchain). Instead, install normally first, then force-reinstall only the packages that don't publish universal2 wheels (bitarray, cffi, pyyaml, tibs) from source with ARCHFLAGS. * Add x86_64-apple-darwin Rust target for tibs cross-compilation tibs uses maturin/Rust and needs the x86_64 target to build a universal2 fat binary on an ARM runner. * Fix cross-build: install Rust via rustup, collect bitstring submodules tibs 0.5.7 requires Rust edition 2024, but the ubuntu 22.04 apt package only provides Cargo 1.75.0. Install Rust via rustup to get a recent enough version. Also add --collect-submodules bitstring for the same PyInstaller hidden import issue as the main build. * Fix cross-build: remove comment inside line continuation A shell comment inside a backslash-continued apt-get command broke the line continuation, causing 'build-essential: command not found'. * Add comment explaining --collect-submodules bitstring
1 parent d45c7fd commit 43be523

2 files changed

Lines changed: 63 additions & 13 deletions

File tree

.github/workflows/build.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ jobs:
7676
python3 python3-pip python3-setuptools python3-wheel python3-dev \
7777
libffi-dev \
7878
libssl-dev \
79-
rustc cargo \
8079
build-essential \
8180
pkg-config \
8281
git \
@@ -97,15 +96,19 @@ jobs:
9796
# Via https://github.com/rust-lang/cargo/issues/10781#issuecomment-1351670409
9897
export CARGO_NET_GIT_FETCH_WITH_CLI=true
9998
set -e
99+
# Install Rust via rustup (apt version is too old for tibs edition2024).
100+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
101+
source "\$HOME/.cargo/env"
100102
export PATH=\$PATH:/home/builder/.local/bin
101103
echo \$HOME
102104
echo \$PATH
103105
cd /sources
104106
python -m pip install --upgrade pip
105107
pip install pyinstaller
106108
pip install --user -e .
107-
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico --add-data="${{ env.STUBS_DIR }}1/*.json:${{ env.STUBS_DIR }}1/" --add-data="${{ env.STUBS_DIR }}2/*.json:${{ env.STUBS_DIR }}2/" esptool.py
108-
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico --add-data="${{ env.EFUSE_DIR }}*.yaml:${{ env.EFUSE_DIR }}" espefuse.py
109+
# bitstring uses dynamic imports that PyInstaller can't detect; collect all submodules explicitly.
110+
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico --collect-submodules bitstring --add-data="${{ env.STUBS_DIR }}1/*.json:${{ env.STUBS_DIR }}1/" --add-data="${{ env.STUBS_DIR }}2/*.json:${{ env.STUBS_DIR }}2/" esptool.py
111+
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico --collect-submodules bitstring --add-data="${{ env.EFUSE_DIR }}*.yaml:${{ env.EFUSE_DIR }}" espefuse.py
109112
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico espsecure.py
110113
pyinstaller --distpath ./${{ matrix.arch }} -F --icon=ci/espressif.ico esp_rfc2217_server.py
111114
./${{ matrix.arch }}/esptool -h

.github/workflows/build_esptool.yml

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@ jobs:
2323
runs-on: ${{ matrix.runner }}
2424
strategy:
2525
matrix:
26-
platform: [macos-aarch64, macos-amd64, windows-amd64, linux-amd64]
26+
platform: [macos-universal, windows-amd64, linux-amd64]
2727
include:
28-
- platform: macos-amd64
29-
separator: ":"
30-
runner: macos-13
31-
- platform: macos-aarch64
28+
- platform: macos-universal
3229
separator: ":"
3330
runner: macos-latest
3431
- platform: windows-amd64
@@ -77,10 +74,21 @@ jobs:
7774
- name: Install dependencies
7875
# Using 6.11.1 as it apparently leads to fewer antivirus false positives.
7976
# See: https://github.com/espressif/python-binary-action/blob/8f20755b770c5f9d03e9ce3002af330828fe473f/action.yml#L35
77+
shell: bash
8078
run: |
8179
python -m pip install --upgrade pip
8280
pip install pyinstaller==6.11.1
83-
pip install --user -e .
81+
if [[ "${{ runner.os }}" == "macOS" ]]; then
82+
# Pip on ARM runners prefers arm64-only wheels over universal2,
83+
# which PyInstaller rejects for universal2 builds. Rebuild
84+
# native extensions from source as fat binaries.
85+
rustup target add x86_64-apple-darwin
86+
export ARCHFLAGS="-arch x86_64 -arch arm64"
87+
pip install --user -e .
88+
pip install --user --force-reinstall --no-binary bitarray,cffi,pyyaml,tibs bitarray cffi pyyaml tibs
89+
else
90+
pip install --user -e .
91+
fi
8492
8593
- name: Import signing keychain (macOS)
8694
if: (runner.os == 'macOS' && (github.event_name == 'release' || github.event.inputs.sign-macos == 'true'))
@@ -97,11 +105,17 @@ jobs:
97105
echo "PYINSTALLER_FLAGS=--codesign-identity ${{ vars.MACOS_TEAM_ID }}" >> $GITHUB_ENV
98106
99107
- name: Build with PyInstaller
108+
shell: bash
100109
run: |
101-
pyinstaller ${{ env.PYINSTALLER_FLAGS }} --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --add-data="${{ env.STUBS_DIR }}1/*.json${{ matrix.separator }}${{ env.STUBS_DIR }}1/" --add-data="${{ env.STUBS_DIR }}2/*.json${{ matrix.separator }}${{ env.STUBS_DIR }}2/" --runtime-hook fix_encoding.py esptool.py
102-
pyinstaller ${{ env.PYINSTALLER_FLAGS }} --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --add-data="${{ env.EFUSE_DIR }}*.yaml${{ matrix.separator }}${{ env.EFUSE_DIR }}" --runtime-hook fix_encoding.py espefuse.py
103-
pyinstaller ${{ env.PYINSTALLER_FLAGS }} --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --runtime-hook fix_encoding.py espsecure.py
104-
pyinstaller ${{ env.PYINSTALLER_FLAGS }} --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --runtime-hook fix_encoding.py esp_rfc2217_server.py
110+
EXTRA_FLAGS=""
111+
if [[ "${{ runner.os }}" == "macOS" ]]; then
112+
EXTRA_FLAGS="--target-architecture universal2"
113+
fi
114+
# bitstring uses dynamic imports that PyInstaller can't detect; collect all submodules explicitly.
115+
pyinstaller ${{ env.PYINSTALLER_FLAGS }} $EXTRA_FLAGS --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --collect-submodules bitstring --add-data="${{ env.STUBS_DIR }}1/*.json${{ matrix.separator }}${{ env.STUBS_DIR }}1/" --add-data="${{ env.STUBS_DIR }}2/*.json${{ matrix.separator }}${{ env.STUBS_DIR }}2/" --runtime-hook fix_encoding.py esptool.py
116+
pyinstaller ${{ env.PYINSTALLER_FLAGS }} $EXTRA_FLAGS --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --collect-submodules bitstring --add-data="${{ env.EFUSE_DIR }}*.yaml${{ matrix.separator }}${{ env.EFUSE_DIR }}" --runtime-hook fix_encoding.py espefuse.py
117+
pyinstaller ${{ env.PYINSTALLER_FLAGS }} $EXTRA_FLAGS --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --runtime-hook fix_encoding.py espsecure.py
118+
pyinstaller ${{ env.PYINSTALLER_FLAGS }} $EXTRA_FLAGS --console --distpath ./${{ env.DISTPATH }} -F --icon=ci/espressif.ico --runtime-hook fix_encoding.py esp_rfc2217_server.py
105119
106120
- name: Sign Windows binary
107121
if: (runner.os == 'Windows' && (github.event_name == 'release' || github.event.inputs.sign-windows == 'true'))
@@ -139,6 +153,15 @@ jobs:
139153
./${{ env.DISTPATH }}/espsecure$EXTEN -h
140154
./${{ env.DISTPATH }}/esp_rfc2217_server$EXTEN -h
141155
156+
- name: Verify universal2 binaries (macOS)
157+
if: runner.os == 'macOS'
158+
run: |
159+
for bin in esptool espefuse espsecure esp_rfc2217_server; do
160+
echo "Checking $bin..."
161+
lipo -info ./${{ env.DISTPATH }}/$bin
162+
arch -x86_64 ./${{ env.DISTPATH }}/$bin -h
163+
done
164+
142165
- name: Add license and readme
143166
shell: bash
144167
run: mv LICENSE README.md ./${{ env.DISTPATH }}
@@ -168,3 +191,27 @@ jobs:
168191
file: ${{ env.ARCHIVE_NAME }}
169192
tag: ${{ github.event.release.tag_name }}
170193
overwrite: true
194+
195+
- name: Upload macOS release aliases
196+
if: github.event_name == 'release' && runner.os == 'macOS'
197+
shell: bash
198+
run: |
199+
for alias in macos-aarch64 macos-amd64; do
200+
cp "${{ env.ARCHIVE_NAME }}" "esptool-${alias}.tar.gz"
201+
done
202+
- name: Upload macOS release (macos-aarch64)
203+
if: github.event_name == 'release' && runner.os == 'macOS'
204+
uses: svenstaro/upload-release-action@v2
205+
with:
206+
repo_token: ${{ secrets.GITHUB_TOKEN }}
207+
file: esptool-macos-aarch64.tar.gz
208+
tag: ${{ github.event.release.tag_name }}
209+
overwrite: true
210+
- name: Upload macOS release (macos-amd64)
211+
if: github.event_name == 'release' && runner.os == 'macOS'
212+
uses: svenstaro/upload-release-action@v2
213+
with:
214+
repo_token: ${{ secrets.GITHUB_TOKEN }}
215+
file: esptool-macos-amd64.tar.gz
216+
tag: ${{ github.event.release.tag_name }}
217+
overwrite: true

0 commit comments

Comments
 (0)