Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 106 additions & 25 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
name: samplerate

on: [push, pull_request]
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
# Weekly run: wheel tests install the newest NumPy/pytest, so this catches
# dependency drift (e.g. the NumPy 2.4 resize break) between releases.
schedule:
- cron: "17 4 * * 1"
workflow_dispatch:

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -10,82 +21,151 @@ jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64
- os: ubuntu-latest
arch: x86_64
cibw_archs: "x86_64"
# Linux ARM64 (native, no QEMU)
- os: ubuntu-24.04-arm
arch: aarch64
cibw_archs: "aarch64"
# Windows AMD64
- os: windows-latest
arch: AMD64
cibw_archs: "AMD64"
# macOS x86_64 (Intel)
- os: macos-15-intel
macoosx_deployment_target: "10.15"
arch: x86_64
cibw_archs: "x86_64"
# macOS ARM64 (Apple Silicon)
- os: macos-latest
macoosx_deployment_target: "11.0"
arch: arm64
cibw_archs: "arm64"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false

- name: Build wheels
uses: pypa/cibuildwheel@v3.3.0
uses: pypa/cibuildwheel@e090b81e30c4d855ea63bf4b6e59204c09a101ae # v4.2.1
with:
extras: uv

- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cibw-wheels-${{ matrix.os }}-${{ matrix.arch }}
path: ./wheelhouse/*.whl

build_sdist:
name: Build source distribution
name: Build and test source distribution
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
fetch-depth: 0 # setuptools-scm needs tags for the version

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: false # output is published; don't restore a cache

- name: Build sdist
run: uv build --sdist
# `uv build` builds the sdist, then a wheel *from* the sdist, so a file
# missing from MANIFEST.in fails here instead of on a user's machine.
- name: Build sdist and a wheel from it
run: uv build

- uses: actions/upload-artifact@v5
- name: Test the wheel built from the sdist
run: |
uv venv
uv pip install dist/*.whl --group test
uv run --no-project pytest tests

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cibw-sdist
path: dist/*.tar.gz
if-no-files-found: error

# Keep the NumPy floor in pyproject.toml honest: oldest supported Python
# with the oldest allowed NumPy.
numpy_oldest:
name: Test oldest supported NumPy
runs-on: ubuntu-latest
permissions:
contents: read
env:
UV_PYTHON: "3.11"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: false

- name: Build and install with the oldest NumPy
run: |
uv venv
floor=$(sed -n 's/.*"numpy>=\([0-9.]*\)".*/\1/p' pyproject.toml)
uv pip install . --group test "numpy==$floor"

- name: Test
run: |
uv run --no-project python -c "import numpy; print('numpy', numpy.__version__)"
uv run --no-project pytest tests

# Early warning: NumPy's nightly builds on the newest CPython. Not a release
# gate; it only runs on the schedule or by hand.
numpy_nightly:
name: Test against NumPy nightly
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0

- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: false

- name: Build and install
run: uv sync --python 3.14 --group test

- name: Install NumPy nightly
run: >
uv pip install --pre --upgrade
--index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
numpy

- name: Test
run: |
uv run --no-sync python -c "import numpy; print('numpy', numpy.__version__)"
uv run --no-sync pytest tests

publish:
name: Publish to PyPI
needs: [build_wheels, build_sdist]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/python-samplerate-ledfx/
url: https://pypi.org/p/samplerate-ledfx/
permissions:
id-token: write # Required for trusted publishing
steps:
- name: Install uv
uses: astral-sh/setup-uv@v7

- name: Download all artifacts
uses: actions/download-artifact@v5
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: cibw-*
Expand All @@ -94,5 +174,6 @@ jobs:
- name: Display structure of downloaded files
run: ls -R dist

# Uses trusted publishing and uploads PEP 740 attestations for each file.
- name: Publish to PyPI
run: uv publish --trusted-publishing always
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
30 changes: 30 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: zizmor

on:
push:
branches: [main]
pull_request:
schedule:
- cron: "17 4 * * 1" # new audits land in zizmor over time
workflow_dispatch:

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
zizmor:
name: Audit GitHub Actions workflows
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
advanced-security: false # fail the job instead of uploading SARIF
3 changes: 0 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
cmake_minimum_required(VERSION 3.15)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)

message(STATUS "Found Python prefix ${PYTHON_PREFIX}")
list(PREPEND CMAKE_PREFIX_PATH "${PYTHON_PREFIX}")

project(python-samplerate)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

Expand Down
69 changes: 69 additions & 0 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Maintaining samplerate-ledfx

