build-vosk #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |