Skip to content
Merged
Show file tree
Hide file tree
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
245 changes: 245 additions & 0 deletions .github/workflows/build-vosk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# vosk publishes no sdist and has no GitHub Actions CI; the wheels come from
# https://github.com/alphacep/vosk-api/blob/v0.3.45/travis/Dockerfile.dockcross-manylinux
# and travis/build-wheels-dockcross.sh, whose CROSS_TRIPLE case already names
# riscv64, built here natively in the manylinux image instead of under dockcross.
name: Build vosk wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/vosk.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-vosk.yml'
- 'docs/packages/vosk.yaml'
push:
branches: [main]
paths:
- '.github/workflows/build-vosk.yml'
- 'docs/packages/vosk.yaml'

run-name: build-vosk ${{ inputs.version && format('- {0}', inputs.version) || '' }}

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
# Upstream's Dockerfiles clone these three branch heads unpinned; pinned to the
# commits they resolve to, so a rebuild of this version stays reproducible.
KALDI_REF: bc5baf14231660bd50b7d05788865b4ac6c34481
OPENFST_REF: 18e94e63870ebcf79ebb42b7035cd3cb626ec090
CLAPACK_REF: f4e1647de96343fb20eea08a9d4f0e0d04c31c51
OPENBLAS_VERSION: v0.3.20
VOSK_MODEL: vosk-model-small-en-us-0.15

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: vosk
version: ${{ inputs.version }}

build_wheels:
name: Build vosk ${{ matrix.version }} py3-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 720
needs: [setup]
if: needs.setup.outputs.versions != '[]'

strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}

env:
VOSK_VERSION: ${{ matrix.version }}

steps:
- name: Checkout alphacep/vosk-api
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: alphacep/vosk-api
ref: v${{ env.VOSK_VERSION }}
path: vosk-api
persist-credentials: false

- name: Build wheel
run: |
mkdir -p wheelhouse
set -o pipefail
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e KALDI_REF \
-e OPENFST_REF \
-e CLAPACK_REF \
-e OPENBLAS_VERSION \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT' 2>&1 | tee build.log
set -eux

dnf install -y --disablerepo=extras --setopt=install_weak_deps=False autoconf automake libtool

fetch() {
mkdir -p "$2" && git -C "$2" init -q .
git -C "$2" fetch -q --depth 1 "$1" "$3"
git -C "$2" checkout -q FETCH_HEAD
}

fetch https://github.com/alphacep/kaldi /opt/kaldi "${KALDI_REF}"
cd /opt/kaldi/tools
git clone -q --depth 1 -b "${OPENBLAS_VERSION}" --single-branch https://github.com/xianyi/OpenBLAS
fetch https://github.com/alphacep/clapack /opt/kaldi/tools/clapack "${CLAPACK_REF}"
fetch https://github.com/alphacep/openfst /opt/kaldi/tools/openfst "${OPENFST_REF}"

# RISCV64_GENERIC is 0.3.20's only riscv64 target with plain C kernels (C910V is RVV 0.7.1);
# upstream's build-dockcross-manylinux.sh passes ARMV8 here. COMMON_OPT adds
# -Wno-error=implicit-function-declaration for exports/gensymbol's prototype-less linktest.c,
# which GCC 14 rejects as an error that the rule's own -w cannot suppress; -O2 is the default it replaces.
make -C OpenBLAS ONLY_CBLAS=1 TARGET=RISCV64_GENERIC HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 \
COMMON_OPT="-O2 -Wno-error=implicit-function-declaration" -j "$(nproc)" all
# install drops ONLY_CBLAS, exactly as upstream's two Dockerfiles do: it implies
# NO_LAPACKE, and Makefile.install only copies lapacke.h without it - which
# matrix/kaldi-blas.h includes unconditionally under HAVE_OPENBLAS.
make -C OpenBLAS TARGET=RISCV64_GENERIC HOSTCC=gcc USE_LOCKING=1 USE_THREAD=0 \
COMMON_OPT="-O2 -Wno-error=implicit-function-declaration" PREFIX="$(pwd)/OpenBLAS/install" install

