Files
voculinux/packaging/native/smoke-test.sh
T
4b7d57c077 feat(packaging): build thin .deb and .rpm and attach them to releases (#913)
* feat(packaging): build thin .deb and .rpm with nfpm and attach them to releases

Adds packaging/native: an nfpm config that produces a .deb and an .rpm
from the release wheel. The packages stay thin - Python, GTK, PyGObject
and the typelibs are distro Depends; only pywhispercpp and pynput are
vendored because the first is unpackaged on both distros and the second
is missing on Fedora. The Debian 12 build image and the nfpm download
are pinned in tool_checksums.txt.

just native-packages builds both in the pinned container, and
native-smoke installs the built package per distro: debian:12 and
ubuntu:24.04 exercise the .deb, fedora:42 the .rpm, then each runs
vocalinux --version plus an import smoke of the app and the vendored
extension. unified-pipeline.yml gates on the same three-distro matrix,
and release.yml attaches both arches to the release and covers them in
SHA256SUMS and the provenance manifest.

Refs #600

* fix(packaging): preserve launcher args and document Wayland input group

The launcher joined $* into an unquoted exec, re-splitting file paths
that contain spaces before argparse saw them. Exec "$@" directly and
re-quote each argument for the inner shell of 'sg input -c'.

On Wayland the packages could not grant the 'input' group membership
evdev hotkeys need; name the usermod step and its caveat in INSTALL.md
and the release notes.

* test(packaging): add return types to the native packaging tests

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-10-07 17:10:00 -07:00

98 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install the built native package inside its distro container and prove the
# app it ships actually runs.
#
# Usage (mount the package dir at /out and this script's dir at /pk):
# docker run --rm -v "$PWD/dist:/out:ro" -v "$PWD/packaging/native:/pk:ro" \
# debian:12 bash /pk/smoke-test.sh
# or: just native-smoke debian:12
#
# What this proves: the package manager accepts the package and resolves its
# declared dependencies, the /usr/bin launcher runs, and the import graph —
# gi, GTK, the app modules and the vendored wheels — resolves against the
# distro interpreter. It is the native-package counterpart of install-gate
# and the AppImage boot matrix: Debian and Ubuntu exercise the .deb, Fedora
# the .rpm.
#
# What it does not prove: dictation. No container here has audio or a
# display, and the point is the package, not the app — the app's own tests
# cover behaviour.
set -euo pipefail
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ -r /etc/os-release ] || fail "/etc/os-release missing — not a distro container?"
# shellcheck disable=SC1091
. /etc/os-release
export DEBIAN_FRONTEND=noninteractive
case "$ID" in
debian|ubuntu)
PKG="$(find /out -name '*.deb' -print -quit)"
[ -n "$PKG" ] || fail "no .deb under /out"
apt-get update
apt-get install -y "$PKG"
;;
fedora)
PKG="$(find /out -name '*.rpm' -print -quit)"
[ -n "$PKG" ] || fail "no .rpm under /out"
dnf install -y "$PKG"
;;
*)
fail "no smoke recipe for distro ID '$ID' — add one alongside the dependency list it is meant to exercise"
;;
esac
echo "== installed: $PKG"
# The launcher must answer: --version is an argparse action that exits
# before check_dependencies() touches GTK, so it proves the wrapper, the
# PYTHONPATH layout and the entry module's import chain — the dependency
# typelibs are exercised by the import smoke below.
vocalinux --version
vocalinux-gui --version
# Import smoke in the shape of the AUR gate: the app modules, the vendored
# pywhispercpp wheel, and the distro gi/GTK typelibs, all through the same
# interpreter and environment the launcher sets up. pynput is only checked
# for presence: its __init__ selects a display backend and raises ImportError
# with no DISPLAY, which is why the app guards it with PYNPUT_AVAILABLE.
PYTHONPATH="/usr/lib/vocalinux/app:/usr/lib/vocalinux/vendor" \
PYTHONNOUSERSITE=1 \
LD_LIBRARY_PATH="/usr/lib/vocalinux/vendor/pywhispercpp.libs" \
/usr/bin/python3 - <<'PY'
import importlib
import importlib.util
import sys
from pathlib import Path
MODULES = (
"vocalinux.main",
"vocalinux.ui.tray_indicator",
"vocalinux.speech_recognition.recognition_manager",
"vocalinux.speech_recognition.command_processor",
"vocalinux.text_injection.text_injector",
"vocalinux.text_injection.ibus_engine",
"vocalinux.ui.keyboard_backends",
"pywhispercpp.model",
)
for module in MODULES:
importlib.import_module(module)
spec = importlib.util.find_spec("pynput")
assert spec and spec.origin and spec.origin.startswith("/usr/lib/vocalinux/vendor/"), \
f"vendored pynput not found on sys.path: {spec}"
tag = f"cpython-{sys.version_info.major}{sys.version_info.minor}-"
exts = [p.name for p in Path("/usr/lib/vocalinux/vendor").glob("_pywhispercpp.*.so")]
assert any(tag in name for name in exts), \
f"no _pywhispercpp extension for {tag[:-1]} among {exts}"
print("import-smoke OK:", ", ".join(MODULES))
PY
echo "SMOKE-TEST PASSED"