diff --git a/.github/workflows/build-hidapi.yml b/.github/workflows/build-hidapi.yml new file mode 100644 index 000000000..5992b85fc --- /dev/null +++ b/.github/workflows/build-hidapi.yml @@ -0,0 +1,190 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on the `build_wheels` job of +# https://github.com/trezor/cython-hidapi/blob/0.15.0/.github/workflows/wheels.yml +name: Build hidapi wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'hidapi version to build (git tag, e.g. 0.15.0)' + required: true + default: '0.15.0' + pull_request: + paths: + - '.github/workflows/build-hidapi.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '0.15.0' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + HIDAPI_VERSION: ${{ inputs.version || '0.15.0' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build hidapi ${{ inputs.version || '0.15.0' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + - name: Checkout cython-hidapi ${{ env.HIDAPI_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: trezor/cython-hidapi + ref: ${{ env.HIDAPI_VERSION }} + submodules: true + persist-credentials: false + + - name: Stage the licence-collection script + run: | + cat > collect-licenses.sh <<'COLLECT_EOF' + #!/bin/bash + # SPDX-FileCopyrightText: 2026 The RISE Project + # SPDX-License-Identifier: MIT + # + # Stage, at the project root, the licence of every shared library + # auditwheel vendors out of the build image alongside libusb/libudev. + # setuptools' default LICENSE* glob copies them into the wheel. + set -euo pipefail + + project="${1:?usage: collect-licenses.sh }" + + # ldd is transitive, so the two linked libs alone cover their whole + # closure; ldd does not list the root itself, so resolve those too. + mapfile -t libs < <( + { + ldd /usr/lib64/libusb-1.0.so | tr ' ' '\n' | grep '^/' + readlink -f /usr/lib64/libusb-1.0.so + ldd /usr/lib64/libudev.so | tr ' ' '\n' | grep '^/' + readlink -f /usr/lib64/libudev.so + } | sort -u + ) + + # `rpm -qf` reports unowned files on stdout, so keep only bare package names. + # glibc and the gcc runtime are on auditwheel's manylinux allowlist and + # are never vendored into the wheel. + mapfile -t pkgs < <( + rpm -qf --qf '%{NAME}\n' "${libs[@]}" 2>/dev/null | + grep -E '^[A-Za-z0-9._+-]+$' | sort -u | + grep -vE '^(glibc|libgcc|libstdc\+\+|gcc)$' + ) + + for pkg in "${pkgs[@]}"; do + mapfile -t files < <(rpm -q --licensefiles "$pkg" 2>/dev/null || true) + + # Some subpackages leave the licence to a sibling of the same source RPM. + if [ -z "${files[0]:-}" ]; then + srpm=$(rpm -q --qf '%{SOURCERPM}\n' "$pkg") + mapfile -t files < <( + rpm -qa --qf '%{SOURCERPM} %{NAME}\n' | + awk -v s="$srpm" '$1 == s { print $2 }' | + xargs -r rpm -q --licensefiles 2>/dev/null | sort -u + ) + fi + + # Others mark it %doc rather than %license, and the image installs no docs. + if [ -z "${files[0]:-}" ]; then + dnf -y --disablerepo=extras reinstall --setopt=tsflags= "$pkg" >/dev/null + mapfile -t files < <(rpm -qd "$pkg" | grep -iE '/(LICEN[CS]E|COPYING|NOTICE)') + fi + + for f in "${files[@]}"; do + [ -f "$f" ] || continue + cp "$f" "$project/LICENSE.${pkg}.$(basename "$f")" + done + compgen -G "$project/LICENSE.$pkg.*" >/dev/null || + { echo "no licence file found for $pkg" >&2; exit 1; } + done + + ls -1 "$project"/LICENSE.* | sed "s|$project/||" + COLLECT_EOF + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # libudev-devel doesn't exist on Rocky 10; systemd-devel provides libudev.so/libudev.h instead (CLAUDE.md gotcha 252) + CIBW_BEFORE_ALL_LINUX: >- + dnf install -y libusb1-devel systemd-devel && + bash {project}/collect-licenses.sh {project} + # mirrors upstream's tests.py, plus hidraw since upstream's own suite never exercises that backend + CIBW_TEST_COMMAND: >- + python {project}/tests.py && + python -c "import hidraw; assert len(hidraw.enumerate()) >= 0" + + - name: Verify the wheel ships both compiled extensions and licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + names = zipfile.ZipFile(sys.argv[1]).namelist() + sos = sorted(n for n in names if n.endswith(".so")) + print("\n".join(sos)) + assert any("/hid." in "/" + n for n in sos), sos + assert any("/hidraw." in "/" + n for n in sos), sos + + lic = sorted(n.split("/")[-1] for n in names if ".dist-info/licenses/" in n and not n.endswith("/")) + print("\n".join(lic)) + expected_pkgs = {"libcap", "libusb1", "systemd-libs"} + have_pkgs = {f.split(".", 2)[1] for f in lic if f.startswith("LICENSE.")} + assert expected_pkgs <= have_pkgs, expected_pkgs - have_pkgs + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: hidapi-${{ env.HIDAPI_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + name: Collect GPL sources for hidapi ${{ inputs.version || '0.15.0' }} + runs-on: ubuntu-24.04-riscv + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # libcap/libusb1/systemd-libs are the copyleft (GPL/LGPL) libraries auditwheel vendors + # out of the build image alongside libusb/libudev. + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc libcap libusb1 systemd-libs + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: hidapi-${{ env.HIDAPI_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish hidapi ${{ inputs.version || '0.15.0' }} + needs: [setup, build_wheels, gpl_sources] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: hidapi-${{ inputs.version || '0.15.0' }}-*-manylinux_riscv64 + gpl-sources-artifact: hidapi-${{ inputs.version || '0.15.0' }}-gpl-sources + gpl-sources-description: gcc and the copyleft libraries bundled in the wheel