fix(void): report pywhispercpp 1.5.0 and load CPU variants at runtime
CI / security (pull_request) Successful in 1m11s
CI / lint (pull_request) Successful in 25s
CI / ai-review (pull_request) Successful in 8s

python3-pywhispercpp was installed as 0.0.0. setup.py passes no version to
setup() and takes it from setuptools-scm, which the Void build did not
install; the template's PYWHISPERCPP_VERSION export is never read. The app
read 0.0.0 as an old binding and skipped GPU device selection. Install
setuptools-scm, pin the version with SETUPTOOLS_SCM_PRETEND_VERSION, and
make the app treat a 0.0.0 distribution as unknown so the runtime feature
probe decides.

The CPU backend was built with every SIMD option off, so the real-time
factor on an AVX2 host was about 1.7. Build with GGML_BACKEND_DL and
GGML_CPU_ALL_VARIANTS instead: one CPU library per instruction-set level,
with the x86-64 baseline always shipped. The binding loads the variants
from its own directory (patches/), and pre_install places them beside the
extension. The app's library preloader skips those variants: they export
identical symbols, so preloading them RTLD_GLOBAL would let one variant
serve every other. The install smoke asserts that the installed package
reports 1.5.0, that the baseline variant holds no AVX code, that the AVX2
variant does, and that the CPU and Vulkan backends load from the package.

