Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ jobs:
working-directory: sdk-python
run: |
pip install --upgrade pip
pip install -e . pytest
# pytest-cov is required even though this job measures no coverage:
# pyproject's [tool.pytest.ini_options] addopts always passes --cov,
# and the run below passes --no-cov to switch it off. Without the
# plugin installed, pytest rejects both as unrecognized arguments and
# exits 4 before collecting a single test — which is why ffi-smoke
# has failed on every run since it was introduced.
pip install -e . pytest pytest-cov

- name: Resolve FFI symbols against the freshly built library
working-directory: sdk-python
Expand Down
72 changes: 64 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,23 @@ jobs:

build-wheels:
needs: prep
name: Build wheel (${{ matrix.platform }})
name: Build wheel (${{ matrix.platform }}/${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
- os: macos-latest
arch: x86_64
- os: ubuntu-24.04-arm
platform: linux
arch: aarch64
- os: macos-14 # Apple Silicon
platform: macos
arch: arm64
- os: macos-13 # Intel
platform: macos
arch: x86_64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sdk-python
Expand Down Expand Up @@ -235,27 +243,75 @@ jobs:
working-directory: sdk-python
run: |
pip install auditwheel patchelf
for plat in manylinux_2_35_x86_64 manylinux_2_31_x86_64 manylinux_2_28_x86_64; do
if auditwheel repair dist/*-linux_x86_64.whl --plat "$plat" -w dist/ 2>/dev/null; then
A="${{ matrix.arch }}"
for plat in "manylinux_2_35_$A" "manylinux_2_31_$A" "manylinux_2_28_$A"; do
if auditwheel repair dist/*-linux_$A.whl --plat "$plat" -w dist/ 2>/dev/null; then
echo "repaired to $plat"; break
fi
done
rm -f dist/*-linux_x86_64.whl || true
rm -f dist/*-linux_$A.whl || true

# The wheel payload is pure data — prebuilt Go binaries and a CGO shared
# library loaded via ctypes. Nothing links against libpython and there is
# no C extension, so the interpreter tag setuptools infers from the build
# host (cp311, because this workflow builds on Python 3.11) is wrong and
# needlessly narrow: it made the wheel invisible to every Python except
# 3.11, sending 3.12/3.13/3.14 users to the sdist instead.
#
# Retag to py3-none so any CPython 3.x resolves it. The platform tag is
# also forced on macOS: setuptools labels the wheel "universal2" because
# the runner's Python is universal2, but the Go toolchain produced a
# single-arch libpilot, so the universal2 claim was a lie that broke
# Intel Macs. Pin the real arch and a 11.0 floor instead of the runner's
# macOS 26, which excluded everyone not yet on Tahoe.
- name: Normalize wheel tags
shell: bash
working-directory: sdk-python
run: |
set -euo pipefail
python -m pip install --upgrade wheel
if [ "${{ matrix.platform }}" = "macos" ]; then
python -m wheel tags --python-tag py3 --abi-tag none \
--platform-tag "macosx_11_0_${{ matrix.arch }}" --remove dist/*.whl
else
python -m wheel tags --python-tag py3 --abi-tag none --remove dist/*.whl
fi
echo "Final wheel(s):"; ls -1 dist/*.whl

- name: Verify
shell: bash
working-directory: sdk-python
run: python -m twine check dist/*

# Guard against shipping an empty wheel: the native runtime is the whole
# point of this package, and MANIFEST.in now prunes it from the sdist.
- name: Assert wheel carries the native runtime
shell: bash
working-directory: sdk-python
run: |
python - <<'PY'
import glob, sys, zipfile
w = glob.glob("dist/*.whl")[0]
names = zipfile.ZipFile(w).namelist()
need = ["pilotprotocol/bin/pilot-daemon", "pilotctl"]
have = [n for n in names if "/bin/" in n]
print(w, "->", have)
if not any("libpilot" in n for n in have) or not any("pilotctl" in n for n in have):
sys.exit("::error::wheel is missing the native runtime")
PY

- name: Upload wheel
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.platform }}
name: dist-${{ matrix.platform }}-${{ matrix.arch }}
path: sdk-python/dist/*.whl
retention-days: 7

- name: Upload sdist (Linux only)
if: matrix.platform == 'linux'
# Exactly one job may publish the sdist, otherwise the artifact name
# collides. It is platform-neutral now (MANIFEST.in prunes bin/), so any
# single job will do.
- name: Upload sdist (Linux x86_64 only)
if: matrix.platform == 'linux' && matrix.arch == 'x86_64'
uses: actions/upload-artifact@v4
with:
name: dist-sdist
Expand Down
21 changes: 16 additions & 5 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ include README.md
include LICENSE
include CHANGELOG.md

# Include all binaries in bin/ directory (the seed cache).
# Dotfiles like .pilot-version need an explicit pattern because some
# setuptools versions skip them under recursive-include.
recursive-include pilotprotocol/bin *
include pilotprotocol/bin/.pilot-version
# The native runtime (pilot-daemon, pilotctl, pilot-updater, libpilot.*) is
# deliberately NOT shipped in the sdist.
#
# Those binaries are platform-specific and are staged into pilotprotocol/bin/
# by the publish workflow just before the wheel is built, so whatever host
# builds the sdist would otherwise bake ITS OWN os/arch binaries into a
# platform-neutral artifact. That is exactly what shipped in <=1.13.6: the
# sdist was cut on the Linux runner, so every platform without a matching
# wheel (macOS on any Python != 3.11, and all arm64 Linux) silently installed
# Linux x86-64 ELF binaries and then failed at run time with a confusing
# "cannot open shared object file" / missing-libpilot.dylib error.
#
# The wheel still carries them via [tool.setuptools.package-data]; only the
# sdist is pruned. Installing from sdist now yields a clearly-diagnosable
# "libpilot not found" instead of a wrong-architecture payload.
prune pilotprotocol/bin

# Include type stubs if any
recursive-include pilotprotocol *.pyi
Expand Down
Loading