From e5eca074d4456582f0faee8dd06bde388abf5cc8 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Wed, 14 Oct 2026 00:54:50 +0000 Subject: [PATCH 1/4] xy: Add version 0.0.8 --- .github/workflows/build-xy.yml | 182 +++++++++++++++++++++++++++++++++ docs/packages/xy.yaml | 5 + 2 files changed, 187 insertions(+) create mode 100644 .github/workflows/build-xy.yml create mode 100644 docs/packages/xy.yaml diff --git a/.github/workflows/build-xy.yml b/.github/workflows/build-xy.yml new file mode 100644 index 000000000000..ca7454eb68b0 --- /dev/null +++ b/.github/workflows/build-xy.yml @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on upstream's release build: +# https://github.com/reflex-dev/xy/blob/v0.0.8/.github/workflows/build_release_artifacts.yml +name: Build xy wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/xy.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-xy.yml' + - 'docs/packages/xy.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-xy.yml' + - 'docs/packages/xy.yaml' + +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 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: xy + version: ${{ inputs.version }} + + build_sdist: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Build xy ${{ matrix.version }} sdist + runs-on: ubuntu-latest + env: + XY_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout xy v${{ env.XY_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: reflex-dev/xy + ref: v${{ env.XY_VERSION }} + fetch-depth: 0 + persist-credentials: false + + - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + enable-cache: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + + - name: Install JS build toolchain + run: npm ci + + - name: Build sdist + run: uv build --sdist + + - name: Verify sdist contents + run: python3 scripts/verify_sdist.py dist/*.tar.gz + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: xy-${{ env.XY_VERSION }}-sdist + path: dist/*.tar.gz + if-no-files-found: error + + build_wheels: + needs: [setup, build_sdist] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Build xy ${{ matrix.version }} py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + env: + XY_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout xy v${{ env.XY_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: reflex-dev/xy + ref: v${{ env.XY_VERSION }} + persist-credentials: false + + - name: Download sdist + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: xy-${{ env.XY_VERSION }}-sdist + path: dist/ + + - id: sdist_path + run: echo "path=$(echo dist/*.tar.gz)" >> "$GITHUB_OUTPUT" + + # The Rust core is a ctypes-loaded cdylib, so the wheel is py3-none: one + # build serves every interpreter and the other identifiers only re-test it. + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: ${{ steps.sdist_path.outputs.path }} + output-dir: wheelhouse/ + env: + CIBW_BUILD: cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64 cp314t-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BEFORE_ALL_LINUX: >- + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + CIBW_ENVIRONMENT_LINUX: >- + PATH="$PATH:$HOME/.cargo/bin" + XY_REQUIRE_CARGO=1 + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # Upstream's full suite needs Chromium, Node and the repo's scripts/; + # these files cover the native core and the exporters without them. + CIBW_TEST_REQUIRES: pytest hypothesis pillow + CIBW_TEST_SOURCES: tests + CIBW_TEST_COMMAND: >- + python -c "import xy.kernels as k; assert k.BACKEND == 'native', k.BACKEND" && + python -m pytest -q + tests/test_kernels.py + tests/test_import.py + tests/test_lod.py + tests/test_ooc.py + tests/test_jpeg.py + tests/test_webp.py + tests/test_image_export.py + tests/test_png_export.py + tests/test_pdf_export.py + tests/test_spatial.py + tests/test_figure.py + tests/test_channel.py + tests/test_scatter.py + tests/test_streaming.py + tests/test_range_indices_rows.py + tests/test_select_polygon.py + tests/test_density_mean_color.py + + - name: Verify wheel contents + run: python3 scripts/verify_wheel.py wheelhouse/*.whl --expect-native + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: xy-${{ env.XY_VERSION }}-py3-none-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish xy ${{ 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: xy-${{ matrix.version }}-py3-none-manylinux_riscv64 diff --git a/docs/packages/xy.yaml b/docs/packages/xy.yaml new file mode 100644 index 000000000000..ad959b44e86b --- /dev/null +++ b/docs/packages/xy.yaml @@ -0,0 +1,5 @@ +package-name: xy +source-code: https://github.com/reflex-dev/xy +license: Apache-2.0 +versions: +- version: 0.0.8 From ca48106cd069acccac0b330d1377e3b87092f227 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 15 Oct 2026 12:16:49 +0000 Subject: [PATCH 2/4] build-xy: extract the sdist before handing it to cibuildwheel cibuildwheel extracts a .tar.gz package-dir to its own temp directory and chdirs the whole build into it, so CIBW_TEST_SOURCES (resolved against the new cwd) never saw the tests/ staged at the checkout root; the job built and installed the wheel fine and only failed ~34 minutes in, in testing, with "cibuildwheel: Test source tests does not exist." Extracting the sdist ourselves and pointing package-dir at the resulting directory keeps the build rooted where the rest of the job expects it. --- .github/workflows/build-xy.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-xy.yml b/.github/workflows/build-xy.yml index ca7454eb68b0..f323253349af 100644 --- a/.github/workflows/build-xy.yml +++ b/.github/workflows/build-xy.yml @@ -111,8 +111,14 @@ jobs: name: xy-${{ env.XY_VERSION }}-sdist path: dist/ + # cibuildwheel chdirs into a temp extraction of a .tar.gz package-dir, so + # CIBW_TEST_SOURCES (resolved against the new cwd) would never see the + # tests/ staged next to it here; extract it ourselves and point + # package-dir at the resulting directory instead (gotcha 251/281). - id: sdist_path - run: echo "path=$(echo dist/*.tar.gz)" >> "$GITHUB_OUTPUT" + run: | + tar zxf dist/*.tar.gz -C dist + echo "path=$(echo dist/*/ | sed 's:/$::')" >> "$GITHUB_OUTPUT" # The Rust core is a ctypes-loaded cdylib, so the wheel is py3-none: one # build serves every interpreter and the other identifiers only re-test it. From fa1c5de552a5c911f6bbaf652f8bee36f413434a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 25 Oct 2026 17:58:37 +0000 Subject: [PATCH 3/4] build-xy: patch verify_wheel.py's compressed-tag filename check The riscv64 build job failed in the 'Verify wheel contents' step for every leg, all 2 hours in: scripts/verify_wheel.py's _require_filename_tag compares the wheel filename's platform segment against WHEEL's Tag: lines as one string, but auditwheel repairs a riscv64 wheel to the compressed tag set manylinux_2_34_riscv64.manylinux_2_39_riscv64 (its policy floor sits below the manylinux_2_39 build image), which WHEEL still lists as two separate Tag: lines. Neither side is wrong; the comparison is (gotcha 510 in the porting skill's references). Patches scripts/verify_wheel.py (checked out fresh from reflex-dev/xy, not carried in our sdist) to split the filename's platform segment on '.' and check each resulting tag individually, which keeps the single-tag case unchanged. Verified against the actual failing filename/WHEEL-tags pair from run 38733621391, plus the single-tag and genuinely-missing-tag cases, with the patched function imported directly. --- .github/workflows/build-xy.yml | 13 ++++ docs/packages/xy.yaml | 1 + ...-compressed-manylinux-tag-set-in-ver.patch | 60 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 patches/xy/0.0.8/0001-scripts-accept-a-compressed-manylinux-tag-set-in-ver.patch diff --git a/.github/workflows/build-xy.yml b/.github/workflows/build-xy.yml index f323253349af..6d0f76717b10 100644 --- a/.github/workflows/build-xy.yml +++ b/.github/workflows/build-xy.yml @@ -105,6 +105,19 @@ jobs: ref: v${{ env.XY_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + # auditwheel's riscv64 policy floor (manylinux_2_34) sits below the + # manylinux_2_39 build image, so the wheel comes out tagged for both and + # scripts/verify_wheel.py's filename check (a single-string comparison) + # chokes on the resulting compressed tag set (CLAUDE.md gotcha 510). + - name: Patch xy source + run: git apply python-wheels/patches/xy/${{ env.XY_VERSION }}/*.patch + - name: Download sdist uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/docs/packages/xy.yaml b/docs/packages/xy.yaml index ad959b44e86b..fe92b86d2a0b 100644 --- a/docs/packages/xy.yaml +++ b/docs/packages/xy.yaml @@ -3,3 +3,4 @@ source-code: https://github.com/reflex-dev/xy license: Apache-2.0 versions: - version: 0.0.8 + patched: true diff --git a/patches/xy/0.0.8/0001-scripts-accept-a-compressed-manylinux-tag-set-in-ver.patch b/patches/xy/0.0.8/0001-scripts-accept-a-compressed-manylinux-tag-set-in-ver.patch new file mode 100644 index 000000000000..829124e64cff --- /dev/null +++ b/patches/xy/0.0.8/0001-scripts-accept-a-compressed-manylinux-tag-set-in-ver.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 25 Oct 2026 17:57:24 +0000 +Subject: [PATCH] scripts: accept a compressed manylinux tag set in + verify_wheel's filename check + +Upstream-Status: Inappropriate [riscv64 auditwheel policy floor sits below our build image, a shape upstream's own cross-compiled legs never produce] + +auditwheel's riscv64 policy floor is manylinux_2_34, one below the +manylinux_2_39 build image our workflow pins, so `auditwheel repair` +tags the wheel for both and the filename's platform segment becomes a +dot-joined compressed tag set: +`manylinux_2_34_riscv64.manylinux_2_39_riscv64`. WHEEL still lists each +one as its own `Tag:` line, which is what pip reads to decide +installability, but _require_filename_tag compared the whole compressed +string against that list as one token and never matched, failing every +riscv64 build: + + wheel verification failed for + wheelhouse/xy-0.0.8-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl: + wheel filename tag + 'py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64' is not + listed in WHEEL tags + ['py3-none-manylinux_2_34_riscv64', 'py3-none-manylinux_2_39_riscv64'] + +Upstream's own release legs are all single-tag (zig cross-compiles +pinned to one glibc floor per target, no compressed sets), so this +never surfaces there. Split the filename's platform segment on '.' and +require each resulting tag to be listed, which keeps the single-tag +case working exactly as before. +--- + scripts/verify_wheel.py | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/scripts/verify_wheel.py b/scripts/verify_wheel.py +index 609bc45..f6ee611 100644 +--- a/scripts/verify_wheel.py ++++ b/scripts/verify_wheel.py +@@ -154,8 +154,17 @@ def _filename_tag(path: Path) -> str: + + + def _require_filename_tag(path: Path, tags: list[str]) -> None: ++ # auditwheel's riscv64 policy floor (manylinux_2_34) sits below the build ++ # image (manylinux_2_39), so `auditwheel repair` tags the wheel for both: ++ # the filename's platform segment is a dot-joined *compressed tag set* ++ # (`manylinux_2_34_riscv64.manylinux_2_39_riscv64`), while WHEEL lists each ++ # one as its own `Tag:` line. Check every compressed segment individually ++ # instead of the whole filename tag as one string, which never matches. + filename_tag = _filename_tag(path) +- if filename_tag not in tags: ++ python, abi, platform = filename_tag.split("-", 2) ++ filename_tags = [f"{python}-{abi}-{plat}" for plat in platform.split(".")] ++ missing = [tag for tag in filename_tags if tag not in tags] ++ if missing: + raise AssertionError( + f"wheel filename tag {filename_tag!r} is not listed in WHEEL tags {tags}" + ) +-- +2.43.0 + From 6aaf63ec38d0407a4a5ce20fff6fbacf5afd83ce Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 2 Nov 2026 10:39:47 +0000 Subject: [PATCH 4/4] build-xy: install numpy and pillow from wheels only in the test phase The riscv64 job failed in cibuildwheel's test-requires install: pillow 13.0.0 and numpy 2.5.4 are on PyPI as sdists with no riscv64 wheel, and pip resolves to the newest version across both indexes, so it took them over the registry's pillow 12.3.0 / numpy 2.5.3 wheels and built them from source. numpy compiled (19 minutes for cp312 alone); pillow died on missing libjpeg headers. PIP_ONLY_BINARY=numpy,pillow in CIBW_TEST_ENVIRONMENT makes pip stop at the newest registry wheel instead (gotcha 210). --- .github/workflows/build-xy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-xy.yml b/.github/workflows/build-xy.yml index 6d0f76717b10..cc7562a9e1bf 100644 --- a/.github/workflows/build-xy.yml +++ b/.github/workflows/build-xy.yml @@ -149,6 +149,9 @@ jobs: PATH="$PATH:$HOME/.cargo/bin" XY_REQUIRE_CARGO=1 PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # Without it pip takes PyPI's newer sdist-only releases over our + # registry's wheels and source-builds them (gotcha 210). + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,pillow # Upstream's full suite needs Chromium, Node and the repo's scripts/; # these files cover the native core and the exporters without them. CIBW_TEST_REQUIRES: pytest hypothesis pillow