From 8abd8cb69e327df94d2309b4b7b1c0ad7aa4a8a3 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 21 Sep 2026 13:50:22 +0000 Subject: [PATCH 1/2] lib3mf: Add version 2.5.0 lib3mf publishes one Linux wheel, py3-none-manylinux2014_x86_64. The tag has no ABI half because the package ships no extension module: the whole API is the ACT-generated ctypes wrapper Lib3MF.py driving lib3mf.so, a 5.3 MB C++ shared library. The platform half is load-bearing all the same, so riscv64 needs a real build rather than a retag. The distribution is cut from 3MFConsortium/lib3mf_python, a packaging repo the source tree never references; the workflow mirrors its python-package.yml. That repo commits lib3mf.so as an x86_64 binary, lifted by prepare_pypi_release.py out of the lib3mf_sdk_v.zip release asset, and the SDK is published for x86_64 Linux only. The library is BSD-3-Clause C++ with a plain CMake build and no architecture gates, so the job builds it from 3MFConsortium/lib3mf at the matching tag with upstream's own cmake/GenerateMake.sh, drops the result over the committed binary and then runs upstream's build_wheels.py unchanged. The patch adds riscv64 to the two hardcoded x86_64 tables that stop that working: setup.py's SUPPORTED_PLATFORMS gate, and get_platform_tag(), which returns the glibc 2.17 manylinux2014 baseline for every Linux host where riscv64's oldest profile is glibc 2.39. zlib, libzip and cpp-base64 are compiled into lib3mf.so and fast_float is inlined into it, so their licences are staged at the project root for setuptools' default glob to ship - upstream's own wheels carry only the binding's BSD licence. Nothing GPL is vendored: the library links libstdc++, libgcc_s, libm and libc and nothing else, exactly as the released x86_64 binary does. Tests are the packaging repo's own: the import and wrapper handshake its CI runs, plus its create_cube example (a full mesh build and 3MF write) and its interpreter-shutdown regression test. --- .github/workflows/build-lib3mf.yml | 158 ++++++++++++++++++ docs/packages/lib3mf.yaml | 5 + ...-building-the-wheel-on-linux-riscv64.patch | 65 +++++++ 3 files changed, 228 insertions(+) create mode 100644 .github/workflows/build-lib3mf.yml create mode 100644 docs/packages/lib3mf.yaml create mode 100644 patches/lib3mf/2.5.0/0001-Support-building-the-wheel-on-linux-riscv64.patch diff --git a/.github/workflows/build-lib3mf.yml b/.github/workflows/build-lib3mf.yml new file mode 100644 index 00000000000..cbc811506aa --- /dev/null +++ b/.github/workflows/build-lib3mf.yml @@ -0,0 +1,158 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on: https://github.com/3MFConsortium/lib3mf_python/blob/v2.5.0/.github/workflows/python-package.yml +name: Build lib3mf wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/lib3mf.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-lib3mf.yml' + - 'docs/packages/lib3mf.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-lib3mf.yml' + - 'docs/packages/lib3mf.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: lib3mf + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Build lib3mf ${{ matrix.version }} py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 360 + + env: + LIB3MF_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout lib3mf_python v${{ env.LIB3MF_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: 3MFConsortium/lib3mf_python + ref: v${{ env.LIB3MF_VERSION }} + persist-credentials: false + + # lib3mf/lib3mf.so is committed to the packaging repo as an x86_64 binary, + # lifted by prepare_pypi_release.py out of the lib3mf_sdk_v.zip + # release asset. That SDK is published for x86_64 Linux only, so build the + # library from its own sources instead (gotcha 77). + - name: Checkout lib3mf v${{ env.LIB3MF_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: 3MFConsortium/lib3mf + ref: v${{ env.LIB3MF_VERSION }} + path: lib3mf-src + submodules: true + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch lib3mf_python source + run: git apply python-wheels/patches/lib3mf/${{ env.LIB3MF_VERSION }}/*.patch + + # lib3mf.so statically links zlib, libzip and cpp-base64 and inlines + # fast_float, but the wheel ships only the binding's own BSD licence. + - name: Stage the statically linked libraries' licences + run: | + cp lib3mf-src/submodules/zlib/LICENSE LICENSE.zlib + cp lib3mf-src/submodules/libzip/LICENSE LICENSE.libzip + cp lib3mf-src/submodules/cpp-base64/LICENSE LICENSE.cpp-base64 + cp lib3mf-src/submodules/fast_float/LICENSE-MIT LICENSE.fast_float + + - name: Pull manylinux_riscv64 image + run: docker pull "${MANYLINUX_RISCV64_IMAGE}" + + - name: Build lib3mf.so and the wheel + run: | + docker run --rm -v "$(pwd):/work" -w /work "${MANYLINUX_RISCV64_IMAGE}" bash -c ' + set -euxo pipefail + cd /work/lib3mf-src + sh cmake/GenerateMake.sh + cmake --build build -j"$(nproc)" + readelf -hd build/lib3mf.so + cd /work + install -m644 -T lib3mf-src/build/lib3mf.so lib3mf/lib3mf.so + export PATH=/opt/python/cp312-cp312/bin:$PATH + pip install -q -U setuptools wheel + python build_wheels.py + pip install -q dist/*.whl + cd /tmp + python -c "from lib3mf import get_wrapper; print(get_wrapper().GetLibraryVersion())" + python /work/examples/create_cube_example_complete.py + python /work/tests/lib3mf_py313_exception.py + ' + + - name: Check the wheel carries the riscv64 lib3mf.so and every licence + run: | + python3 - dist/*.whl <<'EOF' + import sys, zipfile + + expected = {"LICENSE", "LICENSE.zlib", "LICENSE.libzip", + "LICENSE.cpp-base64", "LICENSE.fast_float"} + for whl in sys.argv[1:]: + zf = zipfile.ZipFile(whl) + names = zf.namelist() + assert "lib3mf/lib3mf.so" in names, whl + e_machine = int.from_bytes(zf.read("lib3mf/lib3mf.so")[18:20], "little") + assert e_machine == 243, f"{whl}: e_machine {e_machine} is not riscv64" + licenses = {n.split(".dist-info/licenses/", 1)[1] for n in names + if ".dist-info/licenses/" in n and not n.endswith("/")} + assert licenses == expected, f"{whl}: {sorted(licenses)}" + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: lib3mf-${{ env.LIB3MF_VERSION }}-py3-none-manylinux_riscv64 + path: dist/*.whl + if-no-files-found: error + + publish: + name: Publish lib3mf ${{ 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: lib3mf-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/lib3mf.yaml b/docs/packages/lib3mf.yaml new file mode 100644 index 00000000000..9bc78917dd8 --- /dev/null +++ b/docs/packages/lib3mf.yaml @@ -0,0 +1,5 @@ +package-name: lib3mf +source-code: https://github.com/3MFConsortium/lib3mf_python +license: BSD-3-Clause +versions: +- version: 2.5.0 diff --git a/patches/lib3mf/2.5.0/0001-Support-building-the-wheel-on-linux-riscv64.patch b/patches/lib3mf/2.5.0/0001-Support-building-the-wheel-on-linux-riscv64.patch new file mode 100644 index 00000000000..6e36576d8a5 --- /dev/null +++ b/patches/lib3mf/2.5.0/0001-Support-building-the-wheel-on-linux-riscv64.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 00:00:00 +0000 +Subject: [PATCH] Support building the wheel on linux riscv64 + +The Linux wheel is a per-architecture artifact: lib3mf/lib3mf.so is a +compiled binary and build_wheels.py forces the platform tag by hand, so +the two hardcoded x86_64 assumptions are what stop a riscv64 host from +producing its own wheel. + +setup.py's SUPPORTED_PLATFORMS gate raises before setuptools runs, and +get_platform_tag() returns manylinux2014_x86_64 for every Linux host. +manylinux2014 is a glibc 2.17 baseline that predates riscv64 entirely - +the oldest manylinux profile defined for that architecture is glibc 2.39 +- so the tag has to be selected per architecture rather than shared. + +Nothing else in the packaging is architecture-specific, and x86_64 keeps +the exact tag it had. + +Upstream-Status: To upstream [not yet submitted: the same change is only useful once 3MFConsortium/lib3mf publishes a riscv64 SDK build for prepare_pypi_release.py to pick up, so it needs a maintainer discussion rather than a drive-by PR] +--- + build_wheels.py | 12 +++++++++++- + setup.py | 2 +- + 2 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/build_wheels.py b/build_wheels.py +index eb80ae0..a4457ee 100644 +--- a/build_wheels.py ++++ b/build_wheels.py +@@ -6,11 +6,21 @@ import os + MANIFEST_FILE = "manifest.json" + TEMP_MANIFEST_IN = "MANIFEST.in" + ++# manylinux tag per Linux architecture: the manylinux2014 (glibc 2.17) baseline ++# predates riscv64, whose oldest manylinux profile is glibc 2.39. ++LINUX_PLATFORM_TAGS = { ++ "x86_64": "manylinux2014_x86_64", ++ "riscv64": "manylinux_2_39_riscv64", ++} ++ + def get_platform_tag(): + """Return the appropriate platform tag for wheel building.""" + system = platform.system() + if system == "Linux": +- return "manylinux2014_x86_64" ++ machine = platform.machine() ++ if machine not in LINUX_PLATFORM_TAGS: ++ raise RuntimeError(f"Unsupported Linux architecture: {machine}") ++ return LINUX_PLATFORM_TAGS[machine] + elif system == "Windows": + return "win_amd64" + elif system == "Darwin": +diff --git a/setup.py b/setup.py +index 059882e..97a51c4 100644 +--- a/setup.py ++++ b/setup.py +@@ -24,7 +24,7 @@ else: + + # Restrict installation to only supported platforms and architectures + SUPPORTED_PLATFORMS = { +- "Linux": ["x86_64"], ++ "Linux": ["x86_64", "riscv64"], + "Windows": ["AMD64"], + "Darwin": ["x86_64", "arm64"] # macOS universal (Intel & Apple Silicon) + } From c6ced3ad4d578606062f5e307b081b0b9d5b302a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 21 Sep 2026 14:01:16 +0000 Subject: [PATCH 2/2] lib3mf: build only the library target cmake --build's default target also builds the C++ test suite, which links the vendored libressl in Libraries/ - several thousand extra translation units that never reach the wheel. LIB3MF_TESTS=OFF is the project's own switch for that and leaves lib3mf.so byte-for-byte the library the wheel needs. Cover what the C++ suite would have covered on the reader side by reading the example's cube.3mf back and checking its geometry, instead of only writing it. --- .github/workflows/build-lib3mf.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-lib3mf.yml b/.github/workflows/build-lib3mf.yml index cbc811506aa..a472201d26e 100644 --- a/.github/workflows/build-lib3mf.yml +++ b/.github/workflows/build-lib3mf.yml @@ -95,12 +95,15 @@ jobs: - name: Pull manylinux_riscv64 image run: docker pull "${MANYLINUX_RISCV64_IMAGE}" + # LIB3MF_TESTS=OFF because the default target also builds the C++ test + # suite and the vendored libressl it links, neither of which reaches the + # wheel; the wheel's own tests cover the library below. - name: Build lib3mf.so and the wheel run: | docker run --rm -v "$(pwd):/work" -w /work "${MANYLINUX_RISCV64_IMAGE}" bash -c ' set -euxo pipefail cd /work/lib3mf-src - sh cmake/GenerateMake.sh + sh cmake/GenerateMake.sh -DLIB3MF_TESTS=OFF cmake --build build -j"$(nproc)" readelf -hd build/lib3mf.so cd /work @@ -112,6 +115,17 @@ jobs: cd /tmp python -c "from lib3mf import get_wrapper; print(get_wrapper().GetLibraryVersion())" python /work/examples/create_cube_example_complete.py + python - <