Fixes #43
Fixes #44
This commit is contained in:
2026-11-07 00:03:22 +05:30
parent a4a831c3c6
commit 97f93e9492
11 changed files with 376 additions and 66 deletions
+87 -28
View File
@@ -3,7 +3,8 @@
Spec #16 packages four carried templates in void-packages layout under
``packaging/void``; this ticket owns the hard one, ``python3-pywhispercpp``,
built from the pinned PyPI source distribution with the Vulkan backend,
shared libraries, and portable CPU flags. Ticket #28 builds the container
shared libraries, runtime-selected CPU variants (#44), and a version pinned
through setuptools-scm (#43). Ticket #28 builds the container
smoke gate on top; everything asserted here runs offline from sources
already in the repo.
@@ -24,21 +25,27 @@ BUILD_SH = VOID_DIR / "build-release.sh"
INSTALL_SH = REPO_ROOT / "install.sh"
UV_LOCK = REPO_ROOT / "uv.lock"
#: ggml SIMD options pinned off for the plain x86-64 baseline (#957).
#: GGML_NATIVE=OFF alone is not portable: while ggml's native default is on,
#: every GGML_* SIMD option stays on and still compiles -mavx2 -mfma.
#: Mirrors PORTABLE_GGML_OPTIONS in tests/test_flatpak_packaging.py and the
#: CMAKE_ARGS line in packaging/flatpak/python3-dependencies.yaml.
PORTABLE_GGML_OPTIONS = (
"GGML_NATIVE",
"GGML_SSE42",
"GGML_AVX",
"GGML_AVX2",
"GGML_BMI2",
"GGML_FMA",
"GGML_F16C",
#: CMake arguments of the CPU backend (#44): one library per CPU level, loaded
#: at run time, with the x86-64 baseline always shipped. GGML_NATIVE must stay
#: OFF: ggml refuses it with GGML_BACKEND_DL, and it would compile the builder's
#: instruction set into every variant (#938).
REQUIRED_CMAKE_ARGS = (
"-DGGML_NATIVE=OFF",
"-DGGML_BACKEND_DL=ON",
"-DGGML_CPU_ALL_VARIANTS=ON",
"-DBUILD_SHARED_LIBS=ON",
)
#: The binding patch that loads the backend modules from the extension's directory.
BINDING_PATCH = (
VOID_DIR
/ "srcpkgs"
/ "python3-pywhispercpp"
/ "patches"
/ "load-ggml-backends-beside-module.patch"
)
INSTALL_SMOKE = VOID_DIR / "container-release.sh"
#: install_requires of pywhispercpp's setup.py, mapped to the Void binary
#: package names. Each name was verified to exist as srcpkgs/<name>/template
#: in current void-packages (2026-11-05); the container build re-verifies via
@@ -126,21 +133,57 @@ def test_build_style_is_python3_module() -> None:
assert re.search(r"^build_style=python3-module$", _template(), re.M)
def test_portable_cpu_flags_pin_every_ggml_simd_option_off() -> None:
"""No host-native tuning: the plain x86-64 baseline, like the Flatpak."""
text = _template()
for flag in PORTABLE_GGML_OPTIONS:
assert f"-D{flag}=OFF" in text, (
f"template does not pin {flag} off; the package inherits the "
"builder's instruction set and SIGILLs on older CPUs (#938, #957)"
)
def _code_lines() -> str:
"""Template with comment lines stripped; flags live in code, not prose."""
return "\n".join(line for line in _template().splitlines() if not line.lstrip().startswith("#"))
def test_cpu_backend_is_built_as_runtime_selected_variants() -> None:
"""#44: every CPU level is its own library, and the x86-64 baseline ships."""
text = _code_lines()
for arg in REQUIRED_CMAKE_ARGS:
assert arg in text, f"template does not pass {arg}"
def test_no_simd_option_is_forced_on_for_the_whole_build() -> None:
"""A global SIMD switch would give every variant one instruction set (#938, #957)."""
text = _code_lines()
for feature in ("NATIVE", "SSE42", "AVX", "AVX2", "BMI2", "FMA", "F16C", "AVX512"):
assert f"-DGGML_{feature}=ON" not in text, f"GGML_{feature} forced on for every variant"
def test_backends_are_copied_beside_the_extension_before_install() -> None:
"""#44: whisper.cpp writes the modules to build/bin, which setup.py does not install."""
text = _template()
assert "pre_install()" in text
assert "build/temp.*/_pywhispercpp/bin/libggml-*.so" in text
assert "build/lib.*/" in text
assert "msg_error" in text, "the build must fail loudly when no backend module was built"
def test_binding_loads_the_backends_beside_the_extension() -> None:
"""#44: ggml's default search misses the package directory, so the binding loads it."""
assert BINDING_PATCH.is_file(), f"missing binding patch: {BINDING_PATCH}"
text = BINDING_PATCH.read_text(encoding="utf-8")
assert "--- a/src/main.cpp" in text and "+++ b/src/main.cpp" in text
assert " PYBIND11_MODULE(_pywhispercpp, m) {" in text
assert "+ load_ggml_backends_beside_module();" in text
assert "+ ggml_backend_load_all_from_path(dir.c_str());" in text
def test_install_smoke_checks_version_and_cpu_variants() -> None:
"""#43 and #44: the installed binding reports the pinned version, and its variants load."""
text = INSTALL_SMOKE.read_text(encoding="utf-8")
version = re.search(r"^version=(\S+)", _template(), re.M)
assert version, "template declares no version"
assert (
f'[ "$PWC_VERSION" = "{version.group(1)}" ]' in text
), "the install smoke must assert the installed pywhispercpp reports the template version"
assert "libggml-cpu-x64.so" in text and "libggml-cpu-haswell.so" in text
assert "objdump -d" in text, "the baseline variant must be checked for AVX code"
assert "libggml.so.0" in text, "the backend registry lives in libggml"
def test_no_native_tuning_or_cuda_anywhere() -> None:
"""Void has no CUDA toolkit package; portable means portable."""
text = _code_lines()
@@ -181,15 +224,31 @@ def test_build_time_deps_cover_setup_py() -> None:
text = _template()
host = re.search(r"^hostmakedepends=(.*)$", text, re.M)
assert host, "template declares no hostmakedepends"
for dep in ("cmake", "pkg-config", "python3-setuptools", "python3-wheel"):
for dep in (
"cmake",
"pkg-config",
"python3-setuptools",
"python3-setuptools_scm",
"python3-wheel",
):
assert dep in host.group(1), f"{dep} missing from hostmakedepends"
make = re.search(r"^makedepends=(.*)$", text, re.M)
assert make and "python3-devel" in make.group(1), "compiled extension needs python3-devel"
def test_version_is_injected_for_the_offline_build() -> None:
"""The sdist carries no version.txt; setup.py reads PYWHISPERCPP_VERSION."""
assert "PYWHISPERCPP_VERSION" in _template()
def test_version_is_pinned_through_setuptools_scm() -> None:
"""#43: setup() gets no version; setuptools-scm supplies it, so the build must provide it."""
host = re.search(r"^hostmakedepends=(.*)$", _template(), re.M)
assert host and "python3-setuptools_scm" in host.group(
1
), "without setuptools-scm the build reports version 0.0.0"
assert (
"SETUPTOOLS_SCM_PRETEND_VERSION=${version}" in _code_lines()
), "the xbps build root has no git metadata; pin the template version"
installer = (REPO_ROOT / "requirements" / "installer-build.txt").read_text(encoding="utf-8")
assert re.search(
r"^setuptools-scm==", installer, re.M
), "the installer build tools pin setuptools-scm as well"
def test_build_fetches_nothing_beyond_the_sdist() -> None: