From e1ada4793257a27a0adca1ac337441aa5d38e1f1 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 25 Aug 2026 17:52:55 +0200 Subject: [PATCH 1/5] shapely: add build-shapely.yml for riscv64 wheels Mirrors shapely's release.yml Linux job, narrowed to riscv64. Upstream bakes GEOS 3.13.1 into a derived manylinux image (ci/Dockerfile); we build the same GEOS from source in CIBW_BEFORE_ALL instead, so no riscv64 base image has to be built. Test command, test requirements and skips come from upstream's [tool.cibuildwheel] table unchanged. Validated in quay.io/pypa/manylinux_2_39_aarch64: the wheel carries the three extension modules, the vendored libgeos/libgeos_c and LICENSE_GEOS, and 'pytest --pyargs shapely.tests' reports 6260 passed, 100 skipped, 3 xfailed. --- .github/workflows/build-shapely.yml | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/build-shapely.yml diff --git a/.github/workflows/build-shapely.yml b/.github/workflows/build-shapely.yml new file mode 100644 index 000000000..93b3fa1f3 --- /dev/null +++ b/.github/workflows/build-shapely.yml @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/shapely/shapely/blob/2.1.2/.github/workflows/release.yml +name: Build shapely wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'shapely version to build (git tag, e.g. 2.1.2)' + required: true + default: '2.1.2' + pull_request: + paths: + - '.github/workflows/build-shapely.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '2.1.2' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + SHAPELY_VERSION: ${{ inputs.version || '2.1.2' }} + GEOS_VERSION: '3.13.1' + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + build_wheels: + name: Build shapely ${{ inputs.version || '2.1.2' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 300 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + - name: Checkout shapely ${{ env.SHAPELY_VERSION }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: shapely/shapely + ref: ${{ env.SHAPELY_VERSION }} + fetch-depth: 0 # versioneer derives the version from the tag history + persist-credentials: false + + - name: Add GEOS LICENSE + run: cp ci/wheelbuilder/LICENSE_GEOS . + + - 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 bakes GEOS into a derived manylinux image (ci/Dockerfile); + # building it in the container instead avoids a riscv64 image build. + CIBW_BEFORE_ALL_LINUX: >- + curl -OL --retry 5 https://download.osgeo.org/geos/geos-$GEOS_VERSION.tar.bz2 && + tar xfj geos-$GEOS_VERSION.tar.bz2 && + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF + -S geos-$GEOS_VERSION -B geos-build && + cmake --build geos-build -j "$(nproc)" && + cmake --install geos-build && + ldconfig + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + CIBW_BEFORE_BUILD: pip install cython numpy setuptools wheel + CIBW_ENVIRONMENT_PASS_LINUX: GEOS_VERSION + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + + - name: Check the wheel ships the GEOS licence and the compiled extensions + run: | + whl=(wheelhouse/*.whl) + unzip -l "${whl[0]}" | grep -E 'LICENSE_GEOS|\.so$' + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: shapely-${{ env.SHAPELY_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish shapely ${{ inputs.version || '2.1.2' }} 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: shapely-${{ env.SHAPELY_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 0053e2c6320f0ef66194e7bb5fa91dca67ad92b2 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 25 Aug 2026 17:54:38 +0200 Subject: [PATCH 2/5] shapely: verify the wheel contents with python3 instead of unzip unzip is not guaranteed on the self-hosted riscv64 runner. --- .github/workflows/build-shapely.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-shapely.yml b/.github/workflows/build-shapely.yml index 93b3fa1f3..cc390150f 100644 --- a/.github/workflows/build-shapely.yml +++ b/.github/workflows/build-shapely.yml @@ -73,8 +73,13 @@ jobs: - name: Check the wheel ships the GEOS licence and the compiled extensions run: | - whl=(wheelhouse/*.whl) - unzip -l "${whl[0]}" | grep -E 'LICENSE_GEOS|\.so$' + python3 - <<'EOF' + import glob, zipfile + + names = zipfile.ZipFile(glob.glob("wheelhouse/*.whl")[0]).namelist() + assert any(n.endswith("LICENSE_GEOS") for n in names), names + assert any(n.endswith(".so") for n in names), names + EOF - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: From 222f5450c159390754b8bf7ca4290405a0cee7e9 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 25 Aug 2026 17:55:11 +0200 Subject: [PATCH 3/5] shapely: build GEOS in /tmp so its source tree stays out of the project dir --- .github/workflows/build-shapely.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-shapely.yml b/.github/workflows/build-shapely.yml index cc390150f..90ccfa7e2 100644 --- a/.github/workflows/build-shapely.yml +++ b/.github/workflows/build-shapely.yml @@ -59,6 +59,7 @@ jobs: # Upstream bakes GEOS into a derived manylinux image (ci/Dockerfile); # building it in the container instead avoids a riscv64 image build. CIBW_BEFORE_ALL_LINUX: >- + cd /tmp && curl -OL --retry 5 https://download.osgeo.org/geos/geos-$GEOS_VERSION.tar.bz2 && tar xfj geos-$GEOS_VERSION.tar.bz2 && cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF From 48ca8cb6da61435ab1130d9aa1a6459b399dde81 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 25 Aug 2026 18:02:37 +0200 Subject: [PATCH 4/5] shapely: pin cibuildwheel to 3.4.1 cibuildwheel 4 removed the cpython-freethreading enable group that shapely 2.1.2's pyproject.toml declares, and CIBW_ENABLE appends to the pyproject list instead of replacing it, so the run fails before any build starts. --- .github/workflows/build-shapely.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-shapely.yml b/.github/workflows/build-shapely.yml index 90ccfa7e2..06a781362 100644 --- a/.github/workflows/build-shapely.yml +++ b/.github/workflows/build-shapely.yml @@ -49,8 +49,11 @@ jobs: - name: Add GEOS LICENSE run: cp ci/wheelbuilder/LICENSE_GEOS . + # cibuildwheel 4 dropped the `cpython-freethreading` enable group shapely's + # pyproject.toml still asks for, and CIBW_ENABLE appends to that list rather + # than replacing it, so stay on 3.x as upstream does. - name: Build wheels - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1 with: output-dir: wheelhouse/ only: ${{ matrix.python }}-manylinux_riscv64 From b2b71c16e2203d10053b5c9edde38d65d089c766 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 25 Aug 2026 21:12:32 +0200 Subject: [PATCH 5/5] shapely: build the GEOS manylinux image like upstream does Replaces the CIBW_BEFORE_ALL_LINUX GEOS build with upstream's own recipe: docker/setup-buildx-action + docker/build-push-action over the checkout's ci/Dockerfile, with CIBW_MANYLINUX_RISCV64_IMAGE pointing at the result. The base image is quay.io/pypa/manylinux_2_39_riscv64 pinned to a dated tag, the riscv64 analogue of upstream's `basetag`; buildx, buildkit and the manylinux image all publish riscv64 builds, so the image builds natively on the runner. No ldconfig is needed after `cmake --install`: auditwheel resolves /usr/local/lib from /etc/ld.so.conf.d rather than the ld cache (load_ld_paths(Libc.GLIBC) reports it), which is why upstream's Dockerfile does without one. --- .github/workflows/build-shapely.yml | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-shapely.yml b/.github/workflows/build-shapely.yml index 06a781362..02fdf6061 100644 --- a/.github/workflows/build-shapely.yml +++ b/.github/workflows/build-shapely.yml @@ -25,7 +25,8 @@ permissions: env: SHAPELY_VERSION: ${{ inputs.version || '2.1.2' }} GEOS_VERSION: '3.13.1' - MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + BASEIMAGE: manylinux_2_39_riscv64 + BASETAG: '2026.08.24-1' jobs: build_wheels: @@ -46,6 +47,31 @@ jobs: fetch-depth: 0 # versioneer derives the version from the tag history persist-credentials: false + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + with: + install: true + buildkitd-flags: --debug + + - name: Build Docker image with GEOS + # using build-push-action (without push) to make use of cache arguments + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: ci/Dockerfile + platforms: linux/riscv64 + tags: ${{ env.BASEIMAGE }}_geos:${{ env.GEOS_VERSION }} + build-args: | + BASEIMAGE=quay.io/pypa/${{ env.BASEIMAGE }}:${{ env.BASETAG }} + GEOS_VERSION=${{ env.GEOS_VERSION }} + push: false + load: true + cache-from: type=gha,scope=${{ env.BASEIMAGE }} + cache-to: type=gha,mode=max,scope=${{ env.BASEIMAGE }} + env: + BUILDKIT_PROGRESS: plain + - name: Add GEOS LICENSE run: cp ci/wheelbuilder/LICENSE_GEOS . @@ -58,21 +84,9 @@ jobs: output-dir: wheelhouse/ only: ${{ matrix.python }}-manylinux_riscv64 env: - CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} - # Upstream bakes GEOS into a derived manylinux image (ci/Dockerfile); - # building it in the container instead avoids a riscv64 image build. - CIBW_BEFORE_ALL_LINUX: >- - cd /tmp && - curl -OL --retry 5 https://download.osgeo.org/geos/geos-$GEOS_VERSION.tar.bz2 && - tar xfj geos-$GEOS_VERSION.tar.bz2 && - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF - -S geos-$GEOS_VERSION -B geos-build && - cmake --build geos-build -j "$(nproc)" && - cmake --install geos-build && - ldconfig + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.BASEIMAGE }}_geos:${{ env.GEOS_VERSION }} CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" CIBW_BEFORE_BUILD: pip install cython numpy setuptools wheel - CIBW_ENVIRONMENT_PASS_LINUX: GEOS_VERSION CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ - name: Check the wheel ships the GEOS licence and the compiled extensions