This is a fork of [tuxu/python-samplerate](https://github.com/tuxu/python-samplerate)
with LedFx's GIL handling, build info and CI on top. It wraps two vendored C/C++
projects, pinned by commit in [`external/CMakeLists.txt`](external/CMakeLists.txt).

## What keeps it current

| What | How | Who acts |
| --- | --- | --- |
| GitHub Actions, uv.lock, Python deps | Renovate, from the org preset `github>LedFx/renovate-config`. Non-majors automerge on green CI after 14 days; majors wait 30 days and need a person. | Renovate; majors reviewed by a maintainer |
| pybind11, libsamplerate | Renovate regex manager on `external/CMakeLists.txt`. Never automerged: they change the compiled wheel. | Maintainer reviews |
| Upstream fork | Renovate bumps the marker below when `tuxu/python-samplerate` moves. The PR is the prompt to review upstream. | Maintainer reviews |
| New NumPy / Python releases | Weekly scheduled CI builds and tests every wheel against the newest NumPy, plus NumPy nightly on the newest CPython. | Whoever sees the red run |
| Workflow security | zizmor on every change to `.github/` and weekly. | CI |

## Syncing upstream

Last reviewed upstream commit (Renovate updates this line):

upstream: https://github.com/tuxu/python-samplerate master@40e7810233ff0e9e076f308fb79cfb53087b5bce

When Renovate opens a PR bumping it:

```sh
git remote add upstream https://github.com/tuxu/python-samplerate # once
git fetch upstream
git log --oneline <old-sha>..upstream/master
```

Upstream and this fork have diverged (GIL release, build info), so port fixes by
hand or `git cherry-pick -x` and resolve. Skip upstream CI/release changes; ours
is different. Add a test for anything that touches `src/samplerate.cpp`. Merge
the marker bump in the same PR as the ports, or on its own if nothing applies.

History of what was taken:

- `06e88d1` (#36) resize → view: ported, with a fix for a use-after-free in
upstream's mono `CallbackResampler.read` path.
- `6d68220` (#34) `Python_EXECUTABLE`: ported.
- `96eb024` `-fPIC`, `235d720` py3.8 drop: already here.
- `855b93b`, `40e7810` upstream CI/twine: not applicable.

## Releasing

Tag `vX.Y.Z` on `main`. CI builds wheels and the sdist, then publishes to PyPI
through trusted publishing from the `pypi` environment, with attestations.

## Repository settings

These live in GitHub, not in this repo. Renovate's automerge relies on them:

- Ruleset `main`: changes go through PRs (no approval needed), no force pushes
or deletion, and these checks must pass (from GitHub Actions only): the five
wheel builds, the sdist build, the oldest-NumPy test and zizmor. Repo admins
can bypass it on a PR. Rename a job and you must update the ruleset too.
- `pypi` environment: deploys from `v*` tags only.
- Actions: workflow token is read-only by default and can't approve PRs.
- Security: Dependabot alerts on (Renovate reads them to raise `[SECURITY]`
PRs immediately), secret scanning and push protection on, private
vulnerability reporting on.
- Issues enabled, for Renovate's Dependency Dashboard and bug reports.

## Supported versions

CPython 3.11–3.14 and NumPy >= 1.23.2. When a CPython version reaches end of
life, drop it from `requires-python` and `[tool.cibuildwheel] build`, and raise
the NumPy floor to the first release with wheels for the new oldest Python;
the `numpy_oldest` CI job reads the floor from pyproject.toml.
8 changes: 8 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security policy

Only the latest release on PyPI gets fixes.

Report vulnerabilities privately through
[GitHub's private vulnerability reporting](https://github.com/LedFx/python-samplerate-ledfx/security/advisories/new),
not in a public issue. Bugs in libsamplerate or pybind11 themselves belong
upstream; tell us too so we can update the vendored copy.
16 changes: 11 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ name="samplerate-ledfx"
dynamic = ["version","readme"]
description="Monolithic python wrapper for libsamplerate based on pybind11 and NumPy"
authors=[
{"name" = "LedFx"},
{"name" = "Robin Scheibler", "email" ="fakufaku@gmail.com"},
{"name" = "Tino Wagner", "email" ="ich@tinowagner.com"}
]
requires-python = ">=3.9"
requires-python = ">=3.11"
classifiers=[
"Development Status :: 3 - Alpha",
"Environment :: Console",
Expand All @@ -23,10 +24,15 @@ classifiers=[
]
keywords=["samplerate", "converter", "signal processing", "audio"]
dependencies = [
"numpy>=1.7.0",

"numpy>=1.23.2", # first release with CPython 3.11 wheels; tested in CI
]

[project.urls]
Homepage = "https://github.com/LedFx/python-samplerate-ledfx"
Source = "https://github.com/LedFx/python-samplerate-ledfx"
Issues = "https://github.com/LedFx/python-samplerate-ledfx/issues"
Upstream = "https://github.com/tuxu/python-samplerate"

[dependency-groups]
dev = [
"setuptools>=80",
Expand All @@ -36,7 +42,7 @@ test = [
"pytest",
"pytest-asyncio",
"uvloop>=0.16.0; sys_platform != \"win32\"",
"winloop>=0.3.1; sys_platform == \"win32\" and python_version >= \"3.9\"",
"winloop>=0.3.1; sys_platform == \"win32\"",
]

[tool.setuptools.dynamic]
Expand All @@ -48,7 +54,7 @@ readme = {file = "README.md", content-type = "text/markdown"}
test-groups = ["test"]
test-command = "pytest {project}/tests"
build-frontend = "build[uv]"
build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*","cp314-*"]
build = ["cp311-*", "cp312-*", "cp313-*", "cp314-*"]
# Skip 32-bit builds and musllinux wheels
skip = ["*-win32", "*-manylinux_i686", "*-musllinux*"]

Expand Down
Loading
Loading