Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions .github/workflows/build-apache-tvm-ffi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# 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
Comment thread
luhenry marked this conversation as resolved.
# 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 }}
# Override upstream's build-frontend = "build[uv]". cibuildwheel's audit
# step creates a venv on the *host* runner (not in the container) and,
# when the frontend is build[uv], insists on a host `uv` binary
# (venv.py: `assert uv_path is not None`). The self-hosted riscv runner
# has no host uv, so the abi3 wheel builds fine but the run crashes in
# "Auditing wheel...". Plain `build` uses pip/virtualenv on the host
# instead - the way our local container build was validated.
CIBW_BUILD_FRONTEND: build
# The test-command (pytest {package}/tests/python) and test-groups
# (["test"] pulls in torch/numpy/ml_dtypes/pytest/ninja) are 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:
# Mirror upstream's test-command (pytest {package}/tests/python -vvs)
# but drop two tests that fail only because of this riscv64 runner's
# environment, not the wheel (2300+ others pass; cp314t, which pulls no
# torch, is fully green):
# * test_libinfo.py - every test there shells out to `tvm-ffi-config`
# and asserts the subprocess's stderr is empty. Importing tvm_ffi
# pulls in torch, whose bundled libcpuinfo can't parse this runner's
# /sys CPU topology (core_id reads "-1") and prints
# "Error in cpuinfo: failed to parse ... core_id" to stderr. The
# noise is nondeterministic across the module's subprocess tests, so
# the whole file is dropped via --ignore (an absolute path, so it's
# unambiguous regardless of pytest's rootdir).
# * test_filelock.py::test_concurrent_access - spawns 16 concurrent
# torch-importing workers under a hard 60s wait; the slower riscv64
# runner blows the timeout. Dropped with -k by its (unique) name -
# rootdir-independent, unlike a path-based --deselect (which silently
# no-ops when the nodeid prefix doesn't match pytest's rootdir).
CIBW_TEST_COMMAND: >-
pytest {package}/tests/python -vvs
--ignore {package}/tests/python/test_libinfo.py
-k "not test_concurrent_access"

- 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 }}
Loading