From af6710f68512179c1dcc2b7ea8e891f9e4c0822c Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 7 Sep 2026 19:35:00 +0200 Subject: [PATCH 1/2] pylzss: add build-pylzss.yml for riscv64 wheels Pure-C LZSS compression/decompression extension, no native dependencies. Builds against the manylinux_2_39/musllinux_1_2 riscv64 images for cp312-cp314t. Adds patches/pylzss/0.3.8/0001-*.patch to ship py3c's MIT LICENSE in dist-info/licenses/ - the vendored src/include/py3c headers compile straight into the extension but their notice lives only as a header comment, so setuptools' default root LICEN[CS]E*/COPYING* glob never picks it up (verified against the real PyPI wheel, which ships only COPYING/COPYING.LESSER). The CI test command exercises a real compress/decompress round trip rather than upstream's checked-in test.py, which calls lzss.encode()/decode() - an API the extension has never exported (it's compress()/decompress()); neither of upstream's own CI workflows runs test.py, so that mismatch has gone unnoticed there. --- .github/workflows/build-pylzss.yml | 101 ++++++++++++++++++ ...MIT-LICENSE-in-the-wheel-s-dist-info.patch | 62 +++++++++++ 2 files changed, 163 insertions(+) create mode 100644 .github/workflows/build-pylzss.yml create mode 100644 patches/pylzss/0.3.8/0001-ship-py3c-s-MIT-LICENSE-in-the-wheel-s-dist-info.patch diff --git a/.github/workflows/build-pylzss.yml b/.github/workflows/build-pylzss.yml new file mode 100644 index 000000000..4b75f58d9 --- /dev/null +++ b/.github/workflows/build-pylzss.yml @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on upstream's own wheel build: +# https://github.com/m1stadev/pylzss/blob/v0.3.8/.github/workflows/pypi-build-publish.yml +name: Build pylzss wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'pylzss version/tag to build (git tag without leading v, e.g. 0.3.8)' + required: true + default: '0.3.8' + pull_request: + paths: + - '.github/workflows/build-pylzss.yml' + - 'patches/pylzss/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '0.3.8' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + PYLZSS_VERSION: ${{ inputs.version || '0.3.8' }} + 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 + + build_wheels: + needs: [setup] + name: Build pylzss ${{ inputs.version || '0.3.8' }} ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + libc: [manylinux, musllinux] + + steps: + - name: Checkout pylzss v${{ env.PYLZSS_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: m1stadev/pylzss + ref: v${{ env.PYLZSS_VERSION }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + # py3c's MIT notice lives only as a header comment (src/include/py3c/*), + # not a discoverable LICEN[CS]E*/COPYING* file, so setuptools' default + # root glob never ships it - see the patch commit message. + - name: Patch pylzss source + run: git apply python-wheels/patches/pylzss/${{ env.PYLZSS_VERSION }}/00*.patch + + - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} + # Upstream's own test.py calls lzss.encode()/decode(), which the + # extension has never exported (it's compress()/decompress() - see + # src/pylzss.c's pylzss_methods); neither of upstream's CI workflows + # actually run test.py, so the mismatch went unnoticed there. Test + # the real API with a round trip instead. + CIBW_TEST_COMMAND: >- + python -c " + import lzss, importlib.metadata, zipfile; + assert lzss.__file__.endswith(('.so', '.pyd')), lzss.__file__; + data = b'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' * 50; + assert lzss.decompress(lzss.compress(data)) == data; + licenses = {p.name for p in importlib.metadata.files('pylzss') if '.dist-info/licenses/' in str(p)}; + assert licenses == {'COPYING', 'COPYING.LESSER', 'LICENSE.py3c'}, licenses" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pylzss-${{ env.PYLZSS_VERSION }}-${{ matrix.python }}-${{ matrix.libc }}_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish pylzss ${{ inputs.version || '0.3.8' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: pylzss-${{ inputs.version || '0.3.8' }}-*riscv64 diff --git a/patches/pylzss/0.3.8/0001-ship-py3c-s-MIT-LICENSE-in-the-wheel-s-dist-info.patch b/patches/pylzss/0.3.8/0001-ship-py3c-s-MIT-LICENSE-in-the-wheel-s-dist-info.patch new file mode 100644 index 000000000..ee737b5a4 --- /dev/null +++ b/patches/pylzss/0.3.8/0001-ship-py3c-s-MIT-LICENSE-in-the-wheel-s-dist-info.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 7 Sep 2026 19:32:04 +0200 +Subject: [PATCH] ship py3c's MIT LICENSE in the wheel's dist-info + +setup.py's sole extension compiles src/pylzss.c against the vendored +src/include/py3c headers (py3c.h, which pulls in py3c/compat.h, +py3c/comparison.h and py3c/py3shims.h) for its Python 2/3 string and +comparison shims. Those headers are encukou/py3c, MIT-licensed +(Copyright (c) 2015, Red Hat, Inc.), and their notice lives only as a +comment inside py3c.h itself - there is no separate LICENSE file next +to them for setuptools' default `LICEN[CS]E*`/`COPYING*` root glob to +pick up. + +pyproject.toml declares `license = {text = "LGPL-3.0-only"}` with no +`license-files` key, so that default glob is what ships the project's +own COPYING/COPYING.LESSER; it never reaches into src/include/py3c/, +and reproduced against the real +pylzss-0.3.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +from PyPI, dist-info/ carries only COPYING and COPYING.LESSER - py3c's +MIT notice is compiled straight into the extension and shipped with no +attribution at all. + +Dropping the notice at the project root as LICENSE.py3c is picked up +by that same default glob with no packaging change, landing it in +dist-info/licenses/ alongside the project's own licence. + +Upstream-Status: To upstream [not filed against m1stadev/pylzss from this automated port; a maintainer should report it upstream] +--- + LICENSE.py3c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + create mode 100644 LICENSE.py3c + +diff --git a/LICENSE.py3c b/LICENSE.py3c +new file mode 100644 +index 0000000..f53284c +--- /dev/null ++++ b/LICENSE.py3c +@@ -0,0 +1,21 @@ ++The MIT License (MIT) ++ ++Copyright (c) 2015, Red Hat, Inc. and/or its affiliates ++ ++Permission is hereby granted, free of charge, to any person obtaining a copy ++of this software and associated documentation files (the "Software"), to deal ++in the Software without restriction, including without limitation the rights ++to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++copies of the Software, and to permit persons to whom the Software is ++furnished to do so, subject to the following conditions: ++ ++The above copyright notice and this permission notice shall be included in all ++copies or substantial portions of the Software. ++ ++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++SOFTWARE. +-- +2.50.1 (Apple Git-155) From 6c05ca49fa20b86b285605d7164847d899c9d67a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 7 Sep 2026 19:43:23 +0200 Subject: [PATCH 2/2] build-pylzss.yml: keep the first CIBW_TEST_COMMAND statement on the opening-quote line YAML folds the newline after "python -c "" into a single space, giving the resolved command a leading space before the first statement. cp312/ cp313 reject that with IndentationError: unexpected indent (cp314+ tolerates it, per gotcha 247), which is exactly why every cp312/cp313 job failed while cp314/cp314t passed on the first CI run. --- .github/workflows/build-pylzss.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-pylzss.yml b/.github/workflows/build-pylzss.yml index 4b75f58d9..8497f62d1 100644 --- a/.github/workflows/build-pylzss.yml +++ b/.github/workflows/build-pylzss.yml @@ -76,8 +76,7 @@ jobs: # actually run test.py, so the mismatch went unnoticed there. Test # the real API with a round trip instead. CIBW_TEST_COMMAND: >- - python -c " - import lzss, importlib.metadata, zipfile; + python -c "import lzss, importlib.metadata; assert lzss.__file__.endswith(('.so', '.pyd')), lzss.__file__; data = b'Lorem ipsum dolor sit amet, consectetur adipisicing elit.' * 50; assert lzss.decompress(lzss.compress(data)) == data;