diff --git a/.github/workflows/build-gpiod.yml b/.github/workflows/build-gpiod.yml new file mode 100644 index 000000000..c2e5d7a6e --- /dev/null +++ b/.github/workflows/build-gpiod.yml @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +name: Build gpiod wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'gpiod (Python bindings) version/tag to build, e.g. 2.5.0' + required: true + default: '2.5.0' + libgpiod_version: + description: 'libgpiod C library source version to vendor (LIBGPIOD_VERSION)' + required: true + default: '2.3' + pull_request: + paths: + - '.github/workflows/build-gpiod.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '2.5.0' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + GPIOD_VERSION: ${{ inputs.version || '2.5.0' }} + LIBGPIOD_VERSION: ${{ inputs.libgpiod_version || '2.3' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + MUSLLINUX_RISCV64_IMAGE: quay.io/pypa/musllinux_1_2_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + python_sdist: + needs: [setup] + # Upstream's own generate_pypi_artifacts.sh builds the sdist with + # LIBGPIOD_VERSION set, then feeds *that* sdist to cibuildwheel. Doing the + # same matters: setup.py's fetch_tarball wrapper only skips its + # post-build cleanup (which deletes LICENSE/lib/include again) when + # libgpiod-version.txt is already present and matches -- true for a wheel + # built from this sdist, false for a wheel built straight from a checkout. + runs-on: ubuntu-24.04-riscv + outputs: + sdist_artifact_name: ${{ steps.build_sdist.outputs.sdist_artifact_name }} + package_version: ${{ steps.build_sdist.outputs.package_version }} + steps: + # The Python bindings live in git.kernel.org/libgpiod, tagged + # python-v; that tag isn't mirrored to github.com/brgl/libgpiod, + # so actions/checkout can't reach it. + - name: Checkout gpiod python-v${{ env.GPIOD_VERSION }} + run: | + git clone --depth 1 --branch "python-v${{ env.GPIOD_VERSION }}" \ + https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git gpiod-src + + - 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 + working-directory: gpiod-src/bindings/python + env: + LIBGPIOD_VERSION: ${{ env.LIBGPIOD_VERSION }} + run: | + rm -rf dist/ + uv pip install build packaging + python -m build --sdist + + echo "sdist_artifact_name=$(ls ./dist)" >> "$GITHUB_OUTPUT" + echo "package_version=$(ls ./dist | sed -En 's/gpiod-(.+)\.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: gpiod-src/bindings/python/dist/${{ steps.build_sdist.outputs.sdist_artifact_name }} + if-no-files-found: error + + build_wheels: + needs: [setup, python_sdist] + name: Build gpiod ${{ inputs.version || '2.5.0' }} ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + libc: [manylinux, musllinux] + + steps: + - name: fetch sdist artifact + id: fetch_sdist + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.python_sdist.outputs.sdist_artifact_name }} + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + # cibuildwheel accepts an sdist tarball directly and extracts it. + package-dir: ${{ steps.fetch_sdist.outputs.download-path }}/${{ needs.python_sdist.outputs.sdist_artifact_name }} + output-dir: wheelhouse/ + env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} + # lib/uapi/gpio.h pulls in the system linux/const.h,linux/ioctl.h, + # linux/types.h; musllinux's Alpine base needs linux-headers for + # those, manylinux already has them via glibc-devel. + CIBW_BEFORE_ALL_LINUX: (command -v apk && apk add linux-headers) || true + # Upstream's own generate_pypi_artifacts.sh builds wheels with no test + # step at all: the real test suite needs a gpio-sim kernel module and + # the separately-built libgpiosim helper library (tests/gpiosim/, + # tests/system/), neither available in the build container. Exercise + # the compiled extension and the real chip-open/ioctl error path instead. + CIBW_TEST_COMMAND: | + python3 - <<'PYEOF' + import gpiod + from gpiod import _ext + assert _ext.__file__.endswith(".so"), _ext.__file__ + assert gpiod.__version__ == "${{ env.GPIOD_VERSION }}", gpiod.__version__ + assert gpiod.api_version == "${{ env.LIBGPIOD_VERSION }}", gpiod.api_version + try: + gpiod.Chip("/dev/null") + except OSError: + pass + else: + raise SystemExit("expected OSError for a non-gpiochip device") + PYEOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: gpiod-${{ env.GPIOD_VERSION }}-${{ matrix.python }}-${{ matrix.libc }}_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish gpiod ${{ inputs.version || '2.5.0' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: gpiod-${{ inputs.version || '2.5.0' }}-*riscv64