SPS · log · files · refs · tags · releases · people · languages · GitHub · commit
5571ccb
setup: compile against the target and only offer packaged extras
Parents
Stat
bin/mkiso | 156 ++++++++++++++++++++++++++++++++++++++++++-- bin/mkpkg | 53 +++++++++++++++ bin/setup | 61 +++++++++++++++++ lib/deps.awk | 18 ++--- lib/mkiso/live-plasma | 21 +++++- lib/mkiso/plasma-bins | 2 + lib/mkiso/plasma-trees | 2 + lib/setup/extras | 140 +++++++++++++++++++-------------------- man/mkpkg.1 | 17 +++++ man/setup.1 | 4 +- man/sget.1 | 9 +++ tests/test_build.sh | 48 ++++++++++++++ tests/test_dependencies.sh | 8 +++ tests/test_mkiso.sh | 32 +++++++++ tests/test_setup_catalog.sh | 120 ++++++++++++++++++++++++++++++++++ 15 files changed, 596 insertions(+), 95 deletions(-)
Diff
diff --git a/bin/mkiso b/bin/mkiso
index c6d1e15..7323644 100755
--- a/bin/mkiso
+++ b/bin/mkiso
@@ -352,6 +352,29 @@ mkiso_copy_mesa_deps()
"$mkiso_live/usr/lib/gbm/nvidia-drm_gbm.so"
}
+# Session helpers and plugins plasmashell loads by soname. Tree copies can
+# leave the .so files in place while ldd of plasma-bins never saw them.
+mkiso_copy_plasma_runtime_deps()
+{
+ printf '%s: resolving Plasma session libraries\n' "$sps_prog" >&2
+ for mkiso_so in \
+ /usr/libexec/kactivitymanagerd \
+ /usr/libexec/kscreenlocker_greet \
+ /usr/bin/plasmashell \
+ /usr/bin/plasma_session \
+ /usr/bin/kwin_wayland \
+ /usr/bin/startplasma-wayland \
+ /usr/lib64/libseat.so \
+ /usr/lib64/libseat.so.1 \
+ /usr/lib64/qt6/plugins/wayland-shell-integration/liblayer-shell.so \
+ /usr/lib64/qt6/plugins/styles/breeze6.so \
+ /usr/lib64/qt6/plugins/platforms/libqwayland.so
+ do
+ [ -e "$mkiso_so" ] || [ -L "$mkiso_so" ] || continue
+ mkiso_copy_so_deps "$mkiso_so"
+ done
+}
+
# ldd a host ELF and require each resolved library to exist in the live
# root. Used so we refuse to ship gcc without libmpc or Mesa without SPIRV.
mkiso_verify_elf_in_live()
@@ -617,6 +640,7 @@ mkiso_verify_live()
mkiso_verify_compiler
mkiso_verify_plasma
+ mkiso_verify_setup_catalog
}
# Seeded images ship gcc. The driver DT_NEEDs only libc; cc1 needs
@@ -674,8 +698,10 @@ mkiso_verify_plasma()
[ "$mkiso_session" = plasma ] || return 0
mkiso_miss=
for mkiso_need in usr/bin/kwin_wayland usr/bin/kwin_wayland_wrapper \
- usr/bin/startplasma-wayland usr/lib64/gbm/dri_gbm.so \
- usr/lib64/libxcb-cursor.so.0
+ usr/bin/startplasma-wayland usr/bin/plasmashell \
+ usr/libexec/kactivitymanagerd usr/libexec/kscreenlocker_greet \
+ usr/lib64/gbm/dri_gbm.so usr/lib64/libxcb-cursor.so.0 \
+ etc/xdg/kscreenlockerrc
do
if [ ! -e "$mkiso_live/$mkiso_need" ] &&
[ ! -L "$mkiso_live/$mkiso_need" ]
@@ -701,6 +727,10 @@ mkiso_verify_plasma()
then
mkiso_miss="$mkiso_miss usr/bin/Xwayland"
fi
+ if ! ls "$mkiso_live"/usr/share/fonts/TTF/*.ttf >/dev/null 2>&1
+ then
+ mkiso_miss="$mkiso_miss usr/share/fonts/TTF"
+ fi
[ -z "$mkiso_miss" ] ||
sps_die "$SPS_EX_PACKAGE" "plasma image missing:$mkiso_miss"
for mkiso_g in /usr/lib64/libgallium*.so
@@ -711,6 +741,103 @@ mkiso_verify_plasma()
done
}
+# setup extras/sets/profiles may only name packages that exist in the
+# recipe trees on this image. An extras checklist entry for mpv with no
+# mpv recipe is how live setup died at "package 'mpv' not found".
+mkiso_verify_setup_catalog()
+{
+ mkiso_setupdir=$SPS_LIBDIR/setup
+ mkiso_extras=$mkiso_setupdir/extras
+ [ -f "$mkiso_extras" ] ||
+ sps_die "$SPS_EX_PACKAGE" "setup extras file is missing"
+ LC_ALL=C awk '
+ BEGIN { bad = 0; count = 0 }
+ /^[[:space:]]*$/ || /^#/ { next }
+ {
+ tab = index($0, "\t")
+ if (tab == 0) {
+ printf "mkiso: extras line has no tab: %s\n", $0
+ bad = 1
+ next
+ }
+ name = substr($0, 1, tab - 1)
+ if (name !~ /^[A-Za-z0-9][A-Za-z0-9+_.-]*$/) {
+ printf "mkiso: extras bad name: %s\n", name
+ bad = 1
+ }
+ if (name in seen) {
+ printf "mkiso: extras duplicate: %s\n", name
+ bad = 1
+ }
+ seen[name] = 1
+ count++
+ names[count] = name
+ }
+ END {
+ for (i = 1; i < count; i++)
+ if (names[i] > names[i + 1]) {
+ printf "mkiso: extras not sorted: %s before %s\n", \
+ names[i], names[i + 1]
+ bad = 1
+ }
+ exit bad
+ }
+ ' "$mkiso_extras" ||
+ sps_die "$SPS_EX_PACKAGE" \
+ "setup extras must be unique, tab-separated, and sorted"
+
+ mkiso_recipes=$mkiso_work/catalog-recipes
+ : >"$mkiso_recipes"
+ for mkiso_tree in \
+ "$mkiso_live/usr/src/sps/core" \
+ "$mkiso_live/usr/src/sps/extra"
+ do
+ [ -d "$mkiso_tree" ] || continue
+ find "$mkiso_tree" \( -type d \( -name .git -o -name files -o \
+ -name patches -o -name hooks \) -prune \) -o \
+ -type f -name recipe -print
+ done | while IFS= read -r mkiso_rfile || [ -n "$mkiso_rfile" ]
+ do
+ [ -n "$mkiso_rfile" ] || continue
+ awk '/^name[[:space:]]/ { print $2; exit }' "$mkiso_rfile"
+ done | LC_ALL=C sort -u >"$mkiso_recipes"
+ [ -s "$mkiso_recipes" ] || return 0
+
+ mkiso_wanted=$mkiso_work/catalog-wanted
+ : >"$mkiso_wanted"
+ awk -F '\t' '/^[[:space:]]*$/ || /^#/ { next } { print $1 }' \
+ "$mkiso_extras" >>"$mkiso_wanted"
+ for mkiso_f in "$mkiso_setupdir"/sets/*.set \
+ "$mkiso_setupdir"/profiles/*.profile
+ do
+ [ -f "$mkiso_f" ] || continue
+ awk '/^package[[:space:]]/ { print $2 }' "$mkiso_f" \
+ >>"$mkiso_wanted"
+ done
+ if [ -f "$mkiso_setupdir/hwdetect.sh" ]; then
+ awk '
+ /printf '\''%s\\n'\'' [a-z]/ && $0 !~ /\$/ && $0 !~ /\|/ {
+ for (i = 3; i <= NF; i++) {
+ gsub(/;/, "", $i)
+ if ($i ~ /^[a-z][a-z0-9+-]*$/)
+ print $i
+ }
+ }
+ ' "$mkiso_setupdir/hwdetect.sh" >>"$mkiso_wanted"
+ fi
+ LC_ALL=C sort -u "$mkiso_wanted" -o "$mkiso_wanted"
+ mkiso_miss=
+ while IFS= read -r mkiso_pkg || [ -n "$mkiso_pkg" ]
+ do
+ [ -n "$mkiso_pkg" ] || continue
+ grep -qx "$mkiso_pkg" "$mkiso_recipes" && continue
+ mkiso_miss="$mkiso_miss $mkiso_pkg"
+ done <"$mkiso_wanted"
+ [ -z "$mkiso_miss" ] ||
+ sps_die "$SPS_EX_PACKAGE" \
+ "setup names packages with no recipe:$mkiso_miss"
+}
+
# BusyBox --install -s writes absolute symlinks to the host path. Relative
# links keep applets working inside the squashfs and initramfs.
mkiso_busybox_links()
@@ -772,13 +899,21 @@ mkiso_copy_plasma()
mkiso_copy_path "$mkiso_so"
done
done
- # Full /usr/share/fonts/TTF is hundreds of megabytes. DejaVu is enough
- # for fontconfig's default aliases on the live session.
- for mkiso_font in /usr/share/fonts/TTF/DejaVu*.ttf
+ # Host fontconfig prefers Liberation Sans. DejaVu alone leaves
+ # fc-match pointing at a file that is not on the disc.
+ for mkiso_font in /usr/share/fonts/TTF/DejaVu*.ttf \
+ /usr/share/fonts/TTF/Liberation*.ttf
do
[ -e "$mkiso_font" ] || continue
mkiso_copy_path "$mkiso_font"
done
+ mkdir -p "$mkiso_live/etc/xdg"
+ cat >"$mkiso_live/etc/xdg/kscreenlockerrc" <<'EOF'
+[Daemon]
+Autolock=false
+LockOnResume=false
+Timeout=0
+EOF
# Host /etc/dbus-1 may already contain system-local.conf. live-plasma
# used to skip the root-user override when that file existed, which
# left dbus-daemon --system looking for a messagebus account the live
@@ -806,6 +941,7 @@ EOF
mkdir -p "$mkiso_live/etc/systemd/system"
ln -sf /dev/null "$mkiso_live/etc/systemd/system/dbus.service"
ln -sf /dev/null "$mkiso_live/etc/systemd/system/dbus.socket"
+ mkiso_copy_plasma_runtime_deps
mkiso_copy_mesa_deps
}
@@ -1096,6 +1232,16 @@ if [ "$mkiso_copy_seed" -eq 1 ] && [ -f "$mkiso_libdir/seed-bins" ]; then
done
# cc1 DT_NEEDs libmpc/libisl. ldd of the gcc driver does not.
mkiso_copy_gcc_deps
+ # file(1) is in seed-bins. Autoconf calls it during configure, and
+ # without the magic database it prints "could not find any valid
+ # magic files" and some probes then mis-detect object types.
+ printf '%s: copying file(1) magic database\n' "$sps_prog" >&2
+ for mkiso_magic in /etc/file /usr/share/file /usr/share/misc/magic \
+ /usr/share/misc/magic.mgc /usr/share/file/magic.mgc
+ do
+ [ -e "$mkiso_magic" ] || [ -L "$mkiso_magic" ] || continue
+ mkiso_copy_path "$mkiso_magic"
+ done
for mkiso_cmd in gcc g++ cc c++ as ld ld.bfd
do
mkiso_copy_host_cmd "$mkiso_cmd" || :
diff --git a/bin/mkpkg b/bin/mkpkg
index 53aa62e..93f0af1 100755
--- a/bin/mkpkg
+++ b/bin/mkpkg
@@ -718,6 +718,59 @@ else
run_env=
fi
+# Live setup builds into SPS_ROOT (a mounted disk) with the live compiler.
+# gcc and pkg-config search /usr unless they are pointed at packages pkin
+# already unpacked into the target. That is why alsa-utils configure found
+# host ALSA headers then died with "No linkable libasound was found" after
+# alsa-lib had been installed into the target, not the live root. The same
+# hole hits every later package that links a library from the target.
+if [ -n "${SPS_ROOT:-}" ] && [ "$SPS_ROOT" != / ]; then
+ target_root=$SPS_ROOT
+ target_inc=$target_root/usr/include
+ target_pc=$target_root/usr/lib64/pkgconfig
+ target_pc=$target_pc:$target_root/usr/lib/pkgconfig
+ target_pc=$target_pc:$target_root/usr/share/pkgconfig
+ target_pc=$target_pc:$target_root/lib64/pkgconfig
+ target_pc=$target_pc:$target_root/lib/pkgconfig
+ target_libpath=$target_root/usr/lib64:$target_root/usr/lib
+ target_libpath=$target_libpath:$target_root/lib64:$target_root/lib
+ target_cflags="-I$target_inc"
+ target_ldflags="-L$target_root/usr/lib64 -L$target_root/usr/lib"
+ target_ldflags="$target_ldflags -L$target_root/lib64 -L$target_root/lib"
+
+ PKG_CONFIG_SYSROOT_DIR=$target_root
+ PKG_CONFIG_LIBDIR=$target_pc
+ PKG_CONFIG_PATH=$target_pc${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
+ PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1
+ PKG_CONFIG_ALLOW_SYSTEM_LIBS=1
+ export PKG_CONFIG_SYSROOT_DIR PKG_CONFIG_LIBDIR PKG_CONFIG_PATH \
+ PKG_CONFIG_ALLOW_SYSTEM_CFLAGS PKG_CONFIG_ALLOW_SYSTEM_LIBS
+
+ CPATH=$target_inc${CPATH:+:$CPATH}
+ C_INCLUDE_PATH=$target_inc${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}
+ CPLUS_INCLUDE_PATH=$target_inc${CPLUS_INCLUDE_PATH:+:$CPLUS_INCLUDE_PATH}
+ LIBRARY_PATH=$target_libpath${LIBRARY_PATH:+:$LIBRARY_PATH}
+ LD_LIBRARY_PATH=$target_libpath${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
+ export CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH LIBRARY_PATH \
+ LD_LIBRARY_PATH
+
+ CPPFLAGS="$target_cflags${CPPFLAGS:+ $CPPFLAGS}"
+ CFLAGS="$target_cflags${CFLAGS:+ $CFLAGS}"
+ CXXFLAGS="$target_cflags${CXXFLAGS:+ $CXXFLAGS}"
+ LDFLAGS="$target_ldflags${LDFLAGS:+ $LDFLAGS}"
+ export CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
+
+ CMAKE_PREFIX_PATH=$target_root/usr${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH}
+ CMAKE_INCLUDE_PATH=$target_inc${CMAKE_INCLUDE_PATH:+:$CMAKE_INCLUDE_PATH}
+ CMAKE_LIBRARY_PATH=$target_libpath${CMAKE_LIBRARY_PATH:+:$CMAKE_LIBRARY_PATH}
+ export CMAKE_PREFIX_PATH CMAKE_INCLUDE_PATH CMAKE_LIBRARY_PATH
+
+ ACLOCAL_PATH=$target_root/usr/share/aclocal${ACLOCAL_PATH:+:$ACLOCAL_PATH}
+ export ACLOCAL_PATH
+
+ say "using $target_root for headers, libraries, and pkg-config"
+fi
+
run_phase()
{
phase_name=$1
diff --git a/bin/setup b/bin/setup
index dcf25c8..dac3583 100755
--- a/bin/setup
+++ b/bin/setup
@@ -100,6 +100,55 @@ if [ -f "$SPS_LIBDIR/setup/service.sh" ]; then
# shellcheck disable=SC1091
. "$SPS_LIBDIR/setup/service.sh"
fi
+
+setup_collect_recipe_names()
+{
+ setup_names_out=$1
+ : >"$setup_names_out"
+ for setup_rtree in \
+ /usr/src/sps/core \
+ /usr/src/sps/extra \
+ "${SPS_LIVE_ROOT:-/}/usr/src/sps/core" \
+ "${SPS_LIVE_ROOT:-/}/usr/src/sps/extra"
+ do
+ [ -d "$setup_rtree" ] || continue
+ find "$setup_rtree" \( -type d \( -name .git -o -name files -o \
+ -name patches -o -name hooks \) -prune \) -o \
+ -type f -name recipe -print
+ done | while IFS= read -r setup_rfile || [ -n "$setup_rfile" ]
+ do
+ [ -n "$setup_rfile" ] || continue
+ awk '/^name[[:space:]]/ { print $2; exit }' "$setup_rfile"
+ done | LC_ALL=C sort -u >"$setup_names_out"
+}
+
+setup_reject_unknown_packages()
+{
+ setup_index=${SPS_CACHE:-$setup_root/var/cache/sps}/indexes/packages.index
+ [ -f "$setup_index" ] || return 0
+ [ -s "$setup_pkg_file" ] || return 0
+ setup_unknown=$setup_work/unknown-packages
+ awk -F '\t' '
+ FNR == NR {
+ gsub(/\r/, "")
+ if ($1 != "")
+ want[$1] = 1
+ next
+ }
+ { have[$1] = 1 }
+ END {
+ for (n in want)
+ if (!(n in have))
+ print n
+ }
+ ' "$setup_pkg_file" "$setup_index" | LC_ALL=C sort >"$setup_unknown"
+ [ -s "$setup_unknown" ] || return 0
+ printf '%s: packages not in the recipe index:\n' "$sps_prog" >&2
+ sed 's/^/ /' "$setup_unknown" >&2
+ setup_fail "$SPS_EX_NOTFOUND" \
+ "requested packages are not packaged (see the list above)"
+}
+
setup_work=
setup_interactive=1
setup_plan=0
@@ -817,6 +866,7 @@ fi
setup_work=$(sps_mktemp_dir "${TMPDIR:-/tmp}" setup) || exit $?
trap setup_cleanup 0 HUP INT TERM
+setup_collect_recipe_names "$setup_work/recipe-names"
if [ "$setup_interactive" -eq 1 ]; then
if [ -f /etc/sps/live ]; then
@@ -944,6 +994,15 @@ Use /mnt after mounting the destination filesystem, or after a guided layout (se
setup_et=${setup_exline#* }
[ -n "$setup_en" ] || continue
[ "$setup_et" != "$setup_exline" ] || setup_et=$setup_en
+ # Live recipe trees are the source of truth. Unpackaged
+ # names (mpv, LibreOffice, ...) must not reach sget.
+ if [ -s "$setup_work/recipe-names" ] &&
+ ! grep -qx "$setup_en" "$setup_work/recipe-names"
+ then
+ printf '%s: skipping unpackaged extra %s\n' \
+ "$sps_prog" "$setup_en" >&2
+ continue
+ fi
setup_on=off
case " $setup_extra " in *" $setup_en "*) setup_on=on ;; esac
setup_ex_args="$setup_ex_args $setup_en '$setup_et' $setup_on"
@@ -1706,6 +1765,8 @@ if [ "$setup_do_update" -eq 1 ]; then
src update || setup_fail "$SPS_EX_NETWORK" "src update failed"
fi
+setup_reject_unknown_packages
+
setup_pkgs=
while IFS= read -r setup_pkg; do
[ -n "$setup_pkg" ] || continue
diff --git a/lib/deps.awk b/lib/deps.awk
index 88bd024..097ccbf 100644
--- a/lib/deps.awk
+++ b/lib/deps.awk
@@ -14,8 +14,7 @@ function deps_stderr(message, command) {
}
function deps_error(message) {
- if (!deps_failed)
- deps_stderr((program == "" ? "sget" : program) ": " message)
+ deps_stderr((program == "" ? "sget" : program) ": " message)
deps_failed = 1
}
@@ -96,8 +95,6 @@ function deps_cycle(package, first, i, message) {
}
function deps_visit(package, parent, list, count, items, i, child) {
- if (deps_failed)
- return
if (!(package in deps_selected_priority)) {
if (parent == "")
deps_error("package '" package "' not found")
@@ -123,10 +120,8 @@ function deps_visit(package, parent, list, count, items, i, child) {
for (i = 1; i <= count; i++) {
child = deps_dependency_name(items[i], package)
if (child == "")
- break
+ continue
deps_visit(child, package)
- if (deps_failed)
- break
}
}
@@ -210,11 +205,8 @@ END {
exit 5
if (action == "validate") {
deps_sort_names(deps_names, deps_name_count)
- for (deps_i = 1; deps_i <= deps_name_count; deps_i++) {
+ for (deps_i = 1; deps_i <= deps_name_count; deps_i++)
deps_visit(deps_names[deps_i], "")
- if (deps_failed)
- break
- }
if (deps_failed)
exit 5
exit 0
@@ -229,11 +221,9 @@ END {
if (deps_roots[deps_i] !~ \
/^[A-Za-z0-9][A-Za-z0-9+_.-]*$/) {
deps_error("invalid package name '" deps_roots[deps_i] "'")
- break
+ continue
}
deps_visit(deps_roots[deps_i], "")
- if (deps_failed)
- break
}
if (deps_failed)
exit 5
diff --git a/lib/mkiso/live-plasma b/lib/mkiso/live-plasma
index 0cd960c..17112e3 100644
--- a/lib/mkiso/live-plasma
+++ b/lib/mkiso/live-plasma
@@ -10,7 +10,9 @@ export XDG_RUNTIME_DIR=/run/user/0
export XDG_SESSION_TYPE=wayland
export XDG_SESSION_CLASS=user
export XDG_SESSION_DESKTOP=KDE
+export XDG_CURRENT_DESKTOP=KDE
export DESKTOP_SESSION=plasma
+export XDG_CONFIG_DIRS=/etc/xdg
export TERM=${TERM:-linux}
export LANG=${LANG:-C.UTF-8}
export LC_ALL=${LC_ALL:-C.UTF-8}
@@ -38,6 +40,22 @@ if [ ! -s /root/.config/kdedefaults/ksplashrc ]; then
printf '%s\n' '[KSplash]' 'Engine=KSplashQML' \
'Theme=org.kde.breeze.desktop' >/root/.config/kdedefaults/ksplashrc
fi
+# Live runs as root with no PAM stack. kscreenlocker_greet missing or
+# autolocking leaves KWin's lock surface up: black screen, cursor only.
+mkdir -p /etc/xdg /root/.config
+if [ ! -s /etc/xdg/kscreenlockerrc ]; then
+ printf '%s\n' \
+ '[Daemon]' \
+ 'Autolock=false' \
+ 'LockOnResume=false' \
+ 'Timeout=0' >/etc/xdg/kscreenlockerrc
+fi
+if [ ! -s /root/.config/kscreenlockerrc ]; then
+ cp /etc/xdg/kscreenlockerrc /root/.config/kscreenlockerrc
+fi
+if command -v fc-cache >/dev/null 2>&1; then
+ fc-cache -f >/dev/null 2>&1 || :
+fi
# Live NVIDIA is nouveau. Keep the proprietary stack unloaded.
modprobe nouveau 2>/dev/null || :
@@ -131,7 +149,8 @@ if command -v startplasma-wayland >/dev/null 2>&1; then
chvt 1 2>/dev/null || :
# startplasma needs a *session* bus. The system bus above is not that.
if command -v dbus-run-session >/dev/null 2>&1; then
- exec dbus-run-session -- startplasma-wayland
+ exec dbus-run-session -- startplasma-wayland \
+ >>/run/plasma.log 2>&1
fi
if command -v dbus-launch >/dev/null 2>&1; then
eval "$(dbus-launch --sh-syntax)"
diff --git a/lib/mkiso/plasma-bins b/lib/mkiso/plasma-bins
index 7f301c5..a5f09fe 100644
--- a/lib/mkiso/plasma-bins
+++ b/lib/mkiso/plasma-bins
@@ -51,6 +51,8 @@
/usr/libexec/kf6/kio_http_cache_cleaner
/usr/libexec/kf6/kiod6
/usr/libexec/kf6/kscreen_backend_launcher
+/usr/libexec/kactivitymanagerd
+/usr/libexec/kscreenlocker_greet
/usr/libexec/plasma-dbus-run-session-if-needed
/usr/libexec/plasma-sourceenv.sh
/usr/libexec/plasma-startup-sound
diff --git a/lib/mkiso/plasma-trees b/lib/mkiso/plasma-trees
index 951bd6d..1cf4187 100644
--- a/lib/mkiso/plasma-trees
+++ b/lib/mkiso/plasma-trees
@@ -36,6 +36,8 @@
/usr/lib64/gbm
/usr/share/kwin
/usr/share/kwin-wayland
+/usr/share/kf6
+/usr/share/wallpapers/Next
/etc/fonts
/usr/share/fontconfig
/usr/share/glvnd
diff --git a/lib/setup/extras b/lib/setup/extras
index 1b14166..19a1062 100644
--- a/lib/setup/extras
+++ b/lib/setup/extras
@@ -1,90 +1,82 @@
-neovim Neovim
-vim Vim
-nano GNU nano
-emacs GNU Emacs
-helix Helix editor
-kakoune Kakoune editor
-htop htop
+# Extra packages offered by setup(1). One name<TAB>title per line.
+# Names must exist as recipes in sps-core or sps-extra, appear once,
+# and stay in LC_ALL=C sort order. tests/test_setup_catalog.sh and
+# mkiso refuse anything else. Do not list unpackaged names (mpv, GIMP,
+# LibreOffice, and similar); the extra set or a later recipe is the place
+# for those.
+alsa-utils ALSA utilities
+amd-ucode AMD CPU microcode
+bat bat
+bluez BlueZ Bluetooth
btop btop
-tmux tmux
-sudo sudo
+cmake CMake
+cryptsetup cryptsetup
+cups CUPS printing
+curl curl
+dhcpcd dhcpcd
doas OpenDoas
-zsh Z shell
+dosfstools dosfstools
+e2fsprogs e2fsprogs
+emacs GNU Emacs
+ethtool ethtool
+exfatprogs exFAT tools
+eza eza
+falkon Falkon
+fastfetch fastfetch
+fd fd
+firefox Firefox
fish fish shell
+flatpak Flatpak
+fzf fzf
+gdb GDB
git Git
-rsync rsync
-openssh OpenSSH
gnupg GnuPG
-firefox Firefox
-falkon Falkon
-flatpak Flatpak
-pipewire PipeWire
-wget wget
-curl curl
+go Go
+gptfdisk GPT fdisk
+helix Helix editor
+htop htop
+intel-ucode Intel CPU microcode
+iperf3 iperf3
+iwd iwd
jq jq
-ripgrep ripgrep
-fd fd
-bat bat
-eza eza
-fzf fzf
+kakoune Kakoune editor
lazygit lazygit
-fastfetch fastfetch
-python Python
+linux Linux kernel, upstream defconfig
+linux-desktop Linux kernel, daily desktop config
+lsof lsof
lua Lua
-go Go
-rust Rust
-nodejs Node.js
-perl Perl
-ruby Ruby
-cmake CMake
+mdadm Linux MD RAID
+mesa Mesa
meson Meson
+micro micro editor
+nano GNU nano
+neovim Neovim
+networkmanager NetworkManager
+nftables nftables
ninja Ninja
-gdb GDB
-strace strace
-lsof lsof
nmap nmap
-iperf3 iperf3
-cups CUPS printing
-sddm SDDM
-mesa Mesa
+nodejs Node.js
+nouveau Nouveau (NVIDIA open driver)
+ntfs-3g NTFS driver
nvidia-open NVIDIA open kernel modules
-e2fsprogs e2fsprogs
-dosfstools dosfstools
-cryptsetup cryptsetup
-iwd iwd
-dhcpcd dhcpcd
-micro micro editor
-starship starship prompt
-yq yq
-networkmanager NetworkManager
+openssh OpenSSH
parted GNU parted
-gptfdisk GPT fdisk
-ntfs-3g NTFS driver
-exfatprogs exFAT tools
-mdadm Linux MD RAID
+perl Perl
+pipewire PipeWire
+python Python
+ripgrep ripgrep
+rsync rsync
+ruby Ruby
+rust Rust
+sddm SDDM
smartmontools S.M.A.R.T. tools
-bluez BlueZ Bluetooth
-alsa-utils ALSA utilities
+starship starship prompt
+strace strace
+sudo sudo
+tmux tmux
+vim Vim
vulkan-tools vulkaninfo
-ethtool ethtool
-nftables nftables
+wget wget
wireguard-tools WireGuard tools
-intel-ucode Intel CPU microcode
-amd-ucode AMD CPU microcode
-nouveau Nouveau (NVIDIA open driver)
-linux-desktop Linux kernel, daily desktop config
-linux Linux kernel, upstream defconfig
-xorg-server X.Org server
-mpv mpv media player
-gimp GNU Image Manipulation Program
-inkscape Inkscape
-libreoffice LibreOffice
-krita Krita
-thunderbird Thunderbird
-qemu QEMU
-htop htop
-btop btop
-tmux tmux
-screen GNU Screen
-
-
+yq yq
+zsh Z shell
diff --git a/man/mkpkg.1 b/man/mkpkg.1
index da8ea54..1109973 100644
--- a/man/mkpkg.1
+++ b/man/mkpkg.1
@@ -153,6 +153,23 @@ provides per-package parallelism and is exposed to build commands as
contains downloaded sources;
.B SPS_BUILD
contains temporary build/staging trees.
+When
+.B SPS_ROOT
+is not
+.BR / ,
+every build phase also searches that root for headers, libraries, and
+pkg-config files
+.RB ( CFLAGS ,
+.BR LDFLAGS ,
+.BR LIBRARY_PATH ,
+.BR PKG_CONFIG_SYSROOT_DIR ,
+.BR CMAKE_PREFIX_PATH ).
+Live
+.BR setup (1)
+installs into a mounted target with the live compiler; without this, a
+package cannot link against a library
+.BR pkin (1)
+just unpacked into the target.
With
.BR --build-root ,
.B SYSROOT
diff --git a/man/setup.1 b/man/setup.1
index 98232da..b4a0f78 100644
--- a/man/setup.1
+++ b/man/setup.1
@@ -147,7 +147,9 @@ Choosing zsh or fish also enables the
set.
.TP
.BI --extra " pkg[,pkg...]"
-Individual packages in addition to the profile and sets.
+Individual packages in addition to the profile and sets. The extras
+screen only lists names that exist as recipes in sps-core or sps-extra.
+Unpackaged names are refused after the repository index is built.
.TP
.B --ssh yes|no
Enable the OpenSSH set.
diff --git a/man/sget.1 b/man/sget.1
index 6738115..7faea8a 100644
--- a/man/sget.1
+++ b/man/sget.1
@@ -31,6 +31,15 @@ with the terminal attached so compiler and build-system output appear live.
reports the finished archive;
.BR pkin (1)
reports installation. An exact cached archive is reused without a build.
+When
+.B SPS_ROOT
+is not
+.BR / ,
+.B mkpkg
+compiles and links against libraries already installed in that root so
+each package can see the previous one. Live
+.BR setup (1)
+depends on this.
.SH COMMANDS
.TP
.BI search " pattern"
diff --git a/tests/test_build.sh b/tests/test_build.sh
index 6afa655..f0c10e3 100755
--- a/tests/test_build.sh
+++ b/tests/test_build.sh
@@ -198,4 +198,52 @@ esac
[ ! -e "$tmp/root/usr/share/sysroot-probe" ] ||
fail "builder modified the target root with a build root"
+# When SPS_ROOT is a target disk, gcc must find libraries pkin already
+# installed there. Copying host libasound onto the live ISO would only
+# paper over alsa-utils; the next package would fail the same way.
+if command -v gcc >/dev/null 2>&1; then
+ mkdir -p "$tmp/root/usr/include" "$tmp/root/usr/lib64" \
+ "$tmp/link-recipe" "$tmp/link-out"
+ cat >"$tmp/root/usr/include/spsprobe.h" <<'EOF'
+int sps_probe_lib(void);
+EOF
+ cat >"$tmp/spsprobe.c" <<'EOF'
+int sps_probe_lib(void) { return 0; }
+EOF
+ gcc -shared -fPIC -o "$tmp/root/usr/lib64/libspsprobe.so" \
+ "$tmp/spsprobe.c" ||
+ fail "could not build the target-root probe library"
+ cat >"$tmp/link-recipe/recipe" <<'EOF'
+name target-sysroot-probe
+version 1.0
+release 1
+arch any
+description Confirms SPS_ROOT is searched for headers and libraries
+configure printf '%s\n' "${PKG_CONFIG_SYSROOT_DIR-}" >"$WORK/sysroot"
+configure printf '%s\n' "$LIBRARY_PATH" >"$WORK/libpath"
+configure printf '%s\n' '#include <spsprobe.h>' 'int main(void){return sps_probe_lib();}' >"$WORK/probe.c"
+configure gcc "$WORK/probe.c" -lspsprobe -o "$WORK/probe"
+install mkdir -p "$PKG/usr/share/target-sysroot-probe" "$PKG/usr/bin"
+install cp "$WORK/sysroot" "$WORK/libpath" "$PKG/usr/share/target-sysroot-probe/"
+install cp "$WORK/probe" "$PKG/usr/bin/target-sysroot-probe"
+install chmod 755 "$PKG/usr/bin/target-sysroot-probe"
+EOF
+ link_artifact=$(run_mkpkg --no-download --output "$tmp/link-out" \
+ "$tmp/link-recipe/recipe") ||
+ fail "target-root library probe failed to build"
+ mkdir "$tmp/link-extract"
+ tar -xf "$link_artifact" -C "$tmp/link-extract"
+ assert_equal "$tmp/root" \
+ "$(cat "$tmp/link-extract/usr/share/target-sysroot-probe/sysroot")" \
+ "PKG_CONFIG_SYSROOT_DIR was not set to SPS_ROOT"
+ case "$(cat "$tmp/link-extract/usr/share/target-sysroot-probe/libpath")" in
+ "$tmp/root/usr/lib64:"*) : ;;
+ *) fail "LIBRARY_PATH did not start with the target libdir" ;;
+ esac
+ [ -x "$tmp/link-extract/usr/bin/target-sysroot-probe" ] ||
+ fail "probe binary was not staged"
+ # The .so lives only under SPS_ROOT, so a successful link means gcc
+ # searched the target rather than the live /usr/lib64.
+fi
+
printf '%s\n' 'test_build: ok'
\ No newline at end of file
diff --git a/tests/test_dependencies.sh b/tests/test_dependencies.sh
index cb724f0..1cbe8f3 100755
--- a/tests/test_dependencies.sh
+++ b/tests/test_dependencies.sh
@@ -273,4 +273,12 @@ expect_stale_definition hook
printf '%s\n' '# changed after indexing' >> "$tmp/repo/app/recipe"
expect_stale_definition recipe
+set +e
+"$sget" install nosuch-a nosuch-b >"$tmp/miss.out" 2>"$tmp/miss.err"
+miss_st=$?
+set -e
+[ "$miss_st" -eq 5 ] || fail "two missing packages returned $miss_st"
+contains "$(cat "$tmp/miss.err")" "package 'nosuch-a' not found"
+contains "$(cat "$tmp/miss.err")" "package 'nosuch-b' not found"
+
printf '%s\n' 'dependency resolution and install orchestration tests passed'
diff --git a/tests/test_mkiso.sh b/tests/test_mkiso.sh
index 9ad986b..3a68680 100755
--- a/tests/test_mkiso.sh
+++ b/tests/test_mkiso.sh
@@ -226,3 +226,35 @@ then
fi
done
fi
+grep -q 'copying file(1) magic database' "$mkiso" ||
+ fail 'seeded ISO must copy file(1) magic for autoconf'
+grep -q '/etc/file' "$mkiso" ||
+ fail 'mkiso must copy /etc/file magic used by this host file(1)'
+grep -q kactivitymanagerd "$project_dir/lib/mkiso/plasma-bins" ||
+ fail 'plasma-bins must include kactivitymanagerd'
+grep -q kscreenlocker_greet "$project_dir/lib/mkiso/plasma-bins" ||
+ fail 'plasma-bins must include kscreenlocker_greet'
+grep -q kscreenlockerrc "$mkiso" ||
+ fail 'mkiso must bake kscreenlockerrc so live Plasma does not autolock'
+grep -q Liberation "$mkiso" ||
+ fail 'plasma ISO must copy Liberation fonts matching host fontconfig'
+grep -q XDG_CURRENT_DESKTOP "$project_dir/lib/mkiso/live-plasma" ||
+ fail 'live-plasma must set XDG_CURRENT_DESKTOP=KDE'
+grep -q mkiso_copy_plasma_runtime_deps "$mkiso" ||
+ fail 'plasma ISO must ldd session helpers (kactivitymanagerd, greeter)'
+grep -q 'using .* for headers, libraries, and pkg-config' "$project_dir/bin/mkpkg" ||
+ fail 'mkpkg must compile against SPS_ROOT so setup can link target libs'
+grep -q mkiso_verify_setup_catalog "$mkiso" ||
+ fail 'mkiso must refuse extras/sets that name unpackaged recipes'
+if grep -E '^(htop|btop|tmux)[[:space:]]' "$project_dir/lib/setup/extras" |
+ awk '{c[$1]++} END { for (n in c) if (c[n] > 1) exit 1 }'
+then
+ :
+else
+ fail 'extras must not list htop, btop, or tmux more than once'
+fi
+if grep -E '^(mpv|gimp|libreoffice|qemu|screen)[[:space:]]' \
+ "$project_dir/lib/setup/extras"
+then
+ fail 'extras must not offer unpackaged names such as mpv'
+fi
diff --git a/tests/test_setup_catalog.sh b/tests/test_setup_catalog.sh
new file mode 100644
index 0000000..162cead
--- /dev/null
+++ b/tests/test_setup_catalog.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+# setup extras/sets/profiles must name real, unique, sorted packages.
+
+set -eu
+
+case $0 in */*) test_dir=${0%/*} ;; *) test_dir=. ;; esac
+project_dir=$(CDPATH= cd "$test_dir/.." 2>/dev/null && pwd) || exit 1
+fail() { printf 'test_setup_catalog: %s\n' "$*" >&2; exit 1; }
+
+setup_dir=$project_dir/lib/setup
+extras=$setup_dir/extras
+[ -f "$extras" ] || fail 'lib/setup/extras is missing'
+
+# Unique, tab-separated, LC_ALL=C sorted. Duplicate htop/btop rows and
+# unpackaged names such as mpv are how the extras checklist lied to setup.
+LC_ALL=C awk '
+ BEGIN { bad = 0; count = 0 }
+ /^[[:space:]]*$/ || /^#/ { next }
+ {
+ tab = index($0, "\t")
+ if (tab == 0) {
+ print "extras line has no tab: " $0
+ bad = 1
+ next
+ }
+ name = substr($0, 1, tab - 1)
+ if (name !~ /^[A-Za-z0-9][A-Za-z0-9+_.-]*$/) {
+ print "extras bad name: " name
+ bad = 1
+ }
+ if (name in seen) {
+ print "extras duplicate: " name
+ bad = 1
+ }
+ seen[name] = 1
+ count++
+ names[count] = name
+ }
+ END {
+ for (i = 1; i < count; i++)
+ if (names[i] > names[i + 1]) {
+ print "extras not sorted: " names[i] " before " names[i + 1]
+ bad = 1
+ }
+ if (count < 2) {
+ print "extras is empty"
+ bad = 1
+ }
+ exit bad
+ }
+' "$extras" || fail 'extras must be unique, tab-separated, and sorted'
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-test-catalog.XXXXXX") || fail mktemp
+trap 'rm -rf "$tmp"' 0 HUP INT TERM
+
+awk -F '\t' '/^[[:space:]]*$/ || /^#/ { next } { print $1 }' "$extras" \
+ >"$tmp/wanted"
+for f in "$setup_dir"/sets/*.set "$setup_dir"/profiles/*.profile
+do
+ [ -f "$f" ] || continue
+ awk '/^package[[:space:]]/ { print $2 }' "$f" >>"$tmp/wanted"
+done
+ if [ -f "$setup_dir/hwdetect.sh" ]; then
+ awk '
+ /printf '\''%s\\n'\'' [a-z]/ && $0 !~ /\$/ && $0 !~ /\|/ {
+ for (i = 3; i <= NF; i++) {
+ gsub(/;/, "", $i)
+ if ($i ~ /^[a-z][a-z0-9+-]*$/)
+ print $i
+ }
+ }
+ ' "$setup_dir/hwdetect.sh" >>"$tmp/wanted"
+ fi
+LC_ALL=C sort -u "$tmp/wanted" -o "$tmp/wanted"
+
+# Recipe trees are optional for a tools-only checkout. When they are
+# present, every setup name must exist so live setup cannot offer mpv.
+catalog_trees=
+for d in \
+ ${SPS_CORE:-} ${SPS_EXTRA:-} \
+ ${ISO_CORE:-} ${ISO_EXTRA:-} \
+ /tmp/sps-core /tmp/sps-extra \
+ /usr/src/sps/core /usr/src/sps/extra
+do
+ [ -n "$d" ] || continue
+ [ -d "$d" ] || continue
+ case " $catalog_trees " in *" $d "*) continue ;; esac
+ catalog_trees="$catalog_trees $d"
+done
+
+if [ -z "$catalog_trees" ]; then
+ printf '%s\n' \
+ 'test_setup_catalog: no recipe trees; skipped existence check'
+ printf '%s\n' 'test_setup_catalog: ok'
+ exit 0
+fi
+
+: >"$tmp/recipes"
+for tree in $catalog_trees
+do
+ find "$tree" \( -type d \( -name .git -o -name files -o \
+ -name patches -o -name hooks \) -prune \) -o \
+ -type f -name recipe -print
+done | while IFS= read -r recipe || [ -n "$recipe" ]
+do
+ [ -n "$recipe" ] || continue
+ awk '/^name[[:space:]]/ { print $2; exit }' "$recipe"
+done | LC_ALL=C sort -u >"$tmp/recipes"
+[ -s "$tmp/recipes" ] || fail 'recipe trees contained no name records'
+
+miss=
+while IFS= read -r pkg || [ -n "$pkg" ]
+do
+ [ -n "$pkg" ] || continue
+ grep -qx "$pkg" "$tmp/recipes" && continue
+ miss="$miss $pkg"
+done <"$tmp/wanted"
+[ -z "$miss" ] || fail "setup names packages with no recipe:$miss"
+
+printf '%s\n' 'test_setup_catalog: ok'