Skip to content

docs: Update projects #27

docs: Update projects

docs: Update projects #27

# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# Upstream builds eccodeslib via a private tool (ecmwf-actions/reusable-workflows'
# python-wrapper-wheel.yml + the "wheelmaker" image), not a public workflow, so this
# replays ecmwf/eccodes's own python_wrapper/buildconfig CMAKE_PARAMS directly. The
# built libeccodes.so is byte-identical across upstream's cp310..cp314 wheels (no
# Python C-API use), so this builds one py3-none wheel instead of a per-interpreter
# matrix. ENABLE_ECKIT_GEO is set to its CMake default (OFF; upstream forces it ON)
# since eckit has no riscv64 build yet; this only drops the optional geo-iterator
# backend, not core GRIB/BUFR support.
name: Build eccodeslib wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/eccodeslib.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-eccodeslib.yml'
- 'docs/packages/eccodeslib.yaml'
push:
branches: [main]
paths:
- '.github/workflows/build-eccodeslib.yml'
- 'docs/packages/eccodeslib.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
ECBUILD_VERSION: '3.15.2'
OPENJPEG_VERSION: '2.5.2'
LIBAEC_VERSION: '1.1.4'
jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: eccodeslib
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 eccodeslib ${{ matrix.version }} py3-none-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 420
env:
PACKAGE_VERSION: ${{ matrix.version }}
steps:
- name: Derive the eccodes git tag from the package version
run: echo "ECCODES_VERSION=$(echo "$PACKAGE_VERSION" | sed -E 's/\.[0-9]+$//')" >> "$GITHUB_ENV"
- name: Checkout eccodes ${{ env.ECCODES_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ecmwf/eccodes
ref: ${{ env.ECCODES_VERSION }}
persist-credentials: false
- name: Record the resolved commit
run: echo "ECCODES_COMMIT=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
- name: Write the wheel packaging script
run: |
cat > build_wheel.py <<'PY'
import hashlib
import os
import sys
import zipfile
from base64 import urlsafe_b64encode
PKG = "eccodeslib"
VERSION, PLATFORM_TAG, INSTALL_DIR, LICENSES_DIR, OUT_DIR = sys.argv[1:6]
WHEEL_TAG = f"py3-none-{PLATFORM_TAG}"
DIST_INFO = f"{PKG}-{VERSION}.dist-info"
METADATA = f"""Metadata-Version: 2.4
Name: {PKG}
Version: {VERSION}
Summary: Compiled ecCodes C library (GRIB/BUFR decoding and encoding), no Python bindings
Home-page: https://confluence.ecmwf.int/display/ECC/ecCodes+Home
Project-URL: Source, https://github.com/ecmwf/eccodes
License-Expression: Apache-2.0
"""
WHEEL = f"""Wheel-Version: 1.0
Generator: python-wheels-riscv64-port
Root-Is-Purelib: false
Tag: {WHEEL_TAG}
"""
os.makedirs(OUT_DIR, exist_ok=True)
wheel_path = os.path.join(OUT_DIR, f"{PKG}-{VERSION}-{WHEEL_TAG}.whl")
records = []
def write_file(zf, arcname, path):
with open(path, "rb") as f:
data = f.read()
info = zipfile.ZipInfo(arcname)
info.external_attr = (os.stat(path).st_mode & 0xFFFF) << 16
zf.writestr(info, data, zipfile.ZIP_DEFLATED)
digest = urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode()
records.append(f"{arcname},sha256={digest},{len(data)}")
def write_bytes(zf, arcname, data):
zf.writestr(arcname, data)
digest = urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode()
records.append(f"{arcname},sha256={digest},{len(data)}")
with zipfile.ZipFile(wheel_path, "w", zipfile.ZIP_DEFLATED) as zf:
for root, _dirs, files in os.walk(INSTALL_DIR):
for name in sorted(files):
path = os.path.join(root, name)
rel = os.path.relpath(path, INSTALL_DIR)
write_file(zf, f"{PKG}/{rel}", path)
for name in sorted(os.listdir(LICENSES_DIR)):
write_file(zf, f"{DIST_INFO}/licenses/{name}", os.path.join(LICENSES_DIR, name))
write_bytes(zf, f"{DIST_INFO}/METADATA", METADATA.encode())
write_bytes(zf, f"{DIST_INFO}/WHEEL", WHEEL.encode())
write_bytes(zf, f"{DIST_INFO}/top_level.txt", f"{PKG}\n".encode())
record_name = f"{DIST_INFO}/RECORD"
zf.writestr(record_name, "\n".join(records + [f"{record_name},,"]) + "\n")
print(wheel_path)
PY
- name: Build eccodes and its vendored codecs, then package the wheel
run: |
docker run --rm \
-v "$(pwd)":/workspace \
--workdir /workspace \
-e PACKAGE_VERSION="$PACKAGE_VERSION" \
-e ECCODES_COMMIT="$ECCODES_COMMIT" \
-e ECBUILD_VERSION="$ECBUILD_VERSION" \
-e OPENJPEG_VERSION="$OPENJPEG_VERSION" \
-e LIBAEC_VERSION="$LIBAEC_VERSION" \
"$MANYLINUX_RISCV64_IMAGE" \
bash -c '
set -euo pipefail
dnf -y -q install libpng-devel
mkdir -p /tmp/deps /tmp/build /tmp/install/eccodeslib /tmp/licenses
curl -fsSL "https://github.com/ecmwf/ecbuild/archive/refs/tags/$ECBUILD_VERSION.tar.gz" | tar xz -C /tmp/build
cmake -S "/tmp/build/ecbuild-$ECBUILD_VERSION" -B /tmp/build/ecbuild-build -D CMAKE_INSTALL_PREFIX=/tmp/deps
cmake --build /tmp/build/ecbuild-build -j "$(nproc)"
cmake --install /tmp/build/ecbuild-build
mkdir -p /tmp/build/libaec-src
curl -fsSL "https://github.com/Deutsches-Klimarechenzentrum/libaec/releases/download/v$LIBAEC_VERSION/libaec-$LIBAEC_VERSION.tar.gz" | tar xz --strip-components=1 -C /tmp/build/libaec-src
cmake -S /tmp/build/libaec-src -B /tmp/build/libaec-build \
-D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/tmp/deps -D CMAKE_INSTALL_LIBDIR=lib \
-D BUILD_STATIC_LIBS=OFF -D BUILD_TESTING=OFF
cmake --build /tmp/build/libaec-build -j "$(nproc)"
cmake --install /tmp/build/libaec-build
mkdir -p /tmp/build/openjpeg-src
curl -fsSL "https://github.com/uclouvain/openjpeg/archive/refs/tags/v$OPENJPEG_VERSION.tar.gz" | tar xz --strip-components=1 -C /tmp/build/openjpeg-src
cmake -S /tmp/build/openjpeg-src -B /tmp/build/openjpeg-build \
-D CMAKE_BUILD_TYPE=RelWithDebInfo -D CMAKE_INSTALL_PREFIX=/tmp/deps -D CMAKE_INSTALL_LIBDIR=lib \
-D BUILD_CODEC=OFF
cmake --build /tmp/build/openjpeg-build -j "$(nproc)"
cmake --install /tmp/build/openjpeg-build
cmake -S . -B /tmp/build/eccodes-build \
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D CMAKE_PREFIX_PATH=/tmp/deps \
-D CMAKE_INSTALL_PREFIX=/tmp/install/eccodeslib \
-D CMAKE_INSTALL_LIBDIR=lib64 \
-D Python3_EXECUTABLE=/opt/python/cp312-cp312/bin/python3 \
-D ENABLE_PNG=1 \
-D ENABLE_JPG=1 -D ENABLE_JPG_LIBJASPER=0 -D ENABLE_JPG_LIBOPENJPEG=1 -D OPENJPEG_DIR=/tmp/deps \
-D ENABLE_NETCDF=0 -D ENABLE_FORTRAN=0 \
-D ENABLE_EXAMPLES=0 -D ENABLE_BUILD_TOOLS=1 \
-D ENABLE_INSTALL_ECCODES_DEFINITIONS=0 -D ENABLE_INSTALL_ECCODES_SAMPLES=0 \
-D ENABLE_ECCODES_THREADS=1 -D ENABLE_MEMFS=1 -D ENABLE_ECKIT_GEO=0 \
-D ENABLE_AEC=1
cmake --build /tmp/build/eccodes-build -j "$(nproc)"
cmake --install /tmp/build/eccodes-build
cp /tmp/deps/lib/libopenjp2.so* /tmp/install/eccodeslib/lib64/
cp /tmp/deps/lib/libaec.so* /tmp/install/eccodeslib/lib64/
cp "$(find /usr/lib* -name "libpng16.so.16*" | head -1)" /tmp/install/eccodeslib/lib64/
cp LICENSE /tmp/licenses/eccodes.txt
cp AUTHORS /tmp/licenses/
curl -fsSL "https://raw.githubusercontent.com/uclouvain/openjpeg/v$OPENJPEG_VERSION/LICENSE" -o /tmp/licenses/openjpeg.txt
curl -fsSL "https://raw.githubusercontent.com/MathisRosenhauer/libaec/v$LIBAEC_VERSION/LICENSE.txt" -o /tmp/licenses/libaec.txt
cp /usr/share/licenses/libpng/LICENSE /tmp/licenses/libpng.txt
printf "__version__ = \"%s\"\n__commit_hash__ = \"%s\"\nfindlibs_dependencies = []\n" "$PACKAGE_VERSION" "$ECCODES_COMMIT" > /tmp/install/eccodeslib/__init__.py
python3 build_wheel.py "$PACKAGE_VERSION" manylinux_2_39_riscv64 /tmp/install/eccodeslib /tmp/licenses wheelhouse
'
- name: Check the wheel ships the extensions and the bundled licences
run: |
python3 - wheelhouse/*.whl <<'EOF'
import sys, zipfile
names = zipfile.ZipFile(sys.argv[1]).namelist()
assert any(n.endswith("lib64/libeccodes.so") for n in names), names
for licence in ("eccodes.txt", "openjpeg.txt", "libaec.txt", "libpng.txt"):
assert any(
n.endswith(f".dist-info/licenses/{licence}") for n in names
), (licence, names)
EOF
- name: Smoke-test the built library
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends python3-venv
python3 -m venv .venv
. .venv/bin/activate
# gotcha 234: stock Ubuntu 24.04 apt pip (24.0) has zero manylinux_*_riscv64
# tags, so it rejects the riscv64 wheel outright without upgrading first.
pip install -q --upgrade pip
pip install -q findlibs wheelhouse/*.whl
python3 -c "
import findlibs
lib = findlibs.load('eccodes')
print(lib)
"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: eccodeslib-${{ matrix.version }}-py3-none-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error
publish:
name: Publish eccodeslib ${{ 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: eccodeslib-${{ matrix.version }}-*-manylinux_riscv64