diff --git a/.github/workflows/build-iterfzf.yml b/.github/workflows/build-iterfzf.yml new file mode 100644 index 00000000000..485e96217ba --- /dev/null +++ b/.github/workflows/build-iterfzf.yml @@ -0,0 +1,177 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/dahlia/iterfzf/blob/1.9.0.67.0/.github/workflows/release.yaml +# and https://github.com/dahlia/iterfzf/blob/1.9.0.67.0/.github/workflows/test.yaml +name: Build iterfzf wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/iterfzf.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-iterfzf.yml' + - 'docs/packages/iterfzf.yaml' + - 'patches/iterfzf/**' + push: + branches: [main] + paths: + - '.github/workflows/build-iterfzf.yml' + - 'docs/packages/iterfzf.yaml' + - 'patches/iterfzf/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: iterfzf + 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 iterfzf ${{ matrix.version }} py3-none-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 30 + + env: + ITERFZF_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout iterfzf ${{ matrix.version }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: dahlia/iterfzf + ref: ${{ env.ITERFZF_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/iterfzf/${{ env.ITERFZF_VERSION }}/*.patch + + - name: Install Python + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + python-version: '3.12' + activate-environment: true + enable-cache: false + + - name: Install build + run: uv pip install build + + # GITHUB_TOKEN lifts the anonymous rate limit on the release lookup + # build_dist.py makes to resolve the fzf asset, as upstream's own jobs do. + - name: Build wheel + env: + GITHUB_TOKEN: ${{ github.token }} + run: GOOS=linux GOARCH=riscv64 python -m build --wheel . + + - name: Verify the wheel ships the riscv64 fzf binary + run: | + python - dist/*.whl <<'EOF' + import sys, zipfile + with zipfile.ZipFile(sys.argv[1]) as zf: + names = zf.namelist() + print("\n".join(names)) + assert sys.argv[1].endswith("-py3-none-manylinux_2_39_riscv64.whl"), sys.argv[1] + assert "iterfzf/fzf" in names, names + assert "iterfzf/fzf.exe" not in names, names + licenses = sorted(n.rsplit("/", 1)[-1] for n in names if ".dist-info/licenses/" in n) + assert licenses == ["LICENSE"], licenses + header = zf.read("iterfzf/fzf")[:20] + assert header[:4] == b"\x7fELF", header + assert header[18] == 0xF3, header + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: iterfzf-${{ env.ITERFZF_VERSION }}-py3-none-manylinux_riscv64 + path: dist/*.whl + if-no-files-found: error + + test_wheel: + name: Test iterfzf ${{ 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', '3.14t'] + + env: + ITERFZF_VERSION: ${{ matrix.version }} + + steps: + - name: Download wheel + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: iterfzf-${{ env.ITERFZF_VERSION }}-py3-none-manylinux_riscv64 + + - 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 . iterfzf + uv pip install pytest + + # tox.ini's first two commands; the other two assert that iterfzf([]) + # returns None, which raises AttributeError at this tag on every platform. + - name: Run upstream's wheel checks + run: | + python -c "import sys; sys.path.remove(''); from iterfzf import BUNDLED_EXECUTABLE as e; assert e" + python -c "import sys; sys.path.remove(''); from iterfzf import BUNDLED_EXECUTABLE as e; import os; assert os.execl(e, e, '--version') == 0" + + # The suite resolves fzf from PATH, so it runs the wheel's own binary. + # The three deselected cases open the full-screen picker and need an + # interactive terminal, which is why upstream's CI runs tox and not this. + - name: Run upstream's tests + run: | + PATH="$(python -c 'import iterfzf, os; print(os.path.dirname(iterfzf.__file__))'):$PATH" \ + python -m pytest --pyargs iterfzf.test_iterfzf -v \ + -k 'not test_no_query and not test_supports_color_kwarg and not test_select_one_ambiguous' + + publish: + name: Publish iterfzf ${{ matrix.version }} + needs: [setup, build_wheel, test_wheel] + 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: iterfzf-${{ matrix.version }}-py3-none-manylinux_riscv64 diff --git a/docs/packages/iterfzf.yaml b/docs/packages/iterfzf.yaml new file mode 100644 index 00000000000..aeb339b6e8a --- /dev/null +++ b/docs/packages/iterfzf.yaml @@ -0,0 +1,6 @@ +package-name: iterfzf +source-code: https://github.com/dahlia/iterfzf +license: GPL-3.0-or-later +versions: +- version: 1.9.0.67.0 + patched: true diff --git a/patches/iterfzf/1.9.0.67.0/0001-build_dist-add-riscv64-to-the-platform-tables.patch b/patches/iterfzf/1.9.0.67.0/0001-build_dist-add-riscv64-to-the-platform-tables.patch new file mode 100644 index 00000000000..97c3ee23963 --- /dev/null +++ b/patches/iterfzf/1.9.0.67.0/0001-build_dist-add-riscv64-to-the-platform-tables.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 14:05:00 +0000 +Subject: [PATCH] build_dist: add riscv64 to the platform tables + +Upstream-Status: To upstream [not yet submitted; python-wheels does not open issues/PRs on third-party repos] + +build_dist.py bundles the fzf binary published by junegunn/fzf and names the +wheel from two parallel tables. Neither has a riscv64 row, so the build stops +before it downloads anything: + + KeyError: ('linux', 'riscv64') # GOOS/GOARCH given + RuntimeError: unsupported platform: ('linux', 'riscv64') # native build + +fzf 0.67.0, the version this release bundles, is the first to publish +fzf-0.67.0-linux_riscv64.tar.gz, so only the tables are missing. +goos_goarch_platform_tag_map names the wheel; +manylinux_2_39 is the oldest manylinux profile that exists for riscv64 (glibc +2.39, Rocky 10). platform_machine_goos_goarch_map is the native-detection half +that `pip install iterfzf` from the sdist goes through on a riscv64 host; the +two tables are documented as sharing their key order, so both get the row. + +Signed-off-by: Ludovic Henry +--- + build_dist.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/build_dist.py b/build_dist.py +index 6e5f1ae..dda139c 100644 +--- a/build_dist.py ++++ b/build_dist.py +@@ -206,6 +206,7 @@ goos_goarch_platform_tag_map = { + ("linux", "armv6"): "manylinux_1_2_armv6l", + ("linux", "armv7"): "manylinux_1_2_armv7l", + ("linux", "ppc64le"): "manylinux_2_17_ppc64le.manylinux2014_ppc64le", ++ ("linux", "riscv64"): "manylinux_2_39_riscv64", + ("linux", "s390x"): "manylinux_2_17_s390x.manylinux2014_s390x", + ("windows", "amd64"): "win_amd64", + ("windows", "arm64"): "win_arm64", +@@ -227,6 +228,7 @@ platform_machine_goos_goarch_map = { + ("linux", "armv6l"): ("linux", "armv6"), + ("linux", "armv7l"): ("linux", "armv7"), + ("linux", "ppc64le"): ("linux", "ppc64le"), ++ ("linux", "riscv64"): ("linux", "riscv64"), + ("linux", "s390x"): ("linux", "s390x"), + ("win32", "AMD64"): ("windows", "amd64"), + ("win32", "aarch64"): ("windows", "arm64"),