From c8e8ad156b47591e1abfbf1e85ab661c6e1f17d3 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 12 Sep 2026 09:34:51 +0200 Subject: [PATCH] xrootd: add build-xrootd.yml for riscv64 wheels --- .github/workflows/build-xrootd.yml | 182 +++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 .github/workflows/build-xrootd.yml diff --git a/.github/workflows/build-xrootd.yml b/.github/workflows/build-xrootd.yml new file mode 100644 index 00000000000..ea638266531 --- /dev/null +++ b/.github/workflows/build-xrootd.yml @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/xrootd/xrootd/blob/master/.github/workflows/python.yml +name: Build xrootd wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'xrootd version to build (git tag without leading v, e.g. 6.1.1)' + required: true + default: '6.1.1' + pull_request: + paths: + - '.github/workflows/build-xrootd.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '6.1.1' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + XROOTD_VERSION: ${{ inputs.version || '6.1.1' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build xrootd ${{ inputs.version || '6.1.1' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + - name: Checkout xrootd v${{ env.XROOTD_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: xrootd/xrootd + ref: v${{ env.XROOTD_VERSION }} + persist-credentials: false + + # The tag's VERSION file is an unexpanded `git-archive` export-subst + # placeholder (`$Format:%(describe)$`); setup.py and CMake both fall + # back to `git describe`, which a shallow single-ref checkout can't + # answer. Writing the real version here is the officially supported + # override (cmake/XRootDVersion.cmake reads this file first). + - name: Set version from tag + run: echo -n '${{ env.XROOTD_VERSION }}' > VERSION + + - name: Stage the licence-collection script + run: | + cat > collect-licenses.sh <<'COLLECT_EOF' + #!/bin/bash + # SPDX-FileCopyrightText: 2026 The RISE Project + # SPDX-License-Identifier: MIT + # + # Stage, at the project root, the licence of every shared library + # auditwheel vendors out of the build image alongside the XRootD + # client libraries (OpenSSL, Kerberos, libuuid and their closure). + # setuptools' default LICENSE* glob copies them into the wheel. + set -euo pipefail + + project="${1:?usage: collect-licenses.sh }" + + # ldd is transitive, so these roots cover their whole closure; ldd + # does not list the roots themselves, so resolve those too. libcrypt + # is not pulled in by any -devel package above: XrdUtils' own CMake + # probes for crypt() directly and links libc-adjacent libxcrypt, + # which the base image already ships. + mapfile -t libs < <( + { + for root in libssl.so libcrypto.so libkrb5.so libuuid.so libcrypt.so; do + path="/usr/lib64/${root}" + [ -e "${path}" ] || continue + ldd "${path}" | tr ' ' '\n' | grep '^/' + readlink -f "${path}" + done + } | sort -u + ) + + # `rpm -qf` reports unowned files on stdout, so keep only bare package names. + # glibc, the gcc runtime and zlib are on auditwheel's manylinux allowlist + # (confirmed against the upstream x86_64 wheel's auditwheel-vendored set) + # and are never vendored into the wheel; zlib-ng-compat also ships no + # licence file via any rpm metadata path, so it would break the loop below. + mapfile -t pkgs < <( + rpm -qf --qf '%{NAME}\n' "${libs[@]}" 2>/dev/null | + grep -E '^[A-Za-z0-9._+-]+$' | sort -u | + grep -vE '^(glibc|libgcc|libstdc\+\+|gcc|zlib-ng-compat)$' + ) + + for pkg in "${pkgs[@]}"; do + mapfile -t files < <(rpm -q --licensefiles "$pkg" 2>/dev/null || true) + + # Some subpackages leave the licence to a sibling of the same source RPM. + if [ -z "${files[0]:-}" ]; then + srpm=$(rpm -q --qf '%{SOURCERPM}\n' "$pkg") + mapfile -t files < <( + rpm -qa --qf '%{SOURCERPM} %{NAME}\n' | + awk -v s="$srpm" '$1 == s { print $2 }' | + xargs -r rpm -q --licensefiles 2>/dev/null | sort -u + ) + fi + + # Others mark it %doc rather than %license, and the image installs no docs. + if [ -z "${files[0]:-}" ]; then + dnf -y --disablerepo=extras reinstall --setopt=tsflags= "$pkg" >/dev/null + mapfile -t files < <(rpm -qd "$pkg" | grep -iE '/(LICEN[CS]E|COPYING|NOTICE)') + fi + + for f in "${files[@]}"; do + [ -f "$f" ] || continue + cp "$f" "$project/LICENSE.${pkg}.$(basename "$f")" + done + compgen -G "$project/LICENSE.$pkg.*" >/dev/null || + { echo "no licence file found for $pkg" >&2; exit 1; } + done + + ls -1 "$project"/LICENSE.* | sed "s|$project/||" + COLLECT_EOF + + - 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 }} + CIBW_BEFORE_ALL_LINUX: >- + dnf install -y krb5-devel libuuid-devel openssl-devel && + bash {project}/collect-licenses.sh {project} + CIBW_ENVIRONMENT: CMAKE_ARGS=-DCMAKE_BUILD_TYPE=Release + # Upstream's own manylinux job test step (python.yml): import the + # extension and the pure-Python client, then build a FileSystem + # object (no network I/O, so no server needed). + CIBW_TEST_COMMAND: | + python -m pip show xrootd && + python -c "import XRootD; print(XRootD)" && + python -c "import pyxrootd; print(pyxrootd)" && + python -c "from XRootD import client; help(client)" && + python -c "from XRootD import client; print(client.FileSystem('root://localhost'))" + + - name: Verify the wheel ships the compiled extension and licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + names = zipfile.ZipFile(sys.argv[1]).namelist() + sos = sorted(n for n in names if n.endswith(".so")) + print("\n".join(sos)) + assert any("/client.cpython" in "/" + n for n in sos), sos + + lic = sorted(n.split("/")[-1] for n in names if ".dist-info/licenses/" in n and not n.endswith("/")) + print("\n".join(lic)) + expected_pkgs = {"openssl-libs", "krb5-libs", "libuuid", "libxcrypt"} + have_pkgs = {f.split(".", 2)[1] for f in lic if f.startswith("LICENSE.")} + assert expected_pkgs <= have_pkgs, expected_pkgs - have_pkgs + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: xrootd-${{ env.XROOTD_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish xrootd ${{ inputs.version || '6.1.1' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: xrootd-${{ inputs.version || '6.1.1' }}-*-manylinux_riscv64