From a310fffb24dea5ed76cd8d5f25f2e9d176a90225 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 06:38:49 +0000 Subject: [PATCH 1/3] Add python-stretch riscv64 wheel build python-stretch wraps the header-only Signalsmith Stretch C++ library with nanobind and builds a cp312-abi3 wheel via scikit-build-core. Upstream never tags releases, so the workflow pins the 0.3.1 release commit ac1fbfc, whose tree (with the include/stretch submodule at ffa4598) is byte-identical to the PyPI sdist apart from PKG-INFO. One abi3 build per libc on cp312, re-tested on cp313/cp314. musllinux runs a numpy-free smoke test since numpy has no musllinux riscv64 wheel. The wheel also ships the Signalsmith (MIT) and nanobind (BSD-3-Clause) notices for the code compiled into the extension. --- .github/workflows/build-python-stretch.yml | 144 +++++++++++++++++++++ docs/packages/python-stretch.yaml | 5 + 2 files changed, 149 insertions(+) create mode 100644 .github/workflows/build-python-stretch.yml create mode 100644 docs/packages/python-stretch.yaml diff --git a/.github/workflows/build-python-stretch.yml b/.github/workflows/build-python-stretch.yml new file mode 100644 index 00000000000..46618fa6b69 --- /dev/null +++ b/.github/workflows/build-python-stretch.yml @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on upstream's own wheel build setup: +# https://github.com/gregogiudici/python-stretch/blob/ac1fbfc9f5d6b912e2a9248d8e0f53888846588b/.github/workflows/wheels.yml +# https://github.com/gregogiudici/python-stretch/blob/ac1fbfc9f5d6b912e2a9248d8e0f53888846588b/pyproject.toml +name: Build python-stretch wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/python-stretch.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-python-stretch.yml' + - 'docs/packages/python-stretch.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-python-stretch.yml' + - 'docs/packages/python-stretch.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # Upstream releases without git tags; this is the 0.3.1 release commit, byte-identical + # to the PyPI sdist (submodule included). Update alongside the version in the docs YAML. + PYTHON_STRETCH_REF: ac1fbfc9f5d6b912e2a9248d8e0f53888846588b + 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 + with: + package: python-stretch + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build python-stretch ${{ matrix.version }} cp312-abi3-${{ matrix.libc }}_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + libc: [manylinux, musllinux] + include: + - libc: manylinux + test_requires: pytest numpy + test_command: pytest {project}/tests + # No musllinux riscv64 numpy wheel exists, and every process() call takes one. + - libc: musllinux + test_requires: '' + test_command: >- + python -c "import python_stretch as ps; + s = ps.Signalsmith.Stretch(); + s.preset(2, 44100.0); + s.setTimeFactor(0.5); + s.setTransposeSemitones(12); + assert s.blockSamples() == 5292 and s.intervalSamples() == 2646, (s.blockSamples(), s.intervalSamples()); + print('python_stretch OK')" + + env: + PYTHON_STRETCH_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-stretch ${{ env.PYTHON_STRETCH_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: gregogiudici/python-stretch + ref: ${{ env.PYTHON_STRETCH_REF }} + submodules: true + persist-credentials: false + + - name: Check version + run: grep -qx "version = \"${PYTHON_STRETCH_VERSION}\"" pyproject.toml + + - name: Stage bundled licences + run: | + cp include/stretch/LICENSE.txt LICENSE.signalsmith-stretch.txt + cp include/stretch/dsp/LICENSE.txt LICENSE.signalsmith-dsp.txt + + - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + env: + CIBW_BEFORE_BUILD: >- + python -m pip install nanobind && + python -c "import importlib.metadata as m, shutil; shutil.copy(next(f.locate() for f in m.files('nanobind') if f.name == 'LICENSE'), '{project}/LICENSE.nanobind')" + CIBW_ARCHS: riscv64 + CIBW_BUILD: >- + cp312-${{ matrix.libc }}_riscv64 + cp313-${{ matrix.libc }}_riscv64 + cp314-${{ matrix.libc }}_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: ${{ matrix.test_requires }} + CIBW_TEST_COMMAND: ${{ matrix.test_command }} + + - name: Check the bundled licences made it into the wheel + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + expected = {"LICENSE", "LICENSE.signalsmith-stretch.txt", "LICENSE.signalsmith-dsp.txt", "LICENSE.nanobind"} + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + found = {n.rsplit("/", 1)[1] for n in names if ".dist-info/licenses/" in n} + assert expected <= found, f"{whl}: missing {expected - found}" + assert "-cp312-abi3-" in whl, whl + print(whl, sorted(found)) + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-stretch-${{ env.PYTHON_STRETCH_VERSION }}-cp312-abi3-${{ matrix.libc }}_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish python-stretch ${{ matrix.version }} + needs: [setup, build_wheels] + 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: python-stretch-${{ matrix.version }}-*-riscv64 diff --git a/docs/packages/python-stretch.yaml b/docs/packages/python-stretch.yaml new file mode 100644 index 00000000000..bda70d3c2e4 --- /dev/null +++ b/docs/packages/python-stretch.yaml @@ -0,0 +1,5 @@ +package-name: python-stretch +source-code: https://github.com/gregogiudici/python-stretch +license: MIT +versions: +- version: 0.3.1 From 486c045be68afd66bab0a5c172b87b81faf2d221 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 06:55:44 +0000 Subject: [PATCH 2/3] python-stretch: fix musllinux smoke test expected interval Signalsmith's default preset at 44.1 kHz uses a 5292-sample block and a 1323-sample interval (input latency 2646); the first run asserted the latency value as the interval. --- .github/workflows/build-python-stretch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python-stretch.yml b/.github/workflows/build-python-stretch.yml index 46618fa6b69..63bdce1eb82 100644 --- a/.github/workflows/build-python-stretch.yml +++ b/.github/workflows/build-python-stretch.yml @@ -68,7 +68,7 @@ jobs: s.preset(2, 44100.0); s.setTimeFactor(0.5); s.setTransposeSemitones(12); - assert s.blockSamples() == 5292 and s.intervalSamples() == 2646, (s.blockSamples(), s.intervalSamples()); + assert (s.blockSamples(), s.intervalSamples(), s.inputLatency()) == (5292, 1323, 2646), (s.blockSamples(), s.intervalSamples(), s.inputLatency()); print('python_stretch OK')" env: From af52d8aba0b11e7be6961d551fd0f340596c76d3 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 07:08:02 +0000 Subject: [PATCH 3/3] python-stretch: match both libc artifacts in the publish pattern The artifacts end in -manylinux_riscv64/-musllinux_riscv64, so the '-*-riscv64' glob matched neither and the publish dry-run found no wheels. --- .github/workflows/build-python-stretch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python-stretch.yml b/.github/workflows/build-python-stretch.yml index 63bdce1eb82..c0cf5be5004 100644 --- a/.github/workflows/build-python-stretch.yml +++ b/.github/workflows/build-python-stretch.yml @@ -141,4 +141,4 @@ jobs: secrets: app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} with: - artifact-pattern: python-stretch-${{ matrix.version }}-*-riscv64 + artifact-pattern: python-stretch-${{ matrix.version }}-*riscv64