From dbf7ad7925ec1e023aa6f8c6e6bd22f4724117b1 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Wed, 14 Oct 2026 18:15:30 +0000 Subject: [PATCH 01/10] resdata: Add version 6.3.3 Build resdata's riscv64 manylinux wheels from the upstream tag with cibuildwheel, mirroring upstream's testing.yml. Conan builds the statically linked dependencies from source; their licence texts are staged into the wheel and the elfutils source and recipe are archived for the release. --- .github/workflows/build-resdata.yml | 173 ++++++++++++++++++++++++++++ docs/packages/resdata.yaml | 5 + 2 files changed, 178 insertions(+) create mode 100644 .github/workflows/build-resdata.yml create mode 100644 docs/packages/resdata.yaml diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml new file mode 100644 index 000000000000..cd7a3aa04313 --- /dev/null +++ b/.github/workflows/build-resdata.yml @@ -0,0 +1,173 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/equinor/resdata/blob/6.3.3/.github/workflows/testing.yml +name: Build resdata wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/resdata.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-resdata.yml' + - 'docs/packages/resdata.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-resdata.yml' + - 'docs/packages/resdata.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: resdata + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build resdata ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 300 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Upstream ships no free-threaded wheel. + python: ["cp312", "cp313", "cp314"] + + env: + RESDATA_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout resdata ${{ env.RESDATA_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: equinor/resdata + ref: ${{ env.RESDATA_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 build[uv] needs a host uv the runner has none of (gotcha 13) + CIBW_BUILD_FRONTEND: build + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # Conan links these into libresdata statically and upstream ships no licence + # text for them, so stage each package's own licences at the project root. + CIBW_BEFORE_BUILD: | + set -eu + rm -rf {package}/_skbuild + pip install conan "cmake<4" + conan profile detect --force + conan install {package} --output-folder=/tmp/conan --build=missing --deployer=full_deploy --deployer-folder=/tmp/conan + for dep in /tmp/conan/full_deploy/host/*; do + find "$dep" -path '*/licenses/*' -type f | sort | xargs -r cat > "{package}/LICENSE.${dep##*/}" + test -s "{package}/LICENSE.${dep##*/}" + done + # The registry trails PyPI for these, and pydantic pins pydantic-core exactly. + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,scipy,pandas,pydantic-core + CIBW_TEST_COMMAND: >- + python -c "import importlib.metadata as m; + got = sorted(p.name for p in m.files('resdata') if '.dist-info/licenses/' in str(p)); + assert got == ['COPYING', 'LICENSE', 'LICENSE.backward-cpp', 'LICENSE.bzip2', 'LICENSE.elfutils', 'LICENSE.fmt', 'LICENSE.pybind11', 'LICENSE.xz_utils', 'LICENSE.zlib', 'LICENSE.zstd'], got" && + pytest {package}/tests + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: resdata-${{ env.RESDATA_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources (elfutils) for resdata ${{ matrix.version }} + runs-on: ubuntu-latest + env: + RESDATA_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout resdata ${{ env.RESDATA_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: equinor/resdata + ref: ${{ env.RESDATA_VERSION }} + persist-credentials: false + + - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: '3.12' + activate-environment: true + enable-cache: false + + # libresdata statically links libdw/libelf (GPL-2.0+ or LGPL-3.0+) from the + # elfutils that Conan builds from conan.lock's recipe revision and its patches. + - name: Archive the elfutils source and Conan recipe + run: | + set -euo pipefail + uv pip install conan + conan profile detect + ref=$(python -c 'import json; print(next(r.split("%")[0] for r in json.load(open("conan.lock"))["requires"] if r.startswith("elfutils/")))') + conan download "$ref" -r conancenter --only-recipe + recipe=$(conan cache path "$ref") + read -r url sha256 < <(python - "$recipe/conandata.yml" "${ref#*/}" <<'PY' + import sys, yaml + source = yaml.safe_load(open(sys.argv[1]))["sources"][sys.argv[2].split("#")[0]] + print(source["url"], source["sha256"]) + PY + ) + mkdir -p gpl-sources/recipe + curl -fsSL --retry 5 -o "gpl-sources/${url##*/}" "$url" + echo "$sha256 gpl-sources/${url##*/}" | sha256sum -c - + cp -r "$recipe"/. "$(conan cache path --folder export_source "$ref")"/. gpl-sources/recipe/ + tar -cf gpl-sources.tar gpl-sources + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: resdata-${{ env.RESDATA_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish resdata ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + 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: resdata-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: resdata-${{ matrix.version }}-gpl-sources + gpl-sources-description: elfutils diff --git a/docs/packages/resdata.yaml b/docs/packages/resdata.yaml new file mode 100644 index 000000000000..ca89e845b347 --- /dev/null +++ b/docs/packages/resdata.yaml @@ -0,0 +1,5 @@ +package-name: resdata +source-code: https://github.com/equinor/resdata +license: GPL-3.0-only +versions: +- version: 6.3.3 From 192bfe100634cc5ed90b1d5202171af1434745b7 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 15 Oct 2026 09:14:14 +0000 Subject: [PATCH 02/10] resdata: resolve cmake's riscv64 wheel instead of building it from source pip install "cmake<4" can never resolve a riscv64 wheel -- the cmake PyPI project has published one only from 4.0 on -- so it fell back to building CMake's own C++ source, which failed on a missing OpenSSL dev package this image doesn't carry for an unrelated build dependency. Patches pyproject.toml's build-system requirement to drop the <4 upper bound (CMakeLists.txt's own cmake_minimum_required is 3.16, well under 4.0's removed-compatibility floor) and applies it the same way every other patched port in this repo does (checkout python-wheels alongside the external repo, git apply before the build step); updates CIBW_BEFORE_BUILD's matching manual pip install to match. --- .github/workflows/build-resdata.yml | 11 ++++- ...ct-allow-cmake-4-for-a-riscv64-wheel.patch | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index cd7a3aa04313..80cdcd4c49a3 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -63,6 +63,15 @@ jobs: ref: ${{ env.RESDATA_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply patches + run: git apply python-wheels/patches/resdata/${{ env.RESDATA_VERSION }}/*.patch + - name: Build wheels uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: @@ -78,7 +87,7 @@ jobs: CIBW_BEFORE_BUILD: | set -eu rm -rf {package}/_skbuild - pip install conan "cmake<4" + pip install conan cmake conan profile detect --force conan install {package} --output-folder=/tmp/conan --build=missing --deployer=full_deploy --deployer-folder=/tmp/conan for dep in /tmp/conan/full_deploy/host/*; do diff --git a/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch b/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch new file mode 100644 index 000000000000..1b4a139836a2 --- /dev/null +++ b/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Wed, 15 Oct 2026 00:00:00 +0000 +Subject: [PATCH] pyproject: drop the cmake<4 pin so a riscv64 wheel resolves + +Upstream-Status: Inappropriate [cmake's own PyPI riscv64 wheel only exists from 4.0 on] + +The `cmake` PyPI project has never published a riscv64 wheel for any +3.x release -- checked its full release history via PyPI JSON, zero +riscv64 files anywhere below 4.0 -- so `pip install "cmake<4"` (both +this workflow's own CIBW_BEFORE_BUILD step and the identical pin in +[build-system].requires, which build isolation installs the same way) +can never resolve a prebuilt wheel on riscv64 and instead builds +CMake's own C++ source tree from scratch, which fails here with +"Could NOT find OpenSSL" out of CMake's bundled curl +(Utilities/cmcurl/CMakeLists.txt) -- a build dependency this image +carries nowhere near the rest of this package's own dependency list. + +CMakeLists.txt's own cmake_minimum_required(VERSION 3.16) is well +clear of CMake 4.0's removed-compatibility floor (3.5), and nothing +else in this tree pins a sub-4.0 CMake feature, so dropping the upper +bound lets pip resolve cmake's real riscv64 wheel +(cmake-4.4.4-py3-none-manylinux_2_31_riscv64.whl) instead of building +from source at all. + +Signed-off-by: Ludovic Henry +--- + pyproject.toml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pyproject.toml b/pyproject.toml +index 89aee80..3cc7d79 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -1,6 +1,6 @@ + [build-system] + requires = [ +- "cmake<4", ++ "cmake", + "conan>=2", + "ninja", + "scikit-build", From e5aecc560bd29b3e65a57224b6edb2c9a122db5d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 16 Oct 2026 00:51:18 +0000 Subject: [PATCH 03/10] resdata: install libstdc++-static for Conan's ninja build (gotcha 77) The cp314-manylinux_riscv64 leg died during CIBW_BEFORE_BUILD while Conan built ninja from source: cmake's compiler check link-tests with -static-libstdc++ -static-libgcc, and the manylinux_2_39_riscv64 (Rocky 10) image ships only the shared libstdc++.so, not libstdc++.a, so the link fails with "/usr/bin/ld: cannot find -lstdc++". This happens before any Python-version-specific code runs, so it is not cp314-specific - it would equally hit cp312/cp313 once their legs reach the same step. Same fix already proven in this repo for the identical image/error in build-ddtrace.yml, build-libclang.yml and build-coacd.yml: install libstdc++-static via CIBW_BEFORE_ALL_LINUX (CRB is already enabled on this image, so no --enablerepo flag is needed). --- .github/workflows/build-resdata.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index 80cdcd4c49a3..218fc2d103f2 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -79,6 +79,9 @@ jobs: only: ${{ matrix.python }}-manylinux_riscv64 env: CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # Conan's ninja recipe links its binary -static-libstdc++; the riscv64 image + # ships only the shared libstdc++.so, not the .a (gotcha 77). + CIBW_BEFORE_ALL_LINUX: dnf -y install libstdc++-static # upstream's build[uv] needs a host uv the runner has none of (gotcha 13) CIBW_BUILD_FRONTEND: build CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ From 13959611e82b68a3b530e4f9919a0d657cb8ae12 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 02:18:46 +0000 Subject: [PATCH 04/10] resdata: patch setup.py to invalidate its stale _skbuild cache Fixes PR #3237's riscv64 build failure: scikit-build's check_for_bad_installs() trips on the stale CMake cache left behind by python -m build's own metadata-then-wheel double invocation of setup.py (scikit-build/scikit-build#521, closed not-planned). --- ...he-stale-skbuild-cache-before-each-i.patch | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch diff --git a/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch b/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch new file mode 100644 index 000000000000..85fcc39e06d3 --- /dev/null +++ b/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 17 Oct 2026 00:00:00 +0000 +Subject: [PATCH] setup.py: wipe the stale _skbuild cache before each + invocation + +Upstream-Status: Issue [https://github.com/scikit-build/scikit-build/issues/521] + +`python -m build` runs this file twice per wheel: once for +`prepare_metadata_for_build_wheel`, once for `build_wheel`, each inside +its own isolated build venv with a different path. Both invocations +share the same on-disk `_skbuild/--/cmake-build` +directory (computed deterministically by `get_skbuild_dir()`), and +`run_conan_install()` only `os.makedirs(..., exist_ok=True)`s it rather +than clearing it, so the second invocation's CMake configure reuses the +first invocation's leftover `CMakeCache.txt` and generated +`*.cmake` files, which still point at the first invocation's +(now-deleted) venv. + +On riscv64 this makes the second configure fail with: + + CMake-installed files must be within the project root. + Project Root: + .../_skbuild/linux-riscv64-3.12/cmake-install + Violating Files: + .../_skbuild/linux-riscv64-3.12/cmake-build/python/resdata/.bin/rd_pack.x + ... + +scikit-build's own `check_for_bad_installs()` parses the stale +`*.cmake` files looking for a `${CMAKE_INSTALL_PREFIX}` placeholder to +substitute; the first invocation's now-inconsistent cache entries no +longer carry one, so the raw path it finds is the build directory +itself, which trivially fails the "is this inside the install +directory" check. This is exactly scikit-build/scikit-build#521 +("pip install . should invalidate CMake cache in temporary +directory"), filed in 2020 and closed `not planned` in July 2026 with +no fix landing in classic scikit-build (the maintainer pointed +reporters at scikit-build-core instead, which this project does not +use). Other scikit-build projects hit by the same bug (NIMPA, NIPET, +arcae, tiktaalik) all carry the same workaround upstream never did: +delete the stale cache themselves before configuring. + +Wipe `_skbuild/--/cmake-build` at the top of this +file, before `run_conan_install()` ever populates it, so every +invocation of `setup.py` -- metadata or wheel, first or second -- +always starts from a clean configure instead of a stale one. + +Signed-off-by: Ludovic Henry +--- + setup.py | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/setup.py b/setup.py +index f149797..5b45931 100644 +--- a/setup.py ++++ b/setup.py +@@ -99,6 +99,18 @@ + return cmake_args + + ++# scikit-build (classic) reuses this same _skbuild//cmake-build directory ++# across the separate metadata-prep and wheel-build invocations that `python -m ++# build` makes of this file, each in its own isolated venv with a different ++# path. The CMake cache left behind by the earlier invocation doesn't match ++# the new one's paths, which trips skbuild's own "CMake-installed files must ++# be within the project root" check (scikit-build/scikit-build#521, closed ++# not-planned). Wipe it before each invocation so every build starts from a ++# clean configure. ++import shutil ++ ++shutil.rmtree(get_skbuild_dir(), ignore_errors=True) ++ + skbuild_dir = run_conan_install() + CMAKE_ARGS_FROM_PRESET = get_cmake_args_from_preset(skbuild_dir) + From 7618f8e87a2ddeb2d4c6250dc3ba58a34e59ee4d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 09:49:10 +0000 Subject: [PATCH 05/10] resdata: build with our registry's cmake 3.31.6 wheel instead of CMake 4 The check_for_bad_installs() failure ("CMake-installed files must be within the project root", with .bin/.libs/.include under cmake-build/) is caused by CMake 4, not by a stale _skbuild cache: it reproduces on a fresh x86_64 checkout with cmake 4.4.4 and goes away with cmake 3.31. setup.py passes -DCMAKE_INSTALL_{BIN,LIB,INCLUDE}DIR=python/resdata/... untyped, so they enter the cache as UNINITIALIZED. CMake 4's GNUInstallDirs no longer converts them to PATH, and CMakeLists.txt's if(SKBUILD) block then does set(... CACHE PATH ... FORCE) on them, which turns the relative value into an absolute path under the build directory. The generated cmake_install.cmake then installs to cmake-build/python/resdata/... instead of ${CMAKE_INSTALL_PREFIX}/..., which skbuild rejects. CMake 3.x types the entries in GNUInstallDirs first, so the forced set keeps them relative. Upstream pins cmake<4 for this. The earlier premise that no riscv64 cmake<4 wheel exists was wrong: pypi.riseproject.dev carries cmake-3.31.6-py3-none-manylinux_2_39_riscv64, but pip preferred PyPI's newer, sdist-only 3.31.10 and tried to compile it (gotcha 30). PIP_ONLY_BINARY=cmake makes both the build-isolation install and CIBW_BEFORE_BUILD resolve to our wheel, so restore upstream's cmake<4 and drop both patches along with the steps that applied them. --- .github/workflows/build-resdata.yml | 17 ++--- ...ct-allow-cmake-4-for-a-riscv64-wheel.patch | 42 ----------- ...he-stale-skbuild-cache-before-each-i.patch | 75 ------------------- 3 files changed, 6 insertions(+), 128 deletions(-) delete mode 100644 patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch delete mode 100644 patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index 218fc2d103f2..af7eee8799b7 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -63,15 +63,6 @@ jobs: ref: ${{ env.RESDATA_VERSION }} persist-credentials: false - - name: Checkout python-wheels - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - path: python-wheels - persist-credentials: false - - - name: Apply patches - run: git apply python-wheels/patches/resdata/${{ env.RESDATA_VERSION }}/*.patch - - name: Build wheels uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: @@ -84,13 +75,17 @@ jobs: CIBW_BEFORE_ALL_LINUX: dnf -y install libstdc++-static # upstream's build[uv] needs a host uv the runner has none of (gotcha 13) CIBW_BUILD_FRONTEND: build - CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # PyPI's newest cmake<4 is sdist-only on riscv64, ours is a wheel (gotcha 30); CMake 4 + # turns setup.py's relative CMAKE_INSTALL_*DIR into build-tree paths skbuild rejects. + CIBW_ENVIRONMENT: >- + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=cmake # Conan links these into libresdata statically and upstream ships no licence # text for them, so stage each package's own licences at the project root. CIBW_BEFORE_BUILD: | set -eu rm -rf {package}/_skbuild - pip install conan cmake + pip install conan "cmake<4" conan profile detect --force conan install {package} --output-folder=/tmp/conan --build=missing --deployer=full_deploy --deployer-folder=/tmp/conan for dep in /tmp/conan/full_deploy/host/*; do diff --git a/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch b/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch deleted file mode 100644 index 1b4a139836a2..000000000000 --- a/patches/resdata/6.3.3/0001-pyproject-allow-cmake-4-for-a-riscv64-wheel.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Ludovic Henry -Date: Wed, 15 Oct 2026 00:00:00 +0000 -Subject: [PATCH] pyproject: drop the cmake<4 pin so a riscv64 wheel resolves - -Upstream-Status: Inappropriate [cmake's own PyPI riscv64 wheel only exists from 4.0 on] - -The `cmake` PyPI project has never published a riscv64 wheel for any -3.x release -- checked its full release history via PyPI JSON, zero -riscv64 files anywhere below 4.0 -- so `pip install "cmake<4"` (both -this workflow's own CIBW_BEFORE_BUILD step and the identical pin in -[build-system].requires, which build isolation installs the same way) -can never resolve a prebuilt wheel on riscv64 and instead builds -CMake's own C++ source tree from scratch, which fails here with -"Could NOT find OpenSSL" out of CMake's bundled curl -(Utilities/cmcurl/CMakeLists.txt) -- a build dependency this image -carries nowhere near the rest of this package's own dependency list. - -CMakeLists.txt's own cmake_minimum_required(VERSION 3.16) is well -clear of CMake 4.0's removed-compatibility floor (3.5), and nothing -else in this tree pins a sub-4.0 CMake feature, so dropping the upper -bound lets pip resolve cmake's real riscv64 wheel -(cmake-4.4.4-py3-none-manylinux_2_31_riscv64.whl) instead of building -from source at all. - -Signed-off-by: Ludovic Henry ---- - pyproject.toml | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/pyproject.toml b/pyproject.toml -index 89aee80..3cc7d79 100644 ---- a/pyproject.toml -+++ b/pyproject.toml -@@ -1,6 +1,6 @@ - [build-system] - requires = [ -- "cmake<4", -+ "cmake", - "conan>=2", - "ninja", - "scikit-build", diff --git a/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch b/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch deleted file mode 100644 index 85fcc39e06d3..000000000000 --- a/patches/resdata/6.3.3/0002-setup.py-wipe-the-stale-skbuild-cache-before-each-i.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Ludovic Henry -Date: Sat, 17 Oct 2026 00:00:00 +0000 -Subject: [PATCH] setup.py: wipe the stale _skbuild cache before each - invocation - -Upstream-Status: Issue [https://github.com/scikit-build/scikit-build/issues/521] - -`python -m build` runs this file twice per wheel: once for -`prepare_metadata_for_build_wheel`, once for `build_wheel`, each inside -its own isolated build venv with a different path. Both invocations -share the same on-disk `_skbuild/--/cmake-build` -directory (computed deterministically by `get_skbuild_dir()`), and -`run_conan_install()` only `os.makedirs(..., exist_ok=True)`s it rather -than clearing it, so the second invocation's CMake configure reuses the -first invocation's leftover `CMakeCache.txt` and generated -`*.cmake` files, which still point at the first invocation's -(now-deleted) venv. - -On riscv64 this makes the second configure fail with: - - CMake-installed files must be within the project root. - Project Root: - .../_skbuild/linux-riscv64-3.12/cmake-install - Violating Files: - .../_skbuild/linux-riscv64-3.12/cmake-build/python/resdata/.bin/rd_pack.x - ... - -scikit-build's own `check_for_bad_installs()` parses the stale -`*.cmake` files looking for a `${CMAKE_INSTALL_PREFIX}` placeholder to -substitute; the first invocation's now-inconsistent cache entries no -longer carry one, so the raw path it finds is the build directory -itself, which trivially fails the "is this inside the install -directory" check. This is exactly scikit-build/scikit-build#521 -("pip install . should invalidate CMake cache in temporary -directory"), filed in 2020 and closed `not planned` in July 2026 with -no fix landing in classic scikit-build (the maintainer pointed -reporters at scikit-build-core instead, which this project does not -use). Other scikit-build projects hit by the same bug (NIMPA, NIPET, -arcae, tiktaalik) all carry the same workaround upstream never did: -delete the stale cache themselves before configuring. - -Wipe `_skbuild/--/cmake-build` at the top of this -file, before `run_conan_install()` ever populates it, so every -invocation of `setup.py` -- metadata or wheel, first or second -- -always starts from a clean configure instead of a stale one. - -Signed-off-by: Ludovic Henry ---- - setup.py | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/setup.py b/setup.py -index f149797..5b45931 100644 ---- a/setup.py -+++ b/setup.py -@@ -99,6 +99,18 @@ - return cmake_args - - -+# scikit-build (classic) reuses this same _skbuild//cmake-build directory -+# across the separate metadata-prep and wheel-build invocations that `python -m -+# build` makes of this file, each in its own isolated venv with a different -+# path. The CMake cache left behind by the earlier invocation doesn't match -+# the new one's paths, which trips skbuild's own "CMake-installed files must -+# be within the project root" check (scikit-build/scikit-build#521, closed -+# not-planned). Wipe it before each invocation so every build starts from a -+# clean configure. -+import shutil -+ -+shutil.rmtree(get_skbuild_dir(), ignore_errors=True) -+ - skbuild_dir = run_conan_install() - CMAKE_ARGS_FROM_PRESET = get_cmake_args_from_preset(skbuild_dir) - From 0ffb48081cb25a95f30261ec81c823f7f1576362 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 19:32:14 +0000 Subject: [PATCH 06/10] resdata: fix the test suite hanging on riscv64's unsigned char All three legs of run 39022584611 stopped printing right after tests/rd_tests/test_rd_cmp.py and sat silent until the job's timeout-minutes: 300 cancelled them. The next test is test_rd_file.py::ResdataFileTest::test_broken_file, which opens a text file named CASE.FINIT. The F prefix makes it a formatted file, so rd_kw_fread_header() calls rd_kw_qskip() to find the opening quote. That loop stores fgetc() in a plain char and stops on c == EOF. char is unsigned on riscv64, so EOF reads back as 255, the test is always false, and the loop spins forever at the end of a file with no quote in it. Reproduced on x86_64 by building resdata 6.3.3 with -funsigned-char: test_broken_file hangs in ResdataFile("CASE.FINIT") (pytest-timeout's thread method shows the stack; the default signal method never fires, since control never returns to Python). With the patch, the whole suite passes on that build. util_fskip_lines() and fscanf_grdecl_data() have the same bug, so the patch changes all three variables to int. Upstream fixed them after the 6.3.x branch (ce341a3, d9eacc1, c052640); no 6.3.x release carries the fixes yet. --- .github/workflows/build-resdata.yml | 9 +++ ...-loops-at-EOF-where-char-is-unsigned.patch | 72 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 patches/resdata/6.3.3/0001-Fix-infinite-loops-at-EOF-where-char-is-unsigned.patch diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index af7eee8799b7..b2b37c9776cc 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -63,6 +63,15 @@ jobs: ref: ${{ env.RESDATA_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply patches + run: git apply python-wheels/patches/resdata/${{ env.RESDATA_VERSION }}/*.patch + - name: Build wheels uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: diff --git a/patches/resdata/6.3.3/0001-Fix-infinite-loops-at-EOF-where-char-is-unsigned.patch b/patches/resdata/6.3.3/0001-Fix-infinite-loops-at-EOF-where-char-is-unsigned.patch new file mode 100644 index 000000000000..d460f9f19b53 --- /dev/null +++ b/patches/resdata/6.3.3/0001-Fix-infinite-loops-at-EOF-where-char-is-unsigned.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 17 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Fix infinite loops at EOF where char is unsigned + +Upstream-Status: Backport [https://github.com/equinor/resdata/commit/ce341a3666855c57839466d9daa876252b54c8c0] + +fgetc() returns EOF (-1) as an int. Three loops store it in a plain +char and then test c == EOF; where char is unsigned (riscv64, aarch64) +c becomes 255, the test is always false (GCC: "comparison is always +false due to limited range of data type"), and the loop spins forever +once fgetc() keeps returning EOF: + +- rd_kw_qskip(): any formatted file without a quote character, e.g. + tests/rd_tests/test_rd_file.py::test_broken_file, which hangs the + test suite at that test on riscv64; +- util_fskip_lines(): fixed upstream by the commit above; +- fscanf_grdecl_data(): a trailing "--" comment without a newline. + +Upstream fixed the first as part of the rd::KW rewrite (d9eacc14e, +skip_space_until_quote()) and removed the third file in c05264026; +all three fixes are in 7.0.0b3 and in none of the 6.3.x releases. + +Signed-off-by: Ludovic Henry +--- + lib/resdata/rd_kw.cpp | 2 +- + lib/resdata/rd_kw_grdecl.cpp | 2 +- + lib/util/util.cpp | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/lib/resdata/rd_kw.cpp b/lib/resdata/rd_kw.cpp +index 17e08f4e7..f3c2475d1 100644 +--- a/lib/resdata/rd_kw.cpp ++++ b/lib/resdata/rd_kw.cpp +@@ -1043,7 +1043,7 @@ static bool rd_kw_qskip(FILE *stream) { + const char newline = '\n'; + const char tab = '\t'; + bool OK = true; +- char c; ++ int c; + bool cont = true; + while (cont) { + c = fgetc(stream); +diff --git a/lib/resdata/rd_kw_grdecl.cpp b/lib/resdata/rd_kw_grdecl.cpp +index 55785de1f..34262a88b 100644 +--- a/lib/resdata/rd_kw_grdecl.cpp ++++ b/lib/resdata/rd_kw_grdecl.cpp +@@ -232,7 +232,7 @@ fscanf_grdecl_data(const char *header, bool strict, int &kw_size, + if (fscanf(stream, "%32s", buffer.get()) == 1) { + if (strcmp(buffer.get(), RD_COMMENT_STRING) == 0) { + // We have read a comment marker - just read up to the end of line. +- char c; ++ int c; + while (true) { + c = fgetc(stream); + if (c == newline) +diff --git a/lib/util/util.cpp b/lib/util/util.cpp +index 4014b594b..ad2485779 100644 +--- a/lib/util/util.cpp ++++ b/lib/util/util.cpp +@@ -807,7 +807,7 @@ void util_fskip_lines(FILE *stream, int lines) { + int line_nr = 0; + do { + bool at_eof = false; +- char c; ++ int c; + do { + c = fgetc(stream); + if (c == EOF) +-- +2.47.3 + From dda574a1191e5c5707675fa136f069672669ca8b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 19:32:32 +0000 Subject: [PATCH 07/10] resdata: bound each test at 15 minutes with pytest-timeout's thread method Only the first 27% of the suite has run on riscv64 so far, and a hang there holds a runner for the rest of the job's 300 minutes before it shows up as a bare "cancelled". pytest-timeout is already in upstream's test group, so this adds no dependency. The thread method is the one that works here: test_broken_file's hang is a busy loop in libresdata, so control never returns to Python and the default SIGALRM method never fires (checked on an x86_64 -funsigned-char build). The thread method dumps every thread's stack and exits, which fails the job and names the test. The slowest test seen on the riscv64 runner, test_grid_large_case, takes about 108 s (18.8 s on x86_64), so 900 s leaves wide margin. --- .github/workflows/build-resdata.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index b2b37c9776cc..7bf026210941 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -103,11 +103,12 @@ jobs: done # The registry trails PyPI for these, and pydantic pins pydantic-core exactly. CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,scipy,pandas,pydantic-core + # A hang in native code never returns to Python, so only the thread method can end it. CIBW_TEST_COMMAND: >- python -c "import importlib.metadata as m; got = sorted(p.name for p in m.files('resdata') if '.dist-info/licenses/' in str(p)); assert got == ['COPYING', 'LICENSE', 'LICENSE.backward-cpp', 'LICENSE.bzip2', 'LICENSE.elfutils', 'LICENSE.fmt', 'LICENSE.pybind11', 'LICENSE.xz_utils', 'LICENSE.zlib', 'LICENSE.zstd'], got" && - pytest {package}/tests + pytest --timeout=900 --timeout-method=thread {package}/tests - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: From a01d1aa4752bed53ac1c350842da8fbdf0e2908d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 21:27:06 +0000 Subject: [PATCH 08/10] resdata: raise test_performance_with_many_keys's 10s timeout to 60s The cp313 job did not hang a second time: it stopped at the last test of test_view_summary.py, test_performance_with_many_keys, whose own @pytest.mark.timeout(10) takes precedence over --timeout=900. With the thread method that marker inherits, the timeout ends the whole pytest session instead of failing one test, which made it read as a hang. The work is bounded: a call slower than max_time (1 s) makes pytest-benchmark run exactly 7 calls (2 calibration + 5 rounds). The captured stdout holds 6 complete summary.x tables, so the 10 s ran out during the seventh and last call, at about 1.4 s a call against 0.1 s on x86_64 (1.2 s for the whole test). A cProfile of one call is linear in the 5000 keys, with no quadratic lookup. 60 s keeps the test a regression guard while leaving the riscv64 runners about 5x headroom (gotcha 38). --- ...rmance_with_many_keys-timeout-for-sl.patch | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 patches/resdata/6.3.3/0002-Raise-test_performance_with_many_keys-timeout-for-sl.patch diff --git a/patches/resdata/6.3.3/0002-Raise-test_performance_with_many_keys-timeout-for-sl.patch b/patches/resdata/6.3.3/0002-Raise-test_performance_with_many_keys-timeout-for-sl.patch new file mode 100644 index 000000000000..4a2ab693903f --- /dev/null +++ b/patches/resdata/6.3.3/0002-Raise-test_performance_with_many_keys-timeout-for-sl.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 17 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Raise test_performance_with_many_keys timeout for slower + hosts + +Upstream-Status: Inappropriate [the 10s ceiling is sized for upstream's x86 CI; the riscv64 runners finish the same bounded work in about 12s] + +The test builds a 5000-key summary case and lets pytest-benchmark run +summary.x over it. A call that takes longer than max_time (1s) makes +pytest-benchmark do exactly 2 calibration calls plus min_rounds (5), +so the work is a fixed 7 calls. On x86_64 one call takes about 0.1s +and the whole test about 1.2s, well inside its 10s marker. On the +riscv64 runners one call takes about 1.4s: the timeout fired after +the sixth call had printed its table, during the seventh and last. + +The profile is linear in the number of keys (one SummaryKeyType +lookup per key); nothing loops. Raise the ceiling to 60s so it still +catches a real regression (a quadratic key lookup blows far past it) +without failing on a slower host. + +Signed-off-by: Ludovic Henry +--- + tests/test_view_summary.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/test_view_summary.py b/tests/test_view_summary.py +index 91ae42c..dca1053 100644 +--- a/tests/test_view_summary.py ++++ b/tests/test_view_summary.py +@@ -855,7 +855,7 @@ def test_that_restart_and_base_times_are_concated(capsys): + + + @pytest.mark.usefixtures("use_tmpdir") +-@pytest.mark.timeout(10) ++@pytest.mark.timeout(60) + def test_performance_with_many_keys(monkeypatch, benchmark): + subdir = Path("subdir") + subdir.mkdir() From 420ee600870b7967958dda26d599e0271940b50e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 17 Oct 2026 23:37:11 +0000 Subject: [PATCH 09/10] resdata: pin setuptools_scm's version so the dirty patch tree doesn't poison it The Apply patches step's git apply leaves the setuptools_scm checkout dirty, so git describe reports 6.3.4.dev0+g.d instead of the clean 6.3.3 tag (gotcha 31) - that's why the publish job failed: the wheel's version has no matching entry in docs/packages/resdata.yaml, and PyPI's 6.3.3 build has no dev suffix to compare against anyway. --- .github/workflows/build-resdata.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index 7bf026210941..38681d28bc50 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -86,9 +86,12 @@ jobs: CIBW_BUILD_FRONTEND: build # PyPI's newest cmake<4 is sdist-only on riscv64, ours is a wheel (gotcha 30); CMake 4 # turns setup.py's relative CMAKE_INSTALL_*DIR into build-tree paths skbuild rejects. + # The "Apply patches" step's `git apply` dirties the setuptools_scm checkout, so + # git describe reports a post-6.3.3 dev version instead of the clean tag (gotcha 31). CIBW_ENVIRONMENT: >- PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ PIP_ONLY_BINARY=cmake + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RESDATA=${{ env.RESDATA_VERSION }} # Conan links these into libresdata statically and upstream ships no licence # text for them, so stage each package's own licences at the project root. CIBW_BEFORE_BUILD: | From 068a7c9c0884ceddf8153b5b63fae72a1aabdeea Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 18 Oct 2026 01:40:27 +0000 Subject: [PATCH 10/10] resdata: use the bare SETUPTOOLS_SCM_PRETEND_VERSION, not the _FOR_RESDATA suffix The previous commit's SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RESDATA had no effect: setup.py calls setuptools_scm.get_version() directly without a dist_name argument, so setuptools_scm never learns the package name and can't match the per-package env var. Only the generic SETUPTOOLS_SCM_PRETEND_VERSION, which applies regardless of dist_name, actually reaches get_version() here. Confirmed by the publish job: the wheel version was still the dirty 6.3.4.dev0+g6bed49710.d20261018 string after the first fix. --- .github/workflows/build-resdata.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-resdata.yml b/.github/workflows/build-resdata.yml index 38681d28bc50..67300e35eebd 100644 --- a/.github/workflows/build-resdata.yml +++ b/.github/workflows/build-resdata.yml @@ -88,10 +88,13 @@ jobs: # turns setup.py's relative CMAKE_INSTALL_*DIR into build-tree paths skbuild rejects. # The "Apply patches" step's `git apply` dirties the setuptools_scm checkout, so # git describe reports a post-6.3.3 dev version instead of the clean tag (gotcha 31). + # setup.py calls setuptools_scm.get_version() directly without a dist_name, so only + # the bare SETUPTOOLS_SCM_PRETEND_VERSION is checked - the _FOR_RESDATA suffix needs + # a known dist_name and is silently ignored here. CIBW_ENVIRONMENT: >- PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ PIP_ONLY_BINARY=cmake - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_RESDATA=${{ env.RESDATA_VERSION }} + SETUPTOOLS_SCM_PRETEND_VERSION=${{ env.RESDATA_VERSION }} # Conan links these into libresdata statically and upstream ships no licence # text for them, so stage each package's own licences at the project root. CIBW_BEFORE_BUILD: |