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:
+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())
|
||||
Reference in New Issue
Block a user