From c2c74d9bf415899315827498f8ae4d7ccd67e960 Mon Sep 17 00:00:00 2001 From: VykosMolt Date: Mon, 31 Aug 2026 18:49:17 +0200 Subject: [PATCH 1/3] Add an archinstall-meta package dependency inventory archinstall installs a lot of things it does not depend on. Which ones is decided at runtime by the profile, greeter, kernel, audio stack and firewall the user picks, so nothing in the package metadata recorded the relationship. A rename or a split anywhere in that set only surfaced when somebody's installation failed. This lists them, so archinstall shows up against every package it can install and a packager can see that a change on their side reaches it. The package ships no files; the metadata is the whole point. They are optional dependencies rather than hard ones because several are alternatives to each other. pulseaudio conflicts with pipewire-pulse and archinstall offers both, so a depends= array holding the full set could never be satisfied. The list was derived from the current code rather than copied from the older one in #2406, which has drifted: it has dmenu where sway now installs wmenu, lists profiles that have since been removed, and predates niri and labwc. Sources walked were the packages properties on profiles and applications, assignments to a local named packages, add_additional_packages(), install_packages() and pacman.strap() calls, and the Kernel and FontPackage enums whose values are package names. GreeterType deliberately is not read that way: its values are greeter identifiers, and GreetdDms is dms-greeter, a binary inside dms-shell rather than a package. The packages behind each greeter are named in install_greeter() instead. Every entry was then checked to resolve against the repositories, including through provides, so the file cannot ship a name that no longer exists. Ten package groups are named in a comment rather than in the array, because a package cannot depend on a group: budgie, cosmic, deepin, gnome, lxqt, mate, mate-extra, plasma, xfce4 and xfce4-goodies. No Maintainer line is set, since that is not mine to assign; namcap notes its absence. Whoever takes the package on should add themselves. #2406 is left alone. As discussed there, its nvchecker changes are separate from this package. --- archinstall-meta/PKGBUILD | 208 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 archinstall-meta/PKGBUILD diff --git a/archinstall-meta/PKGBUILD b/archinstall-meta/PKGBUILD new file mode 100644 index 0000000000..4c6c03d499 --- /dev/null +++ b/archinstall-meta/PKGBUILD @@ -0,0 +1,208 @@ +# Packages archinstall may install for its supported installation paths, listed +# so that changes to them are visible from the package metadata. Nothing here +# is installed by this package; keep the list in sync with the profiles and the +# package selection code. +# +# These are optional dependencies because several are alternatives to each +# other: pulseaudio conflicts with pipewire-pulse, and archinstall offers both. + +pkgname=archinstall-meta +pkgver=1 +pkgrel=1 +pkgdesc="Package inventory for archinstall, linking it to what it can install" +arch=(any) +url="https://github.com/archlinux/archinstall" +license=(GPL-3.0-only) +depends=(archinstall) +optdepends=( + # Kernels + 'linux: kernel selection' + 'linux-hardened: kernel selection' + 'linux-lts: kernel selection' + 'linux-rt: kernel selection' + 'linux-rt-lts: kernel selection' + 'linux-zen: kernel selection' + + # Bootloaders, snapshots and other installation options + 'cronie: cron daemon Timeshift snapshots are scheduled with' + 'efibootmgr: UEFI boot entry management for most bootloaders' + 'grub: GRUB bootloader' + 'grub-btrfs: GRUB entries for btrfs snapshots' + 'inotify-tools: required by grub-btrfs snapshot updates' + 'iwd: iwd based network configuration' + 'libfido2: FIDO2 unlocking of encrypted volumes' + 'limine: Limine bootloader' + 'plymouth: graphical boot splash' + 'refind: rEFInd bootloader' + 'snapper: btrfs snapshots with snapper' + 'timeshift: btrfs snapshots with Timeshift' + 'zram-generator: zram swap' + + # Network + 'networkmanager: network configuration' + + # Audio + 'alsa-firmware: audio configuration' + 'gst-plugin-pipewire: audio configuration' + 'libpulse: audio configuration' + 'pipewire: audio configuration' + 'pipewire-alsa: audio configuration' + 'pipewire-jack: audio configuration' + 'pipewire-pulse: audio configuration' + 'pulseaudio: audio configuration' + 'sof-firmware: audio configuration' + 'wireplumber: audio configuration' + + # Fonts + 'noto-fonts: font selection' + 'noto-fonts-cjk: font selection' + 'noto-fonts-emoji: font selection' + 'ttf-dejavu: font selection' + + # Bluetooth + 'bluez: Bluetooth support' + 'bluez-utils: Bluetooth support' + + # Printing + 'cups: printing support' + 'cups-pk-helper: printing support' + 'ghostscript: printing support' + 'system-config-printer: printing support' + + # Firewall + 'firewalld: firewall selection' + 'ufw: firewall selection' + + # Power management + 'power-profiles-daemon: power management selection' + 'tuned: power management selection' + 'tuned-ppd: power management selection' + + # Authentication + 'pam-u2f: U2F authentication' + + # Greeters + 'cosmic-greeter: greeter selection' + 'gdm: greeter selection' + 'greetd: greeter selection' + 'lightdm-slick-greeter: greeter selection' + 'ly: greeter selection' + 'plasma-login-manager: greeter selection' + 'sddm: greeter selection' + + # Desktop profiles + 'alacritty: awesome desktop profile' + 'awesome: awesome desktop profile' + 'breeze-icons: lxqt desktop profile' + 'brightnessctl: sway desktop profile' + 'bspwm: bspwm desktop profile' + 'cinnamon: cinnamon desktop profile' + 'deepin-editor: deepin desktop profile' + 'deepin-terminal: deepin desktop profile' + 'dmenu: bspwm desktop profile' + 'dolphin: hyprland desktop profile' + 'dunst: hyprland desktop profile' + 'engrampa: cinnamon desktop profile' + 'enlightenment: enlightenment desktop profile' + 'feh: awesome desktop profile' + 'foot: river desktop profile' + 'fuzzel: niri desktop profile' + 'gnome-keyring: cinnamon desktop profile' + 'gnome-screenshot: cinnamon desktop profile' + 'gnome-terminal: cinnamon desktop profile' + 'gnome-tweaks: gnome desktop profile' + 'gnu-free-fonts: awesome desktop profile' + 'grim: hyprland desktop profile' + 'gvfs: xfce4 desktop profile' + 'gvfs-smb: cinnamon desktop profile' + 'htop: installed with every desktop profile' + 'hyprland: hyprland desktop profile' + 'i3-wm: i3 desktop profile' + 'i3blocks: i3 desktop profile' + 'i3lock: i3 desktop profile' + 'i3status: i3 desktop profile' + 'kitty: hyprland desktop profile' + 'l3afpad: lxqt desktop profile' + 'labwc: labwc desktop profile' + 'lightdm: i3 desktop profile' + 'lightdm-gtk-greeter: i3 desktop profile' + 'mako: niri desktop profile' + 'mate-terminal: budgie desktop profile' + 'materia-gtk-theme: budgie desktop profile' + 'nano: installed with every desktop profile' + 'nemo: budgie desktop profile' + 'nemo-fileroller: budgie desktop profile' + 'niri: niri desktop profile' + 'openssh: installed with every desktop profile' + 'oxygen-icons: lxqt desktop profile' + 'papirus-icon-theme: budgie desktop profile' + 'pavucontrol: sway desktop profile' + 'plasma-desktop: plasma desktop profile' + 'plasma-meta: plasma desktop profile' + 'polkit-kde-agent: hyprland desktop profile' + 'qt5-wayland: hyprland desktop profile' + 'qt6-wayland: hyprland desktop profile' + 'qtile: qtile desktop profile' + 'river: river desktop profile' + 'rxvt-unicode: bspwm desktop profile' + 'slock: awesome desktop profile' + 'slurp: hyprland desktop profile' + 'smartmontools: installed with every desktop profile' + 'sway: sway desktop profile' + 'swaybg: niri desktop profile' + 'swayidle: niri desktop profile' + 'swaylock: niri desktop profile' + 'sxhkd: bspwm desktop profile' + 'terminology: enlightenment desktop profile' + 'terminus-font: awesome desktop profile' + 'ttf-freefont: lxqt desktop profile' + 'ttf-liberation: awesome desktop profile' + 'uwsm: hyprland desktop profile' + 'vim: installed with every desktop profile' + 'waybar: niri desktop profile' + 'wget: installed with every desktop profile' + 'wmenu: sway desktop profile' + 'wofi: hyprland desktop profile' + 'xarchiver: xfce4 desktop profile' + 'xdg-desktop-portal-gnome: niri desktop profile' + 'xdg-desktop-portal-hyprland: hyprland desktop profile' + 'xdg-desktop-portal-wlr: river desktop profile' + 'xdg-user-dirs: cosmic desktop profile' + 'xdg-user-dirs-gtk: cinnamon desktop profile' + 'xdg-utils: installed with every desktop profile' + 'xdo: bspwm desktop profile' + 'xed: cinnamon desktop profile' + 'xmonad: xmonad desktop profile' + 'xmonad-contrib: xmonad desktop profile' + 'xmonad-extras: xmonad desktop profile' + 'xorg-server: installed with every desktop profile' + 'xorg-xinit: installed with every desktop profile' + 'xorg-xrandr: awesome desktop profile' + 'xorg-xwayland: niri desktop profile' + 'xsel: awesome desktop profile' + 'xss-lock: i3 desktop profile' + 'xterm: awesome desktop profile' + + # Server profiles + 'apache: httpd server profile' + 'cockpit: cockpit server profile' + 'cockpit-packagekit: cockpit server profile' + 'cockpit-storaged: cockpit server profile' + 'docker: docker server profile' + 'lighttpd: lighttpd server profile' + 'mariadb: mariadb server profile' + 'nginx: nginx server profile' + 'postgresql: postgresql server profile' + 'tomcat10: tomcat server profile' + + # Scripts + 'git: minimal script' +) + +# archinstall also installs these package groups. They are named here rather +# than above because a package cannot depend on a group: +# budgie cosmic deepin gnome lxqt mate mate-extra plasma xfce4 xfce4-goodies + +package() { + : +} From d0b8cdbf4d6d403415bbfb4ed6f522d291a734d9 Mon Sep 17 00:00:00 2001 From: VykosMolt Date: Tue, 8 Sep 2026 23:49:20 +0200 Subject: [PATCH 2/3] Generate package inventory from installer choices --- archinstall-meta/PKGBUILD | 720 +++++++++++++----- archinstall/applications/audio.py | 5 +- archinstall/default_profiles/desktop.py | 3 +- .../authentication/authentication_handler.py | 3 +- archinstall/lib/installer.py | 40 +- archinstall/lib/models/package_types.py | 38 + archinstall/lib/network/network_handler.py | 11 +- archinstall/lib/packages/meta.py | 118 +++ archinstall/lib/profile/profiles_handler.py | 17 +- archinstall/scripts/minimal.py | 3 +- tests/test_package_inventory.py | 148 ++++ 11 files changed, 873 insertions(+), 233 deletions(-) create mode 100644 archinstall/lib/packages/meta.py create mode 100644 tests/test_package_inventory.py diff --git a/archinstall-meta/PKGBUILD b/archinstall-meta/PKGBUILD index 4c6c03d499..5ff7931b41 100644 --- a/archinstall-meta/PKGBUILD +++ b/archinstall-meta/PKGBUILD @@ -1,208 +1,538 @@ -# Packages archinstall may install for its supported installation paths, listed -# so that changes to them are visible from the package metadata. Nothing here -# is installed by this package; keep the list in sync with the profiles and the -# package selection code. -# -# These are optional dependencies because several are alternatives to each -# other: pulseaudio conflicts with pipewire-pulse, and archinstall offers both. +# Generated by python -m archinstall.lib.packages.meta pkgname=archinstall-meta pkgver=1 pkgrel=1 -pkgdesc="Package inventory for archinstall, linking it to what it can install" +pkgdesc="Package inventory for archinstall" arch=(any) url="https://github.com/archlinux/archinstall" license=(GPL-3.0-only) depends=(archinstall) optdepends=( - # Kernels - 'linux: kernel selection' - 'linux-hardened: kernel selection' - 'linux-lts: kernel selection' - 'linux-rt: kernel selection' - 'linux-rt-lts: kernel selection' - 'linux-zen: kernel selection' - - # Bootloaders, snapshots and other installation options - 'cronie: cron daemon Timeshift snapshots are scheduled with' - 'efibootmgr: UEFI boot entry management for most bootloaders' - 'grub: GRUB bootloader' - 'grub-btrfs: GRUB entries for btrfs snapshots' - 'inotify-tools: required by grub-btrfs snapshot updates' - 'iwd: iwd based network configuration' - 'libfido2: FIDO2 unlocking of encrypted volumes' - 'limine: Limine bootloader' - 'plymouth: graphical boot splash' - 'refind: rEFInd bootloader' - 'snapper: btrfs snapshots with snapper' - 'timeshift: btrfs snapshots with Timeshift' - 'zram-generator: zram swap' - - # Network - 'networkmanager: network configuration' - - # Audio - 'alsa-firmware: audio configuration' - 'gst-plugin-pipewire: audio configuration' - 'libpulse: audio configuration' - 'pipewire: audio configuration' - 'pipewire-alsa: audio configuration' - 'pipewire-jack: audio configuration' - 'pipewire-pulse: audio configuration' - 'pulseaudio: audio configuration' - 'sof-firmware: audio configuration' - 'wireplumber: audio configuration' - - # Fonts - 'noto-fonts: font selection' - 'noto-fonts-cjk: font selection' - 'noto-fonts-emoji: font selection' - 'ttf-dejavu: font selection' - - # Bluetooth - 'bluez: Bluetooth support' - 'bluez-utils: Bluetooth support' - - # Printing - 'cups: printing support' - 'cups-pk-helper: printing support' - 'ghostscript: printing support' - 'system-config-printer: printing support' - - # Firewall - 'firewalld: firewall selection' - 'ufw: firewall selection' - - # Power management - 'power-profiles-daemon: power management selection' - 'tuned: power management selection' - 'tuned-ppd: power management selection' - - # Authentication - 'pam-u2f: U2F authentication' - - # Greeters - 'cosmic-greeter: greeter selection' - 'gdm: greeter selection' - 'greetd: greeter selection' - 'lightdm-slick-greeter: greeter selection' - 'ly: greeter selection' - 'plasma-login-manager: greeter selection' - 'sddm: greeter selection' - - # Desktop profiles - 'alacritty: awesome desktop profile' - 'awesome: awesome desktop profile' - 'breeze-icons: lxqt desktop profile' - 'brightnessctl: sway desktop profile' - 'bspwm: bspwm desktop profile' - 'cinnamon: cinnamon desktop profile' - 'deepin-editor: deepin desktop profile' - 'deepin-terminal: deepin desktop profile' - 'dmenu: bspwm desktop profile' - 'dolphin: hyprland desktop profile' - 'dunst: hyprland desktop profile' - 'engrampa: cinnamon desktop profile' - 'enlightenment: enlightenment desktop profile' - 'feh: awesome desktop profile' - 'foot: river desktop profile' - 'fuzzel: niri desktop profile' - 'gnome-keyring: cinnamon desktop profile' - 'gnome-screenshot: cinnamon desktop profile' - 'gnome-terminal: cinnamon desktop profile' - 'gnome-tweaks: gnome desktop profile' - 'gnu-free-fonts: awesome desktop profile' - 'grim: hyprland desktop profile' - 'gvfs: xfce4 desktop profile' - 'gvfs-smb: cinnamon desktop profile' - 'htop: installed with every desktop profile' - 'hyprland: hyprland desktop profile' - 'i3-wm: i3 desktop profile' - 'i3blocks: i3 desktop profile' - 'i3lock: i3 desktop profile' - 'i3status: i3 desktop profile' - 'kitty: hyprland desktop profile' - 'l3afpad: lxqt desktop profile' - 'labwc: labwc desktop profile' - 'lightdm: i3 desktop profile' - 'lightdm-gtk-greeter: i3 desktop profile' - 'mako: niri desktop profile' - 'mate-terminal: budgie desktop profile' - 'materia-gtk-theme: budgie desktop profile' - 'nano: installed with every desktop profile' - 'nemo: budgie desktop profile' - 'nemo-fileroller: budgie desktop profile' - 'niri: niri desktop profile' - 'openssh: installed with every desktop profile' - 'oxygen-icons: lxqt desktop profile' - 'papirus-icon-theme: budgie desktop profile' - 'pavucontrol: sway desktop profile' - 'plasma-desktop: plasma desktop profile' - 'plasma-meta: plasma desktop profile' - 'polkit-kde-agent: hyprland desktop profile' - 'qt5-wayland: hyprland desktop profile' - 'qt6-wayland: hyprland desktop profile' - 'qtile: qtile desktop profile' - 'river: river desktop profile' - 'rxvt-unicode: bspwm desktop profile' - 'slock: awesome desktop profile' - 'slurp: hyprland desktop profile' - 'smartmontools: installed with every desktop profile' - 'sway: sway desktop profile' - 'swaybg: niri desktop profile' - 'swayidle: niri desktop profile' - 'swaylock: niri desktop profile' - 'sxhkd: bspwm desktop profile' - 'terminology: enlightenment desktop profile' - 'terminus-font: awesome desktop profile' - 'ttf-freefont: lxqt desktop profile' - 'ttf-liberation: awesome desktop profile' - 'uwsm: hyprland desktop profile' - 'vim: installed with every desktop profile' - 'waybar: niri desktop profile' - 'wget: installed with every desktop profile' - 'wmenu: sway desktop profile' - 'wofi: hyprland desktop profile' - 'xarchiver: xfce4 desktop profile' - 'xdg-desktop-portal-gnome: niri desktop profile' - 'xdg-desktop-portal-hyprland: hyprland desktop profile' - 'xdg-desktop-portal-wlr: river desktop profile' - 'xdg-user-dirs: cosmic desktop profile' - 'xdg-user-dirs-gtk: cinnamon desktop profile' - 'xdg-utils: installed with every desktop profile' - 'xdo: bspwm desktop profile' - 'xed: cinnamon desktop profile' - 'xmonad: xmonad desktop profile' - 'xmonad-contrib: xmonad desktop profile' - 'xmonad-extras: xmonad desktop profile' - 'xorg-server: installed with every desktop profile' - 'xorg-xinit: installed with every desktop profile' - 'xorg-xrandr: awesome desktop profile' - 'xorg-xwayland: niri desktop profile' - 'xsel: awesome desktop profile' - 'xss-lock: i3 desktop profile' - 'xterm: awesome desktop profile' - - # Server profiles - 'apache: httpd server profile' - 'cockpit: cockpit server profile' - 'cockpit-packagekit: cockpit server profile' - 'cockpit-storaged: cockpit server profile' - 'docker: docker server profile' - 'lighttpd: lighttpd server profile' - 'mariadb: mariadb server profile' - 'nginx: nginx server profile' - 'postgresql: postgresql server profile' - 'tomcat10: tomcat server profile' - - # Scripts - 'git: minimal script' + alacritty + alsa-firmware + alsa-utils + amd-ucode + apache + awesome + base + bluez + bluez-utils + breeze-icons + brightnessctl + brltty + bspwm + btrfs-progs + cava + cinnamon + cockpit + cockpit-packagekit + cockpit-storaged + cosmic-greeter + cronie + cups + cups-pk-helper + deepin-editor + deepin-terminal + dkms + dmenu + dms-shell-niri + docker + dolphin + dunst + efibootmgr + engrampa + enlightenment + espeakup + f2fs-tools + feh + firewalld + foot + fuzzel + gdm + ghostscript + git + gnome-keyring + gnome-screenshot + gnome-terminal + gnome-tweaks + gnu-free-fonts + greetd + grim + grub + grub-btrfs + gst-plugin-pipewire + gvfs + gvfs-smb + htop + hyprland + i3-wm + i3blocks + i3lock + i3status + inotify-tools + intel-media-driver + intel-ucode + iwd + kimageformats + kitty + l3afpad + labwc + libfido2 + libpulse + libva-intel-driver + libva-nvidia-driver + libvpl + lightdm + lightdm-gtk-greeter + lightdm-slick-greeter + lighttpd + limine + linux + linux-firmware + linux-hardened + linux-hardened-headers + linux-headers + linux-lts + linux-lts-headers + linux-rt + linux-rt-headers + linux-rt-lts + linux-rt-lts-headers + linux-zen + linux-zen-headers + lvm2 + ly + mako + mariadb + mate-terminal + materia-gtk-theme + matugen + mesa + mkinitcpio + nano + nemo + nemo-fileroller + network-manager-applet + networkmanager + nginx + niri + noto-fonts + noto-fonts-cjk + noto-fonts-emoji + nvidia-open + nvidia-open-dkms + openssh + oxygen-icons + pam-u2f + papirus-icon-theme + pavucontrol + pipewire + pipewire-alsa + pipewire-jack + pipewire-pulse + plasma-desktop + plasma-login-manager + plasma-meta + plymouth + polkit + polkit-kde-agent + postgresql + power-profiles-daemon + pulseaudio + qt5-wayland + qt6-wayland + qtile + refind + river + rxvt-unicode + sddm + seatd + slock + slurp + smartmontools + snapper + sof-firmware + sudo + sway + swaybg + swayidle + swaylock + sxhkd + system-config-printer + terminology + terminus-font + timeshift + tomcat10 + ttf-dejavu + ttf-freefont + ttf-liberation + tuned + tuned-ppd + ufw + uwsm + vim + vpl-gpu-rt + vulkan-intel + vulkan-nouveau + vulkan-radeon + waybar + wget + wireplumber + wmenu + wofi + wpa_supplicant + xarchiver + xdg-desktop-portal-gnome + xdg-desktop-portal-hyprland + xdg-desktop-portal-wlr + xdg-user-dirs + xdg-user-dirs-gtk + xdg-utils + xdo + xed + xf86-video-amdgpu + xf86-video-ati + xf86-video-nouveau + xfsprogs + xmonad + xmonad-contrib + xmonad-extras + xorg-server + xorg-xinit + xorg-xrandr + xorg-xwayland + xsel + xss-lock + xterm + zram-generator + # group: budgie + budgie-backgrounds + budgie-control-center + budgie-desktop + budgie-desktop-services + budgie-desktop-view + budgie-session + # group: cosmic + cosmic-app-library + cosmic-applets + cosmic-bg + cosmic-comp + cosmic-files + cosmic-idle + cosmic-initial-setup + cosmic-launcher + cosmic-monitor + cosmic-notifications + cosmic-osd + cosmic-panel + cosmic-player + cosmic-randr + cosmic-screenshot + cosmic-session + cosmic-settings + cosmic-settings-daemon + cosmic-sound-theme + cosmic-store + cosmic-terminal + cosmic-text-editor + cosmic-wallpapers + cosmic-workspaces + xdg-desktop-portal-cosmic + # group: deepin + ddm + deepin-account-faces + deepin-anything + deepin-api + deepin-app-services + deepin-appearance + deepin-application-manager + deepin-calendar + deepin-control-center + deepin-daemon + deepin-desktop-base + deepin-desktop-schemas + deepin-desktop-theme + deepin-fcitx5configtool-plugin + deepin-file-manager + deepin-gtk-theme + deepin-icon-theme + deepin-image-viewer + deepin-launchpad + deepin-menu + deepin-network-core + deepin-polkit-agent + deepin-polkit-agent-ext-gnomekeyring + deepin-pw-check + deepin-qt5integration + deepin-qt5platform-plugins + deepin-qt6integration + deepin-qt6platform-plugins + deepin-screensaver + deepin-seatd + deepin-services + deepin-session + deepin-session-shell + deepin-session-ui + deepin-shell + deepin-sound-theme + deepin-system-monitor + deepin-turbo + deepin-wallpapers + deepin-widgets + docparser + dtk6core + dtk6declarative + dtk6gui + dtk6log + dtk6widget + dtkcore + dtkdeclarative + dtkgui + dtklog + dtkwidget + treeland + xdg-desktop-portal-dde + # group: gnome + baobab + decibels + epiphany + gnome-backgrounds + gnome-calculator + gnome-calendar + gnome-characters + gnome-clocks + gnome-color-manager + gnome-connections + gnome-console + gnome-contacts + gnome-control-center + gnome-disk-utility + gnome-font-viewer + gnome-logs + gnome-maps + gnome-menus + gnome-music + gnome-remote-desktop + gnome-session + gnome-settings-daemon + gnome-shell + gnome-software + gnome-system-monitor + gnome-text-editor + gnome-tour + gnome-user-docs + gnome-user-share + gnome-weather + grilo-plugins + gst-thumbnailers + gvfs-afc + gvfs-dnssd + gvfs-goa + gvfs-gphoto2 + gvfs-mtp + gvfs-nfs + gvfs-onedrive + gvfs-wsdd + loupe + malcontent + nautilus + orca + papers + rygel + showtime + simple-scan + snapshot + sushi + tecla + yelp + # group: lxqt + lximage-qt + lxqt-about + lxqt-admin + lxqt-archiver + lxqt-config + lxqt-globalkeys + lxqt-menu-data + lxqt-notificationd + lxqt-openssh-askpass + lxqt-panel + lxqt-policykit + lxqt-powermanagement + lxqt-qtplugin + lxqt-runner + lxqt-session + lxqt-sudo + lxqt-themes + obconf-qt + openbox + pavucontrol-qt + pcmanfm-qt + qps + qterminal + screengrab + xdg-desktop-portal-lxqt + # group: mate + caja + marco + mate-backgrounds + mate-control-center + mate-desktop + mate-icon-theme + mate-menus + mate-notification-daemon + mate-panel + mate-polkit + mate-session-manager + mate-settings-daemon + mate-themes + mate-user-guide + # group: mate-extra + atril + caja-actions + caja-audio-video-properties + caja-image-converter + caja-open-terminal + caja-sendto + caja-share + caja-wallpaper + caja-xattr-tags + eom + mate-applets + mate-calc + mate-icon-theme-faenza + mate-media + mate-netbook + mate-power-manager + mate-screensaver + mate-sensors-applet + mate-system-monitor + mate-user-share + mate-utils + mozo + pluma + # group: plasma + aurorae + bluedevil + breeze + breeze-cursors + breeze-gtk + breeze-plymouth + discover + drkonqi + flatpak-kcm + kactivitymanagerd + kde-cli-tools + kde-gtk-config + kdecoration + kdeplasma-addons + kgamma + kglobalacceld + kinfocenter + kmenuedit + knighttime + kpipewire + krdp + kscreen + kscreenlocker + ksshaskpass + ksystemstats + kwallet-pam + kwayland + kwin + kwin-x11 + kwrited + layer-shell-qt + libkscreen + libksysguard + libplasma + milou + ocean-sound-theme + oxygen + oxygen-cursors + oxygen-sounds + plasma-activities + plasma-activities-stats + plasma-bigscreen + plasma-browser-integration + plasma-disks + plasma-firewall + plasma-integration + plasma-keyboard + plasma-nm + plasma-pa + plasma-sdk + plasma-systemmonitor + plasma-thunderbolt + plasma-vault + plasma-welcome + plasma-workspace + plasma-workspace-wallpapers + plasma5support + plymouth-kcm + powerdevil + print-manager + qqc2-breeze-style + sddm-kcm + spectacle + systemsettings + union + wacomtablet + xdg-desktop-portal-kde + # group: xfce4 + exo + garcon + thunar + thunar-volman + tumbler + xfce4-appfinder + xfce4-panel + xfce4-power-manager + xfce4-session + xfce4-settings + xfce4-terminal + xfconf + xfdesktop + xfwm4 + # group: xfce4-goodies + mousepad + parole + ristretto + thunar-archive-plugin + thunar-media-tags-plugin + xfburn + xfce4-battery-plugin + xfce4-clipman-plugin + xfce4-cpufreq-plugin + xfce4-cpugraph-plugin + xfce4-dict + xfce4-diskperf-plugin + xfce4-eyes-plugin + xfce4-fsguard-plugin + xfce4-genmon-plugin + xfce4-mailwatch-plugin + xfce4-mount-plugin + xfce4-mpc-plugin + xfce4-netload-plugin + xfce4-notes-plugin + xfce4-notifyd + xfce4-places-plugin + xfce4-pulseaudio-plugin + xfce4-screensaver + xfce4-screenshooter + xfce4-sensors-plugin + xfce4-smartbookmark-plugin + xfce4-systemload-plugin + xfce4-taskmanager + xfce4-time-out-plugin + xfce4-timer-plugin + xfce4-verve-plugin + xfce4-wavelan-plugin + xfce4-weather-plugin + xfce4-whiskermenu-plugin + xfce4-xkb-plugin ) -# archinstall also installs these package groups. They are named here rather -# than above because a package cannot depend on a group: -# budgie cosmic deepin gnome lxqt mate mate-extra plasma xfce4 xfce4-goodies - package() { - : + : } diff --git a/archinstall/applications/audio.py b/archinstall/applications/audio.py index e5d7971825..720444868f 100644 --- a/archinstall/applications/audio.py +++ b/archinstall/applications/audio.py @@ -3,6 +3,7 @@ from archinstall.lib.hardware import SysInfo from archinstall.lib.log import debug from archinstall.lib.models.application import Audio, AudioConfiguration +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.models.users import User if TYPE_CHECKING: @@ -67,10 +68,10 @@ def install( return if SysInfo.requires_sof_fw(): - install_session.add_additional_packages('sof-firmware') + install_session.add_additional_packages(InstallationPackage.SOF_FIRMWARE.value) if SysInfo.requires_alsa_fw(): - install_session.add_additional_packages('alsa-firmware') + install_session.add_additional_packages(InstallationPackage.ALSA_FIRMWARE.value) match audio_config.audio: case Audio.PIPEWIRE: diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 1196d47742..ef4f03f478 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -4,6 +4,7 @@ from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType, SelectResult from archinstall.lib.log import info from archinstall.lib.menu.helpers import Selection +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -112,7 +113,7 @@ def install(self, install_session: Installer) -> None: install_session.enable_service(profile.services) if not xorg_installed and profile.display_server == DisplayServerType.Xorg: - install_session.add_additional_packages(['xorg-server', 'xorg-xinit']) + install_session.add_additional_packages([InstallationPackage.XORG_SERVER.value, InstallationPackage.XORG_XINIT.value]) xorg_installed = True profile.install(install_session) diff --git a/archinstall/lib/authentication/authentication_handler.py b/archinstall/lib/authentication/authentication_handler.py index bae192fd25..b315148655 100644 --- a/archinstall/lib/authentication/authentication_handler.py +++ b/archinstall/lib/authentication/authentication_handler.py @@ -5,6 +5,7 @@ from archinstall.lib.command import SysCommandWorker from archinstall.lib.log import debug, info from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.models.users import User from archinstall.lib.translationhandler import tr @@ -79,7 +80,7 @@ def _configure_u2f_mapping( ) -> None: debug(f'Setting up U2F login: {u2f_config.u2f_login_method.value}') - install_session.pacman.strap('pam-u2f') + install_session.pacman.strap(InstallationPackage.PAM_U2F.value) print(tr('Setting up U2F login: {}').format(u2f_config.u2f_login_method.value)) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 8c6bee22e5..06a15c3c49 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -49,7 +49,7 @@ from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import MirrorConfiguration from archinstall.lib.models.network import Nic -from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel +from archinstall.lib.models.package_types import DEFAULT_KERNEL, InstallationPackage, Kernel from archinstall.lib.models.packages import Repository from archinstall.lib.models.pacman import PacmanConfiguration from archinstall.lib.models.users import User @@ -799,7 +799,7 @@ def copy_iso_network_config(self, enable_services: bool = False) -> bool: if enable_services: # If we haven't installed the base yet (function called pre-maturely) if self._helper_flags.get('base', False) is False: - self._base_packages.append('iwd') + self._base_packages.append(InstallationPackage.IWD.value) # This function will be called after minimal_installation() # as a hook for post-installs. This hook is only needed if @@ -811,7 +811,7 @@ def post_install_enable_iwd_service(*args: str, **kwargs: str) -> None: # Otherwise, we can go ahead and add the required package # and enable it's service: else: - self.pacman.strap('iwd') + self.pacman.strap(InstallationPackage.IWD.value) self.enable_service('iwd') self.systemd_resolved_stub_mode() @@ -890,7 +890,7 @@ def _prepare_fs_type(self, fs_type: FilesystemType) -> None: def _prepare_encrypt(self, before: str = 'filesystems') -> None: if self._disk_encryption.hsm_device: # Required by mkinitcpio to add support for fido2-device options - self.pacman.strap('libfido2') + self.pacman.strap(InstallationPackage.LIBFIDO2.value) if 'sd-encrypt' not in self._hooks: self._hooks.insert(self._hooks.index(before), 'sd-encrypt') @@ -907,7 +907,7 @@ def minimal_installation( pacman_config: PacmanConfiguration | None = None, ) -> None: if self._disk_config.lvm_config: - lvm = 'lvm2' + lvm = InstallationPackage.LVM2.value self.add_additional_packages(lvm) self._hooks.insert(self._hooks.index('filesystems') - 1, lvm) @@ -999,7 +999,7 @@ def setup_btrfs_snapshot( ) -> None: if snapshot_type == SnapshotType.Snapper: debug('Setting up Btrfs snapper') - self.pacman.strap('snapper') + self.pacman.strap(InstallationPackage.SNAPPER.value) snapper: dict[str, str] = { 'root': '/', @@ -1020,20 +1020,20 @@ def setup_btrfs_snapshot( elif snapshot_type == SnapshotType.Timeshift: debug('Setting up Btrfs timeshift') - self.pacman.strap('cronie') - self.pacman.strap('timeshift') + self.pacman.strap(InstallationPackage.CRONIE.value) + self.pacman.strap(InstallationPackage.TIMESHIFT.value) self.enable_service('cronie.service') if bootloader and bootloader == Bootloader.Grub: debug('Setting up grub integration for either') - self.pacman.strap('grub-btrfs') - self.pacman.strap('inotify-tools') + self.pacman.strap(InstallationPackage.GRUB_BTRFS.value) + self.pacman.strap(InstallationPackage.INOTIFY_TOOLS.value) self._configure_grub_btrfsd(snapshot_type) self.enable_service('grub-btrfsd.service') def setup_swap(self, algo: ZramAlgorithm = ZramAlgorithm.ZSTD) -> None: info('Setting up swap on zram') - self.pacman.strap('zram-generator') + self.pacman.strap(InstallationPackage.ZRAM_GENERATOR.value) info(f'Zram compression algorithm: {algo.value}') @@ -1250,7 +1250,7 @@ def _add_systemd_bootloader( ) -> None: debug('Installing systemd bootloader') - self.pacman.strap('efibootmgr') + self.pacman.strap(InstallationPackage.EFIBOOTMGR.value) if not SysInfo.has_uefi(): raise HardwareIncompatibilityError @@ -1340,7 +1340,7 @@ def _add_grub_bootloader( ) -> None: debug('Installing grub bootloader') - self.pacman.strap('grub') + self.pacman.strap(InstallationPackage.GRUB.value) info(f'GRUB boot partition: {boot_partition.dev_path}') @@ -1354,7 +1354,7 @@ def _add_grub_bootloader( info(f'GRUB EFI partition: {efi_partition.dev_path}') - self.pacman.strap('efibootmgr') # TODO: Do we need? Yes, but remove from minimal_installation() instead? + self.pacman.strap(InstallationPackage.EFIBOOTMGR.value) boot_dir_arg = [] if boot_partition.mountpoint and boot_partition.mountpoint != boot_dir: @@ -1455,7 +1455,7 @@ def _add_limine_bootloader( ) -> None: debug('Installing Limine bootloader') - self.pacman.strap('limine') + self.pacman.strap(InstallationPackage.LIMINE.value) info(f'Limine boot partition: {boot_partition.dev_path}') @@ -1464,7 +1464,7 @@ def _add_limine_bootloader( hook_command = None if SysInfo.has_uefi(): - self.pacman.strap('efibootmgr') + self.pacman.strap(InstallationPackage.EFIBOOTMGR.value) if not efi_partition: raise ValueError('Could not detect efi partition') @@ -1619,7 +1619,7 @@ def _add_efistub_bootloader( ) -> None: debug('Installing efistub bootloader') - self.pacman.strap('efibootmgr') + self.pacman.strap(InstallationPackage.EFIBOOTMGR.value) if not SysInfo.has_uefi(): raise HardwareIncompatibilityError @@ -1675,7 +1675,7 @@ def _add_refind_bootloader( ) -> None: debug('Installing rEFInd bootloader') - self.pacman.strap('refind') + self.pacman.strap(InstallationPackage.REFIND.value) if not SysInfo.has_uefi(): raise HardwareIncompatibilityError @@ -1767,7 +1767,7 @@ def _add_refind_bootloader( def _install_plymouth(self, plymouth: PlymouthTheme) -> None: debug(f'Installing plymouth with theme: {plymouth.value}') - self.add_additional_packages(['plymouth']) + self.add_additional_packages([InstallationPackage.PLYMOUTH.value]) for param in ('quiet', 'splash'): if param not in self._kernel_params: @@ -2041,7 +2041,7 @@ def set_vconsole(self, locale_config: LocaleConfiguration) -> None: font_vconsole = locale_config.console_font if font_vconsole.startswith('ter-'): - self.pacman.strap(['terminus-font']) + self.pacman.strap([InstallationPackage.TERMINUS_FONT.value]) # Ensure /etc exists vconsole_dir: Path = self.target / 'etc' diff --git a/archinstall/lib/models/package_types.py b/archinstall/lib/models/package_types.py index fa44668b06..792b301fe0 100644 --- a/archinstall/lib/models/package_types.py +++ b/archinstall/lib/models/package_types.py @@ -12,3 +12,41 @@ class Kernel(StrEnum): DEFAULT_KERNEL: Final = Kernel.LINUX + + +class InstallationPackage(StrEnum): + ALSA_FIRMWARE = 'alsa-firmware' + COSMIC_GREETER = 'cosmic-greeter' + CRONIE = 'cronie' + EFIBOOTMGR = 'efibootmgr' + GDM = 'gdm' + GIT = 'git' + GREETD = 'greetd' + GRUB = 'grub' + GRUB_BTRFS = 'grub-btrfs' + INOTIFY_TOOLS = 'inotify-tools' + IWD = 'iwd' + LIBFIDO2 = 'libfido2' + LIGHTDM = 'lightdm' + LIGHTDM_GTK_GREETER = 'lightdm-gtk-greeter' + LIGHTDM_SLICK_GREETER = 'lightdm-slick-greeter' + LIMINE = 'limine' + LVM2 = 'lvm2' + LY = 'ly' + NANO = 'nano' + NETWORK_MANAGER_APPLET = 'network-manager-applet' + NETWORKMANAGER = 'networkmanager' + PAM_U2F = 'pam-u2f' + PLASMA_LOGIN_MANAGER = 'plasma-login-manager' + PLYMOUTH = 'plymouth' + REFIND = 'refind' + SDDM = 'sddm' + SNAPPER = 'snapper' + SOF_FIRMWARE = 'sof-firmware' + TERMINUS_FONT = 'terminus-font' + TIMESHIFT = 'timeshift' + WGET = 'wget' + WPA_SUPPLICANT = 'wpa_supplicant' + XORG_SERVER = 'xorg-server' + XORG_XINIT = 'xorg-xinit' + ZRAM_GENERATOR = 'zram-generator' diff --git a/archinstall/lib/network/network_handler.py b/archinstall/lib/network/network_handler.py index 0df7c7870e..c0a5ad2919 100644 --- a/archinstall/lib/network/network_handler.py +++ b/archinstall/lib/network/network_handler.py @@ -2,6 +2,7 @@ from archinstall.lib.installer import Installer from archinstall.lib.models.network import NetworkConfiguration, NicType +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.models.profile import ProfileConfiguration @@ -15,16 +16,16 @@ def install_network_config( # Sources the ISO network configuration to the install medium. installation.copy_iso_network_config(enable_services=True) case NicType.NM | NicType.NM_IWD: - packages = ['networkmanager'] + packages = [InstallationPackage.NETWORKMANAGER.value] if network_config.type == NicType.NM: - packages.append('wpa_supplicant') + packages.append(InstallationPackage.WPA_SUPPLICANT.value) else: - packages.append('iwd') + packages.append(InstallationPackage.IWD.value) if profile_config and profile_config.profile: if profile_config.profile.is_desktop_profile(): - packages.append('network-manager-applet') + packages.append(InstallationPackage.NETWORK_MANAGER_APPLET.value) installation.add_additional_packages(packages) installation.enable_service('NetworkManager.service') @@ -34,7 +35,7 @@ def install_network_config( installation.disable_service('iwd.service') case NicType.IWD: - installation.add_additional_packages(['iwd']) + installation.add_additional_packages([InstallationPackage.IWD.value]) _configure_iwd_standalone(installation) installation.enable_service('iwd.service') installation.enable_service('systemd-networkd.service') diff --git a/archinstall/lib/packages/meta.py b/archinstall/lib/packages/meta.py new file mode 100644 index 0000000000..3758396e07 --- /dev/null +++ b/archinstall/lib/packages/meta.py @@ -0,0 +1,118 @@ +import argparse +import inspect +import shlex +import subprocess +from enum import Enum +from itertools import product +from pathlib import Path + +from archinstall.default_profiles.desktops.plasma import PlasmaFlavor +from archinstall.default_profiles.desktops.utils import SeatAccess +from archinstall.default_profiles.profile import CustomSetting +from archinstall.lib.applications import application_handler +from archinstall.lib.hardware import CPUVendor, GfxPackage +from archinstall.lib.installer import __accessibility_packages__, __packages__ +from archinstall.lib.models.application import FontPackage +from archinstall.lib.models.device import FilesystemType +from archinstall.lib.models.package_types import InstallationPackage, Kernel +from archinstall.lib.profile.profiles_handler import ProfileHandler + +PROFILE_SETTINGS: dict[CustomSetting, type[Enum]] = { + CustomSetting.SeatAccess: SeatAccess, + CustomSetting.PlasmaFlavor: PlasmaFlavor, +} + + +def package_targets() -> set[str]: + if set(PROFILE_SETTINGS) != set(CustomSetting): + raise ValueError('Add missing custom settings to PROFILE_SETTINGS') + + packages = set(__packages__ + __accessibility_packages__) + for package_enum in (Kernel, FontPackage, GfxPackage, InstallationPackage): + packages.update(choice.value for choice in package_enum) + packages.update(f'{kernel.value}-headers' for kernel in Kernel) + packages.update(package for fs in FilesystemType if (package := fs.installation_pkg)) + packages.update(ucode.stem for vendor in CPUVendor if (ucode := vendor.get_ucode())) + + for profile in ProfileHandler().profiles: + original = profile.custom_settings.copy() + packages.update(profile.packages) + for choices in product(*PROFILE_SETTINGS.values()): + profile.custom_settings = original | dict(zip(PROFILE_SETTINGS, (choice.value for choice in choices), strict=True)) + packages.update(profile.packages) + profile.custom_settings = original + + for cls in vars(application_handler).values(): + if inspect.isclass(cls) and cls.__module__.startswith('archinstall.applications.'): + app = cls() + for name, member in inspect.getmembers(cls): + if isinstance(member, property) and (name == 'packages' or name.endswith('_packages')): + packages.update(getattr(app, name)) + return packages + + +def pacman(*args: str) -> list[str]: + return subprocess.run(['pacman', *args], check=True, capture_output=True, text=True).stdout.splitlines() + + +def repository_groups() -> tuple[set[str], dict[str, set[str]]]: + names = set(pacman('-Slq')) + if not names: + raise ValueError('No package database available; refresh it before generating the inventory') + groups: dict[str, set[str]] = {} + for line in pacman('-Sgg'): + group, package = line.split() + groups.setdefault(group, set()).add(package) + return names, groups + + +def render(targets: set[str], names: set[str], groups: dict[str, set[str]]) -> str: + selected_groups = (targets & groups.keys()) - names + direct = targets - selected_groups + entries = [f'\t{shlex.quote(package)}' for package in sorted(direct)] + written = set(direct) + for group in sorted(selected_groups): + entries.append(f'\t# group: {group}') + entries.extend(f'\t{shlex.quote(package)}' for package in sorted(groups[group] - written)) + written.update(groups[group]) + + dependencies = '\n'.join(entries) + return f"""# Generated by python -m archinstall.lib.packages.meta + +pkgname=archinstall-meta +pkgver=1 +pkgrel=1 +pkgdesc="Package inventory for archinstall" +arch=(any) +url="https://github.com/archlinux/archinstall" +license=(GPL-3.0-only) +depends=(archinstall) +optdepends=( +{dependencies} +) + +package() {{ + : +}} +""" + + +def main() -> None: + parser = argparse.ArgumentParser(description='Generate archinstall-meta from the installation code and local package database.') + parser.add_argument('--check', action='store_true', help='fail if the generated PKGBUILD differs') + parser.add_argument('--output', type=Path, default=Path(__file__).resolve().parents[3] / 'archinstall-meta/PKGBUILD') + args = parser.parse_args() + targets = package_targets() + names, groups = repository_groups() + if aliases := sorted(targets - names - groups.keys()): + pacman('-Spdd', '--noconfirm', '--print-format', '%n', '--', *aliases) + content = render(targets, names, groups) + if args.check: + if args.output.read_text() != content: + parser.exit(1, 'Run python -m archinstall.lib.packages.meta to update archinstall-meta/PKGBUILD\n') + else: + args.output.write_text(content) + + +if __name__ == '__main__': + main() diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py index eb54671642..d12823ac51 100644 --- a/archinstall/lib/profile/profiles_handler.py +++ b/archinstall/lib/profile/profiles_handler.py @@ -10,6 +10,7 @@ from archinstall.default_profiles.profile import CustomSetting, GreeterType, Profile from archinstall.lib.hardware import GfxDriver, GfxPackage from archinstall.lib.log import debug, error, info +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.networking import fetch_data_from_url from archinstall.lib.translationhandler import tr @@ -154,29 +155,29 @@ def install_greeter(self, install_session: Installer, greeter: GreeterType) -> N match greeter: case GreeterType.LightdmSlick: - packages = ['lightdm', 'lightdm-slick-greeter'] + packages = [InstallationPackage.LIGHTDM.value, InstallationPackage.LIGHTDM_SLICK_GREETER.value] service = ['lightdm'] case GreeterType.Lightdm: - packages = ['lightdm', 'lightdm-gtk-greeter'] + packages = [InstallationPackage.LIGHTDM.value, InstallationPackage.LIGHTDM_GTK_GREETER.value] service = ['lightdm'] case GreeterType.Sddm: - packages = ['sddm'] + packages = [InstallationPackage.SDDM.value] service = ['sddm'] case GreeterType.Gdm: - packages = ['gdm'] + packages = [InstallationPackage.GDM.value] service = ['gdm'] case GreeterType.Ly: - packages = ['ly'] + packages = [InstallationPackage.LY.value] service = ['ly@tty1'] service_disable = ['getty@tty1'] case GreeterType.CosmicSession: - packages = ['cosmic-greeter'] + packages = [InstallationPackage.COSMIC_GREETER.value] service = ['cosmic-greeter'] case GreeterType.PlasmaLoginManager: - packages = ['plasma-login-manager'] + packages = [InstallationPackage.PLASMA_LOGIN_MANAGER.value] service = ['plasmalogin'] case GreeterType.GreetdDms: - packages = ['greetd'] + packages = [InstallationPackage.GREETD.value] service = ['greetd'] if packages: diff --git a/archinstall/scripts/minimal.py b/archinstall/scripts/minimal.py index 0ab6da83e5..51a7655c2d 100644 --- a/archinstall/scripts/minimal.py +++ b/archinstall/scripts/minimal.py @@ -7,6 +7,7 @@ from archinstall.lib.log import debug, error, info from archinstall.lib.menu.util import delayed_warning from archinstall.lib.models import Bootloader +from archinstall.lib.models.package_types import InstallationPackage from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.users import Password, User from archinstall.lib.network.network_handler import install_network_config @@ -46,7 +47,7 @@ def perform_installation(arch_config_handler: ArchConfigHandler) -> None: config.profile_config, ) - installation.add_additional_packages(['nano', 'wget', 'git']) + installation.add_additional_packages([InstallationPackage.NANO.value, InstallationPackage.WGET.value, InstallationPackage.GIT.value]) profile_config = ProfileConfiguration(MinimalProfile()) profile_handler.install_profile_config(installation, profile_config) diff --git a/tests/test_package_inventory.py b/tests/test_package_inventory.py new file mode 100644 index 0000000000..cfe06fa0f0 --- /dev/null +++ b/tests/test_package_inventory.py @@ -0,0 +1,148 @@ +import ast +import shlex +import sys +from enum import StrEnum +from pathlib import Path + +import pytest + +from archinstall.default_profiles.profile import CustomSetting, Profile, ProfileType +from archinstall.lib.applications import application_handler +from archinstall.lib.models.package_types import InstallationPackage +from archinstall.lib.packages import meta +from archinstall.lib.profile.profiles_handler import ProfileHandler + + +def test_committed_source_targets() -> None: + path = Path(__file__).resolve().parents[1] / 'archinstall-meta/PKGBUILD' + entries = path.read_text().split('optdepends=(\n', 1)[1].split('\n)', 1)[0] + targets = set() + in_group = False + for line in entries.splitlines(): + line = line.strip() + if line.startswith('# group: '): + targets.add(line.removeprefix('# group: ')) + in_group = True + elif not in_group: + targets.update(shlex.split(line)) + assert targets == meta.package_targets(), 'Run python -m archinstall.lib.packages.meta' + + +def test_conditional_package_choices() -> None: + targets = meta.package_targets() + assert {'seatd', 'polkit', 'plasma', 'plasma-meta', 'plasma-desktop'} <= targets + assert {'amd-ucode', 'intel-ucode', 'linux-lts-headers', 'btrfs-progs', 'brltty', 'network-manager-applet', 'dms-shell-niri'} <= targets + + +def test_new_sources_are_discovered(monkeypatch: pytest.MonkeyPatch) -> None: + before = meta.package_targets() + profiles = ProfileHandler().profiles + [Profile('New', ProfileType.Custom, packages=['new-profile'])] + monkeypatch.setattr(ProfileHandler, 'profiles', property(lambda self: profiles)) + + class NewApp: + @property + def packages(self) -> list[str]: + return ['new-app'] + + NewApp.__module__ = 'archinstall.applications.new' + monkeypatch.setattr(application_handler, 'NewApp', NewApp, raising=False) + choices = {package.name: package.value for package in InstallationPackage} | {'NEW': 'new-choice'} + monkeypatch.setattr(meta, 'InstallationPackage', StrEnum('NewPackages', choices)) + assert meta.package_targets() == before | {'new-profile', 'new-app', 'new-choice'} + + +def test_new_setting_requires_coverage(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delitem(meta.PROFILE_SETTINGS, CustomSetting.SeatAccess) + with pytest.raises(ValueError, match='missing custom settings'): + meta.package_targets() + + +def test_group_expansion_preserves_packages_and_aliases() -> None: + content = meta.render({'desktop', 'both', 'virtual'}, {'both'}, {'desktop': {'member'}, 'both': {'wrong'}}) + assert '\n\tboth\n' in content + assert '\n\tvirtual\n' in content + assert '\n\tmember\n' in content + assert '# group: desktop' in content + assert 'wrong' not in content + assert '# group: both' not in content + + +def test_check_reports_drift(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + output = tmp_path / 'PKGBUILD' + targets = {'first', 'desktop', 'virtual'} + monkeypatch.setattr(meta, 'package_targets', lambda: targets) + monkeypatch.setattr(meta, 'repository_groups', lambda: ({'first', 'second'}, {'desktop': {'member'}})) + calls: list[tuple[str, ...]] = [] + + def pacman(*args: str) -> list[str]: + calls.append(args) + return ['provider'] + + monkeypatch.setattr(meta, 'pacman', pacman) + monkeypatch.setattr(sys, 'argv', ['meta', '--output', str(output)]) + meta.main() + assert calls[-1] == ('-Spdd', '--noconfirm', '--print-format', '%n', '--', 'virtual') + before = output.read_text() + monkeypatch.setattr(sys, 'argv', ['meta', '--output', str(output), '--check']) + meta.main() + targets.remove('first') + targets.add('second') + with pytest.raises(SystemExit) as result: + meta.main() + assert result.value.code == 1 + assert output.read_text() == before + + +def is_package_list(node: ast.AST) -> bool: + return (isinstance(node, ast.Name) and node.id == 'packages') or (isinstance(node, ast.Attribute) and node.attr == '_base_packages') + + +def literal_installations(tree: ast.AST) -> list[int]: + roots: list[ast.AST] = [] + for node in ast.walk(tree): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): + if node.func.attr in {'strap', 'add_additional_packages'} or (is_package_list(node.func.value) and node.func.attr in {'append', 'extend'}): + roots.extend(node.args) + roots.extend(keyword.value for keyword in node.keywords if keyword.arg == 'packages') + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + getter = node.name == 'packages' or node.name.endswith('_packages') + if getter and any(isinstance(decorator, ast.Name) and decorator.id == 'property' for decorator in node.decorator_list): + continue + for child in ast.walk(node): + if isinstance(child, (ast.Assign, ast.AnnAssign, ast.AugAssign)): + targets = child.targets if isinstance(child, ast.Assign) else [child.target] + if any(is_package_list(target) for target in targets) and isinstance(child.value, (ast.List, ast.Tuple, ast.Set, ast.Constant)): + roots.append(child.value) + return [node.lineno for root in roots for node in ast.walk(root) if isinstance(node, ast.Constant) and isinstance(node.value, str)] + + +def test_installation_literals_use_metadata() -> None: + root = Path(__file__).resolve().parents[1] / 'archinstall' + trees = {str(path.relative_to(root)): ast.parse(path.read_text()) for path in root.rglob('*.py')} + violations = {path: lines for path, tree in trees.items() if (lines := literal_installations(tree))} + assert not violations, f'Use InstallationPackage for package selections: {violations}' + used = { + node.attr + for tree in trees.values() + for node in ast.walk(tree) + if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id == 'InstallationPackage' + } + assert used == set(InstallationPackage.__members__), 'Remove unused InstallationPackage entries' + + +@pytest.mark.parametrize( + 'source', + [ + "installer.add_additional_packages(['new'])", + "pacman.strap('new')", + "def install():\n packages = ['new']\n installer.add_additional_packages(packages)", + "packages.append('new')", + "installer.add_additional_packages(packages=['new'])", + "def install():\n packages: list[str] = ['new']", + "def install():\n packages += ['new']", + "def install(self):\n self._base_packages += ['new']", + "def install_packages():\n packages = ['new']", + ], +) +def test_new_literal_is_rejected(source: str) -> None: + assert literal_installations(ast.parse(source)) From 3cfbaec54ef8e4495caa04db5bb43ad9af0ee007 Mon Sep 17 00:00:00 2001 From: VykosMolt Date: Wed, 9 Sep 2026 09:34:27 +0200 Subject: [PATCH 3/3] Remove redundant inventory work --- archinstall/lib/packages/meta.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/packages/meta.py b/archinstall/lib/packages/meta.py index 3758396e07..57cd858444 100644 --- a/archinstall/lib/packages/meta.py +++ b/archinstall/lib/packages/meta.py @@ -28,19 +28,18 @@ def package_targets() -> set[str]: raise ValueError('Add missing custom settings to PROFILE_SETTINGS') packages = set(__packages__ + __accessibility_packages__) - for package_enum in (Kernel, FontPackage, GfxPackage, InstallationPackage): + for package_enum in (FontPackage, GfxPackage, InstallationPackage): packages.update(choice.value for choice in package_enum) packages.update(f'{kernel.value}-headers' for kernel in Kernel) packages.update(package for fs in FilesystemType if (package := fs.installation_pkg)) packages.update(ucode.stem for vendor in CPUVendor if (ucode := vendor.get_ucode())) for profile in ProfileHandler().profiles: - original = profile.custom_settings.copy() + original = profile.custom_settings packages.update(profile.packages) for choices in product(*PROFILE_SETTINGS.values()): profile.custom_settings = original | dict(zip(PROFILE_SETTINGS, (choice.value for choice in choices), strict=True)) packages.update(profile.packages) - profile.custom_settings = original for cls in vars(application_handler).values(): if inspect.isclass(cls) and cls.__module__.startswith('archinstall.applications.'):