From c036e72b1a88572d407efbc7e8da46acae1c536c Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 13 Oct 2026 05:22:13 +0000 Subject: [PATCH 1/3] runai-model-streamer-azure: Add version 0.16.1 Azure Blob Storage backend for Run:ai Model Streamer. Same shape as the already-ported S3/GCS backends: a C++ core (libstreamerazure.so, built via bazel against a statically-linked Azure SDK for C++ + OpenSSL + curl + zlib + libxml2) wrapped by a thin Python package. No native Azure account is needed to build or test it: upstream tests this backend against Azurite (the local Azure Storage emulator), and this port does the same, using the well-known Azurite test account key upstream itself documents. The published .so is built the same way as upstream's own release recipe (`make build`); a second, AZURITE_TESTING-enabled .so is built alongside it (upstream's own `make -C cpp build_azure_test`) purely to run the Azurite-backed test, since the production build has no way to point the Azure SDK at a local http endpoint. --- .../build-runai-model-streamer-azure.yml | 486 ++++++++++++++++++ docs/packages/runai-model-streamer-azure.yaml | 5 + ...-riscv64-as-a-supported-architecture.patch | 50 ++ ...ine-triplet-for-builtin-include-dirs.patch | 134 +++++ ...cv64-to-the-Azure-SDK-library-select.patch | 47 ++ ...f-the-statically-linked-dependencies.patch | 34 ++ 6 files changed, 756 insertions(+) create mode 100644 .github/workflows/build-runai-model-streamer-azure.yml create mode 100644 docs/packages/runai-model-streamer-azure.yaml create mode 100644 patches/runai-model-streamer-azure/0.16.1/0001-toolchain-add-riscv64-as-a-supported-architecture.patch create mode 100644 patches/runai-model-streamer-azure/0.16.1/0002-toolchain-use-gcc-s-own-dumpmachine-triplet-for-builtin-include-dirs.patch create mode 100644 patches/runai-model-streamer-azure/0.16.1/0003-third_party-add-riscv64-to-the-Azure-SDK-library-select.patch create mode 100644 patches/runai-model-streamer-azure/0.16.1/0004-azure-ship-the-licences-of-the-statically-linked-dependencies.patch diff --git a/.github/workflows/build-runai-model-streamer-azure.yml b/.github/workflows/build-runai-model-streamer-azure.yml new file mode 100644 index 000000000000..dea85d97106d --- /dev/null +++ b/.github/workflows/build-runai-model-streamer-azure.yml @@ -0,0 +1,486 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +# +# This workflow is based on: https://github.com/run-ai/runai-model-streamer/blob/master/Makefile +# and https://github.com/run-ai/runai-model-streamer/blob/master/.devcontainer/Dockerfile +--- +name: Build runai-model-streamer-azure wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/runai-model-streamer-azure.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-runai-model-streamer-azure.yml' + - 'docs/packages/runai-model-streamer-azure.yaml' + - 'patches/runai-model-streamer-azure/**' + push: + branches: [main] + paths: + - '.github/workflows/build-runai-model-streamer-azure.yml' + - 'docs/packages/runai-model-streamer-azure.yaml' + - 'patches/runai-model-streamer-azure/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + BAZEL_VERSION: '7.6.1' + RULES_PYTHON_VERSION: '0.33.2' + RULES_JAVA_VERSION: '7.6.5' + OPENSSL_VERSION: '3.5.0' + CURL_VERSION: '8.13.0' + ZLIB_VERSION: '1.3.1' + LIBXML2_VERSION: '2.13.5' + AZURE_SDK_CPP_VERSION: 'azure-storage-blobs_12.15.0' + AZURITE_VERSION: '3.37.0' + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: runai-model-streamer-azure + version: ${{ inputs.version }} + + bazel: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Bootstrap bazel (riscv64) + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + + steps: + - name: Restore bazel binary + id: cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: bazel-bin + key: bazel-${{ env.BAZEL_VERSION }}-manylinux_riscv64 + + - name: Bootstrap bazel ${{ env.BAZEL_VERSION }} + if: steps.cache.outputs.cache-hit != 'true' + run: | + mkdir -p bazel-bin + docker run --rm -i --network=host \ + -v "${GITHUB_WORKSPACE}:/work" \ + -w /work \ + -e BAZEL_VERSION="${BAZEL_VERSION}" \ + -e RULES_PYTHON_VERSION="${RULES_PYTHON_VERSION}" \ + -e RULES_JAVA_VERSION="${RULES_JAVA_VERSION}" \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash <<'SCRIPT' + set -eux + + dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip + JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")" + export JAVA_HOME + + # rules_python 0.33.2 has no riscv64 PLATFORMS entry (bazelbuild/bazel#23018). + mkdir -p /tmp/rules_python + curl -fsSLo /tmp/rules_python.tar.gz "https://github.com/bazel-contrib/rules_python/releases/download/${RULES_PYTHON_VERSION}/rules_python-${RULES_PYTHON_VERSION}.tar.gz" + tar -xzf /tmp/rules_python.tar.gz -C /tmp/rules_python --strip-components=1 + sed -i 's|fail("No platform declared for host OS {} on arch {}".format(os_name, arch))|return "x86_64-unknown-linux-gnu"|' \ + /tmp/rules_python/python/private/toolchains_repo.bzl + + # rules_java 7.x maps riscv64 to a stray-colon include path for jni_md.h. + mkdir -p /tmp/rules_java + curl -fsSLo /tmp/rules_java.tar.gz "https://github.com/bazelbuild/rules_java/releases/download/${RULES_JAVA_VERSION}/rules_java-${RULES_JAVA_VERSION}.tar.gz" + tar -xzf /tmp/rules_java.tar.gz -C /tmp/rules_java + sed -i 's|\[":include/linux"\]|["include/linux"]|g' /tmp/rules_java/toolchains/BUILD + + mkdir -p /tmp/bazel-src + cd /tmp/bazel-src + curl -fsSLo dist.zip "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip" + unzip -q dist.zip + + EXTRA_BAZEL_ARGS="--tool_java_runtime_version=local_jdk \ + --override_module=rules_python=/tmp/rules_python \ + --override_module=rules_java=/tmp/rules_java" \ + bash ./compile.sh + install -m 0755 output/bazel /work/bazel-bin/bazel + SCRIPT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: bazel-${{ env.BAZEL_VERSION }}-riscv64 + path: bazel-bin/bazel + if-no-files-found: error + + build_wheel: + name: Build runai-model-streamer-azure ${{ matrix.version }} py3-none-manylinux_riscv64 + needs: [setup, bazel] + if: needs.setup.outputs.versions != '[]' + runs-on: ubuntu-24.04-riscv + timeout-minutes: 300 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + RUNAI_MODEL_STREAMER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout runai-model-streamer ${{ env.RUNAI_MODEL_STREAMER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: run-ai/runai-model-streamer + ref: ${{ env.RUNAI_MODEL_STREAMER_VERSION }} + path: runai-model-streamer + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch runai-model-streamer source + working-directory: runai-model-streamer + run: git apply -v ../python-wheels/patches/runai-model-streamer-azure/${{ env.RUNAI_MODEL_STREAMER_VERSION }}/*.patch + + - name: Download bazel + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: bazel-${{ env.BAZEL_VERSION }}-riscv64 + path: bazel-bin + + - name: Restore OpenSSL, curl, zlib, libxml2 and the Azure SDK + id: deps-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: deps + key: runai-model-streamer-azure-deps-manylinux_2_39_riscv64-${{ hashFiles('python-wheels/.github/workflows/build-runai-model-streamer-azure.yml') }} + + - name: Build OpenSSL, curl, zlib, libxml2 and the Azure SDK + if: steps.deps-cache.outputs.cache-hit != 'true' + run: | + mkdir -p deps/riscv64-ssl deps/riscv64-curl deps/riscv64-zlib deps/riscv64-xml2 deps/riscv64-azure deps/licenses + docker run --rm -i --network=host \ + -v "${GITHUB_WORKSPACE}:/work" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-ssl:/opt/riscv64-ssl" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-curl:/opt/riscv64-curl" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-zlib:/opt/riscv64-zlib" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-xml2:/opt/riscv64-xml2" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-azure:/opt/riscv64-azure" \ + -w /tmp \ + -e OPENSSL_VERSION="${OPENSSL_VERSION}" \ + -e CURL_VERSION="${CURL_VERSION}" \ + -e ZLIB_VERSION="${ZLIB_VERSION}" \ + -e LIBXML2_VERSION="${LIBXML2_VERSION}" \ + -e AZURE_SDK_CPP_VERSION="${AZURE_SDK_CPP_VERSION}" \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash <<'SCRIPT' + set -eux + + # OpenSSL's Configure needs perl modules the image's minimal perl-interpreter lacks. + dnf install -y --setopt=install_weak_deps=False perl + LICENSES=/work/deps/licenses + + curl -fsSL -o "openssl-${OPENSSL_VERSION}.tar.gz" "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz" + tar zxf "openssl-${OPENSSL_VERSION}.tar.gz" + cd "openssl-${OPENSSL_VERSION}" + ./Configure no-ssl2 no-zlib no-shared no-comp no-dynamic-engine enable-ec_nistp_64_gcc_128 \ + --prefix=/opt/riscv64-ssl --openssldir=/opt/riscv64-ssl --libdir=lib linux64-riscv64 + make -j"$(nproc)" > /dev/null + make install_sw > /dev/null + cp LICENSE.txt "$LICENSES/LICENSE.openssl" + cd /tmp + + curl -fsSL -o "curl-${CURL_VERSION}.tar.gz" "https://github.com/curl/curl/releases/download/curl-${CURL_VERSION//./_}/curl-${CURL_VERSION}.tar.gz" + tar zxf "curl-${CURL_VERSION}.tar.gz" + cd "curl-${CURL_VERSION}" + # Keep libcurl.a to the libraries the @azure link line names, whatever -devel the image carries. + CPPFLAGS=-I/opt/riscv64-ssl/include \ + LDFLAGS=-L/opt/riscv64-ssl/lib \ + CFLAGS="-fPIC" \ + ./configure --disable-shared --without-libpsl --with-openssl=/opt/riscv64-ssl --prefix=/opt/riscv64-curl \ + --without-brotli --without-zstd --without-nghttp2 --without-libidn2 + make -j"$(nproc)" > /dev/null + make install > /dev/null + cp COPYING "$LICENSES/LICENSE.curl" + cd /tmp + + curl -fsSL -o "zlib-${ZLIB_VERSION}.tar.gz" "https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION}/zlib-${ZLIB_VERSION}.tar.gz" + tar zxf "zlib-${ZLIB_VERSION}.tar.gz" + cd "zlib-${ZLIB_VERSION}" + # Upstream's Ubuntu gcc defaults to PIE, which is what lets its non-PIC libz.a link into a .so. + CFLAGS="-fPIC" ./configure --static --shared --prefix=/opt/riscv64-zlib + make -j"$(nproc)" > /dev/null + make install > /dev/null + cp LICENSE "$LICENSES/LICENSE.zlib" + cd /tmp + + curl -fsSL -o "libxml2-${LIBXML2_VERSION}.tar.xz" "https://download.gnome.org/sources/libxml2/${LIBXML2_VERSION%.*}/libxml2-${LIBXML2_VERSION}.tar.xz" + tar xf "libxml2-${LIBXML2_VERSION}.tar.xz" + cd "libxml2-${LIBXML2_VERSION}" + CFLAGS="-fPIC" ./configure --disable-shared --enable-static \ + --without-python --without-lzma --without-zlib --prefix=/opt/riscv64-xml2 + make -j"$(nproc)" > /dev/null + make install > /dev/null + cp Copyright "$LICENSES/LICENSE.libxml2" + cd /tmp + + git clone --recurse-submodules --depth 1 --branch "${AZURE_SDK_CPP_VERSION}" https://github.com/Azure/azure-sdk-for-cpp /tmp/azure-sdk-cpp + cd /tmp/azure-sdk-cpp + cp LICENSE.txt "$LICENSES/LICENSE.azure-sdk-for-cpp" + + build_azure_sdk_component() { + local source="$1" + shift + cmake -B "build-${source##*/}" -S "$source" \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=/opt/riscv64-azure \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DCMAKE_PREFIX_PATH="/opt/riscv64-ssl;/opt/riscv64-curl;/opt/riscv64-xml2;/opt/riscv64-azure" \ + -DCURL_LIBRARY=/opt/riscv64-curl/lib/libcurl.a \ + -DCURL_INCLUDE_DIR=/opt/riscv64-curl/include \ + -DOPENSSL_ROOT_DIR=/opt/riscv64-ssl \ + -DOPENSSL_INCLUDE_DIR=/opt/riscv64-ssl/include \ + -DOPENSSL_CRYPTO_LIBRARY=/opt/riscv64-ssl/lib/libcrypto.a \ + -DOPENSSL_SSL_LIBRARY=/opt/riscv64-ssl/lib/libssl.a \ + -DLIBXML2_LIBRARY=/opt/riscv64-xml2/lib/libxml2.a \ + -DLIBXML2_INCLUDE_DIR=/opt/riscv64-xml2/include/libxml2 \ + "$@" + cmake --build "build-${source##*/}" -j"$(nproc)" + cmake --install "build-${source##*/}" + } + + build_azure_sdk_component sdk/core/azure-core -DBUILD_TRANSPORT_CURL=ON -DDISABLE_AZURE_CORE_OPENTELEMETRY=ON + build_azure_sdk_component sdk/storage/azure-storage-common + build_azure_sdk_component sdk/storage/azure-storage-blobs + build_azure_sdk_component sdk/identity/azure-identity + + ls -l /opt/riscv64-azure/lib "$LICENSES" + SCRIPT + + - name: Save OpenSSL, curl, zlib, libxml2 and the Azure SDK + if: steps.deps-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: deps + key: ${{ steps.deps-cache.outputs.cache-primary-key }} + + - name: Build the Azure streamer library and package the wheel + run: | + mkdir -p dist + docker run --rm -i --network=host \ + -v "${GITHUB_WORKSPACE}:/work" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-ssl:/opt/riscv64-ssl" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-curl:/opt/riscv64-curl" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-zlib:/opt/riscv64-zlib" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-xml2:/opt/riscv64-xml2" \ + -v "${GITHUB_WORKSPACE}/deps/riscv64-azure:/opt/riscv64-azure" \ + -w /work/runai-model-streamer \ + -e PACKAGE_VERSION="${RUNAI_MODEL_STREAMER_VERSION}" \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash <<'SCRIPT' + set -eux + + # cpp/toolchain links -l:libstdc++.a, which on this image is its own CRB package. + dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel libstdc++-static + JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")" + export JAVA_HOME + install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel + git config --global --add safe.directory '*' + + # cpp/toolchain/rules.bzl expects the Debian cross-toolchain names; this image's gcc is native. + for tool in gcc g++ ar ld cpp gcov nm objdump strip; do + native="$(command -v "$tool" || true)" + [ -n "$native" ] && ln -sf "$native" "/usr/bin/riscv64-linux-gnu-$tool" + done + + cd cpp + # Production build: same config upstream's own release recipe (`make build`) uses. + bazel build azure:libstreamerazure.so --config=riscv64 + cp bazel-bin/azure/libstreamerazure.so /work/libstreamerazure.so + + # Test-only build: upstream's own test recipe (`make -C cpp build_azure_test`) + # additionally enables AZURITE_TESTING so AZURE_STORAGE_CONNECTION_STRING can + # target Azurite's local http://host:port endpoint; the production build above + # has no way to do that (account auth always resolves to https://.). + bazel build azure:libstreamerazure.so --config=riscv64 --config=azurite + cp bazel-bin/azure/libstreamerazure.so /work/libstreamerazure-azurite-test.so + + cd ../py/runai_model_streamer_azure + cp /work/libstreamerazure.so libstreamerazure.so + cp /work/deps/licenses/LICENSE.* . + /opt/python/cp312-cp312/bin/pip install -q setuptools wheel + /opt/python/cp312-cp312/bin/python setup.py bdist_wheel --plat-name manylinux_2_39_riscv64 + cp dist/*.whl /work/dist/ + SCRIPT + + - name: Verify the wheel ships a riscv64 shared object and its licences + run: | + python3 - dist/*.whl <<'EOF' + import sys, zipfile + with zipfile.ZipFile(sys.argv[1]) as zf: + names = zf.namelist() + so = next(n for n in names if n.endswith("libstreamer/lib/libstreamerazure.so")) + header = zf.read(so)[:20] + assert header[:4] == b"\x7fELF", header + assert header[18] == 0xF3, header # EM_RISCV + licenses = {n.rsplit("/", 1)[-1] for n in names if ".dist-info/licenses/" in n} + print(sorted(licenses)) + for dep in ("openssl", "curl", "zlib", "libxml2", "azure-sdk-for-cpp"): + assert f"LICENSE.{dep}" in licenses, dep + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: runai-model-streamer-azure-${{ matrix.version }}-py3-none-manylinux_riscv64 + path: dist/*.whl + if-no-files-found: error + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: runai-model-streamer-azure-${{ matrix.version }}-azurite-test-lib + path: libstreamerazure-azurite-test.so + if-no-files-found: error + + test_wheel: + name: Test runai-model-streamer-azure ${{ 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) }} + python-version: ['3.12', '3.13', '3.14'] + + env: + RUNAI_MODEL_STREAMER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout runai-model-streamer ${{ env.RUNAI_MODEL_STREAMER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: run-ai/runai-model-streamer + ref: ${{ env.RUNAI_MODEL_STREAMER_VERSION }} + path: runai-model-streamer + persist-credentials: false + + - name: Download wheel + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: runai-model-streamer-azure-${{ env.RUNAI_MODEL_STREAMER_VERSION }}-py3-none-manylinux_riscv64 + path: wheelhouse + + - name: Download the Azurite-testing library + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: runai-model-streamer-azure-${{ env.RUNAI_MODEL_STREAMER_VERSION }}-azurite-test-lib + path: azurite-test-lib + + - 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 and upstream's test dependencies + env: + UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ + UV_INDEX_STRATEGY: unsafe-best-match + # requirements.dev's numpy==1.24.4 has no riscv64 wheel and no longer builds on Python >=3.12. + run: | + echo "numpy>=1.26,<3" > "$RUNNER_TEMP/numpy-override.txt" + uv pip install --override "$RUNNER_TEMP/numpy-override.txt" \ + -r runai-model-streamer/py/runai_model_streamer/requirements.dev \ + pytest \ + "runai-model-streamer[azure]==${RUNAI_MODEL_STREAMER_VERSION}" \ + wheelhouse/*.whl + + - name: Run the Azure package's unit tests against the installed wheel + run: | + site="$(python -c 'import runai_model_streamer_azure, os; print(os.path.dirname(os.path.dirname(runai_model_streamer_azure.__file__)))')" + cd "$site" + python -m unittest discover -b runai_model_streamer_azure + + - name: Run upstream's Azure tests against Azurite + working-directory: runai-model-streamer/tests + env: + # Well-known Azurite storage account key: + # https://github.com/Azure/Azurite?tab=readme-ov-file#default-storage-account + AZURE_STORAGE_CONNECTION_STRING: 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;' + AZURE_CONTAINER: test-bucket + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends nodejs npm + npm install -g "azurite@${AZURITE_VERSION}" + mkdir -p "$RUNNER_TEMP/azurite" + azurite --silent --skipApiVersionCheck --location "$RUNNER_TEMP/azurite" --blobPort 10000 --blobHost 127.0.0.1 & + trap 'kill $!' EXIT + + # The published .so has no way to point at Azurite's local endpoint (see the + # "Test-only build" note above); swap in the AZURITE_TESTING-enabled .so built + # alongside it, matching upstream's own `make test` vs `make build` split. + site="$(python -c 'import sys; print(sys.prefix)')" + cp "$GITHUB_WORKSPACE/azurite-test-lib/libstreamerazure-azurite-test.so" \ + "$site/runai_model_streamer/libstreamer/lib/libstreamerazure.so" + pytest -v azure/ + + 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 runai-model-streamer-azure ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + + env: + RUNAI_MODEL_STREAMER_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: runai-model-streamer-azure-${{ env.RUNAI_MODEL_STREAMER_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish runai-model-streamer-azure ${{ 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: runai-model-streamer-azure-${{ matrix.version }}-py3-none-manylinux_riscv64 + gpl-sources-artifact: runai-model-streamer-azure-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/docs/packages/runai-model-streamer-azure.yaml b/docs/packages/runai-model-streamer-azure.yaml new file mode 100644 index 000000000000..1696e35a0909 --- /dev/null +++ b/docs/packages/runai-model-streamer-azure.yaml @@ -0,0 +1,5 @@ +package-name: runai-model-streamer-azure +source-code: https://github.com/run-ai/runai-model-streamer +license: Apache-2.0 AND MIT AND curl AND Zlib +versions: +- version: 0.16.1 diff --git a/patches/runai-model-streamer-azure/0.16.1/0001-toolchain-add-riscv64-as-a-supported-architecture.patch b/patches/runai-model-streamer-azure/0.16.1/0001-toolchain-add-riscv64-as-a-supported-architecture.patch new file mode 100644 index 000000000000..627eb0aadf10 --- /dev/null +++ b/patches/runai-model-streamer-azure/0.16.1/0001-toolchain-add-riscv64-as-a-supported-architecture.patch @@ -0,0 +1,50 @@ +From 820707245ffc7f9f363fc5de3f5375daacb0a8b9 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 24 Sep 2026 03:32:40 +0000 +Subject: [PATCH] toolchain: add riscv64 as a supported architecture + +configure_toolchain() and its per-arch .bazelrc block are already fully +generic over the arch string (native.platform constraint is +@platforms//cpu:, gcc tools are resolved as +/usr/bin/-linux-gnu-) -- riscv64 was simply never added to +either list. + +No -march flag is added for riscv64 (unlike aarch64's, which exists only +for a google-cloud-cpp ARM dependency libstreamer.so does not build +against): a native riscv64 build uses the compiler's own default target. + +Upstream-Status: To upstream [this port does not open issues/PRs against third-party repos; riscv64 toolchain support is exactly the kind of change upstream would want once a downstream build proves it works] +--- + cpp/.bazelrc | 3 +++ + cpp/toolchain/configure.bzl | 2 +- + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/cpp/.bazelrc b/cpp/.bazelrc +index 830e568..4a6ccd2 100644 +--- a/cpp/.bazelrc ++++ b/cpp/.bazelrc +@@ -19,5 +19,8 @@ build:aarch64 --copt="-march=armv8-a+crc+crypto" + build:x86_64 --platform_suffix=x86_64 + build:x86_64 --platforms=@toolchain//:x86_64 + ++build:riscv64 --platform_suffix=riscv64 ++build:riscv64 --platforms=@toolchain//:riscv64 ++ + # Azurite testing support (enables AZURE_STORAGE_ACCOUNT_KEY for local testing) + build:azurite --define azurite_testing=true +diff --git a/cpp/toolchain/configure.bzl b/cpp/toolchain/configure.bzl +index 6b89f54..9a04125 100644 +--- a/cpp/toolchain/configure.bzl ++++ b/cpp/toolchain/configure.bzl +@@ -4,7 +4,7 @@ load("//toolchain:rules.bzl", "get_target_triplet", "runai_crosstool_tools") + + # This should map to the devcontainer Dockerfile + # We install gcc-x86-64-linux-gnu and gcc-aarch64-linux-gnu toolchains +-ARCHITECTURES = ["x86_64", "aarch64"] ++ARCHITECTURES = ["x86_64", "aarch64", "riscv64"] + OS = "linux-gnu" + + def _get_gcc_version(repository_ctx, arch): +-- +2.43.0 + diff --git a/patches/runai-model-streamer-azure/0.16.1/0002-toolchain-use-gcc-s-own-dumpmachine-triplet-for-builtin-include-dirs.patch b/patches/runai-model-streamer-azure/0.16.1/0002-toolchain-use-gcc-s-own-dumpmachine-triplet-for-builtin-include-dirs.patch new file mode 100644 index 000000000000..367c1fc785ba --- /dev/null +++ b/patches/runai-model-streamer-azure/0.16.1/0002-toolchain-use-gcc-s-own-dumpmachine-triplet-for-builtin-include-dirs.patch @@ -0,0 +1,134 @@ +From 75c45793987328b8011252f0f24950e94456b336 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 24 Sep 2026 08:01:06 +0000 +Subject: [PATCH] toolchain: use gcc's own -dumpmachine triplet for builtin + include dirs + +configure_toolchain()'s cxx_builtin_include_directories are built from the +same os+arch naming-convention triplet (-linux-gnu) used to resolve +tool paths (/usr/bin/-linux-gnu-). That equivalence holds for a +genuine Debian cross-toolchain package (a real x86_64-linux-gnu-gcc reports +exactly that as its own -dumpmachine), but not when the tool path is a +same-arch *native* compiler reachable only via a symlink under the expected +name -- its own compiled-in builtin search path (/usr/lib/gcc///include, etc.) still uses its real vendor triplet regardless +of what name it is invoked under. + +This is exactly riscv64's manylinux_riscv64 image: gcc is Red Hat's native +riscv64-redhat-linux build, symlinked to /usr/bin/riscv64-linux-gnu-gcc so +runai_crosstool_tools() finds it. Bazel's own absolute-path sandboxing then +rejects gcc's builtin headers (e.g. stddef.h) because +/usr/lib/gcc/riscv64-redhat-linux//include is not in the declared +riscv64-linux-gnu-based list. + +Thread the compiler's actual -dumpmachine output down as gcc_machine and use +it (falling back to the naming-convention triplet when unset, so x86_64 and +aarch64 are unaffected) for cxx_builtin_include_directories specifically, +while tool_paths keeps using the naming-convention triplet the workflow's +symlinks provide. + +Upstream-Status: To upstream [this port does not open issues/PRs against third-party repos; making cxx_builtin_include_directories match the compiler's own reported triplet is a general robustness fix upstream would likely want] +--- + cpp/toolchain/configure.bzl | 7 ++++++- + cpp/toolchain/template/toolchain.bzl | 23 ++++++++++++++++++++--- + 2 files changed, 26 insertions(+), 4 deletions(-) + +diff --git a/cpp/toolchain/configure.bzl b/cpp/toolchain/configure.bzl +index 9a04125..510ef90 100644 +--- a/cpp/toolchain/configure.bzl ++++ b/cpp/toolchain/configure.bzl +@@ -11,6 +11,10 @@ def _get_gcc_version(repository_ctx, arch): + gcc_tool = runai_crosstool_tools(get_target_triplet(OS, arch))["gcc"] + return repository_ctx.execute([gcc_tool, "-dumpversion"]).stdout.strip() + ++def _get_gcc_machine(repository_ctx, arch): ++ gcc_tool = runai_crosstool_tools(get_target_triplet(OS, arch))["gcc"] ++ return repository_ctx.execute([gcc_tool, "-dumpmachine"]).stdout.strip() ++ + def _get_host_arch(repository_ctx): + return repository_ctx.execute(["/usr/bin/uname", "-m"]).stdout.strip() + +@@ -18,9 +22,10 @@ def _cc_autoconf_toolchain_impl(repository_ctx): + define_statements = [] + for arch in ARCHITECTURES: + gcc_version = _get_gcc_version(repository_ctx, arch) ++ gcc_machine = _get_gcc_machine(repository_ctx, arch) + host_arch = _get_host_arch(repository_ctx) + toolchain_name = arch +- define_statements.append('define_toolchain(name = "%s", os = "%s", host_arch = "%s", arch = "%s", gcc_version = "%s")' % (toolchain_name, OS, host_arch, arch, gcc_version)) ++ define_statements.append('define_toolchain(name = "%s", os = "%s", host_arch = "%s", arch = "%s", gcc_version = "%s", gcc_machine = "%s")' % (toolchain_name, OS, host_arch, arch, gcc_version, gcc_machine)) + + repository_ctx.template( + "BUILD", +diff --git a/cpp/toolchain/template/toolchain.bzl b/cpp/toolchain/template/toolchain.bzl +index 6a4c6cf..36daffc 100644 +--- a/cpp/toolchain/template/toolchain.bzl ++++ b/cpp/toolchain/template/toolchain.bzl +@@ -73,6 +73,15 @@ def _impl(ctx): + target_triplet = get_target_triplet(ctx.attr.os, ctx.attr.arch) + tool_paths = [tool_path(name = k, path = v) for k, v in runai_crosstool_tools(target_triplet).items()] + ++ # gcc_machine is the triplet gcc itself reports (`gcc -dumpmachine`), which is ++ # what its own builtin include search path is actually built from. It is ++ # normally identical to target_triplet (a real x86_64-linux-gnu/aarch64-linux-gnu ++ # cross-gcc reports exactly that), but differs when the "gcc" behind ++ # target_triplet's tool path is a same-arch native compiler under a different ++ # vendor triplet (e.g. Red Hat's riscv64-redhat-linux) made reachable only by a ++ # symlink under the expected name. ++ include_triplet = ctx.attr.gcc_machine if ctx.attr.gcc_machine else target_triplet ++ + # Documented at + # https://docs.bazel.build/versions/main/skylark/lib/cc_common.html#create_cc_toolchain_config_info. + # +@@ -89,7 +98,7 @@ def _impl(ctx): + abi_version = "unknown", + abi_libc_version = "unknown", + tool_paths = tool_paths, +- cxx_builtin_include_directories = _get_include_directories(target_triplet, ctx.attr.gcc_version, ctx.attr.use_cross), ++ cxx_builtin_include_directories = _get_include_directories(include_triplet, ctx.attr.gcc_version, ctx.attr.use_cross), + features = features + ) + +@@ -111,6 +120,11 @@ _toolchain_config = rule( + mandatory = True, + doc = "GCC major version to use (eg: 9)", + ), ++ "gcc_machine": attr.string( ++ mandatory = False, ++ default = "", ++ doc = "The triplet `gcc -dumpmachine` reports, if it differs from os/arch's own naming convention", ++ ), + "use_cross": attr.bool( + mandatory = False, + default = False, +@@ -121,7 +135,7 @@ _toolchain_config = rule( + ) + + +-def define_toolchain(name, os, host_arch, arch, gcc_version): ++def define_toolchain(name, os, host_arch, arch, gcc_version, gcc_machine = ""): + """"define_toolchain creates rules for a cc_toolchain. + + This expects toolchain tools to exist at a canonical path. +@@ -134,6 +148,9 @@ def define_toolchain(name, os, host_arch, arch, gcc_version): + host_arch: The host architecture for this toolchain + arch: The target architecture for this toochain + gcc_version: The GCC version of this toolchain ++ gcc_machine: The triplet `gcc -dumpmachine` reports for this toolchain's ++ compiler, if it differs from the os/arch naming convention used for ++ the tool paths themselves + """ + + native.platform( +@@ -145,7 +162,7 @@ def define_toolchain(name, os, host_arch, arch, gcc_version): + ) + + toolchain_config_name = "%s_toolchain_config" % name +- _toolchain_config(name = toolchain_config_name, os = os, arch = arch, gcc_version = gcc_version, use_cross = host_arch != arch) ++ _toolchain_config(name = toolchain_config_name, os = os, arch = arch, gcc_version = gcc_version, gcc_machine = gcc_machine, use_cross = host_arch != arch) + + empty_target_name = "%s_empty" % name + empty_target_label = ":%s" % empty_target_name +-- +2.43.0 + diff --git a/patches/runai-model-streamer-azure/0.16.1/0003-third_party-add-riscv64-to-the-Azure-SDK-library-select.patch b/patches/runai-model-streamer-azure/0.16.1/0003-third_party-add-riscv64-to-the-Azure-SDK-library-select.patch new file mode 100644 index 000000000000..8baede7ce57a --- /dev/null +++ b/patches/runai-model-streamer-azure/0.16.1/0003-third_party-add-riscv64-to-the-Azure-SDK-library-select.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 13 Oct 2026 06:00:00 +0000 +Subject: [PATCH] third_party: add riscv64 to the Azure SDK library select + +The @azure alias selects a per-arch azure_library() (headers and static +libraries under /opt/-azure, /opt/-curl, ...) by target CPU, +but only has x86_64 and aarch64 branches, so any other --config fails +with "configurable attribute doesn't match this configuration". +azure_library() itself is already generic over the arch string; add the +matching config_setting and branch for riscv64 (same shape as the S3 +backend's identical gap in cpp/third_party/aws.BUILD). + +Upstream-Status: To upstream [this port does not open issues/PRs against third-party repos; riscv64 support in the Azure backend is exactly the kind of change upstream would want once a downstream build proves it works] +--- + cpp/third_party/azure.BUILD | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/cpp/third_party/azure.BUILD b/cpp/third_party/azure.BUILD +index 518e3b5..8b8c887 100644 +--- a/cpp/third_party/azure.BUILD ++++ b/cpp/third_party/azure.BUILD +@@ -23,13 +23,22 @@ config_setting( + ], + ) + ++config_setting( ++ name = "target_riscv64", ++ constraint_values = [ ++ "@platforms//cpu:riscv64", ++ ], ++) ++ + azure_library(name = "azure_aarch", arch = "aarch64") + azure_library(name = "azure_x86_64", arch = "x86_64") ++azure_library(name = "azure_riscv64", arch = "riscv64") + + alias( + name = "azure", + actual = select({ + ":target_x86_64": ":azure_x86_64", + ":target_aarch64": ":azure_aarch", ++ ":target_riscv64": ":azure_riscv64", + }), + ) +-- +2.43.0 diff --git a/patches/runai-model-streamer-azure/0.16.1/0004-azure-ship-the-licences-of-the-statically-linked-dependencies.patch b/patches/runai-model-streamer-azure/0.16.1/0004-azure-ship-the-licences-of-the-statically-linked-dependencies.patch new file mode 100644 index 000000000000..07142cfbee24 --- /dev/null +++ b/patches/runai-model-streamer-azure/0.16.1/0004-azure-ship-the-licences-of-the-statically-linked-dependencies.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 13 Oct 2026 06:00:00 +0000 +Subject: [PATCH] azure: ship the licences of the statically linked + dependencies + +libstreamerazure.so statically links the Azure SDK for C++ (azure-core, +azure-storage-common, azure-storage-blobs, azure-identity), OpenSSL, +libcurl, zlib and libxml2, but the explicit license_files=("LICENSE",) +only ships the project's own licence. curl's licence and libxml2's and +the Azure SDK's MIT notices require their text to travel with the +binary. Also match LICENSE.* next to setup.py, where the build stages +each dependency's licence text (same fix as the S3 and GCS backends). + +Upstream-Status: To upstream [this port does not open issues/PRs against third-party repos; the upstream wheels have the same gap] +--- + py/runai_model_streamer_azure/setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/py/runai_model_streamer_azure/setup.py b/py/runai_model_streamer_azure/setup.py +index 1d9c8e0..5e2f0c1 100644 +--- a/py/runai_model_streamer_azure/setup.py ++++ b/py/runai_model_streamer_azure/setup.py +@@ -22,7 +22,7 @@ assert_lib_exists() + setup( + name="runai-model-streamer-azure", + version=VERSION, +- license_files=("LICENSE",), ++ license_files=("LICENSE", "LICENSE.*"), + packages=find_packages(), + install_requires=["azure-storage-blob", "azure-identity"], + data_files=[("/runai_model_streamer/libstreamer/lib/", [LIB])], +-- +2.43.0 From 628d9cd75f591840c8ee3b2d8c99152dc31f9006 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 22 Oct 2026 11:07:40 +0000 Subject: [PATCH 2/3] runai-model-streamer-azure: disable the Azure SDK's automatic vcpkg fetch Upstream configures the Azure SDK for C++ with a cross CMAKE_TOOLCHAIN_FILE, which skips its az_vcpkg_integrate() auto-fetch. The native riscv64 build passes no toolchain file, so the SDK cloned vcpkg and tried to bootstrap it, which fails on riscv64 and aborts the azure-core configure. Set AZURE_SDK_DISABLE_AUTO_VCPKG so CMake uses the OpenSSL, curl and libxml2 built earlier in the same step, as upstream does. --- .github/workflows/build-runai-model-streamer-azure.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-runai-model-streamer-azure.yml b/.github/workflows/build-runai-model-streamer-azure.yml index dea85d97106d..bf16c87c21f9 100644 --- a/.github/workflows/build-runai-model-streamer-azure.yml +++ b/.github/workflows/build-runai-model-streamer-azure.yml @@ -235,6 +235,9 @@ jobs: cd /tmp/azure-sdk-cpp cp LICENSE.txt "$LICENSES/LICENSE.azure-sdk-for-cpp" + # Upstream passes a cross CMAKE_TOOLCHAIN_FILE; without one the SDK fetches vcpkg, which cannot bootstrap on riscv64. + export AZURE_SDK_DISABLE_AUTO_VCPKG=1 + build_azure_sdk_component() { local source="$1" shift From 75739a694091b6628c93ec53f91fa9904c67c171 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 15:53:04 +0000 Subject: [PATCH 3/3] runai-model-streamer-azure: raise the bazel bootstrap timeout to 720 minutes With the bazel-7.6.1 cache cold, the bootstrap job was killed by its 120-minute timeout at action 3,399 of 3,931 of compile.sh, still making progress, so the build job was skipped. The previous cold bootstrap of the same version took 98 minutes on a less loaded runner. Use the 720-minute limit the other bazel bootstrap jobs in this repo use. --- .github/workflows/build-runai-model-streamer-azure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-runai-model-streamer-azure.yml b/.github/workflows/build-runai-model-streamer-azure.yml index bf16c87c21f9..dcc8376b0616 100644 --- a/.github/workflows/build-runai-model-streamer-azure.yml +++ b/.github/workflows/build-runai-model-streamer-azure.yml @@ -57,7 +57,7 @@ jobs: if: needs.setup.outputs.versions != '[]' name: Bootstrap bazel (riscv64) runs-on: ubuntu-24.04-riscv - timeout-minutes: 120 + timeout-minutes: 720 steps: - name: Restore bazel binary