From df3e673a4863110e216ed523919770cb5897d49b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:11:00 +0000 Subject: [PATCH 1/6] av: add build-av.yml for riscv64 wheels PyAV compiles ~49 Cython extension modules against FFmpeg. Upstream's tests.yml fetches a prebuilt FFmpeg tarball from PyAV-Org/pyav-ffmpeg in CIBW_BEFORE_BUILD; that project already publishes ffmpeg-manylinux-riscv64.tar.gz, so the recipe carries over unchanged and auditwheel bundles the FFmpeg libraries into the wheel. setup.py turns on Py_LIMITED_API (cp311) for CPython 3.11-3.13, so the build on our cp312 floor emits the same cp311-abi3 wheel upstream ships; free-threaded builds get their own cp314-cp314t wheel. Upstream builds on cp311, which is below this repo's floor. Validated on aarch64 in quay.io/pypa/manylinux_2_39_aarch64: the wheel builds, auditwheel repairs it to 34MB with 32 bundled libraries, and upstream's test command passes 490 tests against the installed wheel. --- .github/workflows/build-av.yml | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/build-av.yml diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml new file mode 100644 index 0000000000..32b6f6d6da --- /dev/null +++ b/.github/workflows/build-av.yml @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/PyAV-Org/PyAV/blob/v18.1.0/.github/workflows/tests.yml +name: Build av wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'av version to build (PyPI version, e.g. 18.1.0)' + required: true + default: '18.1.0' + pull_request: + paths: + - '.github/workflows/build-av.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '18.1.0' }}-${{ 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 18.1.0 there. + AV_VERSION: ${{ inputs.version || '18.1.0' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + build_wheels: + name: Build av ${{ inputs.version || '18.1.0' }} ${{ matrix.tag }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 300 + strategy: + fail-fast: false + matrix: + include: + # setup.py turns on Py_LIMITED_API for 3.11-3.13, so the build on our + # cp312 floor emits upstream's cp311-abi3 wheel; free-threaded can't. + - python: "cp312" + tag: "cp311-abi3" + - python: "cp314t" + tag: "cp314-cp314t" + + steps: + - name: Checkout PyAV v${{ env.AV_VERSION }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: PyAV-Org/PyAV + ref: v${{ env.AV_VERSION }} + persist-credentials: false + + - 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 }} + # Upstream's prebuilt FFmpeg; pyav-ffmpeg already publishes a riscv64 tarball. + CIBW_BEFORE_BUILD: python scripts/fetch-vendor.py --config-file scripts/ffmpeg-latest.json /tmp/vendor + CIBW_ENVIRONMENT: >- + LD_LIBRARY_PATH=/tmp/vendor/lib:$LD_LIBRARY_PATH + PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: pytest numpy + CIBW_TEST_COMMAND: mv {project}/av {project}/av.disabled && python -m pytest {package}/tests && mv {project}/av.disabled {project}/av + + - name: Check the wheel ships the compiled extensions + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + names = zipfile.ZipFile(sys.argv[1]).namelist() + sos = [n for n in names if n.startswith("av/") and n.endswith(".so")] + libs = [n for n in names if n.startswith("av.libs/")] + assert sos, f"no av/*.so in {sys.argv[1]}" + assert any("libavcodec" in n for n in libs), f"no vendored FFmpeg in {sys.argv[1]}" + print(f"{len(sos)} extension modules, {len(libs)} bundled libraries") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: av-${{ env.AV_VERSION }}-${{ matrix.tag }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish av ${{ inputs.version || '18.1.0' }} to GitLab + needs: [build_wheels] + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Publish wheels and open docs PR + uses: riseproject-dev/python-wheels/actions/publish-wheels@main + with: + artifact-pattern: av-${{ env.AV_VERSION }}-*-manylinux_riscv64 + gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} + gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} + gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} + gh-token: ${{ secrets.GITHUB_TOKEN }} From c866a6aa509f77863a397fd836be9dbffcf75724 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:11:00 +0000 Subject: [PATCH 2/6] av: build the abi3 wheel on cp311 setup.py tags the wheel cp311-abi3, so the stable-ABI guarantee only holds if it is compiled against 3.11 headers; building it on cp312 would ship a wheel pip installs on 3.11 that may fail at runtime there. Upstream builds on cp311 for the same reason. numpy resolves to 2.4.3 on 3.11, which our registry ships for riscv64. --- .github/workflows/build-av.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml index 32b6f6d6da..b4b6e44d4e 100644 --- a/.github/workflows/build-av.yml +++ b/.github/workflows/build-av.yml @@ -36,9 +36,10 @@ jobs: fail-fast: false matrix: include: - # setup.py turns on Py_LIMITED_API for 3.11-3.13, so the build on our - # cp312 floor emits upstream's cp311-abi3 wheel; free-threaded can't. - - python: "cp312" + # setup.py turns on Py_LIMITED_API for 3.11-3.13, so this emits one + # cp311-abi3 wheel; free-threaded can't use the stable ABI. Build it on + # cp311, as upstream does: the tag claims 3.11 (gotcha 96). + - python: "cp311" tag: "cp311-abi3" - python: "cp314t" tag: "cp314-cp314t" From 62f408cd310f61ea307b46b8adfac1368884abbf Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:11:00 +0000 Subject: [PATCH 3/6] av: follow upstream's wheel job shape Upstream builds both wheels in one cibuildwheel invocation with CIBW_BUILD: "cp311* cp314t*" and sets CIBW_ENVIRONMENT_LINUX; this was a two-entry matrix with per-entry only:, and CIBW_ENVIRONMENT. Nothing about riscv64 required either change, so drop them. --- .github/workflows/build-av.yml | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml index b4b6e44d4e..99b2b4a689 100644 --- a/.github/workflows/build-av.yml +++ b/.github/workflows/build-av.yml @@ -29,20 +29,9 @@ env: jobs: build_wheels: - name: Build av ${{ inputs.version || '18.1.0' }} ${{ matrix.tag }}-manylinux_riscv64 + name: Build av ${{ inputs.version || '18.1.0' }} manylinux_riscv64 runs-on: ubuntu-24.04-riscv timeout-minutes: 300 - strategy: - fail-fast: false - matrix: - include: - # setup.py turns on Py_LIMITED_API for 3.11-3.13, so this emits one - # cp311-abi3 wheel; free-threaded can't use the stable ABI. Build it on - # cp311, as upstream does: the tag claims 3.11 (gotcha 96). - - python: "cp311" - tag: "cp311-abi3" - - python: "cp314t" - tag: "cp314-cp314t" steps: - name: Checkout PyAV v${{ env.AV_VERSION }} @@ -56,33 +45,34 @@ jobs: uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: output-dir: wheelhouse/ - only: ${{ matrix.python }}-manylinux_riscv64 env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: "cp311* cp314t*" CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} - # Upstream's prebuilt FFmpeg; pyav-ffmpeg already publishes a riscv64 tarball. CIBW_BEFORE_BUILD: python scripts/fetch-vendor.py --config-file scripts/ffmpeg-latest.json /tmp/vendor - CIBW_ENVIRONMENT: >- + CIBW_ENVIRONMENT_LINUX: >- LD_LIBRARY_PATH=/tmp/vendor/lib:$LD_LIBRARY_PATH PKG_CONFIG_PATH=/tmp/vendor/lib/pkgconfig PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_REQUIRES: pytest numpy CIBW_TEST_COMMAND: mv {project}/av {project}/av.disabled && python -m pytest {package}/tests && mv {project}/av.disabled {project}/av - - name: Check the wheel ships the compiled extensions + - name: Check the wheels ship the compiled extensions run: | python3 - wheelhouse/*.whl <<'EOF' import sys, zipfile - names = zipfile.ZipFile(sys.argv[1]).namelist() - sos = [n for n in names if n.startswith("av/") and n.endswith(".so")] - libs = [n for n in names if n.startswith("av.libs/")] - assert sos, f"no av/*.so in {sys.argv[1]}" - assert any("libavcodec" in n for n in libs), f"no vendored FFmpeg in {sys.argv[1]}" - print(f"{len(sos)} extension modules, {len(libs)} bundled libraries") + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + sos = [n for n in names if n.startswith("av/") and n.endswith(".so")] + libs = [n for n in names if n.startswith("av.libs/")] + assert sos, f"no av/*.so in {whl}" + assert any("libavcodec" in n for n in libs), f"no vendored FFmpeg in {whl}" + print(f"{whl}: {len(sos)} extension modules, {len(libs)} bundled libraries") EOF - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: av-${{ env.AV_VERSION }}-${{ matrix.tag }}-manylinux_riscv64 + name: av-${{ env.AV_VERSION }}-manylinux_riscv64 path: wheelhouse/*.whl if-no-files-found: error From 264ae13d50679420951d5375a7b7d35146aa56f5 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:11:00 +0000 Subject: [PATCH 4/6] av: match the publish pattern to the single artifact name Collapsing the matrix to one job renamed the artifact to av--manylinux_riscv64, but artifact-pattern still expected a segment between the version and the platform, so it matched nothing. --- .github/workflows/build-av.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml index 99b2b4a689..79796f4aed 100644 --- a/.github/workflows/build-av.yml +++ b/.github/workflows/build-av.yml @@ -88,7 +88,7 @@ jobs: - name: Publish wheels and open docs PR uses: riseproject-dev/python-wheels/actions/publish-wheels@main with: - artifact-pattern: av-${{ env.AV_VERSION }}-*-manylinux_riscv64 + artifact-pattern: av-${{ env.AV_VERSION }}-manylinux_riscv64 gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} From fd12ab5c798a6ad9e6581d62f6baebcb78baed8b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:12:59 +0000 Subject: [PATCH 5/6] av: ship the vendored FFmpeg licences and publish its sources The wheels bundle 33 prebuilt shared libraries fetched from a pyav-ffmpeg release, and shipped only PyAV's own BSD-3-Clause LICENSE.txt. Two of those libraries are GPL. pyav-ffmpeg patches FFmpeg's configure to move libx264 and libx265 out of EXTERNAL_LIBRARY_GPL_LIST into EXTERNAL_LIBRARY_VERSION3_LIST, so FFmpeg builds without --enable-gpl and reports "LGPL version 3 or later" while libavcodec still has DT_NEEDED on libx264.so.165 and libx265.so.216, both GPL-2.0-or-later. The rest of the stack is LGPL (FFmpeg, GnuTLS, Nettle, GMP, libunistring, alsa-lib, LAME) or permissive. A new vendor_sources job reads the URL and SHA-256 pins out of pyav-ffmpeg's scripts/pkg.py, downloads every source tarball plus the pyav-ffmpeg tree that carries the build recipe and its FFmpeg/GMP/LAME/vpx patches, and extracts each project's licence texts. The sources are published as a GitHub Release asset; the licence texts are staged into the checkout so setuptools' default glob ships them in dist-info/licenses, and the post-build check asserts the whole set is present in every wheel. --- .github/workflows/build-av.yml | 183 ++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml index 79796f4aed..e5d7dd61b8 100644 --- a/.github/workflows/build-av.yml +++ b/.github/workflows/build-av.yml @@ -14,6 +14,7 @@ on: pull_request: paths: - '.github/workflows/build-av.yml' + - 'ci/av/**' concurrency: group: ${{ github.workflow }}-${{ inputs.version || '18.1.0' }}-${{ github.head_ref || github.run_id }} @@ -28,8 +29,165 @@ env: MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 jobs: + # The wheels bundle prebuilt FFmpeg libraries from a pyav-ffmpeg release, + # including GPL x264/x265 and LGPL FFmpeg, GnuTLS, Nettle, GMP, libunistring, + # alsa-lib and LAME. Publishing them obliges us to ship their licence texts + # and to make the corresponding sources permanently available. + vendor_sources: + name: Collect vendored FFmpeg sources + runs-on: ubuntu-latest + + steps: + - name: Checkout PyAV v${{ env.AV_VERSION }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: PyAV-Org/PyAV + ref: v${{ env.AV_VERSION }} + persist-credentials: false + path: pyav + + - name: Download the vendored sources and extract their licences + run: | + cat > "$RUNNER_TEMP/collect-vendor-sources.py" <<'PY' + # SPDX-FileCopyrightText: 2026 The RISE Project + # SPDX-License-Identifier: MIT + """Collect the sources and licence texts of the FFmpeg stack PyAV vendors. + + PyAV's wheels bundle prebuilt shared libraries fetched from a pyav-ffmpeg + release. Several of them are GPL (x264, x265) or LGPL (FFmpeg, GnuTLS, + Nettle, GMP, libunistring, alsa-lib, LAME), so redistributing the wheels + carries a source-distribution obligation. pyav-ffmpeg pins every dependency + by URL and SHA-256 in scripts/pkg.py, which is what this reads. + """ + + import argparse + import hashlib + import json + import re + import subprocess + import sys + import tarfile + from pathlib import Path + + LICENCE_RE = re.compile(r"^(COPYING|COPYRIGHT|LICEN[CS]E|NOTICE)", re.IGNORECASE) + + + def load_packages(pkg_py: str): + namespace: dict = {} + exec(compile(pkg_py, "pkg.py", "exec"), namespace) + # Linux riscv64 enables gnutls, alsa and libvpl; CUDA/AMF/nasm are x86-only. + packages = ( + namespace["gnutls_group"] + + namespace["codec_group"] + + [ + namespace["alsa_package"], + namespace["libvpl_package"], + namespace["ffmpeg_package"], + ] + ) + return sorted(packages, key=lambda p: p.name) + + + def download(package, dest_dir: Path) -> Path: + name = package.source_filename or package.source_url.rsplit("/", 1)[-1] + # A few upstreams name their tarball after the tag alone ("v2.16.0.tar.gz"). + if package.name.replace("-", "").lower() not in name.replace("-", "").lower(): + name = f"{package.name}-{name}" + path = dest_dir / name + subprocess.run( + ["curl", "--location", "--fail", "--silent", "--show-error", + "--output", str(path), package.source_url], + check=True, + ) + digest = hashlib.sha256(path.read_bytes()).hexdigest() + if digest != package.sha256: + raise SystemExit( + f"{package.name}: sha256 mismatch for {package.source_url}\n" + f" expected {package.sha256}\n got {digest}" + ) + print(f"{package.name}: {name} ({path.stat().st_size} bytes, sha256 ok)") + return path + + + def extract_licences(package, tarball: Path, dest_dir: Path) -> None: + chunks = [] + with tarfile.open(tarball) as tar: + for member in tar.getmembers(): + parts = Path(member.name).parts + # Top level of the archive, plus one nested directory (x265 keeps + # its sources under source/, gnutls its licences under doc/). + if not member.isfile() or len(parts) > 3: + continue + if not LICENCE_RE.match(parts[-1]): + continue + handle = tar.extractfile(member) + if handle is None: + continue + text = handle.read().decode("utf-8", "replace") + chunks.append(f"===== {'/'.join(parts[1:])} =====\n\n{text}") + if not chunks: + raise SystemExit(f"{package.name}: no licence file found in {tarball.name}") + header = ( + f"Licence texts for {package.name}, bundled in this wheel as a prebuilt\n" + f"shared library. Source: {package.source_url}\n\n" + ) + (dest_dir / f"LICENSE.{package.name}").write_text(header + "\n\n".join(chunks)) + print(f"{package.name}: {len(chunks)} licence file(s)") + + + def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--pyav-dir", type=Path, required=True) + parser.add_argument("--sources-dir", type=Path, required=True) + parser.add_argument("--licenses-dir", type=Path, required=True) + args = parser.parse_args() + + config = json.loads((args.pyav_dir / "scripts" / "ffmpeg-latest.json").read_text()) + tag = config["url"].split("/download/")[1].split("/")[0] + print(f"pyav-ffmpeg release: {tag}") + + args.sources_dir.mkdir(parents=True, exist_ok=True) + args.licenses_dir.mkdir(parents=True, exist_ok=True) + + # pyav-ffmpeg carries the build recipe and the patches it applies to FFmpeg, + # GMP, LAME and libvpx, so it is part of the corresponding source. + recipe = args.sources_dir / f"pyav-ffmpeg-{tag}.tar.gz" + subprocess.run( + ["curl", "--location", "--fail", "--silent", "--show-error", "--output", str(recipe), + f"https://github.com/PyAV-Org/pyav-ffmpeg/archive/refs/tags/{tag}.tar.gz"], + check=True, + ) + with tarfile.open(recipe) as tar: + member = next(m for m in tar.getmembers() if m.name.endswith("/scripts/pkg.py")) + pkg_py = tar.extractfile(member).read().decode() + + for package in load_packages(pkg_py): + tarball = download(package, args.sources_dir) + extract_licences(package, tarball, args.licenses_dir) + + + if __name__ == "__main__": + sys.exit(main()) + PY + python3 "$RUNNER_TEMP/collect-vendor-sources.py" \ + --pyav-dir pyav --sources-dir sources --licenses-dir licenses + tar -cf gpl-sources.tar -C sources . + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: av-${{ env.AV_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: av-${{ env.AV_VERSION }}-vendor-licenses + path: licenses/ + if-no-files-found: error + build_wheels: name: Build av ${{ inputs.version || '18.1.0' }} manylinux_riscv64 + needs: [vendor_sources] runs-on: ubuntu-24.04-riscv timeout-minutes: 300 @@ -41,6 +199,16 @@ jobs: ref: v${{ env.AV_VERSION }} persist-credentials: false + - name: Fetch the vendored libraries' licence texts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: av-${{ env.AV_VERSION }}-vendor-licenses + path: vendor-licenses + + # setuptools' default license-files glob picks these up from the project root. + - name: Stage the licence texts for packaging + run: cp vendor-licenses/LICENSE.* . + - name: Build wheels uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: @@ -57,17 +225,21 @@ jobs: CIBW_TEST_REQUIRES: pytest numpy CIBW_TEST_COMMAND: mv {project}/av {project}/av.disabled && python -m pytest {package}/tests && mv {project}/av.disabled {project}/av - - name: Check the wheels ship the compiled extensions + - name: Check the wheels ship the compiled extensions and vendored licences run: | python3 - wheelhouse/*.whl <<'EOF' - import sys, zipfile + import pathlib, sys, zipfile + expected = {p.name for p in pathlib.Path("vendor-licenses").iterdir()} for whl in sys.argv[1:]: names = zipfile.ZipFile(whl).namelist() sos = [n for n in names if n.startswith("av/") and n.endswith(".so")] libs = [n for n in names if n.startswith("av.libs/")] + shipped = {n.rsplit("/", 1)[1] for n in names if ".dist-info/licenses/" in n} - {""} assert sos, f"no av/*.so in {whl}" assert any("libavcodec" in n for n in libs), f"no vendored FFmpeg in {whl}" - print(f"{whl}: {len(sos)} extension modules, {len(libs)} bundled libraries") + assert expected <= shipped, f"{whl} is missing {sorted(expected - shipped)}" + print(f"{whl}: {len(sos)} extension modules, {len(libs)} bundled libraries, " + f"{len(shipped)} licence files") EOF - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -78,7 +250,7 @@ jobs: publish: name: Publish av ${{ inputs.version || '18.1.0' }} to GitLab - needs: [build_wheels] + needs: [build_wheels, vendor_sources] runs-on: ubuntu-latest permissions: contents: write @@ -93,3 +265,6 @@ jobs: gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} gh-token: ${{ secrets.GITHUB_TOKEN }} + gpl-sources-artifact: av-${{ env.AV_VERSION }}-gpl-sources + gpl-sources-release-tag: av-v${{ env.AV_VERSION }} + gpl-sources-description: FFmpeg, x264, x265 From 974f4f58d7cbba675d81caf4f2a1584dec2b1f3a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 20 Sep 2026 16:15:01 +0000 Subject: [PATCH 6/6] av: build the versions declared in docs/packages/av.yaml This workflow predates the shared setup/publish workflows: it baked 18.1.0 into a workflow_dispatch default, had no push trigger, and published through actions/publish-wheels, which no longer exists. Declare the version in docs/packages/av.yaml instead, take the build matrix from _setup.yml, and publish with _publish-wheel.yml, which creates the GitHub Release and opens the documentation PR. The GPL sources artifact is passed to it unchanged. The build itself is untouched - same cibuildwheel invocation, same CIBW_BUILD selection, same wheel-content check. --- .github/workflows/build-av.yml | 77 ++++++++++++++++++++++------------ docs/packages/av.yaml | 5 +++ 2 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 docs/packages/av.yaml diff --git a/.github/workflows/build-av.yml b/.github/workflows/build-av.yml index e5d7dd61b8..7b68216f38 100644 --- a/.github/workflows/build-av.yml +++ b/.github/workflows/build-av.yml @@ -8,34 +8,53 @@ on: workflow_dispatch: inputs: version: - description: 'av version to build (PyPI version, e.g. 18.1.0)' - required: true - default: '18.1.0' + description: 'Version glob to (re)build; empty builds every version of docs/packages/av.yaml not released yet' + required: false + default: '' pull_request: + branches: [main] paths: - '.github/workflows/build-av.yml' - - 'ci/av/**' + - 'docs/packages/av.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-av.yml' + - 'docs/packages/av.yaml' concurrency: - group: ${{ github.workflow }}-${{ inputs.version || '18.1.0' }}-${{ github.head_ref || github.run_id }} + group: ${{ github.workflow }}-${{ 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 18.1.0 there. - AV_VERSION: ${{ inputs.version || '18.1.0' }} MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: av + version: ${{ inputs.version }} + # The wheels bundle prebuilt FFmpeg libraries from a pyav-ffmpeg release, # including GPL x264/x265 and LGPL FFmpeg, GnuTLS, Nettle, GMP, libunistring, # alsa-lib and LAME. Publishing them obliges us to ship their licence texts # and to make the corresponding sources permanently available. vendor_sources: - name: Collect vendored FFmpeg sources + name: Collect av ${{ matrix.version }} vendored FFmpeg sources + needs: [setup] + if: needs.setup.outputs.versions != '[]' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + AV_VERSION: ${{ matrix.version }} steps: - name: Checkout PyAV v${{ env.AV_VERSION }} @@ -186,10 +205,18 @@ jobs: if-no-files-found: error build_wheels: - name: Build av ${{ inputs.version || '18.1.0' }} manylinux_riscv64 - needs: [vendor_sources] + name: Build av ${{ matrix.version }} manylinux_riscv64 + needs: [setup, vendor_sources] + if: needs.setup.outputs.versions != '[]' runs-on: ubuntu-24.04-riscv timeout-minutes: 300 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + AV_VERSION: ${{ matrix.version }} steps: - name: Checkout PyAV v${{ env.AV_VERSION }} @@ -249,22 +276,20 @@ jobs: if-no-files-found: error publish: - name: Publish av ${{ inputs.version || '18.1.0' }} to GitLab - needs: [build_wheels, vendor_sources] - runs-on: ubuntu-latest + name: Publish av ${{ matrix.version }} + needs: [setup, vendor_sources, build_wheels] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} permissions: contents: write pull-requests: write - - steps: - - name: Publish wheels and open docs PR - uses: riseproject-dev/python-wheels/actions/publish-wheels@main - with: - artifact-pattern: av-${{ env.AV_VERSION }}-manylinux_riscv64 - gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} - gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} - gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} - gh-token: ${{ secrets.GITHUB_TOKEN }} - gpl-sources-artifact: av-${{ env.AV_VERSION }}-gpl-sources - gpl-sources-release-tag: av-v${{ env.AV_VERSION }} - gpl-sources-description: FFmpeg, x264, x265 + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: av-${{ matrix.version }}-manylinux_riscv64 + gpl-sources-artifact: av-${{ matrix.version }}-gpl-sources + gpl-sources-description: FFmpeg, x264, x265 diff --git a/docs/packages/av.yaml b/docs/packages/av.yaml new file mode 100644 index 0000000000..bdda3bd689 --- /dev/null +++ b/docs/packages/av.yaml @@ -0,0 +1,5 @@ +package-name: av +source-code: https://github.com/PyAV-Org/PyAV +license: BSD-3-Clause +versions: +- version: 18.1.0