Skip to content

workflows: add build-apache-tvm-ffi.yml for riscv64 manywheel builds #1

workflows: add build-apache-tvm-ffi.yml for riscv64 manywheel builds

workflows: add build-apache-tvm-ffi.yml for riscv64 manywheel builds #1

# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on upstream's own wheel build/publish setup:
# https://github.com/apache/tvm-ffi/blob/v0.1.12/.github/workflows/publish_wheel.yml
# https://github.com/apache/tvm-ffi/blob/v0.1.12/.github/actions/build-wheel-for-publish/action.yml
name: Build apache-tvm-ffi wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'apache-tvm-ffi version to build (git tag without leading v, e.g. 0.1.12)'
required: true
default: '0.1.12'
pull_request:
paths:
- '.github/workflows/build-apache-tvm-ffi.yml'
concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '0.1.12' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read # to fetch code (actions/checkout)
env:
# `inputs.version` is empty on pull_request events; default to 0.1.12 there.
TVM_FFI_VERSION: ${{ inputs.version || '0.1.12' }}
UV_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/
UV_INDEX_STRATEGY: unsafe-best-match
UV_ONLY_BINARY: ':all:'
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
jobs:
build_sdist:
# The sdist is architecture-independent: it just packages the source tree,
# the vendored submodules (dlpack, libbacktrace) and a setuptools-scm-derived
# _version.py. Build it once on x86 instead of burning a scarce riscv runner
# (see CLAUDE.md gotcha 4). The resulting sdist is self-contained, so the
# riscv wheel job below needs neither git history nor submodules.
name: Build apache-tvm-ffi ${{ inputs.version || '0.1.12' }} sdist
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
sdist_name: ${{ steps.sdist.outputs.sdist_name }}
steps:
- name: Checkout tvm-ffi v${{ env.TVM_FFI_VERSION }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: apache/tvm-ffi
ref: v${{ env.TVM_FFI_VERSION }}
# submodules must be present so pyproject's sdist.include /3rdparty/**
# globs actually bundle dlpack + libbacktrace; setuptools-scm needs the
# full history + tags to derive the version from the checked-out tag.
submodules: recursive
fetch-depth: 0
fetch-tags: true
persist-credentials: false
- name: Install Python
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: '3.12'
activate-environment: true
enable-cache: false
- name: Build sdist
id: sdist
run: |
uv pip install build twine
python -m build --sdist
twine check dist/*
sdists=(dist/*.tar.gz)
echo "sdist_name=$(basename "${sdists[0]}")" >> "$GITHUB_OUTPUT"
- name: Upload sdist artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: apache-tvm-ffi-${{ env.TVM_FFI_VERSION }}-sdist
path: dist/*.tar.gz
if-no-files-found: error
build_wheels:
needs: build_sdist
name: Build apache-tvm-ffi ${{ inputs.version || '0.1.12' }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# Wheels are abi3 (pyproject sets wheel.py-api = "cp312"), so the single
# cp312 build is loadable on 3.12/3.13/3.14; only the free-threaded
# cp314t build needs its own wheel. This is upstream's cibuildwheel
# `build` list (cp38..cp312 + cp314t) collapsed to the two that differ.
python: [cp312, cp314t]
steps:
- name: Download sdist
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: apache-tvm-ffi-${{ env.TVM_FFI_VERSION }}-sdist
path: dist/
- name: Build wheels
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
# cibuildwheel accepts an sdist tarball directly: it extracts it and
# builds from the self-contained tree (no submodules/git needed here).
package-dir: dist/${{ needs.build_sdist.outputs.sdist_name }}
env:
CIBW_BUILD: ${{ matrix.python }}-*
# pyproject pins [tool.cibuildwheel.linux] archs to x86_64+aarch64;
# override so this native riscv runner builds the riscv64 wheel.
CIBW_ARCHS: riscv64
CIBW_SKIP: '*-musllinux_*'
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# The build frontend (build[uv]), test-command
# (pytest {package}/tests/python) and test-groups (["test"] pulls in
# torch/numpy/ml_dtypes/pytest/ninja) are all inherited from the sdist's
# pyproject.toml, so we build and test exactly the way upstream does.
#
# CIBW_ENVIRONMENT applies to BOTH the build and test phases. We only
# point pip/uv at our registry here (needed so test deps resolve, and
# harmless at build time). We deliberately do NOT set ONLY_BINARY here:
# the build backend needs cython>=3.0, which has no riscv64 wheel on
# any index and must be compiled from its sdist inside the container.
CIBW_ENVIRONMENT: >-
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
UV_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
UV_INDEX_STRATEGY=unsafe-best-match
# Test phase only: force wheels-only so a missing riscv64 wheel for a
# heavy test dep (torch/numpy/ml_dtypes) fails fast instead of silently
# kicking off a multi-hour source build under the runner. GPU-only tests
# self-skip via torch.cuda.is_available().
CIBW_TEST_ENVIRONMENT: >-
PIP_ONLY_BINARY=:all:
UV_ONLY_BINARY=:all:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: apache-tvm-ffi-${{ env.TVM_FFI_VERSION }}-${{ matrix.python }}-manylinux_riscv64
path: ./wheelhouse/*.whl
if-no-files-found: error
publish:
name: Publish apache-tvm-ffi ${{ inputs.version || '0.1.12' }} to GitLab
needs: [build_wheels]
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Publish wheels and open docs PR
uses: riseproject-dev/python-wheels/actions/publish-wheels@main
with:
artifact-pattern: apache-tvm-ffi-${{ env.TVM_FFI_VERSION }}-*-manylinux_riscv64
gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }}
gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }}
gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }}
gh-token: ${{ secrets.GITHUB_TOKEN }}