refactor(installer): generate distro package map (#872)
* refactor(installer): generate distro package map * fix(installer): handle Debian derivative package maps * docs(installer): document generated package map * fix(installer): validate package map and refresh derivative probes * fix(installer): name openSUSE package roles
This commit is contained in:
@@ -82,6 +82,8 @@ just deps # sync .venv with dev+vad extras and the lint group
|
||||
just deps-all # also whisper/vosk/docs; later recipes use --no-sync so they keep it
|
||||
just lock # regenerate uv.lock, requirements/*.txt and the Flatpak dep manifest
|
||||
just lock-check # fail if uv.lock is stale vs pyproject.toml
|
||||
just distro-packages # regenerate install.d/package_map.sh from YAML
|
||||
just distro-packages-check # fail if the generated package map is stale
|
||||
just flatpak-deps # regenerate the Flatpak dep manifest from requirements/runtime.txt (needs PyPI)
|
||||
just flatpak-deps-check # fail if that manifest is behind the export
|
||||
just model-checksums # refresh pinned model digests after adding a model
|
||||
@@ -164,7 +166,8 @@ src/vocalinux/
|
||||
|
||||
Also: `tests/`, `docs/`, `packaging/` (AppImage, AUR, Flatpak), `scripts/`,
|
||||
`install.sh` (bootstrap and orchestration), `install.d/` (sourced installer modules),
|
||||
`uninstall.sh`, `web/`.
|
||||
`uninstall.sh`, `web/`. `scripts/distro-package-map.yaml` owns installer package
|
||||
names; `install.d/package_map.sh` is generated by `just distro-packages`.
|
||||
|
||||
| Task | Start here |
|
||||
|---|---|
|
||||
|
||||
+26
-3
@@ -37,21 +37,24 @@ This installs system dependencies, creates a venv from the system Python, instal
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y python3-pip python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 libgirepository-2.0-dev libgirepository1.0-dev \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libgirepository1.0-dev \
|
||||
build-essential pkg-config libcairo2-dev \
|
||||
python3-dev portaudio19-dev python3-venv xdotool
|
||||
```
|
||||
|
||||
**Debian 12** cannot pip-build PyGObject 3.56 (glib 2.74). Use `./install.sh --dev` for tests and running from source, then `venv/bin/pytest` / `venv/bin/python -m vocalinux.main --debug`.
|
||||
```bash
|
||||
sudo apt install -y python3-pip python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 libgirepository1.0-dev libcairo2-dev \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository1.0-dev libcairo2-dev \
|
||||
build-essential pkg-config \
|
||||
python3-dev portaudio19-dev python3-venv xdotool
|
||||
```
|
||||
|
||||
**Debian 13+:**
|
||||
```bash
|
||||
sudo apt install -y python3-pip python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 libgirepository-2.0-dev libcairo2-dev \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libcairo2-dev \
|
||||
build-essential pkg-config \
|
||||
python3-dev portaudio19-dev python3-venv xdotool
|
||||
```
|
||||
|
||||
@@ -138,7 +141,9 @@ vocalinux/
|
||||
├── tests/
|
||||
├── resources/ # Icons and sounds
|
||||
├── docs/
|
||||
├── install.d/ # Installer modules, including generated package data
|
||||
├── packaging/ # AppImage, AUR, Flatpak
|
||||
├── scripts/ # Maintenance tools and distro package-map source
|
||||
├── snap/ # Snap recipe
|
||||
└── web/ # Marketing site (Next.js)
|
||||
```
|
||||
@@ -150,6 +155,24 @@ vocalinux/
|
||||
| Recognition engines | `src/vocalinux/speech_recognition/recognition_manager.py` |
|
||||
| Text injection | `src/vocalinux/text_injection/text_injector.py` |
|
||||
| Settings / config | `src/vocalinux/ui/config_manager.py`, `settings_dialog.py` |
|
||||
| Installer package names | `scripts/distro-package-map.yaml` |
|
||||
|
||||
### Installer package map
|
||||
|
||||
`scripts/distro-package-map.yaml` is the source of truth for package names,
|
||||
alternatives, and conditional package probes used by `install.sh`. Do not edit
|
||||
the generated `install.d/package_map.sh` directly. After changing the YAML, run:
|
||||
|
||||
```bash
|
||||
just distro-packages
|
||||
just distro-packages-check
|
||||
```
|
||||
|
||||
Commit both the YAML and generated shell module. Package-manager control flow
|
||||
and installation policy remain handwritten in
|
||||
`install.d/system_dependencies.sh`; user systems never parse the YAML. The
|
||||
remote bootstrap's pre-checkout `git` installation is the deliberate exception
|
||||
because the generated module is not available yet.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ The `.desktop` entry is automatically configured with the detected `GI_TYPELIB_P
|
||||
## Officially Supported Distributions
|
||||
|
||||
These distributions are tested and known to work well with Vocalinux. A
|
||||
derivative is judged by the Python it ships rather than by its own version
|
||||
number, which rarely tracks the base release, and `install.sh` checks the
|
||||
interpreter rather than the release label.
|
||||
derivative is judged by the Python and package repositories it ships rather
|
||||
than by its own version number, which rarely tracks the base release. In
|
||||
particular, `install.sh` does not interpret a Debian-based derivative's product
|
||||
version as a Debian release number.
|
||||
|
||||
| Distribution | Version | Status | Notes |
|
||||
|--------------|---------|--------|-------|
|
||||
@@ -72,13 +73,20 @@ These distributions are known to be incompatible:
|
||||
|
||||
| Distribution | Status | Reason |
|
||||
|--------------|--------|--------|
|
||||
| Debian 11 and earlier | ❌ Not Supported | EOL and below the Python 3.11 floor; the installer requires Debian 12+ |
|
||||
| NixOS | ❌ Not Supported | Completely different filesystem layout (/nix/store), incompatible with standard installer |
|
||||
|
||||
## Requirements by Distribution
|
||||
|
||||
These are baseline commands for manual installations. The exact inventory used
|
||||
by `install.sh`, including package alternatives and conditional dependencies,
|
||||
lives in [`scripts/distro-package-map.yaml`](../scripts/distro-package-map.yaml).
|
||||
For the complete manual setup sequence, including native build prerequisites,
|
||||
use [INSTALL_MANUAL.md](INSTALL_MANUAL.md#system-packages-by-distribution).
|
||||
|
||||
### Ubuntu/Debian-based
|
||||
```bash
|
||||
sudo apt install -y python3-gi gir1.2-gtk-3.0 gir1.2-gdkpixbuf-2.0 \
|
||||
sudo apt install -y python3-gi gir1.2-gtk-3.0 gir1.2-ibus-1.0 gir1.2-gdkpixbuf-2.0 \
|
||||
portaudio19-dev python3-dev python3-venv pkg-config
|
||||
```
|
||||
|
||||
@@ -134,13 +142,13 @@ the VOSK engine (`--engine=vosk`) as a fully-packaged alternative.
|
||||
|
||||
### Fedora/RHEL-based
|
||||
```bash
|
||||
sudo dnf install -y python3-gobject gtk3-devel gobject-introspection-devel \
|
||||
sudo dnf install -y python3-gobject gtk3-devel ibus-devel cairo-devel gobject-introspection-devel \
|
||||
portaudio-devel python3-devel python3-virtualenv pkg-config
|
||||
```
|
||||
|
||||
### Arch Linux-based
|
||||
```bash
|
||||
sudo pacman -S --needed python-gobject gtk3 gobject-introspection \
|
||||
sudo pacman -S --needed python-gobject gtk3 ibus gobject-introspection \
|
||||
portaudio python pkg-config
|
||||
```
|
||||
|
||||
@@ -151,9 +159,11 @@ PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.versi
|
||||
sudo zypper install -y \
|
||||
"${PYVER}-pip" "${PYVER}-gobject" "${PYVER}-gobject-cairo" \
|
||||
"${PYVER}-devel" "${PYVER}-virtualenv" \
|
||||
gtk3 typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \
|
||||
gtk3 ibus typelib-1_0-IBus-1_0 \
|
||||
typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \
|
||||
typelib-1_0-Notify-0_7 libnotify4 \
|
||||
gobject-introspection-devel portaudio-devel pkg-config cmake wget curl unzip \
|
||||
gcc gcc-c++ make patchelf \
|
||||
xdotool wtype
|
||||
|
||||
# Optional: only needed for whisper.cpp Vulkan GPU builds
|
||||
@@ -224,7 +234,7 @@ Before running `pip install vocalinux`, `pip install "vocalinux[vosk]"`, or
|
||||
|
||||
#### Ubuntu/Debian
|
||||
```bash
|
||||
sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1 \
|
||||
sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-ibus-1.0 gir1.2-ayatanaappindicator3-0.1 \
|
||||
libgirepository1.0-dev libcairo2-dev portaudio19-dev python3-dev \
|
||||
python3-venv pkg-config xdotool wtype wl-clipboard xclip xsel
|
||||
```
|
||||
@@ -234,14 +244,14 @@ On Ubuntu 24.04+ or Pop!_OS, install `libgirepository-2.0-dev` if
|
||||
|
||||
#### Fedora
|
||||
```bash
|
||||
sudo dnf install python3-gobject gtk3 gtk3-devel libayatana-appindicator-gtk3 \
|
||||
sudo dnf install python3-gobject gtk3 gtk3-devel ibus-devel cairo-devel libayatana-appindicator-gtk3 \
|
||||
gobject-introspection-devel portaudio-devel python3-devel \
|
||||
python3-virtualenv pkg-config xdotool wtype wl-clipboard xclip xsel
|
||||
```
|
||||
|
||||
#### Arch Linux
|
||||
```bash
|
||||
sudo pacman -S python-gobject gtk3 libayatana-appindicator gobject-introspection \
|
||||
sudo pacman -S python-gobject gtk3 ibus libayatana-appindicator gobject-introspection \
|
||||
python-cairo portaudio python-virtualenv pkg-config xdotool wtype wl-clipboard xclip xsel
|
||||
```
|
||||
|
||||
@@ -249,7 +259,8 @@ sudo pacman -S python-gobject gtk3 libayatana-appindicator gobject-introspection
|
||||
```bash
|
||||
PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.version_info.minor}")')
|
||||
|
||||
sudo zypper install "${PYVER}-gobject" "${PYVER}-gobject-cairo" gtk3 \
|
||||
sudo zypper install "${PYVER}-gobject" "${PYVER}-gobject-cairo" gtk3 ibus typelib-1_0-IBus-1_0 \
|
||||
gcc gcc-c++ make patchelf \
|
||||
typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \
|
||||
typelib-1_0-Notify-0_7 libnotify4 \
|
||||
portaudio-devel "${PYVER}-devel" "${PYVER}-virtualenv" pkg-config xdotool wtype wl-clipboard xclip xsel
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ chmod +x Vocalinux-*-x86_64.AppImage # or aarch64
|
||||
./Vocalinux-*-x86_64.AppImage
|
||||
```
|
||||
|
||||
Built against glibc 2.35, so it starts on Debian 12+, Ubuntu 22.04+, Fedora 36+, Arch, and Tumbleweed. Older bases (RHEL 9, Debian 11, Ubuntu 20.04) are below that floor; use the installer or PyPI there.
|
||||
Built against glibc 2.35, so it starts on Debian 12+, Ubuntu 22.04+, Fedora 36+, Arch, and Tumbleweed. Older bases (RHEL 9, Debian 11, Ubuntu 20.04) are below that AppImage floor. The installer and PyPI package still require a distro that ships Python 3.11+, so they are not a workaround for Debian 11 or Ubuntu 20.04.
|
||||
|
||||
Still needs host text-injection tools (`xdotool` on X11; `wtype` / `ydotool` / clipboard tools on Wayland). Current AppImages rebuild whisper.cpp with Vulkan and use the host GPU driver (`vulkaninfo --summary`). Prefer the installer when you want system deps, a CUDA build, and models set up automatically.
|
||||
|
||||
|
||||
+24
-8
@@ -2,12 +2,21 @@
|
||||
|
||||
Use this when the [recommended installer](INSTALL.md) is not a fit, or you want an explicit package list. Prefer `./install.sh` for a normal desktop install.
|
||||
|
||||
The commands below are human-readable baselines for manual installs. The
|
||||
installer's authoritative package inventory, including build helpers and
|
||||
fallbacks, is [`scripts/distro-package-map.yaml`](../scripts/distro-package-map.yaml).
|
||||
Its generated shell module is committed so installations do not need a YAML
|
||||
parser.
|
||||
|
||||
Related: [DISTRO_COMPATIBILITY.md](DISTRO_COMPATIBILITY.md), [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||
|
||||
## System packages by distribution
|
||||
|
||||
Install desktop/system dependencies **before** creating a venv or running `pip install vocalinux`. Pip cannot install GTK typelibs, AppIndicator, PortAudio, or text-injection tools.
|
||||
|
||||
Debian 11 and earlier are unsupported; the Debian instructions start at Debian
|
||||
12, which is the oldest release with the required Python 3.11.
|
||||
|
||||
### Ubuntu
|
||||
|
||||
```bash
|
||||
@@ -15,8 +24,9 @@ sudo apt update
|
||||
sudo apt install -y \
|
||||
python3-pip python3-venv python3-dev \
|
||||
python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 \
|
||||
libgirepository1.0-dev portaudio19-dev \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 \
|
||||
libgirepository1.0-dev libcairo2-dev portaudio19-dev \
|
||||
build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \
|
||||
wget curl unzip
|
||||
|
||||
# Tray: older Ubuntu → gir1.2-appindicator3-0.1
|
||||
@@ -36,8 +46,9 @@ sudo apt update
|
||||
sudo apt install -y \
|
||||
python3-pip python3-venv python3-dev \
|
||||
python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 \
|
||||
libgirepository1.0-dev libcairo2-dev portaudio19-dev \
|
||||
build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \
|
||||
wget curl unzip \
|
||||
gir1.2-ayatanaappindicator3-0.1 \
|
||||
xdotool wtype wl-clipboard xclip xsel
|
||||
@@ -50,8 +61,9 @@ sudo apt update
|
||||
sudo apt install -y \
|
||||
python3-pip python3-venv python3-dev \
|
||||
python3-gi python3-gi-cairo \
|
||||
gir1.2-gtk-3.0 \
|
||||
gir1.2-gtk-3.0 gir1.2-ibus-1.0 \
|
||||
libgirepository-2.0-dev libcairo2-dev portaudio19-dev \
|
||||
build-essential pkg-config cmake libssl-dev autoconf automake libtool patchelf \
|
||||
wget curl unzip \
|
||||
gir1.2-ayatanaappindicator3-0.1 \
|
||||
xdotool wtype wl-clipboard xclip xsel
|
||||
@@ -64,8 +76,9 @@ Debian notes (OpenSSL build deps, ydotool, etc.): [DISTRO_COMPATIBILITY.md](DIST
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
python3-pip python3-devel python3-virtualenv \
|
||||
python3-gobject gtk3 libayatana-appindicator-gtk3 \
|
||||
gobject-introspection-devel portaudio-devel \
|
||||
python3-gobject gtk3 ibus-devel libayatana-appindicator-gtk3 \
|
||||
gobject-introspection-devel portaudio-devel cairo-devel \
|
||||
gcc gcc-c++ make pkg-config cmake patchelf \
|
||||
wget curl unzip xdotool wtype wl-clipboard xclip xsel
|
||||
```
|
||||
|
||||
@@ -75,9 +88,10 @@ Prefer the AUR package (`yay -S vocalinux`). Manual packages:
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed \
|
||||
python-pip python-gobject gtk3 \
|
||||
python-pip python-gobject gtk3 ibus \
|
||||
libayatana-appindicator gobject-introspection \
|
||||
python-cairo portaudio python-virtualenv \
|
||||
base-devel pkg-config cmake patchelf \
|
||||
wget curl unzip xdotool wtype wl-clipboard xclip xsel
|
||||
```
|
||||
|
||||
@@ -89,9 +103,11 @@ PYVER=$(python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.versi
|
||||
sudo zypper install -y \
|
||||
"${PYVER}-pip" "${PYVER}-gobject" "${PYVER}-gobject-cairo" \
|
||||
"${PYVER}-devel" "${PYVER}-virtualenv" \
|
||||
gtk3 typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \
|
||||
gtk3 ibus typelib-1_0-IBus-1_0 \
|
||||
typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1 \
|
||||
typelib-1_0-Notify-0_7 libnotify4 \
|
||||
gobject-introspection-devel portaudio-devel pkg-config cmake \
|
||||
gcc gcc-c++ make patchelf \
|
||||
wget curl unzip xdotool wtype wl-clipboard xclip xsel
|
||||
|
||||
# Optional: whisper.cpp Vulkan builds
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
# Generated by scripts/generate_distro_package_map.py -- do not edit.
|
||||
# Regenerate with `just distro-packages` after editing
|
||||
# scripts/distro-package-map.yaml.
|
||||
|
||||
DEBIAN_13_PLUS_PROBE_PACKAGE=libgirepository-2.0-dev
|
||||
|
||||
load_distro_package_map() {
|
||||
local package_map_key="$1"
|
||||
|
||||
SYSTEM_PACKAGES=()
|
||||
APPINDICATOR_PACKAGES=()
|
||||
GI_DEVELOPMENT_PACKAGES=()
|
||||
OPTIONAL_SYSTEM_PACKAGES=()
|
||||
PYTHON_PIP_SUFFIX=""
|
||||
PYTHON_GOBJECT_SUFFIX=""
|
||||
PYTHON_GOBJECT_CAIRO_SUFFIX=""
|
||||
PYTHON_DEVEL_SUFFIX=""
|
||||
PYTHON_VIRTUALENV_SUFFIX=""
|
||||
PYTHON_VENV_SUFFIX=""
|
||||
SHADER_COMPILER_PACKAGES=()
|
||||
VULKAN_PACKAGES=()
|
||||
XDOTOOL_PACKAGES=()
|
||||
WTYPE_PACKAGES=()
|
||||
YDOTOOL_PACKAGES=()
|
||||
|
||||
case "$package_map_key" in
|
||||
ubuntu)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ibus-1.0 libcairo2-dev cmake python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev xclip xsel wl-clipboard libssl-dev autoconf automake libtool patchelf)
|
||||
APPINDICATOR_PACKAGES=(gir1.2-appindicator3-0.1 gir1.2-ayatanaappindicator3-0.1)
|
||||
GI_DEVELOPMENT_PACKAGES=(libgirepository-2.0-dev libgirepository1.0-dev)
|
||||
OPTIONAL_SYSTEM_PACKAGES=(util-linux-extra)
|
||||
SHADER_COMPILER_PACKAGES=(glslc glslang-tools)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
debian_12)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ibus-1.0 libcairo2-dev cmake python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev xclip xsel wl-clipboard libssl-dev autoconf automake libtool patchelf libgirepository1.0-dev gir1.2-ayatanaappindicator3-0.1)
|
||||
OPTIONAL_SYSTEM_PACKAGES=(util-linux-extra)
|
||||
SHADER_COMPILER_PACKAGES=(glslc glslang-tools)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
debian_13_plus)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ibus-1.0 libcairo2-dev cmake python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev xclip xsel wl-clipboard libssl-dev autoconf automake libtool patchelf libgirepository-2.0-dev gir1.2-ayatanaappindicator3-0.1)
|
||||
OPTIONAL_SYSTEM_PACKAGES=(util-linux-extra)
|
||||
SHADER_COMPILER_PACKAGES=(glslc glslang-tools)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
fedora)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gobject gtk3 ibus-devel gobject-introspection-devel python3-devel portaudio-devel cairo-devel python3-virtualenv pkg-config cmake wget curl unzip vulkan-tools vulkan-loader-devel glslc patchelf xclip xsel wl-clipboard)
|
||||
APPINDICATOR_PACKAGES=(libayatana-appindicator-gtk3 libappindicator-gtk3)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
arch)
|
||||
SYSTEM_PACKAGES=(python-pip python-gobject gtk3 ibus gobject-introspection python-cairo portaudio python-virtualenv pkg-config cmake wget curl unzip base-devel vulkan-tools vulkan-headers shaderc patchelf xclip xsel wl-clipboard)
|
||||
APPINDICATOR_PACKAGES=(libayatana-appindicator libappindicator-gtk3)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
suse)
|
||||
SYSTEM_PACKAGES=(gtk3 ibus typelib-1_0-IBus-1_0 gobject-introspection-devel portaudio-devel pkg-config cmake gcc gcc-c++ make wget curl unzip xclip xsel wl-clipboard typelib-1_0-Notify-0_7 libnotify4 patchelf)
|
||||
APPINDICATOR_PACKAGES=(typelib-1_0-AyatanaAppIndicator3-0_1 typelib-1_0-AppIndicator3-0_1 typelib-1_0-AyatanaAppIndicator-0_1 libayatana-appindicator3-1 libappindicator3-1 libappindicator-gtk3)
|
||||
SHADER_COMPILER_PACKAGES=(shaderc glslang-devel glslang)
|
||||
VULKAN_PACKAGES=(vulkan-tools vulkan-devel)
|
||||
PYTHON_DEVEL_SUFFIX=devel
|
||||
PYTHON_GOBJECT_SUFFIX=gobject
|
||||
PYTHON_GOBJECT_CAIRO_SUFFIX=gobject-cairo
|
||||
PYTHON_PIP_SUFFIX=pip
|
||||
PYTHON_VENV_SUFFIX=venv
|
||||
PYTHON_VIRTUALENV_SUFFIX=virtualenv
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
gentoo)
|
||||
SYSTEM_PACKAGES=(dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator media-libs/portaudio dev-lang/python:3.11 pkgconf cmake media-libs/shaderc dev-util/patchelf x11-misc/xclip x11-misc/xsel gui-apps/wl-clipboard)
|
||||
XDOTOOL_PACKAGES=(x11-misc/xdotool)
|
||||
WTYPE_PACKAGES=(x11-misc/wtype)
|
||||
YDOTOOL_PACKAGES=(app-misc/ydotool)
|
||||
;;
|
||||
|
||||
alpine)
|
||||
SYSTEM_PACKAGES=(py3-gobject3 py3-pip gtk+3.0 py3-cairo portaudio-dev py3-virtualenv pkgconf cmake wget curl unzip shaderc patchelf vulkan-tools xclip xsel wl-clipboard)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
void)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gobject gtk+3 libappindicator-gtk3 gobject-introspection portaudio-devel python3-devel pkg-config cmake wget curl unzip shaderc patchelf Vulkan-Tools xclip xsel wl-clipboard)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
solus)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gobject gtk3 libappindicator gobject-introspection-devel portaudio-devel python3-virtualenv pkg-config cmake wget curl unzip shaderc patchelf vulkan-tools xclip xsel wl-clipboard)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
mageia)
|
||||
SYSTEM_PACKAGES=(python3-pip python3-gobject gtk3 ibus-devel gobject-introspection-devel python3-devel portaudio-devel cairo-devel python3-virtualenv pkg-config cmake wget curl unzip vulkan-tools vulkan-loader-devel glslc patchelf xclip xsel wl-clipboard)
|
||||
XDOTOOL_PACKAGES=(xdotool)
|
||||
WTYPE_PACKAGES=(wtype)
|
||||
YDOTOOL_PACKAGES=(ydotool)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown distro package map key: $package_map_key" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
+164
-153
@@ -121,15 +121,6 @@ PY
|
||||
}
|
||||
|
||||
suse_install_appindicator_runtime() {
|
||||
local APPINDICATOR_PACKAGES=(
|
||||
"typelib-1_0-AyatanaAppIndicator3-0_1"
|
||||
"typelib-1_0-AppIndicator3-0_1"
|
||||
"typelib-1_0-AyatanaAppIndicator-0_1"
|
||||
"libayatana-appindicator3-1"
|
||||
"libappindicator3-1"
|
||||
"libappindicator-gtk3"
|
||||
)
|
||||
|
||||
if suse_appindicator_gi_available; then
|
||||
print_info "AppIndicator/Ayatana GI namespace is already available."
|
||||
return 0
|
||||
@@ -161,63 +152,89 @@ suse_shader_compiler_available() {
|
||||
command_exists glslc || command_exists glslangValidator
|
||||
}
|
||||
|
||||
resolve_debian_package_map_key() {
|
||||
local DEBIAN_MAJOR="${DISTRO_VERSION%%.*}"
|
||||
|
||||
# Only Debian's own VERSION_ID identifies a Debian release. Derivatives
|
||||
# often use an unrelated product version, so select their compatible map
|
||||
# by probing the package inventory instead.
|
||||
if [[ "$DISTRO_ID" == "debian" && "$DEBIAN_MAJOR" =~ ^[0-9]+$ ]]; then
|
||||
if [ "$DEBIAN_MAJOR" -lt 12 ]; then
|
||||
print_error "Debian 12 or newer is required (detected Debian $DEBIAN_MAJOR)." >&2
|
||||
return "$EXIT_MISSING_DEPS"
|
||||
elif [ "$DEBIAN_MAJOR" -ge 13 ]; then
|
||||
echo "debian_13_plus"
|
||||
else
|
||||
echo "debian_12"
|
||||
fi
|
||||
else
|
||||
# A missing cache is not evidence of an older Debian base. Refresh
|
||||
# before probing; keep command substitution's stdout for the key only.
|
||||
sudo apt update >&2 || {
|
||||
print_error "Failed to refresh package indexes for Debian base detection." >&2
|
||||
return "$EXIT_NETWORK"
|
||||
}
|
||||
if apt-cache show "$DEBIAN_13_PLUS_PROBE_PACKAGE" &>/dev/null 2>&1; then
|
||||
echo "debian_13_plus"
|
||||
else
|
||||
echo "debian_12"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to install system dependencies based on the detected distribution
|
||||
install_system_dependencies() {
|
||||
print_info "Installing system dependencies..."
|
||||
|
||||
# Determine which Vulkan shader package is available (glslc for Ubuntu 24.04+, glslang-tools for 22.04)
|
||||
local VULKAN_SHADER_PKG="glslang-tools" # Default fallback
|
||||
if apt-cache show glslc &>/dev/null 2>&1; then
|
||||
VULKAN_SHADER_PKG="glslc"
|
||||
local PACKAGE_MAP_KEY="$DISTRO_FAMILY"
|
||||
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
|
||||
PACKAGE_MAP_KEY=$(resolve_debian_package_map_key) || exit "$?"
|
||||
fi
|
||||
case "$PACKAGE_MAP_KEY" in
|
||||
ubuntu|debian_12|debian_13_plus|fedora|arch|suse|gentoo|alpine|void|solus|mageia)
|
||||
load_distro_package_map "$PACKAGE_MAP_KEY" || exit "$EXIT_MISSING_DEPS"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Define package names for different distributions
|
||||
# GObject Introspection / GLib headers for building PyGObject and friends.
|
||||
# Prefer libgirepository-2.0-dev when available (Ubuntu 24.04+, Pop!_OS Cosmic+,
|
||||
# Debian 13+). When both 1.0 and 2.0 packages exist, install both: 2.0 provides
|
||||
# the modern GLib GI headers that pip builds need, while 1.0 still pulls
|
||||
# gobject-introspection tooling. Older distros that only ship 1.0 keep that.
|
||||
# See #571 (installer previously kept 1.0 whenever apt-cache still listed it).
|
||||
local GI_DEV_PKG="libgirepository1.0-dev"
|
||||
if apt-cache show libgirepository-2.0-dev &>/dev/null 2>&1; then
|
||||
if apt-cache show libgirepository1.0-dev &>/dev/null 2>&1; then
|
||||
GI_DEV_PKG="libgirepository-2.0-dev libgirepository1.0-dev"
|
||||
else
|
||||
GI_DEV_PKG="libgirepository-2.0-dev"
|
||||
if [[ "$DISTRO_FAMILY" == "ubuntu" ]]; then
|
||||
local GI_DEV_PACKAGE
|
||||
local GI_DEV_FOUND="no"
|
||||
for GI_DEV_PACKAGE in "${GI_DEVELOPMENT_PACKAGES[@]}"; do
|
||||
if apt-cache show "$GI_DEV_PACKAGE" &>/dev/null 2>&1; then
|
||||
SYSTEM_PACKAGES+=("$GI_DEV_PACKAGE")
|
||||
GI_DEV_FOUND="yes"
|
||||
fi
|
||||
done
|
||||
if [[ "$GI_DEV_FOUND" == "no" ]]; then
|
||||
SYSTEM_PACKAGES+=("${GI_DEVELOPMENT_PACKAGES[1]}")
|
||||
fi
|
||||
SYSTEM_PACKAGES+=("${APPINDICATOR_PACKAGES[0]}")
|
||||
fi
|
||||
|
||||
# libssl-dev, autoconf, automake, libtool, patchelf are required for pywhispercpp source
|
||||
# builds on Debian. On Ubuntu these are typically pulled in transitively, but on a clean
|
||||
# Debian install they are absent and cause CMake's bootstrap to fail (Hurdle 2 from
|
||||
# https://medium.com/@cslev/talking-to-my-linux-box-without-talking-to-the-cloud-vocalinux-on-debian-without-the-tears-10bf053ea21b).
|
||||
local PYWHISPERCPP_BUILD_DEPS="libssl-dev autoconf automake libtool patchelf"
|
||||
local APT_PACKAGES_UBUNTU="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-appindicator3-0.1 gir1.2-ibus-1.0 $GI_DEV_PKG libcairo2-dev cmake python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev $VULKAN_SHADER_PKG xclip xsel wl-clipboard $PYWHISPERCPP_BUILD_DEPS"
|
||||
local APT_PACKAGES_DEBIAN_BASE="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ibus-1.0 libcairo2-dev cmake python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev $VULKAN_SHADER_PKG xclip xsel wl-clipboard $PYWHISPERCPP_BUILD_DEPS"
|
||||
local APT_PACKAGES_DEBIAN_11_12="$APT_PACKAGES_DEBIAN_BASE libgirepository1.0-dev gir1.2-ayatanaappindicator3-0.1"
|
||||
local APT_PACKAGES_DEBIAN_13_PLUS="$APT_PACKAGES_DEBIAN_BASE libgirepository-2.0-dev gir1.2-ayatanaappindicator3-0.1"
|
||||
# cairo-devel: the pinned pycairo builds from source; Fedora splits its headers/.pc out.
|
||||
local DNF_PACKAGES="python3-pip python3-gobject gtk3 ibus-devel gobject-introspection-devel python3-devel portaudio-devel cairo-devel python3-virtualenv pkg-config cmake wget curl unzip vulkan-tools vulkan-loader-devel glslc patchelf xclip xsel wl-clipboard"
|
||||
local PACMAN_PACKAGES="python-pip python-gobject gtk3 ibus gobject-introspection python-cairo portaudio python-virtualenv pkg-config cmake wget curl unzip base-devel vulkan-tools vulkan-headers shaderc patchelf xclip xsel wl-clipboard"
|
||||
# ibus + typelib-1_0-IBus-1_0, not ibus-devel: the headers are not needed
|
||||
# (IBus is reached through GI at runtime), and requiring them pulls gtk-doc,
|
||||
# which zypper cannot satisfy when awk resolves to busybox-gawk. The runtime
|
||||
# package is needed: ibus_engine.py spawns `ibus-daemon -x -d -r` and shells
|
||||
# out to `ibus engine`, and ibus-devel used to pull it in transitively
|
||||
# (Requires: ibus = %version). The typelib is named as well as implied by
|
||||
# ibus's own typelib(IBus) require, so the GI dependency stays visible here.
|
||||
# gcc/gcc-c++/make: the counterpart of build-essential and base-devel above.
|
||||
# python3-devel supplies headers, not a compiler, and evdev and pyaudio ship
|
||||
# no wheels, so without these they have nothing to build with.
|
||||
local ZYPPER_PACKAGES="gtk3 ibus typelib-1_0-IBus-1_0 gobject-introspection-devel portaudio-devel pkg-config cmake gcc gcc-c++ make wget curl unzip xclip xsel wl-clipboard typelib-1_0-Notify-0_7 libnotify4 patchelf"
|
||||
# Gentoo uses Portage and different package naming convention
|
||||
local EMERGE_PACKAGES="dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator media-libs/portaudio dev-lang/python:3.11 pkgconf cmake media-libs/shaderc dev-util/patchelf x11-misc/xclip x11-misc/xsel gui-apps/wl-clipboard"
|
||||
# Alpine Linux uses apk and has musl libc
|
||||
local APK_PACKAGES="py3-gobject3 py3-pip gtk+3.0 py3-cairo portaudio-dev py3-virtualenv pkgconf cmake wget curl unzip shaderc patchelf vulkan-tools xclip xsel wl-clipboard"
|
||||
# Void Linux uses xbps
|
||||
local XBPS_PACKAGES="python3-pip python3-gobject gtk+3 libappindicator-gtk3 gobject-introspection portaudio-devel python3-devel pkg-config cmake wget curl unzip shaderc patchelf Vulkan-Tools xclip xsel wl-clipboard"
|
||||
# Solus uses eopkg
|
||||
local EOPKG_PACKAGES="python3-pip python3-gobject gtk3 libappindicator gobject-introspection-devel portaudio-devel python3-virtualenv pkg-config cmake wget curl unzip shaderc patchelf vulkan-tools xclip xsel wl-clipboard"
|
||||
if [[ "$DISTRO_FAMILY" == "ubuntu" || "$DISTRO_FAMILY" == "debian" ]]; then
|
||||
local SHADER_PACKAGE="${SHADER_COMPILER_PACKAGES[1]}"
|
||||
local SHADER_CANDIDATE
|
||||
for SHADER_CANDIDATE in "${SHADER_COMPILER_PACKAGES[@]}"; do
|
||||
if apt-cache show "$SHADER_CANDIDATE" &>/dev/null 2>&1; then
|
||||
SHADER_PACKAGE="$SHADER_CANDIDATE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
SYSTEM_PACKAGES+=("$SHADER_PACKAGE")
|
||||
|
||||
local OPTIONAL_PACKAGE
|
||||
for OPTIONAL_PACKAGE in "${OPTIONAL_SYSTEM_PACKAGES[@]}"; do
|
||||
if apt-cache show "$OPTIONAL_PACKAGE" &>/dev/null 2>&1; then
|
||||
SYSTEM_PACKAGES+=("$OPTIONAL_PACKAGE")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
local MISSING_PACKAGES=""
|
||||
local INSTALL_CMD=""
|
||||
@@ -225,26 +242,8 @@ install_system_dependencies() {
|
||||
|
||||
case "$DISTRO_FAMILY" in
|
||||
ubuntu|debian)
|
||||
local APT_PACKAGES="$APT_PACKAGES_UBUNTU"
|
||||
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
|
||||
local DEBIAN_MAJOR="${DISTRO_VERSION%%.*}"
|
||||
if [[ "$DEBIAN_MAJOR" =~ ^[0-9]+$ ]] && [ "$DEBIAN_MAJOR" -ge 13 ]; then
|
||||
APT_PACKAGES="$APT_PACKAGES_DEBIAN_13_PLUS"
|
||||
else
|
||||
APT_PACKAGES="$APT_PACKAGES_DEBIAN_11_12"
|
||||
fi
|
||||
fi
|
||||
|
||||
# util-linux-extra only exists on newer Ubuntu/Debian (24.04+, Debian 13+).
|
||||
# On 22.04 those tools ship in the main util-linux package (always installed).
|
||||
# apt-cache probe handles Ubuntu derivatives (Mint, Zorin) whose VERSION_ID
|
||||
# does not track the Ubuntu base release.
|
||||
if apt-cache show util-linux-extra &>/dev/null 2>&1; then
|
||||
APT_PACKAGES="$APT_PACKAGES util-linux-extra"
|
||||
fi
|
||||
|
||||
# Check for missing packages
|
||||
for pkg in $APT_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! apt_package_installed "$pkg"; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -255,16 +254,16 @@ install_system_dependencies() {
|
||||
sudo apt update || { print_error "Failed to update package lists"; exit "$EXIT_NETWORK"; }
|
||||
|
||||
# Handle appindicator package for Ubuntu (old package deprecated in newer releases)
|
||||
if echo "$MISSING_PACKAGES" | grep -q "gir1.2-appindicator3-0.1"; then
|
||||
FILTERED_PACKAGES=$(echo "$MISSING_PACKAGES" | sed 's/gir1.2-appindicator3-0.1//' | xargs)
|
||||
if [[ "$DISTRO_FAMILY" == "ubuntu" ]] && echo "$MISSING_PACKAGES" | grep -q "${APPINDICATOR_PACKAGES[0]}"; then
|
||||
FILTERED_PACKAGES=$(echo "$MISSING_PACKAGES" | sed "s/${APPINDICATOR_PACKAGES[0]}//" | xargs)
|
||||
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y gir1.2-appindicator3-0.1 2>/dev/null; then
|
||||
print_info "gir1.2-appindicator3-0.1 not available, trying gir1.2-ayatanaappindicator3-0.1..."
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y gir1.2-ayatanaappindicator3-0.1; then
|
||||
print_error "Failed to install appindicator package (tried both gir1.2-appindicator3-0.1 and gir1.2-ayatanaappindicator3-0.1)"
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "${APPINDICATOR_PACKAGES[0]}" 2>/dev/null; then
|
||||
print_info "${APPINDICATOR_PACKAGES[0]} not available, trying ${APPINDICATOR_PACKAGES[1]}..."
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "${APPINDICATOR_PACKAGES[1]}"; then
|
||||
print_error "Failed to install appindicator package (tried both ${APPINDICATOR_PACKAGES[*]})"
|
||||
exit "$EXIT_MISSING_DEPS"
|
||||
fi
|
||||
print_info "Successfully installed gir1.2-ayatanaappindicator3-0.1 (modern replacement)"
|
||||
print_info "Successfully installed ${APPINDICATOR_PACKAGES[1]} (modern replacement)"
|
||||
fi
|
||||
|
||||
if [ -n "$FILTERED_PACKAGES" ]; then
|
||||
@@ -292,7 +291,7 @@ install_system_dependencies() {
|
||||
fi
|
||||
|
||||
# Check for missing packages
|
||||
for pkg in $DNF_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! dnf_package_installed "$pkg"; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -306,7 +305,7 @@ install_system_dependencies() {
|
||||
print_info "All required packages are already installed."
|
||||
fi
|
||||
|
||||
install_preferred_appindicator "$INSTALL_CMD" "libayatana-appindicator-gtk3" "libappindicator-gtk3" dnf_package_installed || exit "$EXIT_MISSING_DEPS"
|
||||
install_preferred_appindicator "$INSTALL_CMD" "${APPINDICATOR_PACKAGES[0]}" "${APPINDICATOR_PACKAGES[1]}" dnf_package_installed || exit "$EXIT_MISSING_DEPS"
|
||||
;;
|
||||
|
||||
arch)
|
||||
@@ -317,7 +316,7 @@ install_system_dependencies() {
|
||||
fi
|
||||
|
||||
# Check for missing packages
|
||||
for pkg in $PACMAN_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! pacman_package_installed "$pkg"; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -331,7 +330,7 @@ install_system_dependencies() {
|
||||
print_info "All required packages are already installed."
|
||||
fi
|
||||
|
||||
install_preferred_appindicator "sudo pacman -S --noconfirm" "libayatana-appindicator" "libappindicator-gtk3" pacman_package_installed || exit "$EXIT_MISSING_DEPS"
|
||||
install_preferred_appindicator "sudo pacman -S --noconfirm" "${APPINDICATOR_PACKAGES[0]}" "${APPINDICATOR_PACKAGES[1]}" pacman_package_installed || exit "$EXIT_MISSING_DEPS"
|
||||
;;
|
||||
|
||||
suse)
|
||||
@@ -344,11 +343,11 @@ install_system_dependencies() {
|
||||
sudo zypper refresh || true
|
||||
|
||||
if [[ "${SELECTED_ENGINE:-whisper_cpp}" == "whisper_cpp" && "${WHISPERCPP_BACKEND:-}" != "cpu" ]]; then
|
||||
ZYPPER_PACKAGES="$ZYPPER_PACKAGES vulkan-tools vulkan-devel"
|
||||
SYSTEM_PACKAGES+=("${VULKAN_PACKAGES[@]}")
|
||||
fi
|
||||
|
||||
local MISSING_ZYPPER_PACKAGES=()
|
||||
for pkg in $ZYPPER_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! suse_package_installed "$pkg"; then
|
||||
MISSING_ZYPPER_PACKAGES+=("$pkg")
|
||||
fi
|
||||
@@ -371,12 +370,12 @@ install_system_dependencies() {
|
||||
local PY_VIRTUALENV_CANDIDATES=()
|
||||
local PY_VENV_CANDIDATES=()
|
||||
|
||||
read -r -a PY_PIP_CANDIDATES <<< "$(suse_python_package_candidates "pip")"
|
||||
read -r -a PY_GOBJECT_CANDIDATES <<< "$(suse_python_package_candidates "gobject")"
|
||||
read -r -a PY_GOBJECT_CAIRO_CANDIDATES <<< "$(suse_python_package_candidates "gobject-cairo")"
|
||||
read -r -a PY_DEVEL_CANDIDATES <<< "$(suse_python_package_candidates "devel")"
|
||||
read -r -a PY_VIRTUALENV_CANDIDATES <<< "$(suse_python_package_candidates "virtualenv")"
|
||||
read -r -a PY_VENV_CANDIDATES <<< "$(suse_python_package_candidates "venv")"
|
||||
read -r -a PY_PIP_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_PIP_SUFFIX")"
|
||||
read -r -a PY_GOBJECT_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_GOBJECT_SUFFIX")"
|
||||
read -r -a PY_GOBJECT_CAIRO_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_GOBJECT_CAIRO_SUFFIX")"
|
||||
read -r -a PY_DEVEL_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_DEVEL_SUFFIX")"
|
||||
read -r -a PY_VIRTUALENV_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_VIRTUALENV_SUFFIX")"
|
||||
read -r -a PY_VENV_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_VENV_SUFFIX")"
|
||||
|
||||
print_info "Resolving openSUSE Python packages for $(suse_python_package_prefix)..."
|
||||
|
||||
@@ -407,13 +406,13 @@ install_system_dependencies() {
|
||||
|
||||
if ! suse_install_appindicator_runtime; then
|
||||
print_error "Failed to install a working AppIndicator/Ayatana GI runtime on openSUSE."
|
||||
print_error "Try manually: sudo zypper install typelib-1_0-AyatanaAppIndicator3-0_1 libayatana-appindicator3-1"
|
||||
print_error "Try manually: sudo zypper install ${APPINDICATOR_PACKAGES[0]} ${APPINDICATOR_PACKAGES[3]}"
|
||||
exit "$EXIT_MISSING_DEPS"
|
||||
fi
|
||||
|
||||
if [[ "${SELECTED_ENGINE:-whisper_cpp}" == "whisper_cpp" && "${WHISPERCPP_BACKEND:-}" != "cpu" ]]; then
|
||||
if ! suse_shader_compiler_available; then
|
||||
if ! suse_install_first_available "Vulkan shader compiler" shaderc glslang-devel glslang; then
|
||||
if ! suse_install_first_available "Vulkan shader compiler" "${SHADER_COMPILER_PACKAGES[@]}"; then
|
||||
print_warning "No Vulkan shader compiler found - whisper.cpp Vulkan build may fail"
|
||||
print_warning "Install shaderc manually for glslc support if you want GPU acceleration."
|
||||
fi
|
||||
@@ -437,7 +436,7 @@ install_system_dependencies() {
|
||||
|
||||
# Check for missing packages
|
||||
MISSING_PACKAGES=""
|
||||
for pkg in $EMERGE_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
# Gentoo uses qlist to check if packages are installed
|
||||
if ! qlist -I "$pkg" >/dev/null 2>&1; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
@@ -467,7 +466,7 @@ install_system_dependencies() {
|
||||
|
||||
# Check for missing packages
|
||||
MISSING_PACKAGES=""
|
||||
for pkg in $APK_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! apk info -e "$pkg" >/dev/null 2>&1; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -493,7 +492,7 @@ install_system_dependencies() {
|
||||
|
||||
# Check for missing packages
|
||||
MISSING_PACKAGES=""
|
||||
for pkg in $XBPS_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! xbps-query "$pkg" >/dev/null 2>&1; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -518,7 +517,7 @@ install_system_dependencies() {
|
||||
|
||||
# Check for missing packages
|
||||
MISSING_PACKAGES=""
|
||||
for pkg in $EOPKG_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
if ! eopkg list-installed | grep -qw "$pkg"; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
fi
|
||||
@@ -546,7 +545,7 @@ install_system_dependencies() {
|
||||
fi
|
||||
|
||||
# Use similar packages to Fedora/RHEL
|
||||
for pkg in $DNF_PACKAGES; do
|
||||
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
||||
# Mageia uses rpm like Fedora
|
||||
if ! rpm -q "$pkg" >/dev/null 2>&1; then
|
||||
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
||||
@@ -604,6 +603,18 @@ install_text_input_tools() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Unsupported distros can continue past install_system_dependencies in
|
||||
# non-interactive mode, but they have no generated package inventory.
|
||||
if [[ -z "${XDOTOOL_PACKAGES+x}" ]]; then
|
||||
print_warning "No text input package map is available for $DISTRO_NAME."
|
||||
print_warning "Install xdotool or wtype manually for your display server."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local XDOTOOL_PKG="${XDOTOOL_PACKAGES[0]}"
|
||||
local WTYPE_PKG="${WTYPE_PACKAGES[0]}"
|
||||
local YDOTOOL_PKG="${YDOTOOL_PACKAGES[0]}"
|
||||
|
||||
# Detect session type more robustly
|
||||
local SESSION_TYPE="unknown"
|
||||
|
||||
@@ -633,64 +644,64 @@ install_text_input_tools() {
|
||||
print_info "Installing Wayland text input tools..."
|
||||
case "$DISTRO_FAMILY" in
|
||||
ubuntu|debian)
|
||||
if ! apt_package_installed "wtype"; then
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! apt_package_installed "$WTYPE_PKG"; then
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
fedora)
|
||||
if command_exists dnf && ! dnf_package_installed "wtype"; then
|
||||
sudo dnf install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
elif command_exists yum && ! rpm -q wtype &>/dev/null; then
|
||||
sudo yum install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if command_exists dnf && ! dnf_package_installed "$WTYPE_PKG"; then
|
||||
sudo dnf install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
elif command_exists yum && ! rpm -q "$WTYPE_PKG" &>/dev/null; then
|
||||
sudo yum install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
arch)
|
||||
if ! pacman_package_installed "wtype"; then
|
||||
sudo pacman -S --noconfirm wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! pacman_package_installed "$WTYPE_PKG"; then
|
||||
sudo pacman -S --noconfirm "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
suse)
|
||||
sudo zypper install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
sudo zypper install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
;;
|
||||
gentoo)
|
||||
if ! qlist -I wtype >/dev/null 2>&1; then
|
||||
sudo emerge wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! qlist -I "$WTYPE_PKG" >/dev/null 2>&1; then
|
||||
sudo emerge "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
alpine)
|
||||
if ! apk info -e wtype >/dev/null 2>&1; then
|
||||
sudo apk add wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! apk info -e "$WTYPE_PKG" >/dev/null 2>&1; then
|
||||
sudo apk add "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
void)
|
||||
if ! xbps-query wtype >/dev/null 2>&1; then
|
||||
sudo xbps-install -Sy wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! xbps-query "$WTYPE_PKG" >/dev/null 2>&1; then
|
||||
sudo xbps-install -Sy "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
solus)
|
||||
if ! eopkg list-installed | grep -qw wtype; then
|
||||
sudo eopkg install wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if ! eopkg list-installed | grep -qw "$WTYPE_PKG"; then
|
||||
sudo eopkg install "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
;;
|
||||
mageia)
|
||||
if command_exists dnf && ! rpm -q wtype >/dev/null 2>&1; then
|
||||
sudo dnf install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
elif command_exists urpmi && ! rpm -q wtype >/dev/null 2>&1; then
|
||||
sudo urpmi -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
if command_exists dnf && ! rpm -q "$WTYPE_PKG" >/dev/null 2>&1; then
|
||||
sudo dnf install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
elif command_exists urpmi && ! rpm -q "$WTYPE_PKG" >/dev/null 2>&1; then
|
||||
sudo urpmi -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "wtype is already installed."
|
||||
fi
|
||||
@@ -706,8 +717,8 @@ install_text_input_tools() {
|
||||
print_info "Attempting to install ydotool for better Wayland compatibility..."
|
||||
case "$DISTRO_FAMILY" in
|
||||
ubuntu|debian)
|
||||
if ! apt_package_installed "ydotool"; then
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y ydotool 2>/dev/null; then
|
||||
if ! apt_package_installed "$YDOTOOL_PKG"; then
|
||||
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "$YDOTOOL_PKG" 2>/dev/null; then
|
||||
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
|
||||
print_warning "ydotool is not packaged in Debian's standard repos."
|
||||
print_info "For full Wayland input support, you can compile ydotool from source:"
|
||||
@@ -724,12 +735,12 @@ install_text_input_tools() {
|
||||
;;
|
||||
fedora)
|
||||
if command_exists dnf; then
|
||||
sudo dnf install -y ydotool 2>/dev/null || print_info "ydotool not available in repos (optional)"
|
||||
sudo dnf install -y "$YDOTOOL_PKG" 2>/dev/null || print_info "ydotool not available in repos (optional)"
|
||||
fi
|
||||
;;
|
||||
arch)
|
||||
if ! pacman_package_installed "ydotool"; then
|
||||
sudo pacman -S --noconfirm ydotool 2>/dev/null || print_info "ydotool not available in repos (optional)"
|
||||
if ! pacman_package_installed "$YDOTOOL_PKG"; then
|
||||
sudo pacman -S --noconfirm "$YDOTOOL_PKG" 2>/dev/null || print_info "ydotool not available in repos (optional)"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@@ -755,64 +766,64 @@ install_text_input_tools() {
|
||||
print_info "Installing X11 text input tools..."
|
||||
case "$DISTRO_FAMILY" in
|
||||
ubuntu|debian)
|
||||
if ! apt_package_installed "xdotool"; then
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! apt_package_installed "$XDOTOOL_PKG"; then
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
fedora)
|
||||
if command_exists dnf && ! dnf_package_installed "xdotool"; then
|
||||
sudo dnf install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
elif command_exists yum && ! rpm -q xdotool &>/dev/null; then
|
||||
sudo yum install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if command_exists dnf && ! dnf_package_installed "$XDOTOOL_PKG"; then
|
||||
sudo dnf install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
elif command_exists yum && ! rpm -q "$XDOTOOL_PKG" &>/dev/null; then
|
||||
sudo yum install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
arch)
|
||||
if ! pacman_package_installed "xdotool"; then
|
||||
sudo pacman -S --noconfirm xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! pacman_package_installed "$XDOTOOL_PKG"; then
|
||||
sudo pacman -S --noconfirm "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
suse)
|
||||
sudo zypper install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
sudo zypper install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
;;
|
||||
gentoo)
|
||||
if ! qlist -I xdotool >/dev/null 2>&1; then
|
||||
sudo emerge xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! qlist -I "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
||||
sudo emerge "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
alpine)
|
||||
if ! apk info -e xdotool >/dev/null 2>&1; then
|
||||
sudo apk add xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! apk info -e "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
||||
sudo apk add "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
void)
|
||||
if ! xbps-query xdotool >/dev/null 2>&1; then
|
||||
sudo xbps-install -Sy xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! xbps-query "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
||||
sudo xbps-install -Sy "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
solus)
|
||||
if ! eopkg list-installed | grep -qw xdotool; then
|
||||
sudo eopkg install xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if ! eopkg list-installed | grep -qw "$XDOTOOL_PKG"; then
|
||||
sudo eopkg install "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
;;
|
||||
mageia)
|
||||
if command_exists dnf && ! rpm -q xdotool >/dev/null 2>&1; then
|
||||
sudo dnf install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
elif command_exists urpmi && ! rpm -q xdotool >/dev/null 2>&1; then
|
||||
sudo urpmi -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
if command_exists dnf && ! rpm -q "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
||||
sudo dnf install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
elif command_exists urpmi && ! rpm -q "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
||||
sudo urpmi -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
||||
else
|
||||
print_info "xdotool is already installed."
|
||||
fi
|
||||
@@ -831,36 +842,36 @@ install_text_input_tools() {
|
||||
# Install both tools based on distribution
|
||||
case "$DISTRO_FAMILY" in
|
||||
ubuntu|debian)
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
fedora|mageia)
|
||||
if command_exists dnf; then
|
||||
sudo dnf install -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo dnf install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
elif command_exists yum; then
|
||||
sudo yum install -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo yum install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
fi
|
||||
# Mageia also supports urpmi
|
||||
if [[ "$DISTRO_FAMILY" == "mageia" ]] && command_exists urpmi; then
|
||||
sudo urpmi -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo urpmi -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
fi
|
||||
;;
|
||||
arch)
|
||||
sudo pacman -S --noconfirm xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo pacman -S --noconfirm "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
suse)
|
||||
sudo zypper install -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo zypper install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
gentoo)
|
||||
sudo emerge xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo emerge "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
alpine)
|
||||
sudo apk add xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo apk add "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
void)
|
||||
sudo xbps-install -Sy xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo xbps-install -Sy "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
solus)
|
||||
sudo eopkg install xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
sudo eopkg install "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
||||
;;
|
||||
*)
|
||||
print_warning "Unsupported distribution for text input tools."
|
||||
|
||||
+1
-1
@@ -707,7 +707,7 @@ source_installer_module() {
|
||||
# Shared by the model installer module and the runtime downloader.
|
||||
MODEL_CHECKSUMS_FILE="$INSTALL_DIR/src/vocalinux/utils/model_checksums.txt"
|
||||
|
||||
for INSTALLER_MODULE in interactive.sh system_dependencies.sh models.sh desktop.sh; do
|
||||
for INSTALLER_MODULE in package_map.sh interactive.sh system_dependencies.sh models.sh desktop.sh; do
|
||||
source_installer_module "$INSTALLER_MODULE" || exit 1
|
||||
done
|
||||
unset INSTALLER_MODULE
|
||||
|
||||
@@ -193,6 +193,15 @@ lock-check:
|
||||
export-check:
|
||||
python3 scripts/check_exports.py
|
||||
|
||||
# Regenerate the shell package inventory consumed by install.sh. The committed
|
||||
# output keeps the runtime installer independent of a YAML parser.
|
||||
distro-packages: _tooling
|
||||
uv run --no-sync python scripts/generate_distro_package_map.py
|
||||
|
||||
# Fail if install.d/package_map.sh is behind its YAML source.
|
||||
distro-packages-check: _tooling
|
||||
uv run --no-sync python scripts/generate_distro_package_map.py --check
|
||||
|
||||
# The export pins names, versions and digests; this looks up the URL that serves
|
||||
# those bytes, so it needs PyPI. tests/test_flatpak_packaging.py checks the same
|
||||
# invariant offline, which is what CI gates on.
|
||||
|
||||
@@ -72,6 +72,7 @@ dev = [
|
||||
"pytest-timeout>=2.1.0",
|
||||
"pre-commit>=3.0.0",
|
||||
"mypy>=1.0.0",
|
||||
"pyyaml>=6.0,<7",
|
||||
]
|
||||
docs = [
|
||||
"sphinx>=6.0.0",
|
||||
|
||||
@@ -993,7 +993,9 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
|
||||
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
# via
|
||||
# pre-commit
|
||||
# vocalinux
|
||||
requests==2.34.2 \
|
||||
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
|
||||
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
|
||||
|
||||
+178
-242
@@ -1,256 +1,192 @@
|
||||
# Package name mapping for different Linux distributions
|
||||
# This file documents the package names used by the installer for each distribution
|
||||
# It serves as a reference for manual installation and for adding support for new distributions
|
||||
# Authoritative distro package inventory for install.sh.
|
||||
#
|
||||
# Edit this file, then run `just distro-packages`. The installer consumes the
|
||||
# generated install.d/package_map.sh and never needs a YAML parser at runtime.
|
||||
# The remote bootstrap's git package is the one deliberate exception: it must
|
||||
# be installed before the tagged repository and this generated map are present.
|
||||
schema_version: 1
|
||||
|
||||
# System packages required for Vocalinux
|
||||
system_packages:
|
||||
# GTK3 and PyGObject
|
||||
gtk3:
|
||||
ubuntu: ["python3-gi", "python3-gi-cairo", "gir1.2-gtk-3.0", "gir1.2-gdkpixbuf-2.0"]
|
||||
debian: ["python3-gi", "python3-gi-cairo", "gir1.2-gtk-3.0", "gir1.2-gdkpixbuf-2.0", "libcairo2-dev"]
|
||||
fedora: ["python3-gobject", "gtk3", "gobject-introspection-devel"]
|
||||
arch: ["python-gobject", "gtk3", "gobject-introspection", "python-cairo"]
|
||||
# Tumbleweed often uses versioned Python package names such as python313-gobject.
|
||||
# NOTE: "python3XY" is a placeholder convention. The XY suffix is resolved at
|
||||
# install time by install.sh via suse_python_package_prefix() and
|
||||
# suse_python_package_candidates(), which try the active Python minor version
|
||||
# first (e.g. python313-gobject) and fall back to the unversioned name.
|
||||
opensuse: ["python3XY-gobject", "python3XY-gobject-cairo", "gtk3", "gobject-introspection-devel"]
|
||||
gentoo: ["dev-python/pygobject:3", "x11-libs/gtk+:3"]
|
||||
alpine: ["py3-gobject3", "py3-cairo", "gtk+3.0"]
|
||||
void: ["python3-gobject", "gtk+3"]
|
||||
solus: ["python3-gobject", "gtk3"]
|
||||
distributions:
|
||||
ubuntu:
|
||||
# libssl/autotools/libtool/patchelf support pywhispercpp source builds.
|
||||
system:
|
||||
- python3-pip
|
||||
- python3-gi
|
||||
- python3-gi-cairo
|
||||
- gir1.2-gtk-3.0
|
||||
- gir1.2-ibus-1.0
|
||||
- libcairo2-dev
|
||||
- cmake
|
||||
- python3-dev
|
||||
- build-essential
|
||||
- portaudio19-dev
|
||||
- python3-venv
|
||||
- pkg-config
|
||||
- wget
|
||||
- curl
|
||||
- unzip
|
||||
- vulkan-tools
|
||||
- libvulkan-dev
|
||||
- xclip
|
||||
- xsel
|
||||
- wl-clipboard
|
||||
- libssl-dev
|
||||
- autoconf
|
||||
- automake
|
||||
- libtool
|
||||
- patchelf
|
||||
appindicator: [gir1.2-appindicator3-0.1, gir1.2-ayatanaappindicator3-0.1]
|
||||
gi_development: [libgirepository-2.0-dev, libgirepository1.0-dev]
|
||||
shader_compiler: [glslc, glslang-tools]
|
||||
optional_system: [util-linux-extra]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# AppIndicator / Ayatana AppIndicator
|
||||
# Prefer Ayatana: legacy Canonical AppIndicator can load without error but
|
||||
# fail to register a StatusNotifierItem on modern KDE Plasma.
|
||||
appindicator:
|
||||
ubuntu: ["gir1.2-appindicator3-0.1"]
|
||||
debian_11: ["gir1.2-ayatanaappindicator3-0.1"]
|
||||
debian_12_plus: ["gir1.2-ayatanaappindicator3-0.1"]
|
||||
fedora: ["libayatana-appindicator-gtk3"]
|
||||
arch: ["libayatana-appindicator"]
|
||||
opensuse: ["typelib-1_0-AyatanaAppIndicator3-0_1", "libayatana-appindicator3-1", "typelib-1_0-AppIndicator3-0_1"]
|
||||
gentoo: ["dev-libs/libayatana-appindicator"]
|
||||
alpine: ["libayatana-appindicator"]
|
||||
void: ["libappindicator"]
|
||||
solus: ["libappindicator"]
|
||||
debian_12:
|
||||
system:
|
||||
- python3-pip
|
||||
- python3-gi
|
||||
- python3-gi-cairo
|
||||
- gir1.2-gtk-3.0
|
||||
- gir1.2-ibus-1.0
|
||||
- libcairo2-dev
|
||||
- cmake
|
||||
- python3-dev
|
||||
- build-essential
|
||||
- portaudio19-dev
|
||||
- python3-venv
|
||||
- pkg-config
|
||||
- wget
|
||||
- curl
|
||||
- unzip
|
||||
- vulkan-tools
|
||||
- libvulkan-dev
|
||||
- xclip
|
||||
- xsel
|
||||
- wl-clipboard
|
||||
- libssl-dev
|
||||
- autoconf
|
||||
- automake
|
||||
- libtool
|
||||
- patchelf
|
||||
- libgirepository1.0-dev
|
||||
- gir1.2-ayatanaappindicator3-0.1
|
||||
shader_compiler: [glslc, glslang-tools]
|
||||
optional_system: [util-linux-extra]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# PortAudio
|
||||
portaudio:
|
||||
ubuntu: ["portaudio19-dev"]
|
||||
debian: ["portaudio19-dev"]
|
||||
fedora: ["portaudio-devel"]
|
||||
arch: ["portaudio"]
|
||||
opensuse: ["portaudio-devel"]
|
||||
gentoo: ["media-libs/portaudio"]
|
||||
alpine: ["portaudio-dev"]
|
||||
void: ["portaudio-devel"]
|
||||
solus: ["portaudio-devel"]
|
||||
debian_13_plus:
|
||||
# Used only to distinguish Debian 13-based derivatives whose own
|
||||
# VERSION_ID does not match the Debian base release.
|
||||
selection_probe: libgirepository-2.0-dev
|
||||
system:
|
||||
- python3-pip
|
||||
- python3-gi
|
||||
- python3-gi-cairo
|
||||
- gir1.2-gtk-3.0
|
||||
- gir1.2-ibus-1.0
|
||||
- libcairo2-dev
|
||||
- cmake
|
||||
- python3-dev
|
||||
- build-essential
|
||||
- portaudio19-dev
|
||||
- python3-venv
|
||||
- pkg-config
|
||||
- wget
|
||||
- curl
|
||||
- unzip
|
||||
- vulkan-tools
|
||||
- libvulkan-dev
|
||||
- xclip
|
||||
- xsel
|
||||
- wl-clipboard
|
||||
- libssl-dev
|
||||
- autoconf
|
||||
- automake
|
||||
- libtool
|
||||
- patchelf
|
||||
- libgirepository-2.0-dev
|
||||
- gir1.2-ayatanaappindicator3-0.1
|
||||
shader_compiler: [glslc, glslang-tools]
|
||||
optional_system: [util-linux-extra]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# Python development
|
||||
python_dev:
|
||||
ubuntu: ["python3-dev", "python3-venv"]
|
||||
debian: ["python3-dev", "python3-venv"]
|
||||
fedora: ["python3-devel", "python3-virtualenv"]
|
||||
arch: ["python"] # Arch includes venv by default
|
||||
# Tumbleweed package names follow the active Python minor version, e.g. python313-devel.
|
||||
# NOTE: "python3XY" is a placeholder — see gtk3.opensuse comment above for details.
|
||||
opensuse: ["python3XY-devel", "python3XY-virtualenv", "python3XY-venv", "python3XY-pip"]
|
||||
gentoo: ["dev-lang/python:3.11"]
|
||||
alpine: ["py3-virtualenv"]
|
||||
void: ["python3-devel"]
|
||||
solus: ["python3-virtualenv"]
|
||||
fedora:
|
||||
# cairo-devel is needed because the pinned pycairo builds from source.
|
||||
system: [python3-pip, python3-gobject, gtk3, ibus-devel, gobject-introspection-devel, python3-devel, portaudio-devel, cairo-devel, python3-virtualenv, pkg-config, cmake, wget, curl, unzip, vulkan-tools, vulkan-loader-devel, glslc, patchelf, xclip, xsel, wl-clipboard]
|
||||
appindicator: [libayatana-appindicator-gtk3, libappindicator-gtk3]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# Package build tools
|
||||
build_tools:
|
||||
ubuntu: ["pkg-config", "wget", "curl", "unzip"]
|
||||
debian: ["pkg-config", "wget", "curl", "unzip"]
|
||||
fedora: ["pkg-config", "wget", "curl", "unzip"]
|
||||
arch: ["pkgconf", "wget", "curl", "unzip"]
|
||||
opensuse: ["pkg-config", "wget", "curl", "unzip"]
|
||||
gentoo: ["dev-util/pkgconfig", "net-misc/wget", "net-misc/curl", "app-arch/unzip"]
|
||||
alpine: ["pkgconf", "wget", "curl", "unzip"]
|
||||
void: ["pkg-config", "wget", "curl", "unzip"]
|
||||
solus: ["pkg-config", "wget", "curl", "unzip"]
|
||||
arch:
|
||||
system: [python-pip, python-gobject, gtk3, ibus, gobject-introspection, python-cairo, portaudio, python-virtualenv, pkg-config, cmake, wget, curl, unzip, base-devel, vulkan-tools, vulkan-headers, shaderc, patchelf, xclip, xsel, wl-clipboard]
|
||||
appindicator: [libayatana-appindicator, libappindicator-gtk3]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# Text injection tools
|
||||
xdotool:
|
||||
ubuntu: ["xdotool"]
|
||||
debian: ["xdotool"]
|
||||
fedora: ["xdotool"]
|
||||
arch: ["xdotool"]
|
||||
opensuse: ["xdotool"]
|
||||
gentoo: ["x11-misc/xdotool"]
|
||||
alpine: ["xdotool"]
|
||||
void: ["xdotool"]
|
||||
solus: ["xdotool"]
|
||||
|
||||
# Clipboard tools used for layout-independent Wayland paste fallback
|
||||
clipboard_tools:
|
||||
ubuntu: ["wl-clipboard", "xclip", "xsel"]
|
||||
debian: ["wl-clipboard", "xclip", "xsel"]
|
||||
fedora: ["wl-clipboard", "xclip", "xsel"]
|
||||
arch: ["wl-clipboard", "xclip", "xsel"]
|
||||
opensuse: ["wl-clipboard", "xclip", "xsel"]
|
||||
gentoo: ["gui-apps/wl-clipboard", "x11-misc/xclip", "x11-misc/xsel"]
|
||||
alpine: ["wl-clipboard", "xclip", "xsel"]
|
||||
void: ["wl-clipboard", "xclip", "xsel"]
|
||||
solus: ["wl-clipboard", "xclip", "xsel"]
|
||||
|
||||
wtype:
|
||||
ubuntu: ["wtype"]
|
||||
debian: ["wtype"]
|
||||
fedora: ["wtype"]
|
||||
arch: ["wtype"]
|
||||
opensuse: ["wtype"]
|
||||
gentoo: ["x11-misc/wtype"]
|
||||
alpine: ["wtype"]
|
||||
void: ["wtype"]
|
||||
solus: ["wtype"]
|
||||
|
||||
ydotool:
|
||||
ubuntu: ["ydotool"]
|
||||
debian: ["ydotool"]
|
||||
fedora: ["ydotool"]
|
||||
arch: ["ydotool"]
|
||||
opensuse: ["ydotool"]
|
||||
gentoo: ["app-misc/ydotool"]
|
||||
alpine: ["ydotool"]
|
||||
void: ["ydotool"]
|
||||
solus: ["ydotool"]
|
||||
|
||||
# Command templates for package managers
|
||||
install_commands:
|
||||
apt: "sudo apt install -y {packages}"
|
||||
apt_update: "sudo apt update"
|
||||
|
||||
dnf: "sudo dnf install -y {packages}"
|
||||
dnf_update: "sudo dnf update -y" # Optional update
|
||||
|
||||
yum: "sudo yum install -y {packages}"
|
||||
|
||||
pacman: "sudo pacman -S --noconfirm {packages}"
|
||||
pacman_update: "sudo pacman -Syu" # Optional update
|
||||
|
||||
zypper: "sudo zypper install -y {packages}"
|
||||
zypper_update: "sudo zypper refresh" # Optional update
|
||||
|
||||
emerge: "sudo emerge {packages}"
|
||||
|
||||
apk: "sudo apk add {packages}"
|
||||
apk_update: "sudo apk update" # Optional update
|
||||
|
||||
xbps: "sudo xbps-install -Sy {packages}"
|
||||
xbps_update: "sudo xbps-install -Sy" # Updates sync during install
|
||||
|
||||
eopkg: "sudo eopkg install -y {packages}"
|
||||
eopkg_update: "sudo eopkg upgrade" # Optional update
|
||||
|
||||
# Distribution family mappings
|
||||
# This helps determine which package manager and package names to use
|
||||
distribution_families:
|
||||
# Ubuntu family
|
||||
- id: ubuntu
|
||||
family: ubuntu
|
||||
package_manager: apt
|
||||
variations: [pop, linuxmint, elementary, zorin]
|
||||
|
||||
# Debian family
|
||||
- id: debian
|
||||
family: debian
|
||||
package_manager: apt
|
||||
variations: []
|
||||
|
||||
# Fedora family
|
||||
- id: fedora
|
||||
family: fedora
|
||||
package_manager: dnf
|
||||
variations: [rhel, centos, rocky, almalinux]
|
||||
|
||||
# Arch family
|
||||
- id: arch
|
||||
family: arch
|
||||
package_manager: pacman
|
||||
variations: [manjaro, endeavouros]
|
||||
|
||||
# openSUSE family
|
||||
- id: opensuse
|
||||
family: suse
|
||||
package_manager: zypper
|
||||
variations: []
|
||||
|
||||
# Gentoo
|
||||
- id: gentoo
|
||||
family: gentoo
|
||||
package_manager: emerge
|
||||
variations: []
|
||||
|
||||
# Alpine
|
||||
- id: alpine
|
||||
family: alpine
|
||||
package_manager: apk
|
||||
variations: []
|
||||
|
||||
# Void Linux
|
||||
- id: void
|
||||
family: void
|
||||
package_manager: xbps
|
||||
variations: []
|
||||
|
||||
# Solus
|
||||
- id: solus
|
||||
family: solus
|
||||
package_manager: eopkg
|
||||
variations: []
|
||||
|
||||
# Mageia
|
||||
- id: mageia
|
||||
family: mageia
|
||||
package_manager: dnf # Prefers dnf, falls back to urpmi
|
||||
variations: []
|
||||
|
||||
# Notes for specific distributions
|
||||
distribution_notes:
|
||||
debian_11:
|
||||
appindicator: "Use gir1.2-ayatanaappindicator3-0.1 instead of gir1.2-appindicator3-0.1"
|
||||
suse:
|
||||
# IBus is a runtime dependency; ibus-devel drags in an unsatisfiable gtk-doc
|
||||
# chain on Tumbleweed. The compilers build evdev and PyAudio from sdists.
|
||||
system: [gtk3, ibus, typelib-1_0-IBus-1_0, gobject-introspection-devel, portaudio-devel, pkg-config, cmake, gcc, gcc-c++, make, wget, curl, unzip, xclip, xsel, wl-clipboard, typelib-1_0-Notify-0_7, libnotify4, patchelf]
|
||||
appindicator: [typelib-1_0-AyatanaAppIndicator3-0_1, typelib-1_0-AppIndicator3-0_1, typelib-1_0-AyatanaAppIndicator-0_1, libayatana-appindicator3-1, libappindicator3-1, libappindicator-gtk3]
|
||||
python_packages:
|
||||
pip: pip
|
||||
gobject: gobject
|
||||
gobject_cairo: gobject-cairo
|
||||
devel: devel
|
||||
virtualenv: virtualenv
|
||||
venv: venv
|
||||
vulkan: [vulkan-tools, vulkan-devel]
|
||||
shader_compiler: [shaderc, glslang-devel, glslang]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
gentoo:
|
||||
general: "Gentoo compiles packages from source. Installation will take longer."
|
||||
python: "Ensure Python 3.11+ is set as the active version"
|
||||
system: [dev-python/pygobject:3, x11-libs/gtk+:3, dev-libs/libayatana-appindicator, media-libs/portaudio, dev-lang/python:3.11, pkgconf, cmake, media-libs/shaderc, dev-util/patchelf, x11-misc/xclip, x11-misc/xsel, gui-apps/wl-clipboard]
|
||||
text_input:
|
||||
xdotool: [x11-misc/xdotool]
|
||||
wtype: [x11-misc/wtype]
|
||||
ydotool: [app-misc/ydotool]
|
||||
|
||||
alpine:
|
||||
general: "Alpine uses musl libc. Some Python packages may not have pre-built wheels."
|
||||
|
||||
nixos:
|
||||
general: "NixOS is not supported due to completely different filesystem layout."
|
||||
system: [py3-gobject3, py3-pip, gtk+3.0, py3-cairo, portaudio-dev, py3-virtualenv, pkgconf, cmake, wget, curl, unzip, shaderc, patchelf, vulkan-tools, xclip, xsel, wl-clipboard]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
void:
|
||||
general: "Void Linux may use either glibc or musl libc. glibc version recommended."
|
||||
system: [python3-pip, python3-gobject, gtk+3, libappindicator-gtk3, gobject-introspection, portaudio-devel, python3-devel, pkg-config, cmake, wget, curl, unzip, shaderc, patchelf, Vulkan-Tools, xclip, xsel, wl-clipboard]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# Architecture-specific library paths
|
||||
library_paths:
|
||||
typelib:
|
||||
# Debian/Ubuntu multiarch paths
|
||||
- /usr/lib/x86_64-linux-gnu/girepository-1.0
|
||||
- /usr/lib/aarch64-linux-gnu/girepository-1.0
|
||||
- /usr/lib/arm-linux-gnueabihf/girepository-1.0
|
||||
- /usr/lib/riscv64-linux-gnu/girepository-1.0
|
||||
- /usr/lib/powerpc64le-linux-gnu/girepository-1.0
|
||||
- /usr/lib/s390x-linux-gnu/girepository-1.0
|
||||
solus:
|
||||
system: [python3-pip, python3-gobject, gtk3, libappindicator, gobject-introspection-devel, portaudio-devel, python3-virtualenv, pkg-config, cmake, wget, curl, unzip, shaderc, patchelf, vulkan-tools, xclip, xsel, wl-clipboard]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
# Fedora/RHEL
|
||||
- /usr/lib64/girepository-1.0
|
||||
|
||||
# Arch, generic
|
||||
- /usr/lib/girepository-1.0
|
||||
|
||||
# Local installations
|
||||
- /usr/local/lib/girepository-1.0
|
||||
- /usr/local/lib64/girepository-1.0
|
||||
|
||||
models:
|
||||
- /usr/local/share/vocalinux/models
|
||||
- /usr/share/vocalinux/models
|
||||
- /usr/lib64/vocalinux/models
|
||||
- /usr/lib/vocalinux/models
|
||||
mageia:
|
||||
# The existing installer deliberately follows Fedora's package inventory.
|
||||
system: [python3-pip, python3-gobject, gtk3, ibus-devel, gobject-introspection-devel, python3-devel, portaudio-devel, cairo-devel, python3-virtualenv, pkg-config, cmake, wget, curl, unzip, vulkan-tools, vulkan-loader-devel, glslc, patchelf, xclip, xsel, wl-clipboard]
|
||||
text_input:
|
||||
xdotool: [xdotool]
|
||||
wtype: [wtype]
|
||||
ydotool: [ydotool]
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the installer's shell package map from its YAML source."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shlex
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / "scripts" / "distro-package-map.yaml"
|
||||
TARGET = ROOT / "install.d" / "package_map.sh"
|
||||
REQUIRED_DISTRIBUTIONS = {
|
||||
"alpine",
|
||||
"arch",
|
||||
"debian_12",
|
||||
"debian_13_plus",
|
||||
"fedora",
|
||||
"gentoo",
|
||||
"mageia",
|
||||
"solus",
|
||||
"suse",
|
||||
"ubuntu",
|
||||
"void",
|
||||
}
|
||||
LIST_FIELDS = {
|
||||
"appindicator",
|
||||
"gi_development",
|
||||
"optional_system",
|
||||
"shader_compiler",
|
||||
"system",
|
||||
"vulkan",
|
||||
}
|
||||
SCALAR_FIELDS = {"selection_probe"}
|
||||
TEXT_TOOLS = {"wtype", "xdotool", "ydotool"}
|
||||
SUSE_PYTHON_ROLES = {"pip", "gobject", "gobject_cairo", "devel", "virtualenv", "venv"}
|
||||
|
||||
|
||||
class UniqueKeyLoader(yaml.SafeLoader):
|
||||
"""Reject duplicate mapping keys instead of silently dropping inventory."""
|
||||
|
||||
def construct_mapping(self, node: yaml.MappingNode, deep: bool = False) -> dict:
|
||||
self.flatten_mapping(node)
|
||||
result = {}
|
||||
for key_node, value_node in node.value:
|
||||
key = self.construct_object(key_node, deep=deep)
|
||||
if not isinstance(key, str):
|
||||
raise ValueError("package map keys must be strings")
|
||||
if key in result:
|
||||
raise ValueError(f"duplicate YAML key: {key}")
|
||||
result[key] = self.construct_object(value_node, deep=deep)
|
||||
return result
|
||||
|
||||
|
||||
def _package_list(value: Any, location: str, *, required: bool = False) -> list[str]:
|
||||
"""Validate and return one package list."""
|
||||
if value is None and not required:
|
||||
return []
|
||||
if not isinstance(value, list) or (required and not value):
|
||||
raise ValueError(f"{location} must be a non-empty list")
|
||||
if any(not isinstance(item, str) or not item for item in value):
|
||||
raise ValueError(f"{location} must contain non-empty strings")
|
||||
if len(value) != len(set(value)):
|
||||
raise ValueError(f"{location} contains duplicate packages")
|
||||
return value
|
||||
|
||||
|
||||
def load_map() -> dict[str, dict[str, Any]]:
|
||||
"""Load and validate the authoritative YAML package map."""
|
||||
document = yaml.load(SOURCE.read_text(encoding="utf-8"), Loader=UniqueKeyLoader)
|
||||
if not isinstance(document, dict) or document.get("schema_version") != 1:
|
||||
raise ValueError("distro package map must use schema_version: 1")
|
||||
distributions = document.get("distributions")
|
||||
if not isinstance(distributions, dict):
|
||||
raise ValueError("distro package map must define a distributions mapping")
|
||||
names = set(distributions)
|
||||
if names != REQUIRED_DISTRIBUTIONS:
|
||||
missing = ", ".join(sorted(REQUIRED_DISTRIBUTIONS - names)) or "none"
|
||||
extra = ", ".join(sorted(names - REQUIRED_DISTRIBUTIONS)) or "none"
|
||||
raise ValueError(f"distribution keys differ (missing: {missing}; extra: {extra})")
|
||||
|
||||
for distro, config in distributions.items():
|
||||
if not isinstance(config, dict):
|
||||
raise ValueError(f"distributions.{distro} must be a mapping")
|
||||
unknown = set(config) - LIST_FIELDS - SCALAR_FIELDS - {"python_packages", "text_input"}
|
||||
if unknown:
|
||||
raise ValueError(f"distributions.{distro} has unknown fields: {sorted(unknown)}")
|
||||
_package_list(config.get("system"), f"distributions.{distro}.system", required=True)
|
||||
for field in LIST_FIELDS - {"system"}:
|
||||
_package_list(config.get(field), f"distributions.{distro}.{field}")
|
||||
selection_probe = config.get("selection_probe")
|
||||
if selection_probe is not None and (
|
||||
distro != "debian_13_plus"
|
||||
or not isinstance(selection_probe, str)
|
||||
or not selection_probe
|
||||
):
|
||||
raise ValueError("selection_probe must be a non-empty string on debian_13_plus only")
|
||||
python_packages = config.get("python_packages")
|
||||
if python_packages is not None:
|
||||
if distro != "suse" or not isinstance(python_packages, dict):
|
||||
raise ValueError("python_packages must be a mapping on suse only")
|
||||
if set(python_packages) != SUSE_PYTHON_ROLES:
|
||||
raise ValueError(
|
||||
f"distributions.suse.python_packages must define {sorted(SUSE_PYTHON_ROLES)}"
|
||||
)
|
||||
if any(not isinstance(value, str) or not value for value in python_packages.values()):
|
||||
raise ValueError("distributions.suse.python_packages values must be strings")
|
||||
text_input = config.get("text_input")
|
||||
if not isinstance(text_input, dict) or set(text_input) != TEXT_TOOLS:
|
||||
raise ValueError(f"distributions.{distro}.text_input must define {sorted(TEXT_TOOLS)}")
|
||||
for tool in TEXT_TOOLS:
|
||||
_package_list(
|
||||
text_input.get(tool),
|
||||
f"distributions.{distro}.text_input.{tool}",
|
||||
required=True,
|
||||
)
|
||||
if len(text_input[tool]) != 1:
|
||||
raise ValueError(
|
||||
f"distributions.{distro}.text_input.{tool} needs exactly one package"
|
||||
)
|
||||
|
||||
for distro in ("ubuntu", "fedora", "arch"):
|
||||
if len(distributions[distro].get("appindicator") or []) != 2:
|
||||
raise ValueError(f"distributions.{distro}.appindicator must define two fallbacks")
|
||||
if len(distributions["suse"].get("appindicator") or []) < 4:
|
||||
raise ValueError("distributions.suse.appindicator must define at least four alternatives")
|
||||
if not distributions["debian_13_plus"].get("selection_probe"):
|
||||
raise ValueError("distributions.debian_13_plus.selection_probe is required")
|
||||
if len(distributions["ubuntu"].get("gi_development") or []) != 2:
|
||||
raise ValueError("distributions.ubuntu.gi_development must define modern and legacy")
|
||||
for distro in ("ubuntu", "debian_12", "debian_13_plus"):
|
||||
if len(distributions[distro].get("shader_compiler") or []) < 2:
|
||||
raise ValueError(f"distributions.{distro}.shader_compiler needs a fallback")
|
||||
if "python_packages" not in distributions["suse"]:
|
||||
raise ValueError("distributions.suse.python_packages is required")
|
||||
for field in ("vulkan", "shader_compiler"):
|
||||
_package_list(distributions["suse"].get(field), f"suse.{field}", required=True)
|
||||
return distributions
|
||||
|
||||
|
||||
def _array_assignment(name: str, values: list[str], indent: str = " ") -> str:
|
||||
quoted = " ".join(shlex.quote(value) for value in values)
|
||||
return f"{indent}{name}=({quoted})"
|
||||
|
||||
|
||||
def render(distributions: dict[str, dict[str, Any]]) -> str:
|
||||
"""Render a deterministic Bash module from the validated map."""
|
||||
debian_13_probe = distributions["debian_13_plus"]["selection_probe"]
|
||||
lines = [
|
||||
"#!/bin/bash",
|
||||
"# Generated by scripts/generate_distro_package_map.py -- do not edit.",
|
||||
"# Regenerate with `just distro-packages` after editing",
|
||||
"# scripts/distro-package-map.yaml.",
|
||||
"",
|
||||
f"DEBIAN_13_PLUS_PROBE_PACKAGE={shlex.quote(debian_13_probe)}",
|
||||
"",
|
||||
"load_distro_package_map() {",
|
||||
' local package_map_key="$1"',
|
||||
"",
|
||||
" SYSTEM_PACKAGES=()",
|
||||
" APPINDICATOR_PACKAGES=()",
|
||||
" GI_DEVELOPMENT_PACKAGES=()",
|
||||
" OPTIONAL_SYSTEM_PACKAGES=()",
|
||||
' PYTHON_PIP_SUFFIX=""',
|
||||
' PYTHON_GOBJECT_SUFFIX=""',
|
||||
' PYTHON_GOBJECT_CAIRO_SUFFIX=""',
|
||||
' PYTHON_DEVEL_SUFFIX=""',
|
||||
' PYTHON_VIRTUALENV_SUFFIX=""',
|
||||
' PYTHON_VENV_SUFFIX=""',
|
||||
" SHADER_COMPILER_PACKAGES=()",
|
||||
" VULKAN_PACKAGES=()",
|
||||
" XDOTOOL_PACKAGES=()",
|
||||
" WTYPE_PACKAGES=()",
|
||||
" YDOTOOL_PACKAGES=()",
|
||||
"",
|
||||
' case "$package_map_key" in',
|
||||
]
|
||||
names = {
|
||||
"system": "SYSTEM_PACKAGES",
|
||||
"appindicator": "APPINDICATOR_PACKAGES",
|
||||
"gi_development": "GI_DEVELOPMENT_PACKAGES",
|
||||
"optional_system": "OPTIONAL_SYSTEM_PACKAGES",
|
||||
"shader_compiler": "SHADER_COMPILER_PACKAGES",
|
||||
"vulkan": "VULKAN_PACKAGES",
|
||||
}
|
||||
for distro, config in distributions.items():
|
||||
lines.append(f" {distro})")
|
||||
for field, variable in names.items():
|
||||
values = _package_list(config.get(field), f"{distro}.{field}")
|
||||
if values:
|
||||
lines.append(_array_assignment(variable, values))
|
||||
python_packages = config.get("python_packages")
|
||||
if python_packages:
|
||||
for role in sorted(SUSE_PYTHON_ROLES):
|
||||
variable = f"PYTHON_{role.upper()}_SUFFIX"
|
||||
lines.append(f" {variable}={shlex.quote(python_packages[role])}")
|
||||
text_input = config["text_input"]
|
||||
lines.append(_array_assignment("XDOTOOL_PACKAGES", text_input["xdotool"]))
|
||||
lines.append(_array_assignment("WTYPE_PACKAGES", text_input["wtype"]))
|
||||
lines.append(_array_assignment("YDOTOOL_PACKAGES", text_input["ydotool"]))
|
||||
lines.extend([" ;;", ""])
|
||||
lines.extend(
|
||||
[
|
||||
" *)",
|
||||
' echo "Unknown distro package map key: $package_map_key" >&2',
|
||||
" return 1",
|
||||
" ;;",
|
||||
" esac",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Generate the map, or check that the committed output is current."""
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true", help="fail if the output is stale")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
generated = render(load_map())
|
||||
except (OSError, ValueError, yaml.YAMLError) as error:
|
||||
print(f"error: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if args.check:
|
||||
current = TARGET.read_text(encoding="utf-8") if TARGET.exists() else ""
|
||||
if current != generated:
|
||||
print(
|
||||
f"{TARGET.relative_to(ROOT)} is stale; run `just distro-packages`",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
TARGET.write_text(generated, encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -138,12 +138,12 @@ class TestCrossDistroCompatibility:
|
||||
assert "typelib-1_0-Notify-0_7" in install_sh_content
|
||||
assert "libnotify4" in install_sh_content
|
||||
assert "suse_install_appindicator_runtime" in install_sh_content
|
||||
assert "shaderc glslang-devel glslang" in install_sh_content
|
||||
|
||||
zypper_line = next(
|
||||
line for line in install_sh_content.splitlines() if "local ZYPPER_PACKAGES=" in line
|
||||
line
|
||||
for line in install_sh_content.splitlines()
|
||||
if "SYSTEM_PACKAGES=(gtk3 ibus " in line
|
||||
)
|
||||
zypper_packages = zypper_line.split('"')[1].split()
|
||||
zypper_packages = zypper_line.removeprefix("SYSTEM_PACKAGES=(").rstrip(")").split()
|
||||
# ibus, not the typelib alone: ibus_engine.py spawns `ibus-daemon -x -d -r`
|
||||
# and shells out to `ibus engine`, neither of which libibus provides.
|
||||
assert "ibus" in zypper_packages
|
||||
@@ -157,10 +157,14 @@ class TestCrossDistroCompatibility:
|
||||
def test_rpm_arch_install_glslc_not_glslang(self, install_sh_content):
|
||||
"""ggml Vulkan needs glslc (shaderc). glslangValidator is not a substitute (#604)."""
|
||||
dnf_line = next(
|
||||
line for line in install_sh_content.splitlines() if "local DNF_PACKAGES=" in line
|
||||
line
|
||||
for line in install_sh_content.splitlines()
|
||||
if "SYSTEM_PACKAGES=(python3-pip python3-gobject gtk3 " in line
|
||||
)
|
||||
pacman_line = next(
|
||||
line for line in install_sh_content.splitlines() if "local PACMAN_PACKAGES=" in line
|
||||
line
|
||||
for line in install_sh_content.splitlines()
|
||||
if "SYSTEM_PACKAGES=(python-pip python-gobject gtk3 " in line
|
||||
)
|
||||
assert "glslc" in dnf_line
|
||||
assert "patchelf" in dnf_line
|
||||
@@ -177,7 +181,9 @@ class TestCrossDistroCompatibility:
|
||||
in install_sh_content
|
||||
)
|
||||
emerge_line = next(
|
||||
line for line in install_sh_content.splitlines() if "local EMERGE_PACKAGES=" in line
|
||||
line
|
||||
for line in install_sh_content.splitlines()
|
||||
if "SYSTEM_PACKAGES=(dev-python/pygobject:3 " in line
|
||||
)
|
||||
assert "media-libs/shaderc" in emerge_line
|
||||
assert "dev-util/patchelf" in emerge_line
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
"""Guards for the YAML-owned installer package inventory."""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / "scripts" / "distro-package-map.yaml"
|
||||
TARGET = ROOT / "install.d" / "package_map.sh"
|
||||
GENERATOR = ROOT / "scripts" / "generate_distro_package_map.py"
|
||||
|
||||
|
||||
def _load_generator() -> Any:
|
||||
spec = importlib.util.spec_from_file_location("generate_distro_package_map", GENERATOR)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_generated_package_map_matches_yaml() -> None:
|
||||
"""A YAML edit cannot leave the installer using yesterday's packages."""
|
||||
generator = _load_generator()
|
||||
assert TARGET.read_text(encoding="utf-8") == generator.render(generator.load_map())
|
||||
|
||||
|
||||
def test_generated_package_map_is_valid_bash() -> None:
|
||||
"""The committed artifact must remain safe for install.sh to source."""
|
||||
result = subprocess.run(["bash", "-n", str(TARGET)], capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_supported_debian_maps_start_at_debian_12() -> None:
|
||||
"""Do not retain an EOL Debian 11 package branch in the source of truth."""
|
||||
distributions = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))["distributions"]
|
||||
debian_maps = {name for name in distributions if name.startswith("debian_")}
|
||||
assert debian_maps == {"debian_12", "debian_13_plus"}
|
||||
|
||||
|
||||
def _resolve_debian_map(
|
||||
distro_id: str, version: str, *, modern_gi: bool, refresh_fails: bool = False
|
||||
) -> subprocess.CompletedProcess:
|
||||
script = f"""
|
||||
set -eu
|
||||
source {shlex.quote(str(TARGET))}
|
||||
source {shlex.quote(str(ROOT / 'install.d' / 'system_dependencies.sh'))}
|
||||
print_error() {{ echo "$*"; }}
|
||||
EXIT_NETWORK=3
|
||||
EXIT_MISSING_DEPS=2
|
||||
CACHE_READY=no
|
||||
sudo() {{
|
||||
[ "$*" = "apt update" ] || exit 99
|
||||
echo "refreshing indexes"
|
||||
[ "$REFRESH_FAILS" = no ] || return 1
|
||||
CACHE_READY=yes
|
||||
}}
|
||||
apt-cache() {{
|
||||
[ "$*" = "show $DEBIAN_13_PLUS_PROBE_PACKAGE" ] || exit 99
|
||||
[ "$CACHE_READY" = yes ] && [ "$HAS_MODERN_GI" = yes ]
|
||||
}}
|
||||
DISTRO_ID="$1"
|
||||
DISTRO_VERSION="$2"
|
||||
resolve_debian_package_map_key
|
||||
"""
|
||||
return subprocess.run(
|
||||
["bash", "-c", script, "bash", distro_id, version],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={
|
||||
**os.environ,
|
||||
"HAS_MODERN_GI": "yes" if modern_gi else "no",
|
||||
"REFRESH_FAILS": "yes" if refresh_fails else "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_native_debian_version_selects_supported_map() -> None:
|
||||
"""Debian itself uses VERSION_ID and rejects its EOL releases."""
|
||||
assert _resolve_debian_map("debian", "11", modern_gi=False).returncode != 0
|
||||
assert _resolve_debian_map("debian", "12", modern_gi=True).stdout.strip() == "debian_12"
|
||||
assert _resolve_debian_map("debian", "13", modern_gi=False).stdout.strip() == "debian_13_plus"
|
||||
|
||||
|
||||
def test_debian_derivative_ignores_unrelated_product_version() -> None:
|
||||
"""A derivative's VERSION_ID must not be interpreted as a Debian release."""
|
||||
legacy = _resolve_debian_map("mx", "11", modern_gi=False)
|
||||
modern = _resolve_debian_map("kali", "2026.3", modern_gi=True)
|
||||
assert legacy.returncode == 0
|
||||
assert legacy.stdout.strip() == "debian_12"
|
||||
assert modern.stdout.strip() == "debian_13_plus"
|
||||
assert "refreshing indexes" in modern.stderr
|
||||
|
||||
|
||||
def test_debian_probe_does_not_fall_back_on_network_failure() -> None:
|
||||
result = _resolve_debian_map("derivative", "7", modern_gi=True, refresh_fails=True)
|
||||
assert result.returncode == 3
|
||||
assert "debian_12" not in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ["vulkan", "shader_compiler"])
|
||||
@pytest.mark.parametrize("value", [None, []])
|
||||
def test_required_suse_build_inventory(tmp_path: Path, field: str, value: Any) -> None:
|
||||
generator = _load_generator()
|
||||
document = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))
|
||||
if value is None:
|
||||
del document["distributions"]["suse"][field]
|
||||
else:
|
||||
document["distributions"]["suse"][field] = value
|
||||
generator.SOURCE = tmp_path / "invalid.yaml"
|
||||
generator.SOURCE.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match=f"suse.{field}"):
|
||||
generator.load_map()
|
||||
|
||||
|
||||
def test_text_tool_cannot_silently_drop_second_package(tmp_path: Path) -> None:
|
||||
generator = _load_generator()
|
||||
document = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))
|
||||
document["distributions"]["ubuntu"]["text_input"]["wtype"].append("extra-package")
|
||||
generator.SOURCE = tmp_path / "invalid.yaml"
|
||||
generator.SOURCE.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="exactly one package"):
|
||||
generator.load_map()
|
||||
|
||||
|
||||
def test_suse_suffix_spelling_is_owned_by_yaml(tmp_path: Path) -> None:
|
||||
generator = _load_generator()
|
||||
document = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))
|
||||
document["distributions"]["suse"]["python_packages"]["pip"] = "renamed-pip"
|
||||
generator.SOURCE = tmp_path / "map.yaml"
|
||||
generator.SOURCE.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
rendered = generator.render(generator.load_map())
|
||||
assert "PYTHON_PIP_SUFFIX=renamed-pip" in rendered
|
||||
|
||||
|
||||
def test_suse_python_roles_are_named_not_positional(tmp_path: Path) -> None:
|
||||
generator = _load_generator()
|
||||
document = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))
|
||||
del document["distributions"]["suse"]["python_packages"]["pip"]
|
||||
document["distributions"]["suse"]["python_packages"]["unexpected"] = "pip"
|
||||
generator.SOURCE = tmp_path / "invalid.yaml"
|
||||
generator.SOURCE.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="python_packages must define"):
|
||||
generator.load_map()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"old,new",
|
||||
[
|
||||
("schema_version: 1", "schema_version: 99\nschema_version: 1"),
|
||||
("wtype: [wtype]", "wtype: [discarded]\n wtype: [wtype]"),
|
||||
],
|
||||
)
|
||||
def test_duplicate_keys_are_rejected(tmp_path: Path, old: str, new: str) -> None:
|
||||
generator = _load_generator()
|
||||
generator.SOURCE = tmp_path / "invalid.yaml"
|
||||
generator.SOURCE.write_text(SOURCE.read_text().replace(old, new, 1), encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="duplicate YAML key"):
|
||||
generator.load_map()
|
||||
|
||||
|
||||
def test_suse_appindicator_alternatives_are_required(tmp_path: Path) -> None:
|
||||
"""Generation fails before an empty openSUSE fallback loop reaches users."""
|
||||
generator = _load_generator()
|
||||
document = yaml.safe_load(SOURCE.read_text(encoding="utf-8"))
|
||||
document["distributions"]["suse"]["appindicator"] = []
|
||||
invalid_source = tmp_path / "invalid.yaml"
|
||||
invalid_source.write_text(yaml.safe_dump(document), encoding="utf-8")
|
||||
generator.SOURCE = invalid_source
|
||||
with pytest.raises(ValueError, match="suse.appindicator"):
|
||||
generator.load_map()
|
||||
|
||||
|
||||
def test_installer_uses_generated_inventory_instead_of_package_lists() -> None:
|
||||
"""Package data belongs in YAML; handwritten shell owns only selection policy."""
|
||||
installer = (ROOT / "install.d" / "system_dependencies.sh").read_text(encoding="utf-8")
|
||||
assert 'load_distro_package_map "$PACKAGE_MAP_KEY"' in installer
|
||||
for old_variable in (
|
||||
"APT_PACKAGES_UBUNTU",
|
||||
"APT_PACKAGES_DEBIAN_BASE",
|
||||
"DNF_PACKAGES",
|
||||
"PACMAN_PACKAGES",
|
||||
"ZYPPER_PACKAGES",
|
||||
"EMERGE_PACKAGES",
|
||||
"APK_PACKAGES",
|
||||
"XBPS_PACKAGES",
|
||||
"EOPKG_PACKAGES",
|
||||
):
|
||||
assert f"local {old_variable}=" not in installer
|
||||
|
||||
|
||||
def test_unsupported_distros_do_not_read_uninitialized_package_arrays() -> None:
|
||||
"""The existing non-interactive unsupported-distro path may continue safely."""
|
||||
installer = (ROOT / "install.d" / "system_dependencies.sh").read_text(encoding="utf-8")
|
||||
guard = installer.index('[[ -z "${XDOTOOL_PACKAGES+x}" ]]')
|
||||
first_read = installer.index("${XDOTOOL_PACKAGES[0]}")
|
||||
assert guard < first_read
|
||||
@@ -3,8 +3,13 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
INSTALLER = Path(__file__).resolve().parents[1] / "install.sh"
|
||||
INSTALLER_MODULES = Path(__file__).resolve().parents[1] / "install.d"
|
||||
PACKAGE_MAP = yaml.safe_load(
|
||||
(INSTALLER.parent / "scripts" / "distro-package-map.yaml").read_text(encoding="utf-8")
|
||||
)["distributions"]
|
||||
|
||||
|
||||
def _installer_source() -> str:
|
||||
@@ -83,40 +88,25 @@ class InstallerCudaDiagnosticsTests(unittest.TestCase):
|
||||
|
||||
def test_installer_includes_xsel_for_wayland_clipboard_fallback(self) -> None:
|
||||
"""Fresh installs should include xsel for ydotool's layout-safe paste path."""
|
||||
source = _installer_source()
|
||||
for distro, config in PACKAGE_MAP.items():
|
||||
for tool in ("xclip", "xsel", "wl-clipboard"):
|
||||
self.assertTrue(
|
||||
any(
|
||||
package == tool or package.endswith(f"/{tool}")
|
||||
for package in config["system"]
|
||||
),
|
||||
f"{distro} does not install {tool}",
|
||||
)
|
||||
|
||||
required_lines = [
|
||||
"local APT_PACKAGES_UBUNTU=",
|
||||
"local APT_PACKAGES_DEBIAN_BASE=",
|
||||
"local DNF_PACKAGES=",
|
||||
"local PACMAN_PACKAGES=",
|
||||
"local ZYPPER_PACKAGES=",
|
||||
"local EMERGE_PACKAGES=",
|
||||
"local APK_PACKAGES=",
|
||||
"local XBPS_PACKAGES=",
|
||||
"local EOPKG_PACKAGES=",
|
||||
]
|
||||
|
||||
for name in required_lines:
|
||||
line = next(line for line in source.splitlines() if name in line)
|
||||
self.assertIn("xclip", line)
|
||||
self.assertIn("xsel", line)
|
||||
self.assertIn("wl-clipboard", line)
|
||||
|
||||
def test_ubuntu_22_package_list_omits_util_linux_extra(self) -> None:
|
||||
def test_optional_apt_package_is_not_in_base_lists(self) -> None:
|
||||
"""util-linux-extra is probed via apt-cache, not hardcoded in base package lists."""
|
||||
source = _installer_source()
|
||||
|
||||
ubuntu_line = next(
|
||||
line for line in source.splitlines() if "local APT_PACKAGES_UBUNTU=" in line
|
||||
)
|
||||
debian_13_line = next(
|
||||
line for line in source.splitlines() if "local APT_PACKAGES_DEBIAN_13_PLUS=" in line
|
||||
)
|
||||
self.assertNotIn("util-linux-extra", ubuntu_line)
|
||||
self.assertNotIn("util-linux-extra", debian_13_line)
|
||||
self.assertIn("apt-cache show util-linux-extra", source)
|
||||
self.assertIn('APT_PACKAGES="$APT_PACKAGES util-linux-extra"', source)
|
||||
for distro in ("ubuntu", "debian_12", "debian_13_plus"):
|
||||
self.assertNotIn("util-linux-extra", PACKAGE_MAP[distro]["system"])
|
||||
self.assertIn("util-linux-extra", PACKAGE_MAP[distro]["optional_system"])
|
||||
self.assertIn('apt-cache show "$OPTIONAL_PACKAGE"', source)
|
||||
self.assertIn('SYSTEM_PACKAGES+=("$OPTIONAL_PACKAGE")', source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -12,6 +12,7 @@ EXPECTED_MODULES = {
|
||||
"desktop.sh",
|
||||
"interactive.sh",
|
||||
"models.sh",
|
||||
"package_map.sh",
|
||||
"system_dependencies.sh",
|
||||
}
|
||||
|
||||
@@ -22,6 +23,7 @@ def test_installer_loads_every_module_from_the_resolved_tree() -> None:
|
||||
assert "source_installer_module()" in source
|
||||
for module in EXPECTED_MODULES:
|
||||
assert module in source
|
||||
assert source.index("package_map.sh") < source.index("system_dependencies.sh")
|
||||
|
||||
remote_handoff = source.index("handoff_to_tagged_installer")
|
||||
module_loader = source.index("source_installer_module()")
|
||||
|
||||
@@ -15,6 +15,7 @@ import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -340,10 +341,9 @@ def test_source_built_runtime_packages_have_build_deps_on_split_devel_distros()
|
||||
"""The pinned runtime builds these from source; distro lists that split
|
||||
-devel packages must carry their build deps."""
|
||||
runtime = (ROOT / "requirements/runtime.txt").read_text(encoding="utf-8")
|
||||
installer = installer_source()
|
||||
|
||||
def package_line(prefix: str) -> str:
|
||||
return next(line for line in installer.splitlines() if f"local {prefix}=" in line)
|
||||
package_map = yaml.safe_load(
|
||||
(ROOT / "scripts" / "distro-package-map.yaml").read_text(encoding="utf-8")
|
||||
)["distributions"]
|
||||
|
||||
build_deps = {
|
||||
"pycairo": ("cairo-devel", "libcairo2-dev"),
|
||||
@@ -353,6 +353,7 @@ def test_source_built_runtime_packages_have_build_deps_on_split_devel_distros()
|
||||
for name, (dnf_pkg, apt_pkg) in build_deps.items():
|
||||
if not re.search(rf"^{name}==", runtime, re.MULTILINE):
|
||||
continue # wheels-only or dropped: nothing to build
|
||||
assert dnf_pkg in package_line("DNF_PACKAGES"), name
|
||||
assert apt_pkg in package_line("APT_PACKAGES_UBUNTU"), name
|
||||
assert apt_pkg in package_line("APT_PACKAGES_DEBIAN_BASE"), name
|
||||
assert dnf_pkg in package_map["fedora"]["system"], name
|
||||
assert apt_pkg in package_map["ubuntu"]["system"], name
|
||||
assert apt_pkg in package_map["debian_12"]["system"], name
|
||||
assert apt_pkg in package_map["debian_13_plus"]["system"], name
|
||||
|
||||
@@ -2958,6 +2958,7 @@ dev = [
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-mock" },
|
||||
{ name = "pytest-timeout" },
|
||||
{ name = "pyyaml" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
@@ -3016,6 +3017,7 @@ requires-dist = [
|
||||
{ name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.10.0" },
|
||||
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.1.0" },
|
||||
{ name = "pywhispercpp", specifier = ">=1.5.0,<2" },
|
||||
{ name = "pyyaml", marker = "extra == 'dev'", specifier = ">=6.0,<7" },
|
||||
{ name = "requests", specifier = ">=2.28.0" },
|
||||
{ name = "sherpa-onnx", marker = "extra == 'parakeet'", specifier = ">=1.12.0,<2" },
|
||||
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">=6.0.0" },
|
||||
|
||||
Reference in New Issue
Block a user