From 5d03ed5de3a2f6836cbd6aaaa1a2f3725661206f Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 09:58:54 +0200 Subject: [PATCH 1/7] pymavlink: add build-pymavlink.yml for riscv64 wheels --- .github/workflows/build-pymavlink.yml | 153 ++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 .github/workflows/build-pymavlink.yml diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml new file mode 100644 index 0000000000..dee26b3e0f --- /dev/null +++ b/.github/workflows/build-pymavlink.yml @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/ArduPilot/pymavlink/blob/v2.4.49/.github/workflows/python-publish.yml +name: Build pymavlink wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'pymavlink version to build (git tag without leading v, e.g. 2.4.49)' + required: true + default: '2.4.49' + pull_request: + paths: + - '.github/workflows/build-pymavlink.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '2.4.49' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + PYMAVLINK_VERSION: ${{ inputs.version || '2.4.49' }} + UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ + UV_INDEX_STRATEGY: unsafe-best-match + UV_ONLY_BINARY: ':all:' + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + python_sdist: + needs: [setup] + runs-on: ubuntu-latest + outputs: + sdist_artifact_name: ${{ steps.build_sdist.outputs.sdist_artifact_name }} + package_version: ${{ steps.build_sdist.outputs.package_version }} + steps: + - name: checkout pymavlink v${{ env.PYMAVLINK_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: ArduPilot/pymavlink + ref: v${{ env.PYMAVLINK_VERSION }} + persist-credentials: false + + - name: setup uv + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: '3.12' + activate-environment: true + enable-cache: false + + - name: build sdist + id: build_sdist + # setup.py's build_py regenerates dialects/*.py from the MAVLink XML message + # definitions, which live in the separate ArduPilot/mavlink repo (same as + # upstream's own build-sdist job); `uv pip install .` triggers that generation + # in place, so the sdist it feeds into `build --sdist` carries the generated + # dialect sources and message_definitions XML, matching the real PyPI sdist. + run: | + set -eux + + git clone --depth=1 https://github.com/ArduPilot/mavlink.git + ln -s "$PWD/mavlink/message_definitions" message_definitions + + rm -rf dist/ + uv pip install build + uv pip install . + python -m build --sdist + + echo "sdist_artifact_name=$(ls ./dist)" >> "$GITHUB_OUTPUT" + echo "package_version=$(ls ./dist | sed -En 's/pymavlink-(.+)\.tar\.gz/\1/p')" >> "$GITHUB_OUTPUT" + + - name: upload sdist artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.build_sdist.outputs.sdist_artifact_name }} + path: dist/${{ steps.build_sdist.outputs.sdist_artifact_name }} + if-no-files-found: error + + linux: + needs: [setup, python_sdist] + name: Build pymavlink ${{ inputs.version || '2.4.49' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + - name: fetch sdist artifact + id: fetch_sdist + uses: actions/download-artifact@v8 + with: + name: ${{ needs.python_sdist.outputs.sdist_artifact_name }} + + - name: setup uv + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: '3.12' + activate-environment: true + enable-cache: false + + - name: build/test wheels + id: build + env: + CIBW_BUILD: ${{ matrix.python }}-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # numpy has no riscv64 wheel on public PyPI; pull it (and anything else + # missing) from our registry inside the test venv. + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: pytest numpy + CIBW_TEST_SOURCES: tests + # test_mavlogdump.py and test_mavxml.py read fixture files (test.BIN, + # 64/65-fields.xml) that upstream's own packaging leaves out of the sdist; + # RallyTest/FenceTest in test_wp.py hit the same gap (rally/fence .txt + # fixtures). Everything else in the suite is self-contained. + CIBW_TEST_COMMAND: >- + python -m pytest {project}/tests + --ignore={project}/tests/test_mavlogdump.py + --ignore={project}/tests/test_mavxml.py + -k "not RallyTest and not FenceTest" + run: | + set -eux + + mkdir pymavlink + + tar zxf ${{ steps.fetch_sdist.outputs.download-path }}/pymavlink-*.tar.gz --strip-components=1 -C pymavlink + uv pip install --upgrade cibuildwheel + + python -m cibuildwheel --output-dir dist ./pymavlink + + echo "artifact_name=$(ls ./dist/)" >> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pymavlink-${{ needs.python_sdist.outputs.package_version }}-${{ matrix.python }}-manylinux_riscv64 + path: ./dist/*.whl + if-no-files-found: error + + publish: + name: Publish pymavlink ${{ inputs.version || '2.4.49' }} + needs: [setup, python_sdist, linux] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: pymavlink-${{ needs.python_sdist.outputs.package_version }}-*-manylinux_riscv64 From f590167af93f3819642aaec2a458e36660959bc3 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 10:34:01 +0200 Subject: [PATCH 2/7] pymavlink: drop CIBW_TEST_SOURCES, resolve tests via {project} instead CIBW_TEST_SOURCES stages relative to cibuildwheel's invocation cwd (the workspace root), not package-dir, so 'tests' never matched the sdist extracted into ./pymavlink. {project}/tests in CIBW_TEST_COMMAND already resolves against package-dir and needs no staging. --- .github/workflows/build-pymavlink.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index dee26b3e0f..78f8d8f067 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -114,7 +114,9 @@ jobs: # missing) from our registry inside the test venv. CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_REQUIRES: pytest numpy - CIBW_TEST_SOURCES: tests + # No CIBW_TEST_SOURCES: it stages relative to cibuildwheel's cwd (the + # workspace root), not package-dir (gotcha 104), so {project}/tests below + # (which resolves against package-dir) reaches the real tests directly. # test_mavlogdump.py and test_mavxml.py read fixture files (test.BIN, # 64/65-fields.xml) that upstream's own packaging leaves out of the sdist; # RallyTest/FenceTest in test_wp.py hit the same gap (rally/fence .txt From f4d5390961f80b2edf00a9decac0e9d17f15910e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 11:37:29 +0200 Subject: [PATCH 3/7] pymavlink: use {package} instead of {project} in CIBW_TEST_COMMAND {project} expands to package-dir's parent (the whole copied cwd), not package-dir itself; {package} is the one that maps to ./pymavlink inside the container, so {project}/tests looked for a nonexistent workspace-root tests directory instead of the real one. --- .github/workflows/build-pymavlink.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index 78f8d8f067..1d40cc3680 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -115,16 +115,17 @@ jobs: CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_REQUIRES: pytest numpy # No CIBW_TEST_SOURCES: it stages relative to cibuildwheel's cwd (the - # workspace root), not package-dir (gotcha 104), so {project}/tests below - # (which resolves against package-dir) reaches the real tests directly. + # workspace root), not package-dir (gotcha 104), so {package}/tests below + # ({package} is the package-dir; {project} is its parent) reaches the + # real tests directly with no staging. # test_mavlogdump.py and test_mavxml.py read fixture files (test.BIN, # 64/65-fields.xml) that upstream's own packaging leaves out of the sdist; # RallyTest/FenceTest in test_wp.py hit the same gap (rally/fence .txt # fixtures). Everything else in the suite is self-contained. CIBW_TEST_COMMAND: >- - python -m pytest {project}/tests - --ignore={project}/tests/test_mavlogdump.py - --ignore={project}/tests/test_mavxml.py + python -m pytest {package}/tests + --ignore={package}/tests/test_mavlogdump.py + --ignore={package}/tests/test_mavxml.py -k "not RallyTest and not FenceTest" run: | set -eux From 3f8dd5922460d307dd1fad60d637d9b3baa40f9b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 12:41:49 +0200 Subject: [PATCH 4/7] pymavlink: add setuptools to CIBW_TEST_REQUIRES for pkg_resources test_trim.py and test_wp.py import pkg_resources at module level; cibuildwheel's test venv is built with --no-setuptools, so it's not present unless requested explicitly. --- .github/workflows/build-pymavlink.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index 1d40cc3680..1f839f9628 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -113,7 +113,9 @@ jobs: # numpy has no riscv64 wheel on public PyPI; pull it (and anything else # missing) from our registry inside the test venv. CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ - CIBW_TEST_REQUIRES: pytest numpy + # test_trim.py/test_wp.py import pkg_resources at module level; cibuildwheel's + # test venv is built with --no-setuptools, so pkg_resources isn't there by default. + CIBW_TEST_REQUIRES: pytest numpy setuptools # No CIBW_TEST_SOURCES: it stages relative to cibuildwheel's cwd (the # workspace root), not package-dir (gotcha 104), so {package}/tests below # ({package} is the package-dir; {project} is its parent) reaches the From 0eb6f481121db2f42a16cb4164cb34db099f1968 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 13:39:07 +0200 Subject: [PATCH 5/7] pymavlink: pin setuptools<82 in CIBW_TEST_REQUIRES for pkg_resources setuptools 82.0.0 (2026-02-08) removed pkg_resources entirely (pypa/setuptools#5007); test_trim.py/test_wp.py still import it at module level, so an unpinned setuptools resolves to a version that no longer has it. --- .github/workflows/build-pymavlink.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index 1f839f9628..37f1d01b36 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -114,8 +114,9 @@ jobs: # missing) from our registry inside the test venv. CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ # test_trim.py/test_wp.py import pkg_resources at module level; cibuildwheel's - # test venv is built with --no-setuptools, so pkg_resources isn't there by default. - CIBW_TEST_REQUIRES: pytest numpy setuptools + # test venv is built with --no-setuptools, and setuptools 82 dropped pkg_resources + # entirely, so pin below that to keep it available. + CIBW_TEST_REQUIRES: pytest numpy setuptools<82 # No CIBW_TEST_SOURCES: it stages relative to cibuildwheel's cwd (the # workspace root), not package-dir (gotcha 104), so {package}/tests below # ({package} is the package-dir; {project} is its parent) reaches the From 0ec8213353a53eac3768ce6feed8ea681f09831f Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 14:17:04 +0200 Subject: [PATCH 6/7] pymavlink: deselect test_wp.py::MAVWPTest::test_save mavutil.set_dialect() runs once at import time, picking its dialect from the MAVLINK20 env var. test_wp.py sets that var before importing mavutil, but earlier test files in the same pytest run (test_fgFDM.py etc.) import mavutil first with the var unset, caching the wrong dialect for the rest of the run. Reproducible with the real upstream wheel too - not riscv64 specific, just an ordering fragility in the test suite's module-level side effects. --- .github/workflows/build-pymavlink.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index 37f1d01b36..83b198345d 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -124,11 +124,17 @@ jobs: # test_mavlogdump.py and test_mavxml.py read fixture files (test.BIN, # 64/65-fields.xml) that upstream's own packaging leaves out of the sdist; # RallyTest/FenceTest in test_wp.py hit the same gap (rally/fence .txt - # fixtures). Everything else in the suite is self-contained. + # fixtures). test_wp.py::MAVWPTest::test_save only passes when it is the + # first test file to import pymavlink.mavutil in the process: mavutil picks + # its MAVLink dialect once at import time from the MAVLINK20 env var, and + # an earlier test file importing it first (with MAVLINK20 unset) leaves the + # wrong dialect cached for the rest of the pytest run. Everything else in + # the suite is self-contained and order-independent. CIBW_TEST_COMMAND: >- python -m pytest {package}/tests --ignore={package}/tests/test_mavlogdump.py --ignore={package}/tests/test_mavxml.py + --deselect={package}/tests/test_wp.py::MAVWPTest::test_save -k "not RallyTest and not FenceTest" run: | set -eux From 75682fb193e0d80f6d097753b7e9fad27db06e05 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 15:09:18 +0200 Subject: [PATCH 7/7] pymavlink: deselect test_save via -k instead of --deselect --deselect's absolute-path node id (/project/pymavlink/tests/test_wp.py::...) did not match pytest's own relative node id for the same test (pytest ran from a different rootdir than {package}), so it silently deselected nothing. -k with both the class and function name as required substrings reaches the same test without colliding with test_mavparm.py's unrelated test_saveload. --- .github/workflows/build-pymavlink.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-pymavlink.yml b/.github/workflows/build-pymavlink.yml index 83b198345d..ce22dbf78c 100644 --- a/.github/workflows/build-pymavlink.yml +++ b/.github/workflows/build-pymavlink.yml @@ -134,8 +134,7 @@ jobs: python -m pytest {package}/tests --ignore={package}/tests/test_mavlogdump.py --ignore={package}/tests/test_mavxml.py - --deselect={package}/tests/test_wp.py::MAVWPTest::test_save - -k "not RallyTest and not FenceTest" + -k "not RallyTest and not FenceTest and not (MAVWPTest and test_save)" run: | set -eux