Skip to content
Draft
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
245 changes: 245 additions & 0 deletions .github/workflows/build-ray-haproxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on:
# https://github.com/ray-project/ray-haproxy/blob/v2.8.25/.github/workflows/release.yml
name: Build ray-haproxy wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/ray-haproxy.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-ray-haproxy.yml'
- 'docs/packages/ray-haproxy.yaml'
- 'patches/ray-haproxy/**'
push:
branches: [main]
paths:
- '.github/workflows/build-ray-haproxy.yml'
- 'docs/packages/ray-haproxy.yaml'
- 'patches/ray-haproxy/**'

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: ray-haproxy
version: ${{ inputs.version }}

build_wheel:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Build ray-haproxy ${{ matrix.version }} py3-none-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 60

env:
RAY_HAPROXY_VERSION: ${{ matrix.version }}
HAPROXY_VERSION: ${{ matrix.version }}

steps:
- name: Checkout ray-haproxy v${{ matrix.version }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ray-project/ray-haproxy
ref: v${{ env.RAY_HAPROXY_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 -v python-wheels/patches/ray-haproxy/${{ env.RAY_HAPROXY_VERSION }}/*.patch

# Upstream's own build step, run against manylinux_2_39_riscv64 instead of
# manylinux2014 (which has no riscv64 image), then packaged the same way
# its release.yml does outside the container.
- name: Build HAProxy and package the wheel
run: |
docker run --rm \
-v "$(pwd)":/workspace \
--workdir /workspace \
-e HAPROXY_VERSION \
-e OUTPUT_DIR=/workspace/dist \
"${{ env.MANYLINUX_RISCV64_IMAGE }}" \
bash -c '
set -euxo pipefail
./ci/build/build-haproxy-dist.sh
mkdir -p ray_haproxy/bin/lib
tar -xzf dist/haproxy-linux-riscv64.tar.gz -C ray_haproxy/bin/
/opt/python/cp312-cp312/bin/python -m pip install -q wheel setuptools
/opt/python/cp312-cp312/bin/python setup.py bdist_wheel --plat-name manylinux_2_39_riscv64
'

- name: Verify the wheel ships the riscv64 binary
run: |
python3 - dist/*.whl <<'EOF'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as zf:
names = zf.namelist()
print("\n".join(names))
binary = next(n for n in names if n.endswith("ray_haproxy/bin/haproxy"))
header = zf.read(binary)[:20]
assert header[:4] == b"\x7fELF", header
assert header[18] == 0xF3, header # e_machine == EM_RISCV
libs = [n for n in names if "ray_haproxy/bin/lib/" in n and not n.endswith("lib/")]
assert libs, "no vendored shared libraries in the wheel"
licenses = sorted(n.rsplit("/", 1)[-1] for n in names if ".data/data/" in n)
assert licenses == ["LICENSE", "THIRD_PARTY_LICENSES"], licenses
EOF

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ray-haproxy-${{ env.RAY_HAPROXY_VERSION }}-py3-none-manylinux_riscv64
path: dist/*.whl
if-no-files-found: error

test_wheel:
name: Test ray-haproxy ${{ matrix.version }} on Python ${{ matrix.python-version }}
needs: [setup, build_wheel]
if: needs.setup.outputs.versions != '[]'
runs-on: ubuntu-24.04-riscv
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
# Upstream tests 3.9-3.12; trimmed to the interpreters this registry targets.
python-version: ['3.12', '3.13', '3.14']

env:
RAY_HAPROXY_VERSION: ${{ matrix.version }}

steps:
# Checked out beside the workspace root (not into it) so the package's
# own ray_haproxy/ source directory can't shadow the installed wheel
# when the smoke test below imports ray_haproxy (gotcha 187).
- name: Checkout ray-haproxy v${{ matrix.version }} (tests)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ray-project/ray-haproxy
ref: v${{ env.RAY_HAPROXY_VERSION }}
path: ray-haproxy-src
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 -C ray-haproxy-src apply -v ../python-wheels/patches/ray-haproxy/${{ env.RAY_HAPROXY_VERSION }}/*.patch

- name: Download wheel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ray-haproxy-${{ env.RAY_HAPROXY_VERSION }}-py3-none-manylinux_riscv64
path: wheelhouse

- name: Install Python
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
enable-cache: false

- name: Install the wheel
run: uv pip install --reinstall --no-index --find-links wheelhouse ray-haproxy

- name: Run upstream's smoke test
run: |
python -c "
from ray_haproxy import get_haproxy_binary
import subprocess, sys
binary = get_haproxy_binary()
print(f'Python {sys.version}')
print(f'Binary: {binary}')
result = subprocess.run([binary, '-v'], capture_output=True, text=True)
print(result.stdout or result.stderr)
assert result.returncode == 0, f'haproxy -v failed: {result.returncode}'
print('OK')
"

# Runs upstream's own vendoring checker (RPATH, ldd resolution, ELF
# sanity) directly on real riscv64 hardware, in place of upstream's
# `verify` job, which spins up six distro containers, several of
# which (amazonlinux, rockylinux:9) have no riscv64 image to run.
- name: Run upstream's vendoring verification
run: |
BINARY="$(python -c 'from ray_haproxy import get_haproxy_binary; print(get_haproxy_binary())')"
chmod +x ray-haproxy-src/ci/verify-vendoring.sh
ray-haproxy-src/ci/verify-vendoring.sh "$BINARY" "$(dirname "$BINARY")/lib"

gpl_sources:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Collect GPL sources for ray-haproxy ${{ matrix.version }}
runs-on: ubuntu-24.04-riscv

env:
RAY_HAPROXY_VERSION: ${{ matrix.version }}

steps:
- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: ./actions/collect-gpl-sources
with:
image: ${{ env.MANYLINUX_RISCV64_IMAGE }}
packages: gcc
output: gpl-sources.tar

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ray-haproxy-${{ env.RAY_HAPROXY_VERSION }}-gpl-sources
path: gpl-sources.tar
if-no-files-found: error

publish:
name: Publish ray-haproxy ${{ matrix.version }}
needs: [setup, build_wheel, test_wheel, 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: ray-haproxy-${{ matrix.version }}-py3-none-manylinux_riscv64
gpl-sources-artifact: ray-haproxy-${{ matrix.version }}-gpl-sources
gpl-sources-description: gcc
5 changes: 5 additions & 0 deletions docs/packages/ray-haproxy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: ray-haproxy
source-code: https://github.com/ray-project/ray-haproxy
license: GNU General Public License v2 (GPLv2)
versions:
- version: 2.8.25
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 28 Sep 2026 00:00:00 +0000
Subject: [PATCH] ci/build: add riscv64 and build against PCRE2 there

build-haproxy-dist.sh's arch normalisation only knows x86_64/aarch64/arm64,
so it exits with "Unsupported architecture: riscv64" before doing anything:

Unsupported architecture: riscv64

Add a riscv64 case. Its dependency install and HAProxy `make` flags also
assume PCRE1 (`pcre-devel`, `USE_PCRE=1`), which manylinux2014 (CentOS 7)
carries; manylinux_2_39_riscv64 (Rocky 10) dropped the legacy PCRE1 package
and only ships `pcre2-devel`. Branch on the riscv64 arch label to install
`pcre2-devel` and build with `USE_PCRE2=1 USE_PCRE2_JIT=1` instead, leaving
the x86_64/aarch64 codepath untouched.

Rocky 10 also splits `FindBin.pm` out of its base Perl into `perl-FindBin`,
which OpenSSL's `Configure` needs and manylinux2014's Perl carries without
a separate package:

Can't locate FindBin.pm in @INC (you may need to install the FindBin module) ... at .../openssl-3.0.15/Configure line 15.

Rocky 10 splits the `lib` pragma out the same way, into `perl-lib`, and
`Configure` needs that too (it's `use`d two lines later than `FindBin`, so
this only surfaced once the FindBin install let Configure get further):

Can't locate lib.pm in @INC (you may need to install the lib module) ... at .../openssl-3.0.15/Configure line 16.

Install both alongside `perl-IPC-Cmd` on riscv64.

Upstream-Status: Inappropriate [manylinux2014 (x86_64/aarch64) still has pcre-devel; this is a difference between manylinux images, not something upstream's existing targets need]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/ci/build/build-haproxy-dist.sh b/ci/build/build-haproxy-dist.sh
--- a/ci/build/build-haproxy-dist.sh
+++ b/ci/build/build-haproxy-dist.sh
@@ -62,6 +62,7 @@
x86_64) ARCH_LABEL="x86_64" ;;
aarch64) ARCH_LABEL="arm64" ;;
arm64) ARCH_LABEL="arm64" ;; # macOS (future)
+ riscv64) ARCH_LABEL="riscv64" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac

@@ -91,7 +92,14 @@
# lua-devel — for HAProxy USE_LUA=1 (Lua 5.1 on CentOS 7)
# ---------------------------------------------------------------------------
echo "==> Installing build dependencies"
-yum install -y perl-IPC-Cmd pcre-devel zlib-devel readline-devel 2>/dev/null
+if [ "$ARCH_LABEL" = "riscv64" ]; then
+ # manylinux_2_39_riscv64 (Rocky 10) dropped the legacy PCRE1 package;
+ # only pcre2-devel is available there. HAProxy's USE_PCRE2 build option
+ # is the equivalent for that library.
+ yum install -y perl-IPC-Cmd perl-FindBin perl-lib pcre2-devel zlib-devel readline-devel 2>/dev/null
+else
+ yum install -y perl-IPC-Cmd pcre-devel zlib-devel readline-devel 2>/dev/null
+fi

# ---------------------------------------------------------------------------
# 1. Build OpenSSL from source
@@ -159,13 +167,19 @@
tar -xzf "$BUILD_DIR/haproxy.tar.gz" -C "$BUILD_DIR" --strip-components=1

echo "==> Compiling HAProxy"
+# manylinux_2_39_riscv64 only has pcre2-devel (see the riscv64 branch above),
+# so build against PCRE2 there instead of PCRE1.
+PCRE_MAKE_VARS=(USE_PCRE=1)
+if [ "$ARCH_LABEL" = "riscv64" ]; then
+ PCRE_MAKE_VARS=(USE_PCRE2=1 USE_PCRE2_JIT=1)
+fi
make -C "$BUILD_DIR" \
TARGET=linux-glibc \
USE_OPENSSL=1 \
SSL_INC="$DEPS_DIR/include" \
SSL_LIB="$OPENSSL_LIB_DIR" \
USE_ZLIB=1 \
- USE_PCRE=1 \
+ "${PCRE_MAKE_VARS[@]}" \
USE_LUA=1 \
LUA_INC="$DEPS_DIR/include" \
LUA_LIB="$DEPS_DIR/lib" \
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 28 Sep 2026 00:00:00 +0000
Subject: [PATCH] ci: verify-vendoring: recognise riscv64 binaries

The ELF sanity check only matches `file`'s output for x86-64 and aarch64, so
it fails a riscv64 binary that is otherwise fine:

FAIL: Unexpected binary type: ELF 64-bit LSB pie executable, UCB RISC-V, ...

Add a branch for `file`'s "RISC-V" architecture string.

Upstream-Status: Inappropriate [only needed once a riscv64 leg exists to call this script; upstream's own x86_64/aarch64 legs never hit this path]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/ci/verify-vendoring.sh b/ci/verify-vendoring.sh
--- a/ci/verify-vendoring.sh
+++ b/ci/verify-vendoring.sh
@@ -128,6 +128,8 @@
pass "Binary is ELF 64-bit x86-64"
elif echo "$FILE_TYPE" | grep -q "ELF 64-bit.*aarch64"; then
pass "Binary is ELF 64-bit aarch64"
+elif echo "$FILE_TYPE" | grep -q "ELF 64-bit.*RISC-V"; then
+ pass "Binary is ELF 64-bit RISC-V"
else
fail "Unexpected binary type: $FILE_TYPE"
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 28 Sep 2026 00:00:00 +0000
Subject: [PATCH] THIRD_PARTY_LICENSES: note PCRE2 on riscv64

The riscv64 build links PCRE2 (previous patch), not the classic PCRE1 this
file's "PCRE" entry names and links to (sourceforge.net/projects/pcre,
pcre.org). Both share the same BSD-style licence text already quoted here,
but point the Source line at PCRE2's own repo too so the notice matches what
the riscv64 wheel actually vendors.

Upstream-Status: Inappropriate [only the riscv64 build in this fork links PCRE2; upstream's own x86_64/aarch64 wheels still vendor PCRE1]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/THIRD_PARTY_LICENSES b/THIRD_PARTY_LICENSES
--- a/THIRD_PARTY_LICENSES
+++ b/THIRD_PARTY_LICENSES
@@ -33,7 +33,9 @@
PCRE (Perl Compatible Regular Expressions)
-------------------------------------------
License: BSD License
+Note: the riscv64 wheel vendors PCRE2 instead; same licence.
Source: https://sourceforge.net/projects/pcre/
+ https://github.com/PCRE2Project/pcre2 (PCRE2, riscv64)
https://www.pcre.org/

Redistribution and use in source and binary forms, with or without
Loading
Loading