From f64cd50588e67a5bce6754ae40906fc80a0b03af Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 10:03:28 +0200 Subject: [PATCH 1/4] praat-parselmouth: add build-praat-parselmouth.yml for riscv64 wheels --- .github/workflows/build-praat-parselmouth.yml | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .github/workflows/build-praat-parselmouth.yml diff --git a/.github/workflows/build-praat-parselmouth.yml b/.github/workflows/build-praat-parselmouth.yml new file mode 100644 index 000000000..2d57b02cd --- /dev/null +++ b/.github/workflows/build-praat-parselmouth.yml @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `wheels` job of upstream's own cibuildwheel config: +# https://github.com/YannickJadoul/Parselmouth/blob/v0.4.7/.github/workflows/wheels.yml +name: Build praat-parselmouth wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'praat-parselmouth version to build (git tag without leading v, e.g. 0.4.7)' + required: true + default: '0.4.7' + pull_request: + paths: + - '.github/workflows/build-praat-parselmouth.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '0.4.7' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # `inputs.version` is empty on pull_request events; default to 0.4.7 there. + PARSELMOUTH_VERSION: ${{ inputs.version || '0.4.7' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build praat-parselmouth ${{ inputs.version || '0.4.7' }} cp312/cp313/cp314-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 480 # ~1M lines of vendored Praat C/C++ (clapack, gsl, glpk, flac, portaudio, espeak, vorbis, opusfile) + + steps: + - name: Checkout Parselmouth v${{ env.PARSELMOUTH_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: YannickJadoul/Parselmouth + ref: v${{ env.PARSELMOUTH_VERSION }} + # praat/ and pybind11/ are vendored directly; only extern/fmt is a real submodule. + submodules: recursive + persist-credentials: false + + - name: Pin cmake build dependency + # Mirrors upstream's own CI (which rewrites pyproject.toml the same way at + # build time): cmake 4 dropped compatibility with cmake_minimum_required + # versions below 3.5, which the vendored pybind11 (3.5) and fmt (3.1...3.18) + # CMakeLists.txt rely on. + run: sed -i 's/"cmake>=3.18"/"cmake>=3.18,<4"/' pyproject.toml + + - name: Build and test wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64 + # No musllinux riscv64 numpy wheel on the registry yet (workflow-anatomy.md). + CIBW_SKIP: "*musllinux*" + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # Builds the heavy vendored `praat` static lib once for the whole job + # instead of once per interpreter (gotcha 15), exactly like upstream's + # own CIBW_BEFORE_ALL. + CIBW_BEFORE_ALL_LINUX: | + cmake -S . -B build_dependencies + cmake --build build_dependencies --target praat -j "$(nproc)" + CIBW_BEFORE_BUILD: rm -rf _skbuild + CIBW_ENVIRONMENT: >- + PARSELMOUTH_EXTRA_CMAKE_ARGS="-DPREBUILT_DEPENDENCIES=$(pwd)/build_dependencies" + CMAKE_BUILD_PARALLEL_LEVEL=4 + PIP_ONLY_BINARY=numpy + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: "pytest<8 pytest-lazy-fixture tgt future" + CIBW_TEST_COMMAND: pytest {project}/tests + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: praat-parselmouth-${{ env.PARSELMOUTH_VERSION }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish praat-parselmouth ${{ inputs.version || '0.4.7' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: praat-parselmouth-${{ inputs.version || '0.4.7' }}-*-manylinux_riscv64 From dcf0604c9153a1c534c216b3990d02fdf1d8003b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 11:01:36 +0200 Subject: [PATCH 2/4] praat-parselmouth: pin cmake to a riscv64-wheel version via PIP_ONLY_BINARY PyPI ships no riscv64 wheel for any cmake release below 4.0 (only our registry does, up to 3.31.6); with the cmake<4 pin and only PIP_EXTRA_INDEX_URL set, pip resolved the numerically highest match (PyPI's 3.31.10, wheel-less) and fell back to a source build that needs OpenSSL dev headers the manylinux_riscv64 image doesn't carry. Scoping PIP_ONLY_BINARY to numpy,cmake makes pip prefer the highest version that actually has a wheel instead (gotcha 84). --- .github/workflows/build-praat-parselmouth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-praat-parselmouth.yml b/.github/workflows/build-praat-parselmouth.yml index 2d57b02cd..0a4dabb03 100644 --- a/.github/workflows/build-praat-parselmouth.yml +++ b/.github/workflows/build-praat-parselmouth.yml @@ -75,7 +75,7 @@ jobs: CIBW_ENVIRONMENT: >- PARSELMOUTH_EXTRA_CMAKE_ARGS="-DPREBUILT_DEPENDENCIES=$(pwd)/build_dependencies" CMAKE_BUILD_PARALLEL_LEVEL=4 - PIP_ONLY_BINARY=numpy + PIP_ONLY_BINARY=numpy,cmake PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_REQUIRES: "pytest<8 pytest-lazy-fixture tgt future" CIBW_TEST_COMMAND: pytest {project}/tests From fa2cd3e018fc6ad146de8719f916643e874ea610 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 12:14:52 +0200 Subject: [PATCH 3/4] praat-parselmouth: patch test_xs's strict FP equality for riscv64 test_xs recomputes each Sampled's x grid in Python and compares it to the C++-computed values with strict == equality; on riscv64 the two independently-computed float64 expressions differ in their last bit or two, failing the assert even though the values agree to machine precision. Wire up patch application (git apply, matching mmcif's pattern) and add patches/praat-parselmouth/** to the pull_request trigger. --- .github/workflows/build-praat-parselmouth.yml | 10 +++++ ...-rounding-differences-in-test_xs-s-g.patch | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 patches/praat-parselmouth/0.4.7/0001-test-tolerate-FP-rounding-differences-in-test_xs-s-g.patch diff --git a/.github/workflows/build-praat-parselmouth.yml b/.github/workflows/build-praat-parselmouth.yml index 0a4dabb03..54658ace5 100644 --- a/.github/workflows/build-praat-parselmouth.yml +++ b/.github/workflows/build-praat-parselmouth.yml @@ -15,6 +15,7 @@ on: pull_request: paths: - '.github/workflows/build-praat-parselmouth.yml' + - 'patches/praat-parselmouth/**' concurrency: group: ${{ github.workflow }}-${{ inputs.version || '0.4.7' }}-${{ github.head_ref || github.run_id }} @@ -48,6 +49,15 @@ jobs: submodules: recursive persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch praat-parselmouth source + run: git apply python-wheels/patches/praat-parselmouth/${{ env.PARSELMOUTH_VERSION }}/*.patch + - name: Pin cmake build dependency # Mirrors upstream's own CI (which rewrites pyproject.toml the same way at # build time): cmake 4 dropped compatibility with cmake_minimum_required diff --git a/patches/praat-parselmouth/0.4.7/0001-test-tolerate-FP-rounding-differences-in-test_xs-s-g.patch b/patches/praat-parselmouth/0.4.7/0001-test-tolerate-FP-rounding-differences-in-test_xs-s-g.patch new file mode 100644 index 000000000..bb1656823 --- /dev/null +++ b/patches/praat-parselmouth/0.4.7/0001-test-tolerate-FP-rounding-differences-in-test_xs-s-g.patch @@ -0,0 +1,44 @@ +From fe441a9a966150645b0ec88f0bc62b7c22f4b797 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 11 Sep 2026 12:13:56 +0200 +Subject: [PATCH] test: tolerate FP rounding differences in test_xs's + grid-formula recomputation + +Upstream-Status: To upstream [not submitted; this port's automation may not open issues or PRs on repos other than riseproject-dev/python-wheels] + +test_xs re-derives each Sampled's x grid in Python (x1 + dx * arange(nx)) +and compares it to the C++-computed xs()/x_grid()/x_bins() with strict +floating-point equality. On riscv64 the two independently-computed +expressions differ in their last bit or two (compiler FMA fusion +differences between GCC's riscv64 codegen and numpy's separate +multiply-then-add), so the exact-equality assert fails even though the +values agree to machine precision. Reproduces on riscv64 only; not seen +on the x86_64/aarch64/arm64/windows runners upstream's own CI covers. + +Switch the three assertions to np.allclose with a tolerance far above +double-precision rounding error but far below any real functional +divergence. +--- + tests/test_sampled.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/tests/test_sampled.py b/tests/test_sampled.py +index fc4c141..7b7cc93 100644 +--- a/tests/test_sampled.py ++++ b/tests/test_sampled.py +@@ -21,9 +21,9 @@ import numpy as np + + + def test_xs(sampled): +- assert np.all(sampled.xs() == sampled.x1 + sampled.dx * np.arange(sampled.nx)) +- assert np.all(sampled.x_grid() == sampled.x1 + sampled.dx * (np.arange(sampled.nx + 1) - 0.5)) +- assert np.all(sampled.x_bins() == np.vstack((sampled.x_grid()[:-1], sampled.x_grid()[1:])).T) ++ assert np.allclose(sampled.xs(), sampled.x1 + sampled.dx * np.arange(sampled.nx), rtol=1e-9, atol=1e-12) ++ assert np.allclose(sampled.x_grid(), sampled.x1 + sampled.dx * (np.arange(sampled.nx + 1) - 0.5), rtol=1e-9, atol=1e-12) ++ assert np.allclose(sampled.x_bins(), np.vstack((sampled.x_grid()[:-1], sampled.x_grid()[1:])).T, rtol=1e-9, atol=1e-12) + + + def test_len(sampled): +-- +2.50.1 (Apple Git-155) + From 5f64cf84c11c57c372b81b799ea098f023fcad54 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 11 Sep 2026 13:59:30 +0200 Subject: [PATCH 4/4] praat-parselmouth: fix publish artifact-pattern to match the single combined-job upload name The build_wheels job uploads one artifact per whole job (cp312/cp313/ cp314 combined into a single cibuildwheel invocation), not one per interpreter, so its name has no per-interpreter segment. The publish job's artifact-pattern still had a -*- wildcard expecting one, which matched nothing and failed with "No wheels found in dist" even though build_wheels had already succeeded. --- .github/workflows/build-praat-parselmouth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-praat-parselmouth.yml b/.github/workflows/build-praat-parselmouth.yml index 54658ace5..207b29791 100644 --- a/.github/workflows/build-praat-parselmouth.yml +++ b/.github/workflows/build-praat-parselmouth.yml @@ -104,4 +104,4 @@ jobs: pull-requests: write uses: $/.github/workflows/_publish-wheel.yml with: - artifact-pattern: praat-parselmouth-${{ inputs.version || '0.4.7' }}-*-manylinux_riscv64 + artifact-pattern: praat-parselmouth-${{ inputs.version || '0.4.7' }}-manylinux_riscv64