mkdir -p clapack/BUILD && cd clapack/BUILD
# clapack declares cmake_minimum_required(VERSION 2.6), which the image's CMake 4 refuses.
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ..
make -j "$(nproc)" -C F2CLIBS
make -j "$(nproc)" -C BLAS
make -j "$(nproc)" -C SRC
find . -name "*.a" | xargs cp -t ../../OpenBLAS/install/lib

cd /opt/kaldi/tools/openfst
autoreconf -i
CFLAGS="-g -O3" ./configure --prefix=/opt/kaldi/tools/openfst --enable-static --enable-shared \
--enable-far --enable-ngram-fsts --enable-lookahead-fsts --with-pic --disable-bin
make -j "$(nproc)"
make install

cd /opt/kaldi/src
./configure --mathlib=OPENBLAS_CLAPACK --shared --use-cuda=no
sed -i 's: -O1 : -O3 :g' kaldi.mk
make -j "$(nproc)" online2 lm rnnlm

cd /work/vosk-api/src
KALDI_ROOT=/opt/kaldi OPENFST_ROOT=/opt/kaldi/tools/openfst \
OPENBLAS_ROOT=/opt/kaldi/tools/OpenBLAS/install make -j "$(nproc)"

export PATH="/opt/python/cp312-cp312/bin:${PATH}"
export PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
python -m pip install -q auditwheel cffi requests setuptools srt tqdm websockets wheel

# libvosk.so statically links all four, and upstream's wheels carry no licence at all.
cd /work/vosk-api
cp COPYING python/LICENSE
cp /opt/kaldi/COPYING python/LICENSE.kaldi
cp /opt/kaldi/tools/openfst/COPYING python/LICENSE.openfst
cp /opt/kaldi/tools/OpenBLAS/LICENSE python/LICENSE.openblas
cp /opt/kaldi/tools/clapack/COPYING python/LICENSE.clapack

VOSK_SOURCE=/work/vosk-api python -m pip wheel /work/vosk-api/python \
--no-deps --no-build-isolation -w /work/dist
auditwheel repair --plat manylinux_2_39_riscv64 -w /work/wheelhouse /work/dist/*.whl
SCRIPT

- name: Upload build log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vosk-${{ env.VOSK_VERSION }}-build-log
path: build.log

- name: Check the wheel bundles libvosk.so and stays interpreter-agnostic
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 "vosk/libvosk.so" in names, names
assert not any(n.endswith(".so") and ".cpython-" in n for n in names), names
# Everything but libstdc++ is linked statically, so auditwheel vendors nothing;
# a vosk.libs/ here would mean the wheel ships a library with no licence text.
assert not any(n.startswith("vosk.libs/") for n in names), names
for want in ("LICENSE", "LICENSE.kaldi", "LICENSE.openfst", "LICENSE.openblas", "LICENSE.clapack"):
assert any(n.endswith("/" + want) for n in names), (want, names)
print(whl, "ok")
EOF

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vosk-${{ env.VOSK_VERSION }}-py3-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

- name: Test wheel
run: |
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \
-e VOSK_MODEL \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

cd /tmp
for pytag in cp312-cp312 cp313-cp313 cp314-cp314 cp314-cp314t; do
PYTHON_BIN="/opt/python/${pytag}/bin/python"
"${PYTHON_BIN}" -m pip install -q /work/wheelhouse/*.whl
"${PYTHON_BIN}" - /work/vosk-api/python/example/test.wav <<'PY'
import json
import os
import sys
import wave

from vosk import Model, KaldiRecognizer, SetLogLevel

SetLogLevel(-1)
model = Model(model_name=os.environ["VOSK_MODEL"])
wf = wave.open(sys.argv[1], "rb")
rec = KaldiRecognizer(model, wf.getframerate())
rec.SetWords(True)
while True:
data = wf.readframes(4000)
if not data:
break
rec.AcceptWaveform(data)
result = json.loads(rec.FinalResult())
print(result["text"])
assert "zero" in result["text"].split(), result
assert len(result["result"]) >= 10, result
PY
done
SCRIPT

publish:
name: Publish vosk ${{ 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: vosk-${{ matrix.version }}-py3-manylinux_riscv64
5 changes: 5 additions & 0 deletions docs/packages/vosk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: vosk
source-code: https://github.com/alphacep/vosk-api
license: Apache-2.0
versions:
- version: 0.3.45
Loading