* 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>
98 lines
3.5 KiB
Bash
Executable File
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"
|