diff --git a/.github/workflows/build-simplepyble.yml b/.github/workflows/build-simplepyble.yml new file mode 100644 index 00000000000..da644d0d0f4 --- /dev/null +++ b/.github/workflows/build-simplepyble.yml @@ -0,0 +1,227 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on the `build-wheels` job of +# https://github.com/simpleble/simpleble/blob/v1.1.0/.github/workflows/ci_simplepyble.yml +name: Build simplepyble wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/simplepyble.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-simplepyble.yml' + - 'docs/packages/simplepyble.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-simplepyble.yml' + - 'docs/packages/simplepyble.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: simplepyble + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build simplepyble ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # upstream skips cp314t and musllinux + python: ["cp312", "cp313", "cp314"] + + env: + SIMPLEPYBLE_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout simpleble v${{ env.SIMPLEPYBLE_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: simpleble/simpleble + ref: v${{ env.SIMPLEPYBLE_VERSION }} + 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 libdbus-1. + # setuptools' default LICENSE* glob copies them into the wheel. + set -euo pipefail + + project="${1:?usage: collect-licenses.sh }" + + # ldd is transitive, so libdbus-1 alone covers its whole closure; + # ldd does not list the root itself, so resolve that too. + mapfile -t libs < <( + { + ldd /usr/lib64/libdbus-1.so | tr ' ' '\n' | grep '^/' + readlink -f /usr/lib64/libdbus-1.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 }} + CIBW_BEFORE_ALL_LINUX: >- + dnf install -y dbus-devel && + bash {project}/collect-licenses.sh {project} + CIBW_TEST_REQUIRES: pytest + # upstream's suite targets the Plain (mock) backend; only these two are backend-independent + CIBW_TEST_COMMAND: >- + pytest -v {project}/simplepyble/test/test_simpleble.py + -k "test_configuration_parity or test_platform_enums" && + python -c "import simplepyble; assert simplepyble.get_operating_system() == simplepyble.OperatingSystem.LINUX" + + - name: Verify the wheel ships the extension and every vendored library's licence + run: | + python3 - wheelhouse/*.whl <<'EOF' + import json, sys, zipfile + z = zipfile.ZipFile(sys.argv[1]) + names = z.namelist() + sos = sorted(n for n in names if n.endswith(".so") or ".so." in n) + print("\n".join(sos)) + assert any(n.startswith("simplepyble/_simplepyble.") 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)) + assert "LICENSE.md" in lic, lic + have_pkgs = {f.split(".", 2)[1] for f in lic if f.startswith("LICENSE.") and f != "LICENSE.md"} + + sbom = next(n for n in names if n.endswith("sboms/auditwheel.cdx.json")) + vendored = { + c["purl"].split("/")[2].split("@")[0] + for c in json.loads(z.read(sbom))["components"] + if c.get("purl", "").startswith("pkg:rpm/") + } + print("vendored:", sorted(vendored)) + assert "dbus-libs" in vendored, vendored + assert vendored <= have_pkgs, vendored - have_pkgs + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: simplepyble-${{ env.SIMPLEPYBLE_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources for simplepyble ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + + env: + SIMPLEPYBLE_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # dbus-libs/libcap/systemd-libs are the copyleft (GPL/LGPL) libraries auditwheel + # vendors out of the build image alongside libdbus-1. + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc dbus-libs libcap systemd-libs + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: simplepyble-${{ env.SIMPLEPYBLE_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish simplepyble ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: simplepyble-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: simplepyble-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc and the copyleft libraries bundled in the wheel diff --git a/docs/packages/simplepyble.yaml b/docs/packages/simplepyble.yaml new file mode 100644 index 00000000000..a2f8141f8cb --- /dev/null +++ b/docs/packages/simplepyble.yaml @@ -0,0 +1,5 @@ +package-name: simplepyble +source-code: https://github.com/simpleble/simpleble +license: BUSL-1.1 +versions: +- version: 1.1.0