Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions .github/workflows/build-resdata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# 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: 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:
output-dir: wheelhouse/
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
# 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).
# 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=${{ 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: |
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
# 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 --timeout=900 --timeout-method=thread {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
5 changes: 5 additions & 0 deletions docs/packages/resdata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: resdata
source-code: https://github.com/equinor/resdata
license: GPL-3.0-only
versions:
- version: 6.3.3
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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 <git@ludovic.dev>
---
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

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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 <git@ludovic.dev>
---
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()
Loading