fix(flatpak): bundle onnxruntime for Silero VAD (#1002)

* fix(flatpak): bundle onnxruntime for Silero VAD

Bundle the [vad] extra in the Flatpak and select the matching CPython wheel for each architecture.

Fixes #969

* docs(flatpak): keep the runtime.txt history in the sync script docstring

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-21 00:27:05 -07:00
committed by GitHub
co-authored by Jatin K Malik
parent e53fc2ceb0
commit 1909f45fdf
7 changed files with 277 additions and 54 deletions
+71 -13
View File
@@ -24,6 +24,7 @@ from pathlib import Path
import pytest
from packaging.markers import Marker
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet
from packaging.utils import canonicalize_name
from packaging.version import Version
@@ -43,7 +44,7 @@ PORTABLE_GGML_OPTIONS = (
REPO_ROOT = Path(__file__).resolve().parents[1]
PYPROJECT = REPO_ROOT / "pyproject.toml"
EXPORT = REPO_ROOT / "requirements" / "runtime.txt"
EXPORT = REPO_ROOT / "requirements" / "vad.txt"
FLATPAK_DIR = REPO_ROOT / "packaging" / "flatpak"
DEPS_YAML = FLATPAK_DIR / "python3-dependencies.yaml"
MANIFEST = FLATPAK_DIR / "com.vocalinux.Vocalinux.yml"
@@ -70,6 +71,8 @@ RUNTIME_PROVIDED = {"pycairo"}
#: Build backends for the sdists the sandbox compiles under
#: --no-build-isolation. Never imported at runtime, so never in the export.
BUILD_BACKENDS = {"meson-python", "pyproject-metadata"}
BINARY_ONLY = {"onnxruntime"}
FLATPAK_ARCHES = ("x86_64", "aarch64")
def _locked() -> dict:
@@ -95,7 +98,7 @@ def _locked() -> dict:
return selected
def _flatpak_sources() -> list:
def _flatpak_sources() -> list[dict[str, str | None]]:
"""Every PyPI artifact the Flatpak downloads: name, version, digest, filename."""
lines = DEPS_YAML.read_text(encoding="utf-8").splitlines()
sources = []
@@ -106,6 +109,11 @@ def _flatpak_sources() -> list:
filename = url.group(1)
digest = re.match(r"^\s*sha256: ([0-9a-f]{64})\s*$", lines[index + 1])
assert digest, f"{DEPS_YAML.name}:{index + 2}: PyPI source with no sha256 under it"
arch_match = (
re.match(r"^\s*only-arches: \[([a-z0-9_]+)\]\s*$", lines[index + 2])
if index + 2 < len(lines)
else None
)
if filename.endswith(".whl"):
name, version = filename.split("-")[0], filename.split("-")[1]
else:
@@ -117,6 +125,7 @@ def _flatpak_sources() -> list:
"version": version,
"sha256": digest.group(1),
"filename": filename,
"only_arches": arch_match.group(1) if arch_match else None,
}
)
assert sources, "no PyPI sources found; the parser and the file disagree"
@@ -150,7 +159,7 @@ def test_every_source_is_the_version_the_lock_resolved(source: dict[str, str]) -
)
assert source["version"] == locked["version"], (
f"{source['name']}: Flatpak builds {source['version']},"
f" requirements/runtime.txt pins {locked['version']}."
f" requirements/vad.txt pins {locked['version']}."
" Run `just flatpak-deps`."
)
@@ -164,11 +173,11 @@ def test_every_digest_is_one_uv_recorded(source: dict[str, str]) -> None:
hashes = _locked()[source["name"]]["hashes"]
assert source["sha256"] in hashes, (
f"{source['filename']} carries a digest absent from"
" requirements/runtime.txt. Run `just flatpak-deps`."
" requirements/vad.txt. Run `just flatpak-deps`."
)
def test_the_flatpak_carries_every_runtime_dependency() -> None:
def test_the_flatpak_carries_every_exported_dependency() -> None:
"""A missing one fails at first launch, not at build time, because the
project is installed with --no-deps and nothing resolves the gap."""
missing = sorted(set(_locked()) - set(_flatpak_versions()) - RUNTIME_PROVIDED)
@@ -178,6 +187,49 @@ def test_the_flatpak_carries_every_runtime_dependency() -> None:
)
def test_every_vad_extra_package_is_built_by_the_flatpak() -> None:
"""Keep optional VAD dependencies in the Flatpak export (#969)."""
with PYPROJECT.open("rb") as handle:
vad_dependencies = tomllib.load(handle)["project"]["optional-dependencies"]["vad"]
vad_packages = {
canonicalize_name(Requirement(dependency).name) for dependency in vad_dependencies
}
missing = sorted(vad_packages - set(_flatpak_versions()))
assert not missing, f"the Flatpak does not build [vad] packages: {', '.join(missing)}"
def test_the_silero_model_is_included_as_package_data() -> None:
"""The Flatpak app install must ship the model alongside Silero VAD."""
with PYPROJECT.open("rb") as handle:
package_data = tomllib.load(handle)["tool"]["setuptools"]["package-data"]
assert "data/*.onnx" in package_data["vocalinux.speech_recognition"]
assert (
REPO_ROOT / "src" / "vocalinux" / "speech_recognition" / "data" / "silero_vad.onnx"
).is_file()
def test_each_binary_only_package_has_one_wheel_per_flatpak_architecture() -> None:
"""Each binary-only wheel is restricted to its matching Flatpak architecture."""
sources = _flatpak_sources()
for name in BINARY_ONLY:
package_sources = [
source
for source in sources
if source["name"] == name and source["filename"] is not None
]
for arch in FLATPAK_ARCHES:
arch_sources = [
source for source in package_sources if source["filename"].endswith(f"_{arch}.whl")
]
assert (
len(arch_sources) == 1
), f"{name} needs exactly one wheel for {arch}, found {len(arch_sources)}"
source = arch_sources[0]
assert (
source["only_arches"] == arch
), f"{source['filename']} must carry only-arches: [{arch}]"
def test_the_flatpak_builds_nothing_the_lock_does_not_resolve() -> None:
"""#705 deleted pydub, lxml, tqdm and python-xlib from `pyproject.toml`.
@@ -266,18 +318,24 @@ def test_the_manifest_env_agrees_with_the_module() -> None:
@pytest.mark.parametrize("source", _flatpak_sources(), ids=lambda s: s["name"])
def test_no_source_is_pinned_to_one_python_abi(source: dict[str, str]) -> None:
"""Universal wheel or sdist, never a cp-tagged wheel.
"""Keep universal wheels or sdists except binary-only runtime wheels.
The SDK's Python minor version is not ours to choose and moves with the
GNOME runtime. An abi3 or cp313 wheel builds today and stops resolving the
day the runtime bumps, with nothing in this repo changed.
Binary-only wheels must match the CPython ABI of GNOME 50. An unrelated ABI
tag would make a runtime Python bump fail during the offline build.
"""
if not source["filename"].endswith(".whl"):
return
assert source["filename"].endswith("-none-any.whl"), (
f"{source['filename']} is pinned to one interpreter ABI; take the sdist"
" and let the sandbox build it against the SDK's own Python"
)
if source["name"] in BINARY_ONLY:
python_tag = TARGET_ENV["python_version"].replace(".", "")
assert re.search(
rf"-cp{python_tag}-cp{python_tag}-manylinux_.+_(x86_64|aarch64)\.whl$",
source["filename"],
), f"{source['filename']} is not a wheel for the Flatpak's CPython runtime"
else:
assert source["filename"].endswith("-none-any.whl"), (
f"{source['filename']} is pinned to one interpreter ABI; take the sdist"
" and let the sandbox build it against the SDK's own Python"
)
def _on_event_block(event: str) -> str:
+79
View File
@@ -0,0 +1,79 @@
"""Unit tests for the Flatpak dependency artifact selector."""
import importlib.util
from pathlib import Path
from typing import Any
import pytest
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "sync_flatpak_deps.py"
SPEC = importlib.util.spec_from_file_location("sync_flatpak_deps", SCRIPT)
assert SPEC is not None and SPEC.loader is not None
SYNC: Any = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(SYNC)
def _wheel(filename: str, digest: str) -> dict[str, object]:
"""Return a small PyPI wheel record for artifact selection tests."""
return {
"packagetype": "bdist_wheel",
"filename": filename,
"digests": {"sha256": digest},
"url": f"https://example.invalid/{filename}",
}
def _install_artifacts(monkeypatch: pytest.MonkeyPatch, artifacts: list[dict[str, object]]) -> None:
"""Replace the PyPI lookup with deterministic artifact records."""
def fake_pypi_artifacts(name: str, version: str) -> list[dict[str, object]]:
return artifacts
monkeypatch.setattr(SYNC, "pypi_artifacts", fake_pypi_artifacts)
def test_binary_only_selection_uses_matching_cpython_architecture_wheel(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Select the locked cp313 x86_64 wheel, not other ABI or arch variants."""
artifacts = [
_wheel("onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_x86_64.whl", "a" * 64),
_wheel("onnxruntime-1.29.0-cp312-cp312-manylinux_2_28_x86_64.whl", "b" * 64),
_wheel("onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_aarch64.whl", "c" * 64),
_wheel("onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_x86_64.whl", "d" * 64),
]
_install_artifacts(monkeypatch, artifacts)
selected = SYNC.choose_artifact(
"onnxruntime",
"1.29.0",
{"a" * 64, "b" * 64, "c" * 64, "d" * 64},
arch="x86_64",
)
assert selected == (artifacts[-1]["url"], "d" * 64)
def test_binary_only_selection_fails_when_matching_wheel_digest_is_not_locked(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Do not select a compatible wheel unless uv recorded its digest."""
wheel = _wheel("onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_x86_64.whl", "e" * 64)
_install_artifacts(monkeypatch, [wheel])
with pytest.raises(SystemExit, match="not among the hashes uv locked"):
SYNC.choose_artifact("onnxruntime", "1.29.0", {"f" * 64}, arch="x86_64")
def test_non_binary_selection_prefers_universal_wheel(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Non-binary dependencies retain the universal-wheel preference."""
source = _wheel("sample-1.0.tar.gz", "1" * 64)
source["packagetype"] = "sdist"
universal = _wheel("sample-1.0-py3-none-any.whl", "2" * 64)
_install_artifacts(monkeypatch, [source, universal])
selected = SYNC.choose_artifact("sample", "1.0", {"1" * 64, "2" * 64})
assert selected == (universal["url"], "2" * 64)