From cbbc258a1cbafe89cb37c7eee4feee4b7166f304 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 26 Sep 2026 01:44:13 +0000 Subject: [PATCH 1/3] Add libuuu riscv64 wheel build libuuu is NXP's ctypes wrapper around libuuu.so, the library half of mfgtools' uuu flashing tool. Upstream's wheels are py3-none- because the only native code is that shared library, compiled from the mfgtools C++ sources by cibuildwheel's before-all. Mirror upstream's build_wrapper.yaml, narrowed to riscv64: the same cibuildwheel build of ./wrapper on cp39 from the uuu_ tag. The vcpkg dependency build is replaced by the image's libusb1, bzip2, zstd, OpenSSL and zlib packages plus a static tinyxml2 10.0.0 (the version upstream's vcpkg baseline resolves; not packaged in Rocky 10). The wheel ships the mfgtools and tinyxml2 licences and those of every library auditwheel vendors, and the copyleft ones' sources are collected for the release. Upstream's tests run on cp39 inside cibuildwheel and on cp310-cp314 afterwards, minus test_get_platform_info, which asserts the architecture is one of upstream's four. --- .github/workflows/build-libuuu.yml | 256 +++++++++++++++++++++++++++++ docs/packages/libuuu.yaml | 5 + 2 files changed, 261 insertions(+) create mode 100644 .github/workflows/build-libuuu.yml create mode 100644 docs/packages/libuuu.yaml diff --git a/.github/workflows/build-libuuu.yml b/.github/workflows/build-libuuu.yml new file mode 100644 index 00000000000..29501cea3ed --- /dev/null +++ b/.github/workflows/build-libuuu.yml @@ -0,0 +1,256 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on the `build-wheels` job of +# https://github.com/nxp-imx/mfgtools/blob/uuu_1.5.243/.github/workflows/build_wrapper.yaml +name: Build libuuu wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/libuuu.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-libuuu.yml' + - 'docs/packages/libuuu.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-libuuu.yml' + - 'docs/packages/libuuu.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 + # The tinyxml2 version upstream's vcpkg-configuration.json baseline resolves. + TINYXML2_VERSION: '10.0.0' + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: libuuu + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build libuuu ${{ matrix.version }} py3-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + LIBUUU_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout mfgtools uuu_${{ env.LIBUUU_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: nxp-imx/mfgtools + ref: uuu_${{ env.LIBUUU_VERSION }} + fetch-depth: 0 + submodules: false + persist-credentials: false + + - name: Validate git versioning + run: | + if ! version=$(git describe --tags --long 2>/dev/null); then + echo "Error: No git tags found. Cannot determine version." + git tag -l + exit 1 + fi + echo "Found 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, in the package directory, the licence of every shared library + # auditwheel vendors out of the build image alongside libuuu.so. + # setuptools' default LICENSE* glob copies them into the wheel. + set -euo pipefail + + package="${1:?usage: collect-licenses.sh }" + lib="${2:?usage: collect-licenses.sh }" + + # ldd is transitive, so libuuu.so alone covers its whole closure. + mapfile -t libs < <(ldd "$lib" | tr ' ' '\n' | grep '^/' | 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 + # and are never vendored into the wheel. + 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" "$package/LICENSE.${pkg}.$(basename "$f")" + done + compgen -G "$package/LICENSE.$pkg.*" >/dev/null || + { echo "no licence file found for $pkg" >&2; exit 1; } + done + + ls -1 "$package"/LICENSE.* | sed "s|$package/||" + COLLECT_EOF + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: wrapper + output-dir: wheelhouse/ + only: cp39-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_ENVIRONMENT_PASS_LINUX: TINYXML2_VERSION + # Upstream's build_linux.sh builds the deps with vcpkg, which has no riscv64 binary + # cache; take them from the image instead, and build tinyxml2 (not in Rocky 10) static. + CIBW_BEFORE_ALL_LINUX: >- + dnf install -y ninja-build libusb1-devel bzip2-devel libzstd-devel openssl-devel zlib-devel && + mkdir -p /tmp/tinyxml2 && + curl -sSLf "https://github.com/leethomason/tinyxml2/archive/refs/tags/${TINYXML2_VERSION}.tar.gz" + | tar -xz -C /tmp/tinyxml2 --strip-components=1 && + cmake -S /tmp/tinyxml2 -B /tmp/tinyxml2/build -G Ninja -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -Dtinyxml2_BUILD_TESTING=OFF -DCMAKE_INSTALL_LIBDIR=lib && + cmake --build /tmp/tinyxml2/build --target install && + cd {package} && + PKG_CONFIG_PATH=/usr/local/lib/pkgconfig cmake -B build -G Ninja && + cmake --build build && + mkdir -p libuuu/lib/linux/riscv64 && + cp build/libuuu.so libuuu/lib/linux/riscv64/ && + cp {project}/LICENSE LICENSE.mfgtools && + cp /tmp/tinyxml2/LICENSE.txt LICENSE.tinyxml2 && + bash {project}/collect-licenses.sh {package} build/libuuu.so + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_SOURCES: wrapper/tests + # test_get_platform_info asserts the architecture is one of upstream's four. + CIBW_TEST_COMMAND: pytest wrapper/tests -k "not test_get_platform_info" + + - name: Check the wheel bundles libuuu.so and the licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + assert "-py3-none-" in whl and whl.endswith("manylinux_2_39_riscv64.whl"), whl + names = zipfile.ZipFile(whl).namelist() + assert "libuuu/lib/linux/riscv64/libuuu.so" in names, names + print("\n".join(n for n in names if n.startswith("libuuu.libs/"))) + lic = sorted(n.split("/")[-1] for n in names if ".dist-info/licenses/" in n and not n.endswith("/")) + print("\n".join(lic)) + for want in ("LICENSE", "LICENSE.mfgtools", "LICENSE.tinyxml2"): + assert want in lic, (want, lic) + expected_pkgs = {"bzip2-libs", "libusb1", "libzstd", "openssl-libs", "systemd-libs"} + have_pkgs = {f.split(".", 2)[1] for f in lic if f.count(".") >= 2} + assert expected_pkgs <= have_pkgs, expected_pkgs - have_pkgs + print(whl, "ok") + EOF + + # The wheel is interpreter-agnostic, but cibuildwheel only tests it on cp39. + - name: Test the wheel on the other interpreters + run: | + docker run --rm -i -v "${GITHUB_WORKSPACE}:/work:ro" "${MANYLINUX_RISCV64_IMAGE}" bash <<'SCRIPT' + set -eux + cp -r /work/wrapper/tests /tmp/tests + cd /tmp + for py in cp310-cp310 cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314; do + "/opt/python/${py}/bin/python" -m venv "/tmp/venv-${py}" + "/tmp/venv-${py}/bin/python" -m pip install -q pytest /work/wheelhouse/*.whl + "/tmp/venv-${py}/bin/python" -m pytest -p no:cacheprovider tests -k "not test_get_platform_info" + done + SCRIPT + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: libuuu-${{ env.LIBUUU_VERSION }}-py3-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 for libuuu ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + + env: + LIBUUU_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # libcap/libusb1/systemd-libs are the copyleft (GPL/LGPL) libraries auditwheel vendors + # out of the build image alongside libuuu.so. + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc libcap libusb1 systemd-libs + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: libuuu-${{ env.LIBUUU_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish libuuu ${{ 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: libuuu-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: libuuu-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc and the copyleft libraries bundled in the wheel diff --git a/docs/packages/libuuu.yaml b/docs/packages/libuuu.yaml new file mode 100644 index 00000000000..c6d820c382f --- /dev/null +++ b/docs/packages/libuuu.yaml @@ -0,0 +1,5 @@ +package-name: libuuu +source-code: https://github.com/nxp-imx/mfgtools +license: BSD-3-Clause +versions: +- version: 1.5.243 From f61c3380c4c2a3a0faf2d9415ff6be39415b2e98 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 26 Sep 2026 02:42:39 +0000 Subject: [PATCH 2/3] libuuu: pin setuptools-scm below 10.2 for the cp39 build The cp39 wheel build crashes in egg_info with "TypeError: expected string or bytes-like object" from importlib_metadata's normalize(). This is not riscv64-specific: it reproduces on x86_64 CPython 3.9 with the same sdist-less checkout. setuptools-scm 10.2.0 (the first 10.x to allow Python 3.9 again) makes its egg_info run() create libuuu.egg-info and write scm_*.json into it before calling setuptools' run(), which is exactly the window setuptools closed by preloading the egg_info.writers entry points (pypa/pyproject-hooks#206). libuuu's in-tree backend declares backend-path = ["."], so pyproject_hooks' _BackendPathFinder yields the wrapper directory's distributions through the stdlib importlib.metadata. On 3.9 a stdlib PathDistribution has no _normalized_name and the half-written egg-info has no Name, so the entry-point scan fails. Python 3.10+ derives the name from the directory and builds fine; setuptools-scm 9.2.2 and 10.2.0-10.3.4 were bisected on cp39 as good and bad respectively. Upstream released 1.5.243 in December 2025 with setuptools-scm 9.2.2, so constrain to <10.2 via PIP_CONSTRAINT and PIP_BUILD_CONSTRAINT. --- .github/workflows/build-libuuu.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-libuuu.yml b/.github/workflows/build-libuuu.yml index 29501cea3ed..929ae06ebde 100644 --- a/.github/workflows/build-libuuu.yml +++ b/.github/workflows/build-libuuu.yml @@ -143,6 +143,7 @@ jobs: # Upstream's build_linux.sh builds the deps with vcpkg, which has no riscv64 binary # cache; take them from the image instead, and build tinyxml2 (not in Rocky 10) static. CIBW_BEFORE_ALL_LINUX: >- + echo 'setuptools-scm<10.2' > /build-constraints.txt && dnf install -y ninja-build libusb1-devel bzip2-devel libzstd-devel openssl-devel zlib-devel && mkdir -p /tmp/tinyxml2 && curl -sSLf "https://github.com/leethomason/tinyxml2/archive/refs/tags/${TINYXML2_VERSION}.tar.gz" @@ -159,6 +160,13 @@ jobs: cp {project}/LICENSE LICENSE.mfgtools && cp /tmp/tinyxml2/LICENSE.txt LICENSE.tinyxml2 && bash {project}/collect-licenses.sh {package} build/libuuu.so + # setuptools-scm 10.2.0 creates the egg-info before setuptools preloads the + # egg_info.writers entry points (pypa/pyproject-hooks#206). With the in-tree + # backend's backend-path = ["."], cp39's stdlib importlib.metadata then sees a + # nameless libuuu.egg-info and egg_info crashes. 1.5.243 was released with 9.2.2. + CIBW_ENVIRONMENT: >- + PIP_CONSTRAINT=/build-constraints.txt + PIP_BUILD_CONSTRAINT=/build-constraints.txt CIBW_TEST_REQUIRES: pytest CIBW_TEST_SOURCES: wrapper/tests # test_get_platform_info asserts the architecture is one of upstream's four. From 7e6abd90d3f153adf47dce568afd3bb247c73449 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 26 Sep 2026 02:52:17 +0000 Subject: [PATCH 3/3] libuuu: upgrade pip in the extra-interpreter test venvs A venv on the image's cp310 is seeded with pip 23.0.1, whose vendored packaging lists no riscv64 in _have_compatible_abi, so it rejects the manylinux_2_39_riscv64 wheel as unsupported on this platform. --- .github/workflows/build-libuuu.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-libuuu.yml b/.github/workflows/build-libuuu.yml index 929ae06ebde..ca73fa2b283 100644 --- a/.github/workflows/build-libuuu.yml +++ b/.github/workflows/build-libuuu.yml @@ -200,6 +200,8 @@ jobs: cd /tmp for py in cp310-cp310 cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314; do "/opt/python/${py}/bin/python" -m venv "/tmp/venv-${py}" + # ensurepip can seed a pip whose packaging predates riscv64 manylinux tags (23.0.1 on cp310). + "/tmp/venv-${py}/bin/python" -m pip install -q -U pip "/tmp/venv-${py}/bin/python" -m pip install -q pytest /work/wheelhouse/*.whl "/tmp/venv-${py}/bin/python" -m pytest -p no:cacheprovider tests -k "not test_get_platform_info" done